[PYTHONMAC-SIG] New improved ProgressBar [ MacPython EasyDialogs.py ]

Steven D. Majewski sdm7g@virginia.edu
11 May 97 15:23:40 -0400

 I had some time in between projects, while waiting for a vendor to
deliver some new drivers, to try to finish up some old half-finished
projects. Since Python 1.5 is impending, I thought I'ld try to clean
up my changes to EasyDialogs and try to get Guido & Jack to include
it in the next Mac Python distribution. ( Jack -- please remember about
the changes to the template to allow all files rather than just TEXT
files. I have some template Mac file-munger applications that I would
like to distribute, and those changes and these below are the things
that have kept me from doing it  -- I wanted it to be a simple MacPython
example, but having to drag out ResEdit right away keeps it from being
a simple procedure. ) 

 Here is a new version of EasyDialogs.py with an improved ProgressBar
which handles Drag events ( so you should change the window type of 
the resource to a movable modal dialog type window -- the one with a 
wider drag region, but no close or zoom boxes. ) and clicking on the
Cancel/Stop button. 

  I would like to add a second label line. When I tried using this in some
file conversion programs, and I wanted to echo pathnames, it often 
overflowed the space for the text. Most of the progress bars generated
by the finder and various utilities like Stuff-it, etc. all have two
lines of text. 
  Any preferences towards either allowing PB.label( string1, string2 ) 
vs. PB.label1( text ) &  PB.label2( text ) ? 

  I'm also considering changing the resource numbers to more closely
follow Mac conventions - for example, Cancel or Stop button should 
usually be = 2  as Cancel is defined in some headers to be that value. 
I don't think changing the resource numbers should have any effect 
outside this code. 

Previous changes from EasyDialogs in 1.4 distribution:
   Changed Progress.Bar.label from an attribute to a method.
   ProgressBar.label()  [with no args] returns the label. 
   ProgressBar.label( text ) sets the label string and causes dialog

   Added ProgressBar.inc( *amount ) to increment the bar and redraw/update.
   A couple of bug fixes -- one that cures occasional crash on update.

Latest changes:
   ProgressBar._update() handles drag and cancel events. 

You need to ResEdit the progress bar dialog resource in PythonCore: 
   [1] The cancel button must be enabled. ( Required )
   [2] Change the window to a movable dialog type. ( Highly Recommended )
   [3] Change the button text from "Cancel" to "Stop" ( Recommended by Mac
					 			        guidelines as less ambiguous )

( #1 above kept me confused about why some of the dialog routines didn't 
 work as I expected. It seems to have been shipped as disabled in the
 distribution. )

Comments or suggestions ?

-- Steve Majewski 

"""Easy to use dialogs.

Message(msg) -- display a message and an OK button.
AskString(prompt, default) -- ask for a string, display OK and Cancel
AskYesNoCancel(question, default) -- display a question and Yes, No and
Cancel buttons.
bar = Progress(label, maxvalue) -- Display a progress bar
bar.set(value) -- Set value
bar.inc( *amount ) -- increment value by amount (default=1)
bar.label( *newlabel ) -- get or set text label. 

More documentation in each function.
This module uses DLOG resources 256, 257 and 258.
Based upon STDWIN dialogs with the same names and functions.

from Dlg import GetNewDialog, SetDialogItemText, GetDialogItemText,
import Qd
import QuickDraw
import Dlg,Win,Evt,Events # sdm7g

def Message(msg):
	"""Display a MESSAGE string.
	Return when the user clicks the OK button or presses Return.
	The MESSAGE string can be at most 255 characters long.
	id = 256
	d = GetNewDialog(id, -1)
	if not d:
		print "Can't get DLOG resource with id =", id
	tp, h, rect = d.GetDialogItem(2)
	SetDialogItemText(h, msg)
	while 1:
		n = ModalDialog(None)
		if n == 1:

def AskString(prompt, default = ""):
	"""Display a PROMPT string and a text entry field with a DEFAULT string.
	Return the contents of the text entry field when the user clicks the
	OK button or presses Return.
	Return None when the user clicks the Cancel button.
	If omitted, DEFAULT is empty.
	The PROMPT and DEFAULT strings, as well as the return value,
	can be at most 255 characters long.
	id = 257
	d = GetNewDialog(id, -1)
	if not d:
		print "Can't get DLOG resource with id =", id
	tp, h, rect = d.GetDialogItem(3)
	SetDialogItemText(h, prompt)
	tp, h, rect = d.GetDialogItem(4)
	SetDialogItemText(h, default)
#	d.SetDialogItem(4, 0, 255)
	while 1:
		n = ModalDialog(None)
		if n == 1:
			tp, h, rect = d.GetDialogItem(4)
			return GetDialogItemText(h)
		if n == 2: return None

def AskYesNoCancel(question, default = 0):
##	"""Display a QUESTION string which can be answered with Yes or No.
##	Return 1 when the user clicks the Yes button.
##	Return 0 when the user clicks the No button.
##	Return -1 when the user clicks the Cancel button.
##	When the user presses Return, the DEFAULT value is returned.
##	If omitted, this is 0 (No).
##	The QUESTION strign ca be at most 255 characters.
##	"""
	id = 258
	d = GetNewDialog(id, -1)
	if not d:
		print "Can't get DLOG resource with id =", id
	# Button assignments:
	# 1 = default (invisible)
	# 2 = Yes
	# 3 = No
	# 4 = Cancel
	# The question string is item 5
	tp, h, rect = d.GetDialogItem(5)
	SetDialogItemText(h, question)
	if default == 1:
	elif default == 0:
	elif default == -1:
	while 1:
		n = ModalDialog(None)
		if n == 1: return default
		if n == 2: return 1
		if n == 3: return 0
		if n == 4: return -1


screenbounds = Qd.qd.screenBits.bounds
screenbounds = screenbounds[0]+4, screenbounds[1]+4, \
	screenbounds[2]-4, screenbounds[3]-4

class ProgressBar:
	def __init__(self, label="Working...", maxval=100):
		self.maxval = maxval
		self.curval = -1
		self.d = GetNewDialog(259, -1)
		self.label( label )

	def __del__( self ):
		del self.d 
	def label( self, *newstr ):		# sdm7g -- new routine: change text
		if newstr : self._label = newstr[0]
		tp, text_h, rect = self.d.GetDialogItem(2)
		SetDialogItemText(text_h, self._label )		

	def _update(self, value):
		self.d.BringToFront()	# sdm7g -- this seems to cure Modal bus-error crash
		tp, h, bar_rect = self.d.GetDialogItem(3)
		Qd.FrameRect(bar_rect)	# Draw outline
		inner_rect = Qd.InsetRect(bar_rect, 1, 1)
		l, t, r, b = inner_rect

		Qd.PaintRect((l, t, int(l + (r-l)*value/self.maxval), b))	# Draw bar

		Qd.PaintRect((int(l + (r-l)*value/self.maxval), t, r, b))	# Clear rest
		# Restore settings
		# Test for cancel button
		ready, ev = Evt.WaitNextEvent( Events.mDownMask, 1  )
		if ready : 
			what,msg,when,where,mod = ev
			part = Win.FindWindow( where )[0]
			if Dlg.IsDialogEvent( ev ):
				ds = Dlg.DialogSelect( ev )
				if ds[0] and ds[1] == self.d  and ds[-1] == 1 :
					raise KeyboardInterrupt, ev
				if part == 4 :	# inDrag 
					self.d.DragWindow( where, screenbounds )
					MacOS.HandleEvent( ev ) 
	def set(self, value):
		if value < 0: value = 0
		if value > self.maxval: value = self.maxval
		self.curval = value			# sdm7g -- didn't update curval 

	def inc( self, *n ):			# sdm7g -- add increment method 
		if not n: n = 1
		else: n = n[0] 
		self.set( self.curval + n )

import time
import MacOS

def test():
	Message("Testing EasyDialogs.")
	ok = AskYesNoCancel("Do you want to proceed?")
	if ok > 0:
		s = AskString("Enter your first name")
		Message("Thank you,\015%s" % `s`)
	text = ( "Working Hard...", "Hardly Working..." , 
			"So far, so good!", "Keep on truckin'" )
	bar = ProgressBar( text[0], 100)
		appsw = MacOS.EnableAppswitch( 0 )
		for i in range(100):
			time.sleep( 0.1 )
			if ( i % 10 == 0 ) : bar.label( text[ (i/10) % 4 ] )
		bar.label( "Done." )
		time.sleep( 0.3 )	# give'em a chance to see the done.
		del bar
		MacOS.EnableAppswitch( appsw )

if __name__ == '__main__':
	except KeyboardInterrupt:
		Message( "Operation Canceled." )

PYTHONMAC-SIG  - SIG on Python for the Apple Macintosh

send messages to: pythonmac-sig@python.org
administrivia to: pythonmac-sig-request@python.org