mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			411 lines
		
	
	
	
		
			9.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			411 lines
		
	
	
	
		
			9.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
| # Module 'Buttons'
 | |
| 
 | |
| 
 | |
| # Import module 'rect' renamed as '_rect' to avoid exporting it on
 | |
| # 'from Buttons import *'
 | |
| #
 | |
| import rect
 | |
| _rect = rect
 | |
| del rect
 | |
| 
 | |
| 
 | |
| # Field indices in mouse event detail
 | |
| #
 | |
| _HV = 0
 | |
| _CLICKS = 1
 | |
| _BUTTON = 2
 | |
| _MASK = 3
 | |
| 
 | |
| 
 | |
| # LabelAppearance provides defaults for all appearance methods.
 | |
| # selected state not visible
 | |
| # disabled --> crossed out
 | |
| # hilited  --> inverted
 | |
| #
 | |
| class LabelAppearance:
 | |
| 	#
 | |
| 	# Initialization
 | |
| 	#
 | |
| 	def init_appearance(self):
 | |
| 		self.bounds = _rect.empty
 | |
| 		self.enabled = 1
 | |
| 		self.hilited = 0
 | |
| 		self.selected = 0
 | |
| 		self.text = ''
 | |
| 	#
 | |
| 	# Size enquiry
 | |
| 	#
 | |
| 	def getminsize(self, (m, (width, height))):
 | |
| 		width = max(width, m.textwidth(self.text) + 6)
 | |
| 		height = max(height, m.lineheight() + 6)
 | |
| 		return width, height
 | |
| 	#
 | |
| 	def getbounds(self):
 | |
| 		return self.bounds
 | |
| 	#
 | |
| 	# Changing the parameters
 | |
| 	#
 | |
| 	def settext(self, text):
 | |
| 		self.text = text
 | |
| 		if self.bounds <> _rect.empty:
 | |
| 			self.recalctextpos()
 | |
| 			self.redraw()
 | |
| 	#
 | |
| 	def setbounds(self, bounds):
 | |
| 		self.bounds = bounds
 | |
| 		if self.bounds <> _rect.empty:
 | |
| 			self.recalc()
 | |
| 	#
 | |
| 	def realize(self):
 | |
| 		pass
 | |
| 	#
 | |
| 	# Changing the state bits
 | |
| 	#
 | |
| 	def enable(self, flag):
 | |
| 		if flag <> self.enabled:
 | |
| 			self.enabled = flag
 | |
| 			if self.bounds <> _rect.empty:
 | |
| 				self.flipenable(self.parent.begindrawing())
 | |
| 	#
 | |
| 	def hilite(self, flag):
 | |
| 		if flag <> self.hilited:
 | |
| 			self.hilited = flag
 | |
| 			if self.bounds <> _rect.empty:
 | |
| 				self.fliphilite(self.parent.begindrawing())
 | |
| 	#
 | |
| 	def select(self, flag):
 | |
| 		if flag <> self.selected:
 | |
| 			self.selected = flag
 | |
| 			if self.bounds <> _rect.empty:
 | |
| 				self.redraw()
 | |
| 	#
 | |
| 	# Recalculate the box bounds and text position.
 | |
| 	# This can be overridden by buttons that draw different boxes
 | |
| 	# or want their text in a different position.
 | |
| 	#
 | |
| 	def recalc(self):
 | |
| 		if self.bounds <> _rect.empty:
 | |
| 			self.recalcbounds()
 | |
| 			self.recalctextpos()
 | |
| 	#
 | |
| 	def recalcbounds(self):
 | |
| 		self.hilitebounds = _rect.inset(self.bounds, (3, 3))
 | |
| 		self.crossbounds = self.bounds
 | |
| 	#
 | |
| 	def recalctextpos(self):
 | |
| 		(left, top), (right, bottom) = self.bounds
 | |
| 		m = self.parent.beginmeasuring()
 | |
| 		h = (left + right - m.textwidth(self.text)) / 2
 | |
| 		v = (top + bottom - m.lineheight()) / 2
 | |
| 		self.textpos = h, v
 | |
| 	#
 | |
| 	# Generic drawing interface.
 | |
| 	# Do not override redraw() or draw() methods; override drawit() c.s.
 | |
| 	#
 | |
| 	def redraw(self):
 | |
