import Qd
import TE
import Fm
import waste
import WASTEconst
import Res
import Evt
import Events
import Scrap
import string

import Win
import Wbase
import Wkeys
import Wcontrols
import PyFontify
from types import *
import Fonts
import TextEdit



class TextBox(Wbase.Widget):
	
	"""A static text widget"""
	
	def __init__(self, possize, text="", align=TextEdit.teJustLeft, 
				fontsettings=None,
				backgroundcolor=(0xffff, 0xffff, 0xffff)
				):
		if fontsettings is None:
			import W
			fontsettings = W.getdefaultfont()
		Wbase.Widget.__init__(self, possize)
		self.fontsettings = fontsettings
		self.text = text
		self.align = align
		self._backgroundcolor = backgroundcolor
	
	def draw(self, visRgn = None):
		if self._visible:
			(font, style, size, color) = self.fontsettings
			fontid = GetFNum(font)
			savestate = Qd.GetPenState()
			Qd.TextFont(fontid)
			Qd.TextFace(style)
			Qd.TextSize(size)
			Qd.RGBForeColor(color)
			Qd.RGBBackColor(self._backgroundcolor)
			TE.TETextBox(self.text, self._bounds, self.align)
			Qd.RGBBackColor((0xffff, 0xffff, 0xffff))
			Qd.SetPenState(savestate)
	
	def get(self):
		return self.text
	
	def set(self, text):
		self.text = text
		if self._parentwindow and self._parentwindow.wid:
			self.SetPort()
			self.draw()


class _ScrollWidget:
	
	# to be overridden
	def getscrollbarvalues(self):
		return None, None
	
	# internal method
	def updatescrollbars(self):
		vx, vy = self.getscrollbarvalues()
		if self._parent._barx:
			if vx <> None:
				self._parent._barx.enable(1)
				self._parent._barx.set(vx)
			else:
				self._parent._barx.enable(0)
		if self._parent._bary:
			if vy <> None:
				self._parent._bary.enable(1)
				self._parent._bary.set(vy)
			else:
				self._parent._bary.enable(0)
	

UNDOLABELS = [	# Indexed by WEGetUndoInfo() value
	None, "", "typing", "Cut", "Paste", "Clear", "Drag", "Style",
	"Ruler", "backspace", "delete", "transform", "resize"]


