[PYTHONMAC-SIG] Waste

Jack Jansen Jack.Jansen@cwi.nl
Mon, 17 Mar 1997 16:57:37 +0100


This is a multipart MIME message.

--===_0_Mon_Mar_17_16:53:53_CET_1997
Content-Type: text/plain; charset=us-ascii

Okay, here is the minimal html browser. No guarantees that it'll work out of 
the box in a standard python environment, I pulled it out of our multimedia 
presentation software. It should be interesting reading, though, if you want 
to play with waste.
It has no comments, probably some debugging stuff and *really* isn't fit for 
distribution.

Let me know if someone actually wants to use this and misses something,
--
Jack Jansen             | ++++ stop the execution of Mumia Abu-Jamal ++++
Jack.Jansen@cwi.nl      | ++++ if you agree copy these lines to your sig ++++
http://www.cwi.nl/~jack | see http://www.xs4all.nl/~tank/spg-l/sigaction.htm 


--===_0_Mon_Mar_17_16:53:53_CET_1997
Content-Type: text/plain; charset=us-ascii
Content-Description: mac_image.py

"""mac_image - Helper routines (hacks) for images"""
import imgformat
import Qd
import time
import struct

#
# Hack alert: this number gives the offset of the string buffer in a
# python string object from the object address (what is returned by id()).
# In some Pythons it is 12, in some 16. An incorrect value here will result
# a shifted image on-screen.
ID_TO_DATA_OFFSET=16

_fmt_to_mac = {
	imgformat.macrgb16 : (16, 16, 3, 5),
}

def mkpixmap(w, h, fmt, data):
	"""kludge a pixmap together"""
	fmtinfo = _fmt_to_mac[fmt]
	
	rv = struct.pack("lhhhhhhhlllhhhhlll",
		id(data)+ID_TO_DATA_OFFSET,
		w*2 + 0x8000,
		0, 0, h, w,
		0,
		0, 0, # XXXX?
		72<<16, 72<<16,
		fmtinfo[0], fmtinfo[1],
		fmtinfo[2], fmtinfo[3],
		0, 0, 0)
##	print 'Our pixmap, size %d:'%len(rv)
##	dumppixmap(rv)
	return Qd.RawBitMap(rv)

def dumppixmap(data):
	baseAddr, \
		rowBytes, \
		t, l, b, r, \
		pmVersion, \
		packType, packSize, \
		hRes, vRes, \
		pixelType, pixelSize, \
		cmpCount, cmpSize, \
		planeBytes, pmTable, pmReserved \
			= struct.unpack("lhhhhhhhlllhhhhlll", data)
	print 'Base:       0x%x'%baseAddr
	print 'rowBytes:   %d (0x%x)'%(rowBytes&0x3fff, rowBytes)
	print 'rect:       %d, %d, %d, %d'%(t, l, b, r)
	print 'pmVersion:  0x%x'%pmVersion
	print 'packing:    %d %d'%(packType, packSize)
	print 'resolution: %f x %f'%(float(hRes)/0x10000, float(vRes)/0x10000)
	print 'pixeltype:  %d, size %d'%(pixelType, pixelSize)
	print 'components: %d, size %d'%(cmpCount, cmpSize)
	print 'planeBytes: %d (0x%x)'%(planeBytes, planeBytes)
	print 'pmTable:    0x%x'%pmTable
	print 'pmReserved: 0x%x'%pmReserved
	for i in range(0, len(data), 16):
		for j in range(16):
			if i + j < len(data):
				print '%02.2x'%ord(data[i+j]),
		print

--===_0_Mon_Mar_17_16:53:53_CET_1997
Content-Type: text/plain; charset=us-ascii
Content-Description: htmlwidget.py

# A minimal html widget.
#

import Win
import Qd
import QuickDraw
import Res
import Fm
import Ctl
import Controls
import waste
import WASTEconst
import os
import regsub
import string
import htmllib
import urllib
import img
import imgformat
import mac_image
import formatter

LEFTMARGIN=4
TOPMARGIN=4
RIGHTMARGIN=2
BOTTOMMARGIN=0
SCROLLBARWIDTH=16
IMAGEBORDER=2

# Sizes for HTML tag types
HTML_SIZE={
	'h1': 4,
	'h2': 2
}

