1. Rob Lanphier
  2. urwid

Commits

ian  committed 77fad9b

release 0.9.2

  • Participants
  • Parent commits f523745
  • Branches default
  • Tags release-0.9.2

Comments (0)

Files changed (9)

File input_test.py

View file
+#!/usr/bin/python
+#
+# Urwid keyboard input test app
+#    Copyright (C) 2004-2006  Ian Ward
+#
+#    This library is free software; you can redistribute it and/or
+#    modify it under the terms of the GNU Lesser General Public
+#    License as published by the Free Software Foundation; either
+#    version 2.1 of the License, or (at your option) any later version.
+#
+#    This library is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#    Lesser General Public License for more details.
+#
+#    You should have received a copy of the GNU Lesser General Public
+#    License along with this library; if not, write to the Free Software
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# Urwid web site: http://excess.org/urwid/
+
+"""
+Keyboard test application
+"""
+
+import urwid.curses_display
+import urwid.raw_display
+import urwid.web_display
+import urwid
+
+import sys
+
+try: True # old python?
+except: False, True = 0, 1
+		
+if urwid.web_display.is_web_request():
+	Screen = urwid.web_display.Screen
+else:
+	if len(sys.argv)>1 and sys.argv[1][:1] == "r":
+		Screen = urwid.raw_display.Screen
+	else:
+		Screen = urwid.curses_display.Screen
+
+class KeyTest:
+	def __init__(self):
+		self.ui = Screen()
+		header = urwid.Text("Values from get_input(). Q exits.")
+		header = urwid.AttrWrap(header,'header')
+		self.l = []
+		self.listbox = urwid.ListBox(self.l)
+		listbox = urwid.AttrWrap(self.listbox, 'listbox')
+		self.top = urwid.Frame( listbox, header )
+		
+	def main(self):
+		self.ui.register_palette([
+			('header', 'black', 'dark cyan', 'standout'),
+			('key', 'yellow', 'dark blue', 'bold'),
+			('listbox', 'light gray', 'black' ),
+			])
+		self.ui.run_wrapper(self.run)
+	
+	def run(self):
+		self.ui.set_mouse_tracking()
+		
+		cols, rows = self.ui.get_cols_rows()
+
+		keys = ['not q']
+		while 'q' not in keys and 'Q' not in keys:
+			if keys:
+				self.ui.draw_screen((cols,rows),
+					self.top.render((cols,rows),focus=True))
+			keys = self.ui.get_input()
+			if 'window resize' in keys:
+				cols, rows = self.ui.get_cols_rows()
+			if not keys:
+				continue
+			t = []
+			a = []
+			for k in keys:
+				if urwid.is_mouse_event(k):
+					t += ["('",('key',k[0]),
+						"', ", ('key',`k[1]`),
+						", ", ('key',`k[2]`),
+						", ", ('key',`k[3]`),
+						") "]
+				else:
+					t += ["'",('key',k),"' "]
+			
+			self.l.append(urwid.Text(t))
+			self.listbox.set_focus(len(self.l)-1,'above')
+				
+
+
+def main():
+	urwid.web_display.set_preferences('KeyTest')
+	if urwid.web_display.handle_short_request():
+		return
+	KeyTest().main()
+	
+
+if '__main__'==__name__ or urwid.web_display.is_web_request():
+	main()

File reference.html

View file
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stop&nbsp;Urwid&nbsp;from&nbsp;consuming&nbsp;100%&nbsp;cpu&nbsp;during&nbsp;a&nbsp;gradual<br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;window&nbsp;resize&nbsp;operation</tt></dd></dl>
 