class EditText(Wbase.SelectableWidget, _ScrollWidget):
	
	"""A text edit widget, mainly for simple entry fields."""
	
	def __init__(self, possize, text="", 
				callback=None, inset=(3, 3), 
				fontsettings=None,
				tabsettings = (32, 0),
				readonly = 0):
		if fontsettings is None:
			import W
			fontsettings = W.getdefaultfont()
		Wbase.SelectableWidget.__init__(self, possize)
		self.temptext = text
		self.ted = None
		self.selection = None
		self._callback = callback
		self.changed = 0
		self.selchanged = 0
		self._selected = 0
		self._enabled = 1
		self.wrap = 1
		self.readonly = readonly
		self.fontsettings = fontsettings
		self.tabsettings = tabsettings
		if type(inset) <> TupleType:
			self.inset = (inset, inset)
		else:
			self.inset = inset
	
	def open(self):
		if not hasattr(self._parent, "_barx"):
			self._parent._barx = None
		if not hasattr(self._parent, "_bary"):
			self._parent._bary = None
		self._calcbounds()
		self.SetPort()
		viewrect, destrect = self._calctextbounds()
		flags = self._getflags()
		self.ted = waste.WENew(destrect, viewrect, flags)
		self.ted.WEInstallTabHooks()
		self.ted.WESetAlignment(WASTEconst.weFlushLeft)
		self.setfontsettings(self.fontsettings)
		self.settabsettings(self.tabsettings)
		self.ted.WEUseText(Res.Resource(self.temptext))
		self.ted.WECalText()
		if self.selection:
			self.setselection(self.selection[0], self.selection[1])
			self.selection = None
		else:
			self.selview()
		self.temptext = None
		self.updatescrollbars()
		self.bind("pageup", self.scrollpageup)
		self.bind("pagedown", self.scrollpagedown)
		self.bind("top", self.scrolltop)
		self.bind("bottom", self.scrollbottom)
		self.selchanged = 0
	
	def close(self):
		self._parent._barx = None
		self._parent._bary = None
		self.ted = None
		self.temptext = None
		Wbase.SelectableWidget.close(self)
	
	def gettabsettings(self):
		return self.tabsettings
	
	def settabsettings(self, (tabsize, tabmode)):
		self.tabsettings = (tabsize, tabmode)
		if hasattr(self.ted, "WESetTabSize"):
			port = self._parentwindow.wid.GetWindowPort()
			if tabmode:
				(font, style, size, color) = self.getfontsettings()
				savesettings = GetPortFontSettings(port)
				SetPortFontSettings(port, (font, style, size))
				tabsize = Qd.StringWidth(' ' * tabsize)
				SetPortFontSettings(port, savesettings)
			tabsize = max(tabsize, 1)
			self.ted.WESetTabSize(tabsize)
			self.SetPort()
			Qd.EraseRect(self.ted.WEGetViewRect())
			self.ted.WEUpdate(port.visRgn)
	
	def getfontsettings(self):
		import Res
		(font, style, size, color) = self.ted.WEGetRunInfo(0)[4]
		font = Fm.GetFontName(font)
		return (font, style, size, color)
	
	def setfontsettings(self, (font, style, size, color)):
		self.SetPort()
		if type(font) <> StringType:
			font = Fm.GetFontName(font)
		self.fontsettings = (font, style, size, color)
		fontid = GetFNum(font)
		readonly = self.ted.WEFeatureFlag(WASTEconst.weFReadOnly, -1)
		if readonly:
			self.ted.WEFeatureFlag(WASTEconst.weFReadOnly, 0)
		try:
			self.ted.WEFeatureFlag(WASTEconst.weFInhibitRecal, 1)
			selstart, selend = self.ted.WEGetSelection()
			self.ted.WESetSelection(0, self.ted.WEGetTextLength())
			self.ted.WESetStyle(WASTEconst.weDoFace, (0, 0, 0, (0, 0, 0)))
			self.ted.WESetStyle(WASTEconst.weDoFace | 
						WASTEconst.weDoColor | 
						WASTEconst.weDoFont | 
						WASTEconst.weDoSize, 
						(fontid, style, size, color))
			self.ted.WEFeatureFlag(WASTEconst.weFInhibitRecal, 0)
			self.ted.WECalText()
			self.ted.WESetSelection(selstart, selend)
		finally:
			if readonly:
				self.ted.WEFeatureFlag(WASTEconst.weFReadOnly, 1)
		viewrect = self.ted.WEGetViewRect()
		Qd.EraseRect(viewrect)
		self.ted.WEUpdate(self._parentwindow.wid.GetWindowPort().visRgn)
		self.selchanged = 1
		self.updatescrollbars()
	
	def adjust(self, oldbounds):
		self.SetPort()
		if self._selected and self._parentwindow._hasselframes:
			self.GetWindow().InvalWindowRect(Qd.InsetRect(oldbounds, -3, -3))
			self.GetWindow().InvalWindowRect(Qd.InsetRect(self._bounds, -3, -3))
		else:
			self.GetWindow().InvalWindowRect(oldbounds)
			self.GetWindow().InvalWindowRect(self._bounds)
		viewrect, destrect = self._calctextbounds()
		self.ted.WESetViewRect(viewrect)
		self.ted.WESetDestRect(destrect)
		if self.wrap:
			self.ted.WECalText()
		if self.ted.WEGetDestRect()[3] < viewrect[1]:
			self.selview()
		self.updatescrollbars()
	
	# interface -----------------------
	# selection stuff
	def selview(self):
		self.ted.WESelView()
	
	def selectall(self):
		self.ted.WESetSelection(0, self.ted.WEGetTextLength())
		self.selchanged = 1
		self.updatescrollbars()
	
	def selectline(self, lineno, charoffset = 0):
		newselstart, newselend = self.ted.WEGetLineRange(lineno)
		# Autoscroll makes the *end* of the selection visible, which, 
		# in the case of a whole line, is the beginning of the *next* line. 
		# So sometimes it leaves our line just above the view rect. 
		# Let's fool Waste by initially selecting one char less:
		self.ted.WESetSelection(newselstart + charoffset, newselend-1)
		self.ted.WESetSelection(newselstart + charoffset, newselend)
		self.selchanged = 1
		self.updatescrollbars()
	
	def getselection(self):
		if self.ted:
			return self.ted.WEGetSelection()
		else:
			return self.selection
	
	def setselection(self, selstart, selend):
		self.selchanged = 1
		if self.ted:
			self.ted.WESetSelection(selstart, selend)
			self.ted.WESelView()
			self.updatescrollbars()
		else:
			self.selection = selstart, selend
	
	def offsettoline(self, offset):
		return self.ted.WEOffsetToLine(offset)
	
	def countlines(self):
		return self.ted.WECountLines()
	
	def getselectedtext(self):
		selstart, selend = self.ted.WEGetSelection()
		return self.ted.WEGetText().data[selstart:selend]
	
	def expandselection(self):
		oldselstart, oldselend = self.ted.WEGetSelection()
		selstart, selend = min(oldselstart, oldselend), max(oldselstart, oldselend)
		if selstart <> selend and chr(self.ted.WEGetChar(selend-1)) == '\r':
			selend = selend - 1
		newselstart, dummy = self.ted.WEFindLine(selstart, 0)
		dummy, newselend = self.ted.WEFindLine(selend, 0)
		if oldselstart <> newselstart or  oldselend <> newselend:
			self.ted.WESetSelection(newselstart, newselend)
			self.updatescrollbars()
		self.selchanged = 1
	
	def insert(self, text):
		self.ted.WEInsert(text, None, None)
		self.changed = 1
		self.selchanged = 1
	
	# text
	def set(self, text):
		if not self.ted:
			self.temptext = text
		else:
			self.ted.WEUseText(Res.Resource(text))
			self.ted.WECalText()
			self.SetPort()
			viewrect, destrect = self._calctextbounds()
			self.ted.WESetViewRect(viewrect)
			self.ted.WESetDestRect(destrect)
			rgn = Qd.NewRgn()
			Qd.RectRgn(rgn, viewrect)
			Qd.EraseRect(viewrect)
			self.draw(rgn)
			#self.GetWindow().InvalWindowRect(self.ted.WEGetViewRect())
			self.updatescrollbars()
	
	def get(self):
		if not self._parent:
			return self.temptext
		else:
			return self.ted.WEGetText().data
	
	# events
	def key(self, char, event):
		(what, message, when, where, modifiers) = event
		if self._enabled and not modifiers & Events.cmdKey or char in Wkeys.arrowkeys:
			self.ted.WEKey(ord(char), modifiers)
			if char not in Wkeys.navigationkeys:
				self.changed = 1
			if char not in Wkeys.scrollkeys:
				self.selchanged = 1
			self.updatescrollbars()
			if self._callback:
				Wbase.CallbackCall(self._callback, 0, char, modifiers)
	
	def click(self, point, modifiers):
		if not self._enabled:
			return
		self.ted.WEClick(point, modifiers, Evt.TickCount())
		self.selchanged = 1
		self.updatescrollbars()
		return 1
	
	def idle(self):
		self.SetPort()
		self.ted.WEIdle()
	
	def rollover(self, point, onoff):
		if onoff:
			Wbase.SetCursor("iBeam")
	
	def activate(self, onoff):
		self._activated = onoff
		if self._selected and self._visible:
			if onoff:
				self.ted.WEActivate()
			else:
				self.ted.WEDeactivate()
			if self._selected:
				self.drawselframe(onoff)
	
	def select(self, onoff, isclick = 0):
		if Wbase.SelectableWidget.select(self, onoff):
			return
		self.SetPort()
		if onoff:
			self.ted.WEActivate()
			if self._parentwindow._tabbable and not isclick:
				self.selectall()
		else:
			self.ted.WEDeactivate()
		self.drawselframe(onoff)
	
	def draw(self, visRgn = None):
		if self._visible:
			if not visRgn:
				visRgn = self._parentwindow.wid.GetWindowPort().visRgn
			self.ted.WEUpdate(visRgn)
			if self._selected and self._activated:
				self.drawselframe(1)
			Qd.FrameRect(self._bounds)
	
	# scrolling
	def scrollpageup(self):
		if self._parent._bary and self._parent._bary._enabled:
			self.vscroll("++")
	
	def scrollpagedown(self):
		if self._parent._bary and self._parent._bary._enabled:
			self.vscroll("--")
	
	def scrolltop(self):
		if self._parent._bary and self._parent._bary._enabled:
			self.vscroll(0)
		if self._parent._barx and self._parent._barx._enabled:
			self.hscroll(0)
	
	def scrollbottom(self):
		if self._parent._bary and self._parent._bary._enabled:
			self.vscroll(32767)
	
	# menu handlers
	def domenu_copy(self, *args):
		selbegin, selend = self.ted.WEGetSelection()
		if selbegin == selend:
			return
		Scrap.ZeroScrap()
		self.ted.WECopy()
		self.updatescrollbars()
	
	def domenu_cut(self, *args):
		selbegin, selend = self.ted.WEGetSelection()
		if selbegin == selend:
			return
		Scrap.ZeroScrap()
		self.ted.WECut()
		self.updatescrollbars()
		self.selview()
		self.changed = 1
		self.selchanged = 1
		if self._callback:
			Wbase.CallbackCall(self._callback, 0, "", None)
	
	def domenu_paste(self, *args):
		if not self.ted.WECanPaste():
			return
		self.selview()
		self.ted.WEPaste()
		self.updatescrollbars()
		self.changed = 1
		self.selchanged = 1
		if self._callback:
			Wbase.CallbackCall(self._callback, 0, "", None)
	
	def domenu_clear(self, *args):
		self.ted.WEDelete()
		self.selview()
		self.updatescrollbars()
		self.changed = 1
		self.selchanged = 1
		if self._callback:
			Wbase.CallbackCall(self._callback, 0, "", None)
	
	def domenu_undo(self, *args):
		which, redo = self.ted.WEGetUndoInfo()
		if not which: 
			return
		self.ted.WEUndo()
		self.updatescrollbars()
		self.changed = 1
		self.selchanged = 1
		if self._callback:
			Wbase.CallbackCall(self._callback, 0, "", None)
	
	def can_undo(self, menuitem):
		#doundo = self.ted.WEFeatureFlag(WASTEconst.weFUndo, -1)
		#print doundo
		#if not doundo:
		#	return 0
		which, redo = self.ted.WEGetUndoInfo()
		if which < len(UNDOLABELS):
			which = UNDOLABELS[which]
		else:
			which = ""
		if which == None: 
			return None
		if redo:
			which = "Redo "+which
		else:
			which = "Undo "+which
		menuitem.settext(which)
		return 1
	
	def domenu_selectall(self, *args):
		self.selectall()
	
	# private
	def getscrollbarvalues(self):
		dr = self.ted.WEGetDestRect()
		vr = self.ted.WEGetViewRect()
		vx = Wcontrols._scalebarvalue(dr[0], dr[2], vr[0], vr[2])
		vy = Wcontrols._scalebarvalue(dr[1], dr[3], vr[1], vr[3])
		return vx, vy
	
	def vscroll(self, value):
		lineheight = self.ted.WEGetHeight(0, 1)
		dr = self.ted.WEGetDestRect()
		vr = self.ted.WEGetViewRect()
		destheight = dr[3] - dr[1]
		viewheight = vr[3] - vr[1]
		viewoffset = maxdelta = vr[1] - dr[1]
		mindelta = vr[3] - dr[3]
		if value == "+":
			delta = lineheight
		elif value == "-":
			delta = - lineheight
		elif value == "++":
			delta = viewheight - lineheight
		elif value == "--":
			delta = lineheight - viewheight
		else:	# in thumb
			cur = (32767L * viewoffset) / (destheight - viewheight)
			delta = (cur-value)*(destheight - viewheight)/32767
			if abs(delta - viewoffset) <=2:
				# compensate for irritating rounding error
				delta = viewoffset
		delta = min(maxdelta, delta)
		delta = max(mindelta, delta)
		self.ted.WEScroll(0, delta)
		self.updatescrollbars()
	
	def hscroll(self, value):
		dr = self.ted.WEGetDestRect()
		vr = self.ted.WEGetViewRect()
		destwidth = dr[2] - dr[0]
		viewwidth = vr[2] - vr[0]
		viewoffset = maxdelta = vr[0] - dr[0]
		mindelta = vr[2] - dr[2]
		if value == "+":
			delta = 32
		elif value == "-":
			delta = - 32
		elif value == "++":
			delta = 0.5 * (vr[2] - vr[0])
		elif value == "--":
			delta = 0.5 * (vr[0] - vr[2])
		else:	# in thumb
			cur = (32767 * viewoffset) / (destwidth - viewwidth)
			delta = (cur-value)*(destwidth - viewwidth)/32767
			if abs(delta - viewoffset) <=2:
				# compensate for irritating rounding error
				delta = viewoffset
		delta = min(maxdelta, delta)
		delta = max(mindelta, delta)
		self.ted.WEScroll(delta, 0)
		self.updatescrollbars()
	
	# some internals
	def _getflags(self):
		flags = WASTEconst.weDoAutoScroll | WASTEconst.weDoMonoStyled
		if self.readonly:
			flags = flags | WASTEconst.weDoReadOnly
		else:
			flags = flags | WASTEconst.weDoUndo
		return flags
	
	def _getviewrect(self):
		return Qd.InsetRect(self._bounds, self.inset[0], self.inset[1])
	
	def _calctextbounds(self):
		viewrect = l, t, r, b = self._getviewrect()
		if self.ted:
			dl, dt, dr, db = self.ted.WEGetDestRect()
			vl, vt, vr, vb = self.ted.WEGetViewRect()
			yshift = t - vt
			if (db - dt) < (b - t):
				destrect = viewrect
			else:
				destrect = l, dt + yshift, r, db + yshift
		else:
			destrect = viewrect
		return viewrect, destrect
		