class HTMLWidget:
	def __init__(self, window, rect, name):
		init_waste()
		self.last_mouse_was_down = 0
		self.url = ''
		self.bary = None
		self.anchor_offsets = []
		self.anchor_hrefs = []
		self.bg_color = (0xffff, 0xffff, 0xffff)
		self.fg_color = (0, 0, 0)
		self.an_color = (0xffff, 0, 0)
		self.font_normal = Fm.GetFNum('Times')
		self.font_tt = Fm.GetFNum('Courier')
		self.font_size = 12
		self.name = name
		self.wid = window
		l, t, r, b = rect
		self.rect = rect
		vr = l+LEFTMARGIN, t+TOPMARGIN, r-RIGHTMARGIN, b-BOTTOMMARGIN
		dr = (0, 0, vr[2]-vr[0], 0)
		Qd.SetPort(window)
		Qd.TextFont(4)
		Qd.TextSize(9)
		flags = WASTEconst.weDoAutoScroll | WASTEconst.weDoOutlineHilite
		self.ted = waste.WENew(dr, vr, flags)
		self.createscrollbars()
		self.do_activate()
		
	def close(self):
		Qd.SetPort(self.wid) # XXXX Needed?
		if self.bary:
			self.bary.DisposeControl()
		del self.bary
		del self.ted
		del self.wid
		
	def setcolors(self, bg, fg, an):
		self.bg_color = bg
		self.fg_color = fg
		self.an_color = an
		
	def setfonts(self, normal, tt, defsize):
		if normal != None:
			self.font_normal = normal
		if tt != None:
			self.font_tt = tt
		if defsize != None:
			self.font_size = defsize
		
	def createscrollbars(self, reset=0):
		#
		# See if we need them.
		#
		if self.bary:
			self.bary.DisposeControl()
		self.bary = None
		l, t, r, b = self.rect
		if reset:
			self.ted.WECalText()
		vr = self.ted.WEGetViewRect()
		dr = self.ted.WEGetDestRect()
		need_bary = ((dr[3]-dr[1]) >= (vr[3]-vr[1]))
		if need_bary:
			vr = l+LEFTMARGIN, t+TOPMARGIN, r-(RIGHTMARGIN+SCROLLBARWIDTH-1), 
b-BOTTOMMARGIN
			dr = dr[0], dr[1], dr[0]+vr[2]-vr[0], dr[3]
			self.ted.WESetViewRect(vr)
			self.ted.WESetDestRect(dr)
			self.ted.WECalText()
			vr = self.ted.WEGetViewRect()
			dr = self.ted.WEGetDestRect()
			rect = r-(SCROLLBARWIDTH-1), t-1, r+1, b+1
			if reset:
				vy = 0
			else:
				vy = self.getybarvalue()
			self.bary = Ctl.NewControl(self.wid, rect, "", 1, vy, 0, 
dr[3]-dr[1]-(vr[3]-vr[1]), 16, 0)
			if not self.activated: self.bary.HiliteControl(255)
			self.updatedocview()
		else:
			vr = l+LEFTMARGIN, t+TOPMARGIN, r-RIGHTMARGIN, b-BOTTOMMARGIN
			dr = dr[0], dr[1], dr[0]+vr[2]-vr[0], dr[3]
			self.ted.WESetViewRect(vr)
			self.ted.WESetDestRect(dr)
			self.ted.WECalText()
			self.bary = None
			self.ted.WEScroll(vr[0]-dr[0], vr[1]-dr[1]) # Test....
		
	def getybarvalue(self):
		vr = self.ted.WEGetViewRect()
		dr = self.ted.WEGetDestRect()
		return vr[1]-dr[1]

	def updatescrollbars(self):
		"""Update scrollbars to reflect current state of document"""
		if not self.bary or not self.activated:
			return 0
		vy = self.getybarvalue()
		self.bary.SetControlValue(vy)
		max = self.bary.GetControlMaximum()
		if vy > max:
			self.updatedocview()
			
	def updatedocview(self):
		"""Update document view to reflect state of scrollbars"""
		vr = self.ted.WEGetViewRect()
		dr = self.ted.WEGetDestRect()
		value = self.bary.GetControlValue()
		self.ted.WEScroll(vr[0]-dr[0], vr[1]-dr[1]-value)
		
	def scrollbar_callback(self, which, where):
		if which != self.bary:
			return 0
		#
		# Get current position
		#
		l, t, r, b = self.ted.WEGetViewRect()
		#
		# "line" size is minimum of top and bottom line size
		#
		topline_off,dummy = self.ted.WEGetOffset((l+1,t+1))
		topline_num = self.ted.WEOffsetToLine(topline_off)
		toplineheight = self.ted.WEGetHeight(topline_num, topline_num+1)
			
		botline_off, dummy = self.ted.WEGetOffset((l+1, b-1))
		botline_num = self.ted.WEOffsetToLine(botline_off)
		botlineheight = self.ted.WEGetHeight(botline_num, botline_num+1)
		
		if botlineheight == 0:
			botlineheight = self.ted.WEGetHeight(botline_num-1, botline_num)
		if botlineheight < toplineheight:
			lineheight = botlineheight
		else:
			lineheight = toplineheight
		if lineheight <= 0:
			lineheight = 1
		pageheight = (b-t)-lineheight
		if pageheight <= 0:
			pageheight = lineheight
		#
		# Now do the command.
		#
		value = self.bary.GetControlValue()
		if where == Controls.inUpButton:
			value = value - lineheight
		elif where == Controls.inPageUp:
			value = value - pageheight
		elif where == Controls.inDownButton:
			value = value + lineheight
		elif where == Controls.inPageDown:
			value = value + pageheight
		self.bary.SetControlValue(value)
		self.updatedocview()
		return 1
		
	def do_activate(self):
		Qd.SetPort(self.wid)
		self.ted.WEActivate()
		if self.bary:
			self.bary.HiliteControl(0)
		self.activated = 1

	def do_deactivate(self):
		Qd.SetPort(self.wid)
		self.ted.WEDeactivate()
		if self.bary:
			self.bary.HiliteControl(255)
		self.activated = 0
		
	def do_update(self):
		visregion = self.wid.GetWindowPort().visRgn
		myregion = Qd.NewRgn()
		Qd.RectRgn(myregion, self.rect) # or is it self.ted.WEGetViewRect() ?
		Qd.SectRgn(myregion, visregion, myregion)
		# Waste doesn't honour the clipping region, do it ourselves
		clipregion = Qd.NewRgn()
		Qd.GetClip(clipregion)
		Qd.SectRgn(myregion, clipregion, myregion)
		if Qd.EmptyRgn(myregion):
			return
		Qd.RGBBackColor(self.bg_color)
		Qd.RGBForeColor((0, 0xffff, 0)) # DBG
		Qd.EraseRgn(visregion)
		self.ted.WEUpdate(myregion)