| 		if self.bounds <> _rect.empty:
 | |
| 			d = self.parent.begindrawing()
 | |
| 			d.erase(self.bounds)
 | |
| 			self.draw(d, self.bounds)
 | |
| 	#
 | |
| 	def draw(self, (d, area)):
 | |
| 		area = _rect.intersect(area, self.bounds)
 | |
| 		if area = _rect.empty:
 | |
| 			return
 | |
| 		d.cliprect(area)
 | |
| 		self.drawit(d)
 | |
| 		d.noclip()
 | |
| 	#
 | |
| 	# The drawit() method is fairly generic but may be overridden.
 | |
| 	#
 | |
| 	def drawit(self, d):
 | |
| 		self.drawpict(d)
 | |
| 		if self.text:
 | |
| 			d.text(self.textpos, self.text)
 | |
| 		if not self.enabled:
 | |
| 			self.flipenable(d)
 | |
| 		if self.hilited:
 | |
| 			self.fliphilite(d)
 | |
| 	#
 | |
| 	# Default drawing detail functions.
 | |
| 	# Overriding these is normally sufficient to get different
 | |
| 	# appearances.
 | |
| 	#
 | |
| 	def drawpict(self, d):
 | |
| 		pass
 | |
| 	#
 | |
| 	def flipenable(self, d):
 | |
| 		_xorcross(d, self.crossbounds)
 | |
| 	#
 | |
| 	def fliphilite(self, d):
 | |
| 		d.invert(self.hilitebounds)
 | |
| 
 | |
| 
 | |
| # A Strut is a label with no width of its own.
 | |
| 
 | |
| class StrutAppearance(LabelAppearance):
 | |
| 	#
 | |
| 	def getminsize(self, (m, (width, height))):
 | |
| 		height = max(height, m.lineheight() + 6)
 | |
| 		return width, height
 | |
| 	#
 | |
| 
 | |
| 
 | |
| # ButtonAppearance displays a centered string in a box.
 | |
| # selected --> bold border
 | |
| # disabled --> crossed out
 | |
| # hilited  --> inverted
 | |
| #
 | |
| class ButtonAppearance(LabelAppearance):
 | |
| 	#
 | |
| 	def drawpict(self, d):
 | |
| 		d.box(_rect.inset(self.bounds, (1, 1)))
 | |
| 		if self.selected:
 | |
| 			# Make a thicker box
 | |
| 			d.box(self.bounds)
 | |
| 			d.box(_rect.inset(self.bounds, (2, 2)))
 | |
| 			d.box(_rect.inset(self.bounds, (3, 3)))
 | |
| 	#
 | |
| 
 | |
| 
 | |
| # CheckAppearance displays a small square box and a left-justified string.
 | |
| # selected --> a cross appears in the box
 | |
| # disabled --> whole button crossed out
 | |
| # hilited  --> box is inverted
 | |
| #
 | |
| class CheckAppearance(LabelAppearance):
 | |
| 	#
 | |
| 	def getminsize(self, (m, (width, height))):
 | |
| 		minwidth = m.textwidth(self.text) + 6
 | |
| 		minheight = m.lineheight() + 6
 | |
| 		width = max(width, minwidth + minheight + m.textwidth(' '))
 | |
| 		height = max(height, minheight)
 | |
| 		return width, height
 | |
| 	#
 | |
| 	def drawpict(self, d):
 | |
| 		d.box(self.boxbounds)
 | |
| 		if self.selected: _xorcross(d, self.boxbounds)
 | |
| 	#
 | |
| 	def recalcbounds(self):
 | |
| 		LabelAppearance.recalcbounds(self)
 | |
| 		(left, top), (right, bottom) = self.bounds
 | |
| 		self.size = bottom - top - 4
 | |
| 		self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2)
 | |
| 		self.hilitebounds = self.boxbounds
 | |
| 	#
 | |
| 	def recalctextpos(self):
 | |
| 		m = self.parent.beginmeasuring()
 | |
| 		(left, top), (right, bottom) = self.boxbounds
 | |
| 		h = right + m.textwidth(' ')
 | |
| 		v = top + (self.size - m.lineheight()) / 2
 | |
| 		self.textpos = h, v
 | |
| 	#
 | |
| 
 | |
| 
 | |