class TextEditor(EditText):
	
	"""A text edit widget."""
	
	def __init__(self, possize, text="", callback=None, wrap=1, inset=(4, 4),
				fontsettings=None,
				tabsettings=(32, 0),
				readonly=0):
		EditText.__init__(self, possize, text, callback, inset, fontsettings, tabsettings, readonly)
		self.wrap = wrap
	
	def _getflags(self):
		flags = WASTEconst.weDoAutoScroll | WASTEconst.weDoMonoStyled | \
				WASTEconst.weDoOutlineHilite
		if self.readonly:
			flags = flags | WASTEconst.weDoReadOnly
		else:
			flags = flags | WASTEconst.weDoUndo
		return flags
	
	def _getviewrect(self):
		l, t, r, b = self._bounds
		return (l + 5, t + 2, r, b - 2)
	
	def _calctextbounds(self):
		if self.wrap:
			return EditText._calctextbounds(self)
		else:
			viewrect = l, t, r, b = self._getviewrect()
			if self.ted:
				dl, dt, dr, db = self.ted.WEGetDestRect()
				vl, vt, vr, vb = self.ted.WEGetViewRect()
				xshift = l - vl
				yshift = t - vt
				if (db - dt) < (b - t):
					yshift = t - dt
				destrect = (dl + xshift, dt + yshift, dr + xshift, db + yshift)
			else:
				destrect = (l, t, r + 5000, b)
			return viewrect, destrect
	
	def draw(self, visRgn = None):
		if self._visible:
			if not visRgn:
				visRgn = self._parentwindow.wid.GetWindowPort().visRgn
			self.ted.WEUpdate(visRgn)
			if self._selected and self._activated:
				self.drawselframe(1)