+<dl><dt><a name="Screen-set_mouse_tracking"><strong>set_mouse_tracking</strong></a>(self)</dt><dd><tt>Enable&nbsp;mouse&nbsp;tracking.&nbsp;&nbsp;<br>
+&nbsp;<br>
+After&nbsp;calling&nbsp;this&nbsp;function&nbsp;get_input&nbsp;will&nbsp;include&nbsp;mouse<br>
+click&nbsp;events&nbsp;along&nbsp;with&nbsp;keystrokes.</tt></dd></dl>
+
 <dl><dt><a name="Screen-signal_init"><strong>signal_init</strong></a>(self)</dt><dd><tt>Called&nbsp;in&nbsp;the&nbsp;startup&nbsp;of&nbsp;run&nbsp;wrapper&nbsp;to&nbsp;set&nbsp;the&nbsp;SIGWINCH&nbsp;<br>
 signal&nbsp;handler&nbsp;to&nbsp;self.<strong>_sigwinch_handler</strong>.<br>
 &nbsp;<br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stop&nbsp;urwid&nbsp;from&nbsp;consuming&nbsp;100%&nbsp;cpu&nbsp;during&nbsp;a&nbsp;gradual<br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;window&nbsp;resize&nbsp;operation</tt></dd></dl>
 
+<dl><dt><a name="Screen-set_mouse_tracking"><strong>set_mouse_tracking</strong></a>(self)</dt><dd><tt>Enable&nbsp;mouse&nbsp;tracking.&nbsp;&nbsp;<br>
+&nbsp;<br>
+After&nbsp;calling&nbsp;this&nbsp;function&nbsp;get_input&nbsp;will&nbsp;include&nbsp;mouse<br>
+click&nbsp;events&nbsp;along&nbsp;with&nbsp;keystrokes.</tt></dd></dl>
+
 <h2>Top-level widgets</h2><h3><a name="BoxWidget">class <strong>BoxWidget</strong></a> <span style="font-size:small; padding-left: 20px">[<a href="#top">back to top</a>]</span></h3>Methods defined here:<br>
 <dl><dt><a name="BoxWidget-selectable"><strong>selectable</strong></a>(self)</dt><dd><tt>Return&nbsp;True.&nbsp;&nbsp;Selectable&nbsp;by&nbsp;default.</tt></dd></dl>
 
 wrap&nbsp;--&nbsp;wrap&nbsp;mode&nbsp;for&nbsp;text&nbsp;layout<br>
 layout&nbsp;--&nbsp;layout&nbsp;object&nbsp;to&nbsp;use,&nbsp;defaults&nbsp;to&nbsp;StandardTextLayout</tt></dd></dl>
 
-<dl><dt><a name="Text-get_line_translation"><strong>get_line_translation</strong></a>(self, maxcol)</dt><dd><tt>Return&nbsp;layout&nbsp;structure&nbsp;for&nbsp;mapping&nbsp;self.<strong>text</strong>&nbsp;to&nbsp;a&nbsp;canvas.</tt></dd></dl>
+<dl><dt><a name="Text-get_line_translation"><strong>get_line_translation</strong></a>(self, maxcol, ta<font color="#909090">=None</font>)</dt><dd><tt>Return&nbsp;layout&nbsp;structure&nbsp;for&nbsp;mapping&nbsp;self.<strong>text</strong>&nbsp;to&nbsp;a&nbsp;canvas.</tt></dd></dl>
 
 <dl><dt><a name="Text-get_text"><strong>get_text</strong></a>(self)</dt><dd><tt>get_text()&nbsp;-&gt;&nbsp;text,&nbsp;attributes<br>
 &nbsp;<br>
 
 <dl><dt><a name="Edit-get_edit_text"><strong>get_edit_text</strong></a>(self)</dt><dd><tt>Return&nbsp;the&nbsp;edit&nbsp;text&nbsp;for&nbsp;this&nbsp;widget.</tt></dd></dl>
 
+<dl><dt><a name="Edit-get_line_translation"><strong>get_line_translation</strong></a>(self, maxcol, ta<font color="#909090">=None</font>)</dt></dl>
+
 <dl><dt><a name="Edit-get_pref_col"><strong>get_pref_col</strong></a>(self, (maxcol,))</dt><dd><tt>Return&nbsp;the&nbsp;preferred&nbsp;column&nbsp;for&nbsp;the&nbsp;cursor,&nbsp;or&nbsp;the<br>
 current&nbsp;cursor&nbsp;x&nbsp;value.</tt></dd></dl>
 
 
 <hr>
 Methods inherited from Text:<br>
