X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/8b9a4190f70909de9568f45389e7aa3ecbc66b8a..8f8c4b40456b5a4ef56a704c69570c7a1c86e714:/wxPython/wx/lib/editor/editor.py diff --git a/wxPython/wx/lib/editor/editor.py b/wxPython/wx/lib/editor/editor.py index 5b9cde8714..ac2ec28545 100644 --- a/wxPython/wx/lib/editor/editor.py +++ b/wxPython/wx/lib/editor/editor.py @@ -1,8 +1,975 @@ +#---------------------------------------------------------------------- +# Name: wxPython.lib.editor.Editor +# Purpose: An intelligent text editor with colorization capabilities. +# +# Original +# Authors: Dirk Holtwic, Robin Dunn +# +# New +# Authors: Adam Feuer, Steve Howell +# +# History: +# This code used to support a fairly complex subclass that did +# syntax coloring and outliner collapse mode. Adam and Steve +# inherited the code, and added a lot of basic editor +# functionality that had not been there before, such as cut-and-paste. +# +# +# Created: 15-Dec-1999 +# RCS-ID: $Id$ +# Copyright: (c) 1999 by Dirk Holtwick, 1999 +# Licence: wxWindows license +#---------------------------------------------------------------------- +# 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net) +# +# o 2.5 compatability update. +# +# 12/21/2003 - Jeff Grimmett (grimmtooth@softhome.net) +# +# o wxEditor -> Editor +# -"""Renamer stub: provides a way to drop the wx prefix from wxPython objects.""" +import os +import time + +import wx + +import selection +import images + +#---------------------------- + +def ForceBetween(min, val, max): + if val > max: + return max + if val < min: + return min + return val + + +def LineTrimmer(lineOfText): + if len(lineOfText) == 0: + return "" + elif lineOfText[-1] == '\r': + return lineOfText[:-1] + else: + return lineOfText + +def LineSplitter(text): + return map (LineTrimmer, text.split('\n')) + + +#---------------------------- + +class Scroller: + def __init__(self, parent): + self.parent = parent + self.ow = 0 + self.oh = 0 + self.ox = 0 + self.oy = 0 + + def SetScrollbars(self, fw, fh, w, h, x, y): + if (self.ow != w or self.oh != h or self.ox != x or self.oy != y): + self.parent.SetScrollbars(fw, fh, w, h, x, y) + self.ow = w + self.oh = h + self.ox = x + self.oy = y + +#---------------------------------------------------------------------- + +class Editor(wx.ScrolledWindow): + + def __init__(self, parent, id, + pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): + + wx.ScrolledWindow.__init__(self, parent, id, + pos, size, + style|wx.WANTS_CHARS) + + self.isDrawing = False + + self.InitCoords() + self.InitFonts() + self.SetColors() + self.MapEvents() + self.LoadImages() + self.InitDoubleBuffering() + self.InitScrolling() + self.SelectOff() + self.SetFocus() + self.SetText([""]) + self.SpacesPerTab = 4 + +##------------------ Init stuff + + def InitCoords(self): + self.cx = 0 + self.cy = 0 + self.oldCx = 0 + self.oldCy = 0 + self.sx = 0 + self.sy = 0 + self.sw = 0 + self.sh = 0 + self.sco_x = 0 + self.sco_y = 0 + + def MapEvents(self): + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_SCROLLWIN, self.OnScroll) + self.Bind(wx.EVT_CHAR, self.OnChar) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + +##------------------- Platform-specific stuff + + def NiceFontForPlatform(self): + if wx.Platform == "__WXMSW__": + font = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL) + else: + font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL, False) + if wx.Platform == "__WXMAC__": + font.SetNoAntiAliasing() + return font + + def UnixKeyHack(self, key): + # + # this will be obsolete when we get the new wxWindows patch + # + # 12/14/03 - jmg + # + # Which patch? I don't know if this is needed, but I don't know + # why it's here either. Play it safe; leave it in. + # + if key <= 26: + key += ord('a') - 1 + return key + +##-------------------- UpdateView/Cursor code + + def OnSize(self, event): + self.AdjustScrollbars() + self.SetFocus() + + def SetCharDimensions(self): + # TODO: We need a code review on this. It appears that Linux + # improperly reports window dimensions when the scrollbar's there. + self.bw, self.bh = self.GetClientSize() + + if wx.Platform == "__WXMSW__": + self.sh = self.bh / self.fh + self.sw = (self.bw / self.fw) - 1 + else: + self.sh = self.bh / self.fh + if self.LinesInFile() >= self.sh: + self.bw = self.bw - wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X) + self.sw = (self.bw / self.fw) - 1 + + self.sw = (self.bw / self.fw) - 1 + if self.CalcMaxLineLen() >= self.sw: + self.bh = self.bh - wx.SystemSettings_GetMetric(wx.SYS_HSCROLL_Y) + self.sh = self.bh / self.fh + + + def UpdateView(self, dc = None): + if dc is None: + dc = wx.ClientDC(self) + if dc.Ok(): + self.SetCharDimensions() + self.KeepCursorOnScreen() + self.DrawSimpleCursor(0,0, dc, True) + self.Draw(dc) + + def OnPaint(self, event): + dc = wx.PaintDC(self) + if self.isDrawing: + return + self.isDrawing = True + self.UpdateView(dc) + wx.CallAfter(self.AdjustScrollbars) + self.isDrawing = False + + def OnEraseBackground(self, evt): + pass + +##-------------------- Drawing code + + def InitFonts(self): + dc = wx.ClientDC(self) + self.font = self.NiceFontForPlatform() + dc.SetFont(self.font) + self.fw = dc.GetCharWidth() + self.fh = dc.GetCharHeight() + + def SetColors(self): + self.fgColor = wx.NamedColour('black') + self.bgColor = wx.NamedColour('white') + self.selectColor = wx.Colour(238, 220, 120) # r, g, b = emacsOrange + + def InitDoubleBuffering(self): + pass + + def DrawEditText(self, t, x, y, dc): + dc.DrawText(t, (x * self.fw, y * self.fh)) + + def DrawLine(self, line, dc): + if self.IsLine(line): + l = line + t = self.lines[l] + dc.SetTextForeground(self.fgColor) + fragments = selection.Selection( + self.SelectBegin, self.SelectEnd, + self.sx, self.sw, line, t) + x = 0 + for (data, selected) in fragments: + if selected: + dc.SetTextBackground(self.selectColor) + if x == 0 and len(data) == 0 and len(fragments) == 1: + data = ' ' + else: + dc.SetTextBackground(self.bgColor) + self.DrawEditText(data, x, line - self.sy, dc) + x += len(data) + + def Draw(self, odc=None): + if not odc: + odc = wx.ClientDC(self) + + bmp = wx.EmptyBitmap(max(1,self.bw), max(1,self.bh)) + dc = wx.BufferedDC(odc, bmp) + if dc.Ok(): + dc.SetFont(self.font) + dc.SetBackgroundMode(wx.SOLID) + dc.SetTextBackground(self.bgColor) + dc.SetTextForeground(self.fgColor) + dc.Clear() + for line in range(self.sy, self.sy + self.sh): + self.DrawLine(line, dc) + if len(self.lines) < self.sh + self.sy: + self.DrawEofMarker(dc) + self.DrawCursor(dc) + +##------------------ eofMarker stuff + + def LoadImages(self): + self.eofMarker = images.GetBitmap(images.EofImageData) + + def DrawEofMarker(self,dc): + x = 0 + y = (len(self.lines) - self.sy) * self.fh + hasTransparency = 1 + dc.DrawBitmap(self.eofMarker, (x, y), hasTransparency) + +##------------------ cursor-related functions + + def DrawCursor(self, dc = None): + if not dc: + dc = wx.ClientDC(self) + + if (self.LinesInFile())maxlen: + maxlen = len(line) + return maxlen + + def KeepCursorOnScreen(self): + self.sy = ForceBetween(max(0, self.cy-self.sh), self.sy, self.cy) + self.sx = ForceBetween(max(0, self.cx-self.sw), self.sx, self.cx) + self.AdjustScrollbars() + + def HorizBoundaries(self): + self.SetCharDimensions() + maxLineLen = self.CalcMaxLineLen() + self.sx = ForceBetween(0, self.sx, max(self.sw, maxLineLen - self.sw + 1)) + self.cx = ForceBetween(self.sx, self.cx, self.sx + self.sw - 1) + + def VertBoundaries(self): + self.SetCharDimensions() + self.sy = ForceBetween(0, self.sy, max(self.sh, self.LinesInFile() - self.sh + 1)) + self.cy = ForceBetween(self.sy, self.cy, self.sy + self.sh - 1) + + def cVert(self, num): + self.cy = self.cy + num + self.cy = ForceBetween(0, self.cy, self.LinesInFile() - 1) + self.sy = ForceBetween(self.cy - self.sh + 1, self.sy, self.cy) + self.cx = min(self.cx, self.CurrentLineLength()) + + def cHoriz(self, num): + self.cx = self.cx + num + self.cx = ForceBetween(0, self.cx, self.CurrentLineLength()) + self.sx = ForceBetween(self.cx - self.sw + 1, self.sx, self.cx) + + def AboveScreen(self, row): + return row < self.sy + + def BelowScreen(self, row): + return row >= self.sy + self.sh + + def LeftOfScreen(self, col): + return col < self.sx + + def RightOfScreen(self, col): + return col >= self.sx + self.sw + +##----------------- data structure helper functions + + def GetText(self): + return self.lines + + def SetText(self, lines): + self.InitCoords() + self.lines = lines + self.UnTouchBuffer() + self.SelectOff() + self.AdjustScrollbars() + self.UpdateView(None) + + def IsLine(self, lineNum): + return (0<=lineNum) and (lineNum self.nextScrollTime: + self.nextScrollTime = time.time() + self.SCROLLDELAY + return True + else: + return False + + def SetScrollTimer(self): + oneShot = True + self.scrollTimer.Start(1000*self.SCROLLDELAY/2, oneShot) + self.Bind(wx.EVT_TIMER, self.OnTimer) + + def OnTimer(self, event): + screenX, screenY = wx.GetMousePosition() + x, y = self.ScreenToClientXY(screenX, screenY) + self.MouseToRow(y) + self.MouseToCol(x) + self.SelectUpdate() + +##-------------------------- Mouse off screen functions + + def HandleAboveScreen(self, row): + self.SetScrollTimer() + if self.CanScroll(): + row = self.sy - 1 + row = max(0, row) + self.cy = row + + def HandleBelowScreen(self, row): + self.SetScrollTimer() + if self.CanScroll(): + row = self.sy + self.sh + row = min(row, self.LinesInFile() - 1) + self.cy = row + + def HandleLeftOfScreen(self, col): + self.SetScrollTimer() + if self.CanScroll(): + col = self.sx - 1 + col = max(0,col) + self.cx = col + + def HandleRightOfScreen(self, col): + self.SetScrollTimer() + if self.CanScroll(): + col = self.sx + self.sw + col = min(col, self.CurrentLineLength()) + self.cx = col + +##------------------------ mousing functions + + def MouseToRow(self, mouseY): + row = self.sy + (mouseY/ self.fh) + if self.AboveScreen(row): + self.HandleAboveScreen(row) + elif self.BelowScreen(row): + self.HandleBelowScreen(row) + else: + self.cy = min(row, self.LinesInFile() - 1) + + def MouseToCol(self, mouseX): + col = self.sx + (mouseX / self.fw) + if self.LeftOfScreen(col): + self.HandleLeftOfScreen(col) + elif self.RightOfScreen(col): + self.HandleRightOfScreen(col) + else: + self.cx = min(col, self.CurrentLineLength()) + + def MouseToCursor(self, event): + self.MouseToRow(event.GetY()) + self.MouseToCol(event.GetX()) + + def OnMotion(self, event): + if event.LeftIsDown() and self.HasCapture(): + self.Selecting = True + self.MouseToCursor(event) + self.SelectUpdate() + + def OnLeftDown(self, event): + self.MouseToCursor(event) + self.SelectBegin = (self.cy, self.cx) + self.SelectEnd = None + self.UpdateView() + self.CaptureMouse() + + def OnLeftUp(self, event): + if not self.HasCapture(): + return + + if self.SelectEnd is None: + self.OnClick() + else: + self.Selecting = False + self.SelectNotify(False, self.SelectBegin, self.SelectEnd) + + self.ReleaseMouse() + self.scrollTimer.Stop() + + +#------------------------- Scrolling + + def HorizScroll(self, event, eventType): + maxLineLen = self.CalcMaxLineLen() + + if eventType == wx.EVT_SCROLLWIN_LINEUP: + self.sx -= 1 + elif eventType == wx.EVT_SCROLLWIN_LINEDOWN: + self.sx += 1 + elif eventType == wx.EVT_SCROLLWIN_PAGEUP: + self.sx -= self.sw + elif eventType == wx.EVT_SCROLLWIN_PAGEDOWN: + self.sx += self.sw + elif eventType == wx.EVT_SCROLLWIN_TOP: + self.sx = self.cx = 0 + elif eventType == wx.EVT_SCROLLWIN_BOTTOM: + self.sx = maxLineLen - self.sw + self.cx = maxLineLen + else: + self.sx = event.GetPosition() + + self.HorizBoundaries() + + def VertScroll(self, event, eventType): + if eventType == wx.EVT_SCROLLWIN_LINEUP: + self.sy -= 1 + elif eventType == wx.EVT_SCROLLWIN_LINEDOWN: + self.sy += 1 + elif eventType == wx.EVT_SCROLLWIN_PAGEUP: + self.sy -= self.sh + elif eventType == wx.EVT_SCROLLWIN_PAGEDOWN: + self.sy += self.sh + elif eventType == wx.EVT_SCROLLWIN_TOP: + self.sy = self.cy = 0 + elif eventType == wx.EVT_SCROLLWIN_BOTTOM: + self.sy = self.LinesInFile() - self.sh + self.cy = self.LinesInFile() + else: + self.sy = event.GetPosition() + + self.VertBoundaries() + + def OnScroll(self, event): + dir = event.GetOrientation() + eventType = event.GetEventType() + if dir == wx.HORIZONTAL: + self.HorizScroll(event, eventType) + else: + self.VertScroll(event, eventType) + self.UpdateView() + + + def AdjustScrollbars(self): + for i in range(2): + self.SetCharDimensions() + self.scroller.SetScrollbars( + self.fw, self.fh, + self.CalcMaxLineLen()+3, max(self.LinesInFile()+1, self.sh), + self.sx, self.sy) + +#------------ backspace, delete, return + + def BreakLine(self, event): + if self.IsLine(self.cy): + t = self.lines[self.cy] + self.lines = self.lines[:self.cy] + [t[:self.cx],t[self.cx:]] + self.lines[self.cy+1:] + self.cVert(1) + self.cx = 0 + self.TouchBuffer() + + def InsertChar(self,char): + if self.IsLine(self.cy): + t = self.lines[self.cy] + t = t[:self.cx] + char + t[self.cx:] + self.SetTextLine(self.cy, t) + self.cHoriz(1) + self.TouchBuffer() + + def JoinLines(self): + t1 = self.lines[self.cy] + t2 = self.lines[self.cy+1] + self.cx = len(t1) + self.lines = self.lines[:self.cy] + [t1 + t2] + self.lines[self.cy+2:] + self.TouchBuffer() + + + def DeleteChar(self,x,y,oldtext): + newtext = oldtext[:x] + oldtext[x+1:] + self.SetTextLine(y, newtext) + self.TouchBuffer() + + + def BackSpace(self, event): + t = self.GetTextLine(self.cy) + if self.cx>0: + self.DeleteChar(self.cx-1,self.cy,t) + self.cHoriz(-1) + self.TouchBuffer() + elif self.cx == 0: + if self.cy > 0: + self.cy -= 1 + self.JoinLines() + self.TouchBuffer() + else: + wx.Bell() + + def Delete(self, event): + t = self.GetTextLine(self.cy) + if self.cx31) and (key<256): + self.InsertChar(chr(key)) + else: + wx.Bell() + return + self.UpdateView() + self.AdjustScrollbars() + + def OnChar(self, event): + key = event.KeyCode() + filters = [self.AltKey, + self.MoveSpecialControlKey, + self.ControlKey, + self.SpecialControlKey, + self.MoveSpecialKey, + self.ShiftKey, + self.NormalChar] + for filter in filters: + if filter(event,key): + break + return 0 + +#----------------------- Eliminate memory leaks + + def OnDestroy(self, event): + self.mdc = None + self.odc = None + self.bgColor = None + self.fgColor = None + self.font = None + self.selectColor = None + self.scrollTimer = None + self.eofMarker = None + +#-------------------- Abstract methods for subclasses + + def OnClick(self): + pass + + def SelectNotify(self, Selecting, SelectionBegin, SelectionEnd): + pass -from wx import _rename -from wxPython.lib.editor import editor -_rename(globals(), editor.__dict__, modulename='lib.editor.editor') -del editor -del _rename