| # RadioAppearance displays a round indicator and a left-justified string.
 | |
| # selected --> a dot appears in the indicator
 | |
| # disabled --> whole button crossed out
 | |
| # hilited  --> indicator is inverted
 | |
| #
 | |
| class RadioAppearance(CheckAppearance):
 | |
| 	#
 | |
| 	def drawpict(self, d):
 | |
| 		(left, top), (right, bottom) = self.boxbounds
 | |
| 		radius = self.size / 2
 | |
| 		center = left + radius, top + radius
 | |
| 		d.circle(center, radius)
 | |
| 		if self.selected:
 | |
| 			d.fillcircle(center, radius*3/5)
 | |
| 	#
 | |
| 
 | |
| 
 | |
| # NoReactivity ignores mouse events.
 | |
| #
 | |
| class NoReactivity:
 | |
| 	def init_reactivity(self): pass
 | |
| 
 | |
| 
 | |
| # BaseReactivity defines hooks and asks for mouse events,
 | |
| # but provides only dummy mouse event handlers.
 | |
| # The trigger methods call the corresponding hooks set by the user.
 | |
| # Hooks (and triggers) mean the following:
 | |
| # down_hook	called on some mouse-down events
 | |
| # move_hook	called on some mouse-move events
 | |
| # up_hook	called on mouse-up events
 | |
| # on_hook	called for buttons with on/off state, when it goes on
 | |
| # hook		called when a button 'fires' or a radiobutton goes on
 | |
| # There are usually extra conditions, e.g., hooks are only called
 | |
| # when the button is enabled, or active, or selected (on).
 | |
| #
 | |
| class BaseReactivity:
 | |
| 	#
 | |
| 	def init_reactivity(self):
 | |
| 		self.down_hook = self.move_hook = self.up_hook = \
 | |
| 			self.on_hook = self.off_hook = \
 | |
| 			self.hook = self.active = 0
 | |
| 		self.parent.need_mouse(self)
 | |
| 	#
 | |
| 	def mousetest(self, hv):
 | |
| 		return _rect.pointinrect(hv, self.bounds)
 | |
| 	#
 | |
| 	def mouse_down(self, detail):
 | |
| 		pass
 | |
| 	#
 | |
| 	def mouse_move(self, detail):
 | |
| 		pass
 | |
| 	#
 | |
| 	def mouse_up(self, detail):
 | |
| 		pass
 | |
| 	#
 | |
| 	def down_trigger(self):
 | |
| 		if self.down_hook: self.down_hook(self)
 | |
| 	#
 | |
| 	def move_trigger(self):
 | |
| 		if self.move_hook: self.move_hook(self)
 | |
| 	#
 | |
| 	def up_trigger(self):
 | |
| 		if self.up_hook: self.up_hook(self)
 | |
| 	#
 | |
| 	def on_trigger(self):
 | |
| 		if self.on_hook: self.on_hook(self)
 | |
| 	#
 | |
| 	def off_trigger(self):
 | |
| 		if self.off_hook: self.off_hook(self)
 | |
| 	#
 | |
| 	def trigger(self):
 | |
| 		if self.hook: self.hook(self)
 | |
| 
 | |
| 
 | |
| # ToggleReactivity acts like a simple pushbutton.
 | |
| # It toggles its hilite state on mouse down events.
 | |
| #
 | |
| class ToggleReactivity(BaseReactivity):
 | |
| 	#
 | |
| 	def mouse_down(self, detail):
 | |
| 		if self.enabled and self.mousetest(detail[_HV]):
 | |
| 			self.active = 1
 | |
| 			self.hilite(not self.hilited)
 | |
| 			self.down_trigger()
 | |
| 	#
 | |
| 	def mouse_move(self, detail):
 | |
| 		if self.active:
 | |
| 			self.move_trigger()
 | |
| 	#
 | |
| 	def mouse_up(self, detail):
 | |
| 		if self.active:
 | |
| 			self.up_trigger()
 | |
| 			self.active = 0
 | |
| 	#
 | |
| 	def down_trigger(self):
 | |
| 		if self.hilited:
 | |
| 			self.on_trigger()
 | |
| 		else:
 | |
| 			self.off_trigger()
 | |
| 		self.trigger()
 | |
| 	#
 | |
| 
 | |
| 
 | |