-<dl><dt><a name="Edit-get_line_translation"><strong>get_line_translation</strong></a>(self, maxcol)</dt><dd><tt>Return&nbsp;layout&nbsp;structure&nbsp;for&nbsp;mapping&nbsp;self.<strong>text</strong>&nbsp;to&nbsp;a&nbsp;canvas.</tt></dd></dl>
-
 <dl><dt><a name="Edit-rows"><strong>rows</strong></a>(self, (maxcol,), focus<font color="#909090">=False</font>)</dt><dd><tt>Return&nbsp;the&nbsp;number&nbsp;of&nbsp;rows&nbsp;the&nbsp;rendered&nbsp;text&nbsp;spans.</tt></dd></dl>
 
 <dl><dt><a name="Edit-set_align_mode"><strong>set_align_mode</strong></a>(self, mode)</dt><dd><tt>Set&nbsp;text&nbsp;alignment&nbsp;/&nbsp;justification.&nbsp;&nbsp;<br>
 
 <dl><dt><a name="IntEdit-get_edit_text"><strong>get_edit_text</strong></a>(self)</dt><dd><tt>Return&nbsp;the&nbsp;edit&nbsp;text&nbsp;for&nbsp;this&nbsp;widget.</tt></dd></dl>
 
+<dl><dt><a name="IntEdit-get_line_translation"><strong>get_line_translation</strong></a>(self, maxcol, ta<font color="#909090">=None</font>)</dt></dl>
+
 <dl><dt><a name="IntEdit-get_pref_col"><strong>get_pref_col</strong></a>(self, (maxcol,))</dt><dd><tt>Return&nbsp;the&nbsp;preferred&nbsp;column&nbsp;for&nbsp;the&nbsp;cursor,&nbsp;or&nbsp;the<br>
 current&nbsp;cursor&nbsp;x&nbsp;value.</tt></dd></dl>
 
 
 <hr>
 Methods inherited from Text:<br>
-<dl><dt><a name="IntEdit-get_line_translation"><strong>get_line_translation</strong></a>(self, maxcol)</dt><dd><tt>Return&nbsp;layout&nbsp;structure&nbsp;for&nbsp;mapping&nbsp;self.<strong>text</strong>&nbsp;to&nbsp;a&nbsp;canvas.</tt></dd></dl>
-
 <dl><dt><a name="IntEdit-rows"><strong>rows</strong></a>(self, (maxcol,), focus<font color="#909090">=False</font>)</dt><dd><tt>Return&nbsp;the&nbsp;number&nbsp;of&nbsp;rows&nbsp;the&nbsp;rendered&nbsp;text&nbsp;spans.</tt></dd></dl>
 
 <dl><dt><a name="IntEdit-set_align_mode"><strong>set_align_mode</strong></a>(self, mode)</dt><dd><tt>Set&nbsp;text&nbsp;alignment&nbsp;/&nbsp;justification.&nbsp;&nbsp;<br>

File setup.py

View file
 
 import os
 