##		self.updatescrollbars()
		
	def do_moveresize(self, rect):
		l, t, r, b = rect
		self.rect = rect
		Qd.SetPort(self.wid)
		Qd.RGBBackColor(self.bg_color)
		vr = l+LEFTMARGIN, t+TOPMARGIN, r-RIGHTMARGIN, b-BOTTOMMARGIN
		self.ted.WESetViewRect(vr)
		Win.InvalRect(self.rect)
		self.createscrollbars()
		
	def do_click(self, down, local, evt):
		(what, message, when, where, modifiers) = evt
		Qd.SetPort(self.wid)
		Qd.RGBBackColor(self.bg_color)
		if down:
			# Check for control
			ptype, ctl = Ctl.FindControl(local, self.wid)
			if ptype and ctl:
				part = ctl.TrackControl(local)
				if part:
					self.scrollbar_callback(ctl, part)
					return
			# Remember, so we react to mouse-up next time
			self.last_mouse_was_down = 1
		else:
			if not self.last_mouse_was_down:
				# Two ups in a row,
				# probably due to window-raise or something
				return
			self.last_mouse_was_down = 0
			
			# Check for anchor
			if not self._cbanchor:
				return
			off, edge = self.ted.WEGetOffset(local)
			for i in range(len(self.anchor_offsets)):
				p0, p1 = self.anchor_offsets[i]
				if p0 <= off < p1:
					href = self.anchor_hrefs[i]
					self._cbanchor(href)
					return

	def do_char(self, ch, event):
		pass # Do nothing.				
		
	def insert_html(self, data, url):
		Qd.SetPort(self.wid)
		Qd.RGBBackColor(self.bg_color)
		
		if data == '':
			self.ted.WEFeatureFlag(WASTEconst.weFInhibitRecal, 1)
			self.ted.WESetSelection(0, 0x3fffffff)
			self.ted.WEDelete()
			self.ted.WEFeatureFlag(WASTEconst.weFInhibitRecal, 0)
			Win.InvalRect(self.rect)
			self.createscrollbars(reset=1)
			self.anchor_offsets = []
			return
		f = MyFormatter(self)
		
		# Remember where we are, and don't update
		Qd.SetPort(self.wid)
		self.ted.WESetSelection(0, 0x3fffffff)
		self.ted.WEDelete()
		self.ted.WEFeatureFlag(WASTEconst.weFInhibitRecal, 1)

		self.html_init()
		p = MyHTMLParser(f)
		p.url = url  # Tell it the URL, for relative images
		p.feed(data)
		
		self.anchor_hrefs = p.anchorlist[:]

		# Restore updating, recalc, set focus
		self.ted.WESetSelection(0, 0)
		self.ted.WEFeatureFlag(WASTEconst.weFInhibitRecal, 0)
		Win.InvalRect(self.rect)

		self.createscrollbars(reset=1)
		if self.name == 'HelpHeader':
			vr = self.ted.WEGetViewRect()
			dr = self.ted.WEGetDestRect()
		
	def mysetstyle(self, which, how):
		self.ted.WESelView()
		self.ted.WESetStyle(which, how)
		self.parent.updatemenubar()
		
	#
	# Methods for getting at the anchors
	#
	def GetHRefs(self):
		return self.anchor_hrefs[:]
		
	def setanchorcallback(self, cb):
		self._cbanchor = cb
								
		
	#
	# Methods for writer class for html formatter
	#
	
	def html_init(self):
		self.para_count = 0
		self.html_font = [0, 0, 0, 0]
		self.html_style = 0
		self.html_color = self.fg_color
		self.new_font(self.html_font)
		self.last_anchor_begin_pos = -1
		self.anchor_offsets = []
	
	def new_font(self, font):
		self.delayed_para_send()