import re
commentPat = re.compile("[ \t]*\(#\)")
indentPat = re.compile("\t*")

class PyEditor(TextEditor):
	
	"""A specialized Python source edit widget"""
	
	def __init__(self, possize, text="", callback=None, inset=(4, 4),
				fontsettings=None,
				tabsettings=(32, 0),
				readonly=0,
				debugger=None,
				file=''):
		TextEditor.__init__(self, possize, text, callback, 0, inset, fontsettings, tabsettings, readonly)
		self.bind("cmd[", self.domenu_shiftleft)
		self.bind("cmd]", self.domenu_shiftright)
		self.bind("cmdshift[", self.domenu_uncomment)
		self.bind("cmdshift]", self.domenu_comment)
		self.file = file	# only for debugger reference
		self._debugger = debugger
		if debugger:
			debugger.register_editor(self, self.file)
	
	def domenu_shiftleft(self):
		self.expandselection()
		selstart, selend = self.ted.WEGetSelection()
		selstart, selend = min(selstart, selend), max(selstart, selend)
		snippet = self.getselectedtext()
		lines = string.split(snippet, '\r')
		for i in range(len(lines)):
			if lines[i][:1] == '\t':
				lines[i] = lines[i][1:]
		snippet = string.join(lines, '\r')
		self.insert(snippet)
		self.ted.WESetSelection(selstart, selstart + len(snippet))
	
	def domenu_shiftright(self):
		self.expandselection()
		selstart, selend = self.ted.WEGetSelection()
		selstart, selend = min(selstart, selend), max(selstart, selend)
		snippet = self.getselectedtext()
		lines = string.split(snippet, '\r')
		for i in range(len(lines) - (not lines[-1])):
			lines[i] = '\t' + lines[i]
		snippet = string.join(lines, '\r')
		self.insert(snippet)
		self.ted.WESetSelection(selstart, selstart + len(snippet))
	
	def domenu_uncomment(self):
		self.expandselection()
		selstart, selend = self.ted.WEGetSelection()
		selstart, selend = min(selstart, selend), max(selstart, selend)
		snippet = self.getselectedtext()
		lines = string.split(snippet, '\r')
		for i in range(len(lines)):
			m = commentPat.match(lines[i])
			if m:
				pos = m.start(1)
				lines[i] = lines[i][:pos] + lines[i][pos+1:]
		snippet = string.join(lines, '\r')
		self.insert(snippet)
		self.ted.WESetSelection(selstart, selstart + len(snippet))
	
	def domenu_comment(self):
		self.expandselection()
		selstart, selend = self.ted.WEGetSelection()
		selstart, selend = min(selstart, selend), max(selstart, selend)
		snippet = self.getselectedtext()
		lines = string.split(snippet, '\r')
		indent = 3000 # arbitrary large number...
		for line in lines:
			if string.strip(line):
				m = indentPat.match(line)
				if m:
					indent = min(indent, m.regs[0][1])
				else:
					indent = 0
					break
		for i in range(len(lines) - (not lines[-1])):
			lines[i] = lines[i][:indent] + "#" + lines[i][indent:]
		snippet = string.join(lines, '\r')
		self.insert(snippet)
		self.ted.WESetSelection(selstart, selstart + len(snippet))
	
	def setfile(self, file):
		self.file = file
	
	def set(self, text, file = ''):
		oldfile = self.file
		self.file = file
		if self._debugger:
			self._debugger.unregister_editor(self, oldfile)
			self._debugger.register_editor(self, file)
		TextEditor.set(self, text)
	
	def close(self):
		if self._debugger:
			self._debugger.unregister_editor(self, self.file)
			self._debugger = None
		TextEditor.close(self)		
	
	def click(self, point, modifiers):
		if not self._enabled:
			return
		if self._debugger and self.pt_in_breaks(point):
			self.breakhit(point, modifiers)
		elif self._debugger:
			bl, bt, br, bb = self._getbreakrect()
			Qd.EraseRect((bl, bt, br-1, bb))
			TextEditor.click(self, point, modifiers)
			self.drawbreakpoints()
		else:
			TextEditor.click(self, point, modifiers)
			if self.ted.WEGetClickCount() >= 3:
				# select block with our indent
				lines = string.split(self.get(), '\r')
				selstart, selend = self.ted.WEGetSelection()
				lineno = self.ted.WEOffsetToLine(selstart)
				tabs = 0
				line = lines[lineno]
				while line[tabs:] and line[tabs] == '\t':
					tabs = tabs + 1
				tabstag = '\t' * tabs
				fromline = 0
				toline = len(lines)
				if tabs:
					for i in range(lineno - 1, -1, -1):
						line = lines[i]
						if line[:tabs] <> tabstag:
							fromline = i + 1
							break
					for i in range(lineno + 1, toline):
						line = lines[i]
						if line[:tabs] <> tabstag:
							toline = i - 1
							break
				selstart, dummy = self.ted.WEGetLineRange(fromline)
				dummy, selend = self.ted.WEGetLineRange(toline)
				self.ted.WESetSelection(selstart, selend)
	
	def breakhit(self, point, modifiers):
		if not self.file:
			return
		destrect = self.ted.WEGetDestRect()
		offset, edge = self.ted.WEGetOffset(point)
		lineno = self.ted.WEOffsetToLine(offset) + 1
		if point[1] <= destrect[3]:
			self._debugger.clear_breaks_above(self.file, self.countlines())
			self._debugger.toggle_break(self.file, lineno)
		else:
			self._debugger.clear_breaks_above(self.file, lineno)
	
	def key(self, char, event):
		(what, message, when, where, modifiers) = event
		if modifiers & Events.cmdKey and not char in Wkeys.arrowkeys:
			return
		if char == '\r':
			selstart, selend = self.ted.WEGetSelection()
			selstart, selend = min(selstart, selend), max(selstart, selend)
			lastchar = chr(self.ted.WEGetChar(selstart-1))
			if lastchar <> '\r' and selstart:
				pos, dummy = self.ted.WEFindLine(selstart, 0)
				lineres = Res.Resource('')
				self.ted.WECopyRange(pos, selstart, lineres, None, None)
				line = lineres.data + '\n'
				tabcount = self.extratabs(line)
				self.ted.WEKey(ord('\r'), 0)
				for i in range(tabcount):
					self.ted.WEKey(ord('\t'), 0)
			else:
				self.ted.WEKey(ord('\r'), 0)
		elif char in ')]}':
			self.ted.WEKey(ord(char), modifiers)
			self.balanceparens(char)
		else:
			self.ted.WEKey(ord(char), modifiers)
		if char not in Wkeys.navigationkeys:
			self.changed = 1
		self.selchanged = 1
		self.updatescrollbars()
	
	def balanceparens(self, char):
		if char == ')':
			target = '('
		elif char == ']':
			target = '['
		elif char == '}':
			target = '{'
		recursionlevel = 1
		selstart, selend = self.ted.WEGetSelection()
		count = min(selstart, selend) - 2
		mincount = max(0, count - 2048)
		lastquote = None
		while count > mincount:
			testchar = chr(self.ted.WEGetChar(count))
			if testchar in "\"'" and chr(self.ted.WEGetChar(count - 1)) <> '\\':
				if lastquote == testchar:
					recursionlevel = recursionlevel - 1
					lastquote = None
				elif not lastquote:
					recursionlevel = recursionlevel + 1
					lastquote = testchar
			elif not lastquote and testchar == char:
				recursionlevel = recursionlevel + 1
			elif not lastquote and testchar == target:
				recursionlevel = recursionlevel - 1
				if recursionlevel == 0:
					import time
					autoscroll = self.ted.WEFeatureFlag(WASTEconst.weFAutoScroll, -1)
					if autoscroll:
						self.ted.WEFeatureFlag(WASTEconst.weFAutoScroll, 0)
					self.ted.WESetSelection(count, count + 1)
					time.sleep(0.2)
					self.ted.WESetSelection(selstart, selend)
					if autoscroll:
						self.ted.WEFeatureFlag(WASTEconst.weFAutoScroll, 1)
					break
			count = count - 1
	
	def extratabs(self, line):
		tabcount = 0
		for c in line:
			if c <> '\t':
				break
			tabcount = tabcount + 1
		last = 0
		cleanline = ''
		tags = PyFontify.fontify(line)
		# strip comments and strings
		for tag, start, end, sublist in tags:
			if tag in ('string', 'comment'):
				cleanline = cleanline + line[last:start]
				last = end
		cleanline = cleanline + line[last:]
		cleanline = string.strip(cleanline)
		if cleanline and cleanline[-1] == ':':
			tabcount = tabcount + 1
		else:
			# extra indent after unbalanced (, [ or {
			for open, close in (('(', ')'), ('[', ']'), ('{', '}')):
				count = string.count(cleanline, open)
				if count and count > string.count(cleanline, close):
					tabcount = tabcount + 2
					break
		return tabcount
	
	def rollover(self, point, onoff):
		if onoff:
			if self._debugger and self.pt_in_breaks(point):
				Wbase.SetCursor("arrow")
			else:
				Wbase.SetCursor("iBeam")
	
	def draw(self, visRgn = None):
		TextEditor.draw(self, visRgn)
		if self._debugger:
			self.drawbreakpoints()
	
	def showbreakpoints(self, onoff):
		if (not not self._debugger) <> onoff:
			if onoff:
				if not __debug__:
					import W
					raise W.AlertError, "CanŐt debug in –Optimize bytecode” mode.\r(see –Default startup options” in EditPythonPreferences)"
				import PyDebugger
				self._debugger = PyDebugger.getdebugger()
				self._debugger.register_editor(self, self.file)
			elif self._debugger:
				self._debugger.unregister_editor(self, self.file)
				self._debugger = None
			self.adjust(self._bounds)
	
	def togglebreakpoints(self):
		self.showbreakpoints(not self._debugger)
	
	def clearbreakpoints(self):
		if self.file:
			self._debugger.clear_all_file_breaks(self.file)
	
	def editbreakpoints(self):
		if self._debugger:
			self._debugger.edit_breaks()
			self._debugger.breaksviewer.selectfile(self.file)
	
	def drawbreakpoints(self, eraseall = 0):
		breakrect = bl, bt, br, bb = self._getbreakrect()
		br = br - 1
		self.SetPort()
		Qd.PenPat(Qd.qd.gray)
		Qd.PaintRect((br, bt, br + 1, bb))
		Qd.PenNormal()
		self._parentwindow.tempcliprect(breakrect)
		Qd.RGBForeColor((0xffff, 0, 0))
		try:
			lasttop = bt
			self_ted = self.ted
			Qd_PaintOval = Qd.PaintOval
			Qd_EraseRect = Qd.EraseRect
			for lineno in self._debugger.get_file_breaks(self.file):
				start, end = self_ted.WEGetLineRange(lineno - 1)
				if lineno <> self_ted.WEOffsetToLine(start) + 1:
					# breakpoints beyond our text: erase rest, and back out
					Qd_EraseRect((bl, lasttop, br, bb))
					break
				(x, y), h = self_ted.WEGetPoint(start, 0)
				bottom = y + h
				#print y, (lasttop, bottom)
				if bottom > lasttop:
					Qd_EraseRect((bl, lasttop, br, y + h * eraseall))
					lasttop = bottom
				redbullet = bl + 2, y + 3, bl + 8, y + 9
				Qd_PaintOval(redbullet)
			else:
				Qd_EraseRect((bl, lasttop, br, bb))
			Qd.RGBForeColor((0, 0, 0))
		finally:
			self._parentwindow.restoreclip()
	
	def updatescrollbars(self):
		if self._debugger:
			self.drawbreakpoints(1)
		TextEditor.updatescrollbars(self)
	
	def pt_in_breaks(self, point):
		return Qd.PtInRect(point, self._getbreakrect())
	
	def _getbreakrect(self):
		if self._debugger:
			l, t, r, b = self._bounds
			return (l+1, t+1, l + 12, b-1)
		else:
			return (0, 0, 0, 0)
	
	def _getviewrect(self):
		l, t, r, b = self._bounds
		if self._debugger:
			return (l + 17, t + 2, r, b - 2)
		else:
			return (l + 5, t + 2, r, b - 2)
	
	def _calctextbounds(self):
		viewrect = l, t, r, b = self._getviewrect()
		if self.ted:
			dl, dt, dr, db = self.ted.WEGetDestRect()
			vl, vt, vr, vb = self.ted.WEGetViewRect()
			xshift = l - vl
			yshift = t - vt
			if (db - dt) < (b - t):
				yshift = t - dt
			destrect = (dl + xshift, dt + yshift, dr + xshift, db + yshift)
		else:
			destrect = (l, t, r + 5000, b)
		return viewrect, destrect


def GetFNum(fontname):
	"""Same as Fm.GetFNum(), but maps a missing font to Monaco instead of the system font."""
	if fontname <> Fm.GetFontName(0):
		fontid = Fm.GetFNum(fontname)
		if fontid == 0:
			fontid = Fonts.monaco
	else:
		fontid = 0
	return fontid

# b/w compat. Anyone using this?
GetFName = Fm.GetFontName

def GetPortFontSettings(port):
	return Fm.GetFontName(port.txFont), port.txFace, port.txSize

def SetPortFontSettings(port, (font, face, size)):
	saveport = Qd.GetPort()
	Qd.SetPort(port)
	Qd.TextFont(GetFNum(font))
	Qd.TextFace(face)
	Qd.TextSize(size)
	Qd.SetPort(saveport)