-release = "0.9.1"
+release = "0.9.2"
 
 setup_d = {
 	'name':"urwid",

File test_urwid.py

View file
 
 class EditRenderTest(unittest.TestCase):
 	def rtest(self, w, expected_text, expected_cursor):
+		get_cursor = w.get_cursor_coords((4,))
+		assert get_cursor == expected_cursor, "got: "+`get_cursor`+" expected: "+`expected_cursor`
 		r = w.render((4,), focus = 1)
 		assert r.text == expected_text, "got: "+`r.text`+" expected: "+`expected_text`
 		assert r.cursor == expected_cursor, "got: "+`r.cursor`+" expected: "+`expected_cursor`
 
 	
-	def testSpaceWrap(self):
+	def test1_SpaceWrap(self):
 		w = urwid.Edit("","blah blah")
 		w.set_edit_pos(0)
 		self.rtest(w,["blah","blah"],(0,0))
 		w.set_edit_pos(9)
 		self.rtest(w,["blah","lah "],(3,1))
 	
-	def testClipWrap(self):
+	def test2_ClipWrap(self):
 		w = urwid.Edit("","blah\nblargh",1)
 		w.set_wrap_mode('clip')
 		w.set_edit_pos(0)
 		w.set_edit_pos(6)
 		self.rtest(w,["blah","larg"],(0,1))
 	
-	def testAnyWrap(self):
+	def test3_AnyWrap(self):
 		w = urwid.Edit("","blah blah")
 		w.set_wrap_mode('any')
 		
 		self.rtest(w,["blah"," bla","h   "],(1,2))
 
+	def test4_CursorNudge(self):
+		w = urwid.Edit("","hi",align='right')
+		w.keypress((4,),'end')
+
+		self.rtest(w,[" hi "],(3,0))
+		
+		w.keypress((4,),'left')
+		self.rtest(w,["  hi"],(3,0))
+		
+
 
 class SelectableText(urwid.Text):
 	def selectable(self):
 		g = urwid.BarGraph( ['black','red','blue'],
 				None, {(1,0):'red/black', (2,1):'blue/red'})
 		g.set_data( data, top )
-		rval, ignore = g.calculate_display((5,3))
+		rval = g.calculate_display((5,3))
 		assert rval == exp, "%s expected %s, got %s"%(desc,`exp`,`rval`)
 	
 	def test1(self):

File urwid/curses_display.py

View file
 # replace control characters with ?'s
 _trans_table = "?"*32+"".join([chr(x) for x in range(32,256)])
 
-WINDOW_RESIZE = 410
 
 class Screen:
 	def __init__(self):
 		self.palette[name] = (i, fg_b, mono)
 		
 	
+	def set_mouse_tracking(self):
+		"""
+		Enable mouse tracking.  
+		
+		After calling this function get_input will include mouse
+		click events along with keystrokes.
+		"""
+		rval = curses.mousemask( 0 
+			| curses.BUTTON1_PRESSED | curses.BUTTON1_RELEASED
+			| curses.BUTTON2_PRESSED | curses.BUTTON2_RELEASED
+			| curses.BUTTON3_PRESSED | curses.BUTTON3_RELEASED
+			| curses.BUTTON4_PRESSED | curses.BUTTON4_RELEASED
+			| curses.BUTTON_SHIFT | curses.BUTTON_ALT
+			| curses.BUTTON_CTRL )
+	
 	def run_wrapper(self,fn):
 		"""Call fn in fullscreen mode.  Return to normal on exit.
 		
 		
 		while key >= 0:
 			raw.append(key)
-			if key==WINDOW_RESIZE: 
+			if key==curses.KEY_RESIZE: 
 				resize = True
-				key = self._getch_nodelay()
-				continue
-			keys.append(key)
+			elif key==curses.KEY_MOUSE:
+				keys += self._encode_mouse_event()
+			else:
+				keys.append(key)
 			key = self._getch_nodelay()
 
 		processed = []
 		return processed, raw
 		
 		
+	def _encode_mouse_event(self):
+		# convert back to escape sequence
+		(id,x,y,z,bstate) = curses.getmouse()
+		if bstate & curses.BUTTON1_PRESSED:	b = 0
+		elif bstate & curses.BUTTON2_PRESSED:	b = 1
+		elif bstate & curses.BUTTON3_PRESSED:	b = 2
+		elif bstate & curses.BUTTON4_PRESSED:	b = 64
+		else:
+			#assert 0, `bstate, bstate&curses.BUTTON1_RELEASED, \
+			#	bstate&curses.BUTTON2_RELEASED, \
+			#	bstate&curses.BUTTON3_RELEASED, \
+			#	bstate&curses.BUTTON4_RELEASED`
+			#x = bstate&curses.BUTTON1_RELEASED
+			#y = bstate&curses.BUTTON2_RELEASED
+			b = 3
+		
+		if bstate & curses.BUTTON_SHIFT:	b |= 4
+		if bstate & curses.BUTTON_ALT:		b |= 8
+		if bstate & curses.BUTTON_CTRL:		b |= 16
+		
+		return [ 27, ord('['), ord('M'), b + 32, x + 33, y + 33 ]
 			
 
 	def _dbg_instr(self): # messy input string (intended for debugging)

File urwid/escape.py

View file
 		(11,12,13,14,15,17,18,19,20,21,23,24,25,26,28,29,31,32,33,34),
 		('f1','f2','f3','f4','f5','f6','f7','f8','f9','f10','f11',
 		'f12','f13','f14','f15','f16','f17','f18','f19','f20'))
+] + [
+	# mouse reporting (special handling done in KeyqueueTrie)
+	('[M', 'mouse')
 ]
 
 class KeyqueueTrie:
 	
 	def get_recurse(self, root, keys, more_fn):
 		if type(root) != type({}):
+			if root == "mouse":
+				return self.read_mouse_info( keys, more_fn )
 			return (root, keys)
 		if not keys:
 			# get more keys
 		if not root.has_key(keys[0]):
 			return None
 		return self.get_recurse( root[keys[0]], keys[1:], more_fn )
+	
+	def read_mouse_info(self, keys, more_fn):
+		while len(keys) < 3:
+			key = more_fn()
+			if key < 0:
+				return None
+			keys.append(key)
+		
+		b = keys[0] - 32
+		x, y = keys[1] - 33, keys[2] - 33  # start from 0
+		
+		prefix = ""
+		if b & 4:	prefix = prefix + "shift "
+		if b & 8:	prefix = prefix + "meta "
+		if b & 16:	prefix = prefix + "ctrl "
+
+		if b & 3 == 3:	
+			action = "release"
+			b = 0
+		else:
+			action = "press"
+			b = ((b&64)/64*3) + (b & 3) + 1
+
+		return ( (prefix + "mouse " + action, b, x, y), keys[3:] )
+
 		
 
 #################################################
 HIDE_CURSOR = ESC+"[?25l"
 SHOW_CURSOR = ESC+"[?25h"
 
+MOUSE_TRACKING_ON = ESC+"[?1000h"
+MOUSE_TRACKING_OFF = ESC+"[?1000l"
+
 _fg_attr = {
 	'default':	"0;39",
 	'black':	"0;30",

File urwid/raw_display.py

View file
 		"""
 		signal.signal(signal.SIGWINCH, signal.SIG_DFL)
       
+	def set_mouse_tracking(self):
+		"""
+		Enable mouse tracking.  
+		
+		After calling this function get_input will include mouse
+		click events along with keystrokes.
+		"""
+		sys.stdout.write(escape.MOUSE_TRACKING_ON)
+		
 	def run_wrapper(self,fn):
 		""" Call fn and reset terminal on exit.
 		"""
 					0, self.maxrow)
 			sys.stdout.write( escape.set_attributes( 
 				'default', 'default') 
+				+ escape.MOUSE_TRACKING_OFF
 				+ move_cursor + "\n" + escape.SHOW_CURSOR )
 			
 	def get_input(self, raw_keys=False):