##		print 'FONT', font # DBG
		if font == None:
			font = (0, 0, 0, 0)
		font = map(lambda x:x, font)
		for i in range(len(font)):
			if font[i] == None:
				font[i] = self.html_font[i]
		[size, italic, bold, tt] = font
		self.html_font = font[:]
		if tt:
			font = self.font_tt
		else:
			font = self.font_normal
		if HTML_SIZE.has_key(size):
			size = HTML_SIZE[size]
		else:
			size = 0
		size = size + self.font_size
		face = 0
		if bold: face = face | 1
		if italic: face = face | 2
		face = face | self.html_style
		self.ted.WESetStyle(WASTEconst.weDoFont | WASTEconst.weDoFace | 
				WASTEconst.weDoReplaceFace | WASTEconst.weDoSize | WASTEconst.weDoColor,
				(font, face, size, self.html_color))
		
	def new_margin(self, margin, level):
		self.delayed_para_send()
		self.ted.WEInsert('[Margin %s %s]'%(margin, level), None, None)
		
	def new_spacing(self, spacing):
		self.delayed_para_send()
		self.ted.WEInsert('[spacing %s]'%spacing, None, None)
			
	def new_styles(self, styles):
		self.delayed_para_send()
		self.html_style = 0
		self.html_color = self.fg_color
		if 'anchor' in styles:
			self.html_style = self.html_style | 4
			self.html_color = self.an_color
			dummy, self.anchor_begin_pos = self.ted.WEGetSelection()
		elif self.anchor_begin_pos >= 0:
			dummy, endpos = self.ted.WEGetSelection()
			self.anchor_offsets.append(self.anchor_begin_pos, endpos)
			self.anchor_begin_pos = -1
		self.new_font(self.html_font)

	def send_paragraph(self, blankline):
		self.para_count = self.para_count + blankline + 1
		
	def delayed_para_send(self):
		if not self.para_count: return
		self.ted.WEInsert('\r'*self.para_count, None, None)
		self.para_count = 0
		
	def send_line_break(self):
		self.delayed_para_send()
		self.ted.WEInsert('\r', None, None)
		
	def send_hor_rule(self, *args, **kw):
		# Ignore ruler options, for now
		self.delayed_para_send()
		dummydata = Res.Resource('')
		self.ted.WEInsertObject('rulr', dummydata, (0,0))
		
	def send_label_data(self, data):
		self.delayed_para_send()
		self.ted.WEInsert(data, None, None)
		
	def send_flowing_data(self, data):
		self.delayed_para_send()
		self.ted.WEInsert(data, None, None)
		
	def send_literal_data(self, data):
		self.delayed_para_send()
		data = regsub.gsub('\n', '\r', data)
		data = string.expandtabs(data)
		self.ted.WEInsert(data, None, None)
		
	def send_image(self, data):
		self.delayed_para_send()
		self.ted.WEInsertObject('GIF ', data, (0, 0))
		
class MyFormatter(formatter.AbstractFormatter):

	def __init__(self, writer):
		formatter.AbstractFormatter.__init__(self, writer)
		self.parskip = 1
		
	def my_add_image(self, image):
		self.writer.send_image(image)
			