| # TriggerReactivity acts like a fancy pushbutton.
 | |
| # It hilites itself while the mouse is down within its bounds.
 | |
| #
 | |
| class TriggerReactivity(BaseReactivity):
 | |
| 	#
 | |
| 	def mouse_down(self, detail):
 | |
| 		if self.enabled and self.mousetest(detail[_HV]):
 | |
| 			self.active = 1
 | |
| 			self.hilite(1)
 | |
| 			self.down_trigger()
 | |
| 	#
 | |
| 	def mouse_move(self, detail):
 | |
| 		if self.active:
 | |
| 			self.hilite(self.mousetest(detail[_HV]))
 | |
| 			if self.hilited:
 | |
| 				self.move_trigger()
 | |
| 	#
 | |
| 	def mouse_up(self, detail):
 | |
| 		if self.active:
 | |
| 			self.hilite(self.mousetest(detail[_HV]))
 | |
| 			if self.hilited:
 | |
| 				self.up_trigger()
 | |
| 				self.trigger()
 | |
| 			self.active = 0
 | |
| 			self.hilite(0)
 | |
| 	#
 | |
| 
 | |
| 
 | |
| # CheckReactivity handles mouse events like TriggerReactivity,
 | |
| # It overrides the up_trigger method to flip its selected state.
 | |
| #
 | |
| class CheckReactivity(TriggerReactivity):
 | |
| 	#
 | |
| 	def up_trigger(self):
 | |
| 		self.select(not self.selected)
 | |
| 		if self.selected:
 | |
| 			self.on_trigger()
 | |
| 		else:
 | |
| 			self.off_trigger()
 | |
| 		self.trigger()
 | |
| 
 | |
| 
 | |
| # RadioReactivity turns itself on and the other buttons in its group
 | |
| # off when its up_trigger method is called.
 | |
| #
 | |
| class RadioReactivity(TriggerReactivity):
 | |
| 	#
 | |
| 	def init_reactivity(self):
 | |
| 		TriggerReactivity.init_reactivity(self)
 | |
| 		self.group = []
 | |
| 	#
 | |
| 	def up_trigger(self):
 | |
| 		for b in self.group:
 | |
| 			if b <> self:
 | |
| 				if b.selected:
 | |
| 					b.select(0)
 | |
| 					b.off_trigger()
 | |
| 		self.select(1)
 | |
| 		self.on_trigger()
 | |
| 		self.trigger()
 | |
| 
 | |
| 
 | |
| # Auxiliary class for 'define' method.
 | |
| # Call the initializers in the right order.
 | |
| #
 | |
| class Define:
 | |
| 	#
 | |
| 	def define(self, parent):
 | |
| 		self.parent = parent
 | |
| 		parent.addchild(self)
 | |
| 		self.init_appearance()
 | |
| 		self.init_reactivity()
 | |
| 		return self
 | |
| 	#
 | |
| 	def destroy(self):
 | |
| 		self.parent = 0
 | |
| 	#
 | |
| 	def definetext(self, (parent, text)):
 | |
| 		self = self.define(parent)
 | |
| 		self.settext(text)
 | |
| 		return self
 | |
| 
 | |
| 
 | |
| # Subroutine to cross out a rectangle.
 | |
| #
 | |
| def _xorcross(d, bounds):
 | |
| 	((left, top), (right, bottom)) = bounds
 | |
| 	# This is s bit funny to make it look better
 | |
| 	left = left + 2
 | |
| 	right = right - 2
 | |
| 	top = top + 2
 | |
| 	bottom = bottom - 3
 | |
| 	d.xorline(((left, top), (right, bottom)))
 | |
| 	d.xorline((left, bottom), (right, top))
 | |
| 
 | |
| 
 | |
| # Ready-made button classes.
 | |
| #
 | |
| class Label(NoReactivity, LabelAppearance, Define): pass
 | |
| class Strut(NoReactivity, StrutAppearance, Define): pass
 | |
| class PushButton(TriggerReactivity, ButtonAppearance, Define): pass
 | |
| class CheckButton(CheckReactivity, CheckAppearance, Define): pass
 | |
| class RadioButton(RadioReactivity, RadioAppearance, Define): pass
 | |
| class ToggleButton(ToggleReactivity, ButtonAppearance, Define): pass
 | 
