from Carbon import Evt, Events, Fm, Fonts from Carbon import Qd, Res, Scrap from Carbon import TE, TextEdit, Win from Carbon import App from Carbon.Appearance import kThemeStateActive, kThemeStateInactive import waste import WASTEconst import Wbase import Wkeys import Wcontrols import PyFontify import string from types import TupleType, StringType 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 getscrollrects(self): """Return (destrect, viewrect).""" return None, None # internal method def updatescrollbars(self): (dl, dt, dr, db), (vl, vt, vr, vb) = self.getscrollrects() if self._parent._barx: viewwidth = vr - vl destwidth = dr - dl bar = self._parent._barx bar.setmax(destwidth - viewwidth) # MacOS 8.1 doesn't automatically disable # scrollbars whose max <= min bar.enable(destwidth > viewwidth) bar.setviewsize(viewwidth) bar.set(vl - dl) if self._parent._bary: viewheight = vb - vt destheight = db - dt bar = self._parent._bary bar.setmax(destheight - viewheight) # MacOS 8.1 doesn't automatically disable # scrollbars whose max <= min bar.enable(destheight > viewheight) bar.setviewsize(viewheight) bar.set(vt - dt) 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.oldselection = 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 textchanged(self, all=0): self.changed = 1 def selectionchanged(self): self.selchanged = 1 self.oldselection = self.getselection() 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): from Carbon 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.selectionchanged() self.updatescrollbars() def adjust(self, oldbounds): self.SetPort() # Note: if App.DrawThemeEditTextFrame is ever used, it will be necessary # to unconditionally outset the invalidated rectangles, since Appearance # frames are drawn outside the bounds. 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.selectionchanged() 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.selectionchanged() self.updatescrollbars() def getselection(self): if self.ted: return self.ted.WEGetSelection() else: return self.selection def setselection(self, selstart, selend): self.selectionchanged() 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, 1) dummy, newselend = self.ted.WEFindLine(selend, 1) if oldselstart <> newselstart or oldselend <> newselend: self.ted.WESetSelection(newselstart, newselend) self.updatescrollbars() self.selectionchanged() def insert(self, text): self.ted.WEInsert(text, None, None) self.textchanged() self.selectionchanged() # 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.updatescrollbars() self.textchanged(1) 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.textchanged() if char not in Wkeys.scrollkeys: self.selectionchanged() 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.selectionchanged() 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._visible: self.SetPort() # DISABLED! There are too many places where it is assumed that # the frame of an EditText item is 1 pixel, inside the bounds. #state = [kThemeStateActive, kThemeStateInactive][not onoff] #App.DrawThemeEditTextFrame(Qd.InsetRect(self._bounds, 1, 1), state) if self._selected: if onoff: self.ted.WEActivate() else: self.ted.WEDeactivate() 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) # DISABLED! There are too many places where it is assumed that # the frame of an EditText item is 1 pixel, inside the bounds. #state = [kThemeStateActive, kThemeStateInactive][not self._activated] #App.DrawThemeEditTextFrame(Qd.InsetRect(self._bounds, 1, 1), state) Qd.FrameRect(self._bounds) if self._selected and self._activated: self.drawselframe(1) # 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(self._parent._bary.getmin()) if self._parent._barx and self._parent._barx._enabled: self.hscroll(self._parent._barx.getmin()) def scrollbottom(self): if self._parent._bary and self._parent._bary._enabled: self.vscroll(self._parent._bary.getmax()) # menu handlers def domenu_copy(self, *args): selbegin, selend = self.ted.WEGetSelection() if selbegin == selend: return if hasattr(Scrap, 'ZeroScrap'): Scrap.ZeroScrap() else: Scrap.ClearCurrentScrap() self.ted.WECopy() self.updatescrollbars() def domenu_cut(self, *args): selbegin, selend = self.ted.WEGetSelection() if selbegin == selend: return if hasattr(Scrap, 'ZeroScrap'): Scrap.ZeroScrap() else: Scrap.ClearCurrentScrap() self.ted.WECut() self.updatescrollbars() self.selview() self.textchanged() self.selectionchanged() 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.textchanged() self.selectionchanged() if self._callback: Wbase.CallbackCall(self._callback, 0, "", None) def domenu_clear(self, *args): self.ted.WEDelete() self.selview() self.updatescrollbars() self.textchanged() self.selectionchanged() 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.textchanged() self.selectionchanged() 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 getscrollrects(self): return self.ted.WEGetDestRect(), self.ted.WEGetViewRect() def vscroll(self, value): lineheight = self.ted.WEGetHeight(0, 1) dr = self.ted.WEGetDestRect() vr = self.ted.WEGetViewRect() viewheight = vr[3] - vr[1] 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 delta = vr[1] - dr[1] - value 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 delta = vr[0] - dr[0] - value #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) def activate(self, onoff): self._activated = onoff if self._visible: self.SetPort() # doesn't draw frame, as EditText.activate does if self._selected: if onoff: self.ted.WEActivate() else: self.ted.WEDeactivate() self.drawselframe(onoff) import re commentPat = re.compile("[ \t]*(#)") indentPat = re.compile("[ \t]*") kStringColor = (0, 0x7fff, 0) kCommentColor = (0, 0, 0xb000) 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.bind("cmdshiftd", self.alldirty) self.file = file # only for debugger reference self._debugger = debugger if debugger: debugger.register_editor(self, self.file) self._dirty = (0, None) self.do_fontify = 0 #def open(self): # TextEditor.open(self) # if self.do_fontify: # self.fontify() # self._dirty = (None, None) def _getflags(self): flags = (WASTEconst.weDoDrawOffscreen | WASTEconst.weDoUseTempMem | WASTEconst.weDoAutoScroll | WASTEconst.weDoOutlineHilite) if self.readonly: flags = flags | WASTEconst.weDoReadOnly else: flags = flags | WASTEconst.weDoUndo return flags def textchanged(self, all=0): self.changed = 1 if all: self._dirty = (0, None) return oldsel = self.oldselection sel = self.getselection() if not sel: # XXX what to do? return selstart, selend = sel selstart, selend = min(selstart, selend), max(selstart, selend) if oldsel: oldselstart, oldselend = min(oldsel), max(oldsel) selstart, selend = min(selstart, oldselstart), max(selend, oldselend) startline = self.offsettoline(selstart) endline = self.offsettoline(selend) selstart, _ = self.ted.WEGetLineRange(startline) _, selend = self.ted.WEGetLineRange(endline) if selstart > 0: selstart = selstart - 1 self._dirty = (selstart, selend) def idle(self): self.SetPort() self.ted.WEIdle() if not self.do_fontify: return start, end = self._dirty if start is None: return textLength = self.ted.WEGetTextLength() if end is None: end = textLength if start >= end: self._dirty = (None, None) else: self.fontify(start, end) self._dirty = (None, None) def alldirty(self, *args): self._dirty = (0, None) def fontify(self, start=0, end=None): #W.SetCursor('watch') if self.readonly: self.ted.WEFeatureFlag(WASTEconst.weFReadOnly, 0) self.ted.WEFeatureFlag(WASTEconst.weFOutlineHilite, 0) self.ted.WEDeactivate() self.ted.WEFeatureFlag(WASTEconst.weFAutoScroll, 0) self.ted.WEFeatureFlag(WASTEconst.weFUndo, 0) pytext = self.get().replace("\r", "\n") if end is None: end = len(pytext) else: end = min(end, len(pytext)) selstart, selend = self.ted.WEGetSelection() self.ted.WESetSelection(start, end) self.ted.WESetStyle(WASTEconst.weDoFace | WASTEconst.weDoColor, (0, 0, 12, (0, 0, 0))) tags = PyFontify.fontify(pytext, start, end) styles = { 'string': (WASTEconst.weDoColor, (0, 0, 0, kStringColor)), 'keyword': (WASTEconst.weDoFace, (0, 1, 0, (0, 0, 0))), 'comment': (WASTEconst.weDoFace | WASTEconst.weDoColor, (0, 0, 0, kCommentColor)), 'identifier': (WASTEconst.weDoColor, (0, 0, 0, (0xbfff, 0, 0))) } setselection = self.ted.WESetSelection setstyle = self.ted.WESetStyle for tag, start, end, sublist in tags: setselection(start, end) mode, style = styles[tag] setstyle(mode, style) self.ted.WESetSelection(selstart, selend) self.SetPort() self.ted.WEFeatureFlag(WASTEconst.weFAutoScroll, 1) self.ted.WEFeatureFlag(WASTEconst.weFUndo, 1) self.ted.WEActivate() self.ted.WEFeatureFlag(WASTEconst.weFOutlineHilite, 1) if self.readonly: self.ted.WEFeatureFlag(WASTEconst.weFReadOnly, 1) 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.textchanged() self.selectionchanged() 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) Qd.QDFlushPortBuffer(self._parentwindow.wid, None) # needed under OSX 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)