class MyHTMLParser(htmllib.HTMLParser):
	
	def anchor_bgn(self, href, name, type):
		self.anchor = href
		if self.anchor:
			self.anchorlist.append(href)
			self.formatter.push_style('anchor')

	def anchor_end(self):
		if self.anchor:
			self.anchor = None
			self.formatter.pop_style()
			
	def end_p(self):
		# It seems most browsers treat </p> as <p>...
		self.do_p(())
	
	def start_p(self, attrs):
		# It seems most browsers treat </p> as <p>...
		self.do_p(attrs)
	
	def handle_image(self, src, alt, ismap, align, width, height):
##		print 'IMAGE', self.url, src
		url = urllib.basejoin(self.url, src)
##		print 'URL', url
		fname = urllib.urlretrieve(url)[0]
##		print 'FILENAME', fname
		try:
			image = img.reader(imgformat.macrgb16, fname)
		except img.error:
			print 'Html: failed to get image', fname
			self.formatter.add_flowing_data(alt)
			return
		data = image.read()
		handle = _gifkeeper.new(fname, image.width, image.height, data)
		self.formatter.my_add_image(handle)


waste_inited = 0

def init_waste():
	global waste_inited
	if waste_inited:
		return
	waste_inited = 1
	# Ruler handlers
	waste.WEInstallObjectHandler('rulr', 'new ', newRuler)
	waste.WEInstallObjectHandler('rulr', 'draw', drawRuler)
	waste.WEInstallObjectHandler('rulr', 'free', freeRuler)
	# GIF handlers
	waste.WEInstallObjectHandler('GIF ', 'new ', newGIF)
	waste.WEInstallObjectHandler('GIF ', 'draw', drawGIF)
	waste.WEInstallObjectHandler('GIF ', 'free', freeGIF)
	
def newRuler(obj):
	"""Insert a new ruler. Make it as wide as the window minus 2 pixels"""
	ted = obj.WEGetObjectOwner()
	l, t, r, b = ted.WEGetDestRect()
	return r-l, 4
	
def drawRuler((l, t, r, b), obj):
	y = (t+b)/2
	Qd.MoveTo(l+2, y)
	Qd.LineTo(r-2, y)
	return 0
	
def freeRuler(*args):
	return 0
	
def newGIF(obj):
	handle = obj.WEGetObjectDataHandle()
	width, height, pixmap = _gifkeeper.get(handle.data)
	return width+2*IMAGEBORDER, height+2*IMAGEBORDER
	
def drawGIF((l,t,r,b),obj):
	handle = obj.WEGetObjectDataHandle()
	width, height, pixmap = _gifkeeper.get(handle.data)
	srcrect = 0, 0, width, height
	dstrect = l+IMAGEBORDER, t+IMAGEBORDER, r-IMAGEBORDER, b-IMAGEBORDER
	port = Qd.GetPort()
	bg = port.rgbBkColor
	fg = port.rgbFgColor
	Qd.RGBBackColor((0xffff, 0xffff, 0xffff))
	Qd.RGBForeColor((0,0,0))
##	Qd.CopyBits(pixmap, port.portBits, srcrect, dstrect,
##		QuickDraw.srcCopy+QuickDraw.ditherCopy, None)
	Qd.CopyBits(pixmap, port.portBits, srcrect, dstrect,
		QuickDraw.srcCopy, None)
	Qd.RGBBackColor(bg)
	Qd.RGBForeColor(fg)
	# XXXX paste pixmap on screen
	return 0
	
def freeGIF(obj):
	handle = obj.WEGetObjectDataHandle()
	_gifkeeper.delete(handle.data)
	return 0
	
class _Gifkeeper:
	def __init__(self):
		self.dict = {}
		
	def new(self, name, width, height, data):
		if self.dict.has_key(name):
			self.dict[name][0] = self.dict[name][0] + 1
			return self.dict[name][1]
		pixmap = mac_image.mkpixmap(width, height, imgformat.macrgb16, data)
		handle = Res.Resource(name)
		self.dict[name] = [1, handle, pixmap, data, width, height]
		return handle
		
	def get(self, name):
		[cnt, handle, pixmap, data, width, height] = self.dict[name]
		return width, height, pixmap
		
	def delete(self, name):
		self.dict[name][0] = self.dict[name][0] - 1
		if self.dict[name][0] == 0:
			del self.dict[name]
			
_gifkeeper = _Gifkeeper()


	

--===_0_Mon_Mar_17_16:53:53_CET_1997--



_______________
PYTHONMAC-SIG  - SIG on Python for the Apple Macintosh

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