File urwid/util.py

View file
 	
 	raise TagMarkupException, "Invalid markup element: %s" % `tm`
 
+
+def is_mouse_event( ev ):
+	return type(ev) == type(()) and len(ev)==4 and ev[0].find("mouse")>=0

File urwid/widget.py

View file
 		top -- number of blank lines above
 		bottom -- number of blank lines below
 		"""
-		self.div_char=div_char
+		self.div_char=Text(div_char).render((1,)).text[0]
 		self.top=top
 		self.bottom=bottom
 		
 		Render contents with wrapping and alignment.  Return canvas.
 		"""
 
-		if maxcol == self._cache_maxcol:
-			text, attr = self.get_text()
-		else:	# update self._cache_translation
-			text, attr = self._update_cache_translation( maxcol )
+		text, attr = self.get_text()
+		trans = self.get_line_translation( maxcol, (text,attr) )
 			
-		return apply_text_layout( text, attr, self._cache_translation, 
-			maxcol )
+		return apply_text_layout( text, attr, trans, maxcol )
 		
 
 	def rows(self,(maxcol,), focus=False):
 		"""Return the number of rows the rendered text spans."""
 		return len(self.get_line_translation(maxcol))
 
-	def get_line_translation(self,maxcol):
+	def get_line_translation(self, maxcol, ta=None):
 		"""Return layout structure for mapping self.text to a canvas.
 		"""
 		# uses cached translation if available.  If set_text is not
 		# to None before calling this method.
 		
 		if not self._cache_maxcol or self._cache_maxcol != maxcol:
-			self._update_cache_translation(maxcol)
+			self._update_cache_translation(maxcol, ta)
 		return self._cache_translation
 
-	def _update_cache_translation(self,maxcol):
-		text, attr = self.get_text()
+	def _update_cache_translation(self,maxcol, ta):
+		if ta:
+			text, attr = ta
+		else:
+			text, attr = self.get_text()
 		self._cache_maxcol = maxcol
 		self._cache_translation = self._calc_line_translation(
 			text, maxcol )
-		return text, attr
 	
 	def _calc_line_translation(self, text, maxcol ):
 		return self.layout.layout(
 			self.set_edit_pos(edit_pos)
 		self.highlight = None
 		self.pref_col_maxcol = None, None
-		self._shift_view_to_cursor = 0
+		self._shift_view_to_cursor = False
 	
 	def get_text(self):
 		"""get_text() -> text, attributes
 		focus.
 		"""
 		
-		if focus:
-			# keep the cursor visible on clipped edit fields
-			self._shift_view_to_cursor = 1
-			self._cache_maxcol = None
-		elif self._shift_view_to_cursor:
-			self._shift_view_to_cursor = 0
-			self._cache_maxcol = None
+		self._shift_view_to_cursor = not not focus # force bool
 		
 		d = Text.render(self,(maxcol,))
 		if focus:
 		#	d.coords['highlight'] = [ hstart, hstop ]
 		return d
 	
-	def _calc_line_translation(self, text, maxcol ):
-		trans = Text._calc_line_translation(self, text, maxcol)
+	def get_line_translation(self, maxcol, ta=None ):
+		trans = Text.get_line_translation(self, maxcol, ta)
 		if not self._shift_view_to_cursor: 
 			return trans
 		
+		text, ignore = self.get_text()
 		x,y = calc_coords( text, trans, 
 			self.edit_pos + len(self.caption) )
 		if x < 0:
-			trans[y] = shift_line( trans[y], -x )
+			return ( trans[:y]
+				+ [shift_line(trans[y],-x)]
+				+ trans[y+1:] )
 		elif x >= maxcol:
-			trans[y] = shift_line( trans[y], -(x-maxcol+1) )
+			return ( trans[:y] 
+				+ [shift_line(trans[y],-(x-maxcol+1))]
+				+ trans[y+1:] )
 		return trans
 			
 
 	def get_cursor_coords(self,(maxcol,)):
 		"""Return the (x,y) coordinates of cursor within widget."""
 
-		if self.wrap_mode == 'clip':
-			# keep the cursor visible on clipped edit fields
-			self._shift_view_to_cursor = 1
-			self._cache_maxcol = None
-
+		self._shift_view_to_cursor = True
 		return self.position_coords(maxcol,self.edit_pos)
 	
 	
 		p = pos + len(self.caption)
 		trans = self.get_line_translation(maxcol)
 		x,y = calc_coords(self.get_text()[0], trans,p)
-		if x >= maxcol: x = maxcol-1
 		return x,y