fda1446558
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@25961 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
976 lines
28 KiB
Python
976 lines
28 KiB
Python
#----------------------------------------------------------------------
|
|
# 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
|
|
#
|
|
|
|
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())<self.cy: #-1 ?
|
|
self.cy = self.LinesInFile()-1
|
|
s = self.lines[self.cy]
|
|
|
|
x = self.cx - self.sx
|
|
y = self.cy - self.sy
|
|
self.DrawSimpleCursor(x, y, dc)
|
|
|
|
|
|
def DrawSimpleCursor(self, xp, yp, dc = None, old=False):
|
|
if not dc:
|
|
dc = wx.ClientDC(self)
|
|
|
|
if old:
|
|
xp = self.sco_x
|
|
yp = self.sco_y
|
|
|
|
szx = self.fw
|
|
szy = self.fh
|
|
x = xp * szx
|
|
y = yp * szy
|
|
dc.Blit((x,y), (szx,szy), dc, (x,y), wx.SRC_INVERT)
|
|
self.sco_x = xp
|
|
self.sco_y = yp
|
|
|
|
##-------- Enforcing screen boundaries, cursor movement
|
|
|
|
def CalcMaxLineLen(self):
|
|
"""get length of longest line on screen"""
|
|
maxlen = 0
|
|
for line in self.lines[self.sy:self.sy+self.sh]:
|
|
if len(line) >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.LinesInFile())
|
|
|
|
def GetTextLine(self, lineNum):
|
|
if self.IsLine(lineNum):
|
|
return self.lines[lineNum]
|
|
return ""
|
|
|
|
def SetTextLine(self, lineNum, text):
|
|
if self.IsLine(lineNum):
|
|
self.lines[lineNum] = text
|
|
|
|
def CurrentLineLength(self):
|
|
return len(self.lines[self.cy])
|
|
|
|
def LinesInFile(self):
|
|
return len(self.lines)
|
|
|
|
def UnTouchBuffer(self):
|
|
self.bufferTouched = False
|
|
|
|
def BufferWasTouched(self):
|
|
return self.bufferTouched
|
|
|
|
def TouchBuffer(self):
|
|
self.bufferTouched = True
|
|
|
|
|
|
##-------------------------- Mouse scroll timing functions
|
|
|
|
def InitScrolling(self):
|
|
# we don't rely on the windows system to scroll for us; we just
|
|
# redraw the screen manually every time
|
|
self.EnableScrolling(False, False)
|
|
self.nextScrollTime = 0
|
|
self.SCROLLDELAY = 0.050 # seconds
|
|
self.scrollTimer = wx.Timer(self)
|
|
self.scroller = Scroller(self)
|
|
|
|
def CanScroll(self):
|
|
if time.time() > 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.cx<len(t):
|
|
self.DeleteChar(self.cx,self.cy,t)
|
|
self.TouchBuffer()
|
|
else:
|
|
if self.cy < len(self.lines) - 1:
|
|
self.JoinLines()
|
|
self.TouchBuffer()
|
|
|
|
def Escape(self, event):
|
|
self.SelectOff()
|
|
|
|
def TabKey(self, event):
|
|
numSpaces = self.SpacesPerTab - (self.cx % self.SpacesPerTab)
|
|
self.SingleLineInsert(' ' * numSpaces)
|
|
|
|
##----------- selection routines
|
|
|
|
def SelectUpdate(self):
|
|
self.SelectEnd = (self.cy, self.cx)
|
|
self.SelectNotify(self.Selecting, self.SelectBegin, self.SelectEnd)
|
|
self.UpdateView()
|
|
|
|
def NormalizedSelect(self):
|
|
(begin, end) = (self.SelectBegin, self.SelectEnd)
|
|
(bRow, bCol) = begin
|
|
(eRow, eCol) = end
|
|
if (bRow < eRow):
|
|
return (begin, end)
|
|
elif (eRow < bRow):
|
|
return (end, begin)
|
|
else:
|
|
if (bCol < eCol):
|
|
return (begin, end)
|
|
else:
|
|
return (end, begin)
|
|
|
|
def FindSelection(self):
|
|
if self.SelectEnd is None or self.SelectBegin is None:
|
|
wx.Bell()
|
|
return None
|
|
(begin, end) = self.NormalizedSelect()
|
|
(bRow, bCol) = begin
|
|
(eRow, eCol) = end
|
|
return (bRow, bCol, eRow, eCol)
|
|
|
|
def SelectOff(self):
|
|
self.SelectBegin = None
|
|
self.SelectEnd = None
|
|
self.Selecting = False
|
|
self.SelectNotify(False,None,None)
|
|
|
|
def CopySelection(self, event):
|
|
selection = self.FindSelection()
|
|
if selection is None:
|
|
return
|
|
(bRow, bCol, eRow, eCol) = selection
|
|
|
|
if bRow == eRow:
|
|
self.SingleLineCopy(bRow, bCol, eCol)
|
|
else:
|
|
self.MultipleLineCopy(bRow, bCol, eRow, eCol)
|
|
|
|
def OnCopySelection(self, event):
|
|
self.CopySelection(event)
|
|
self.SelectOff()
|
|
|
|
def CopyToClipboard(self, linesOfText):
|
|
do = wx.TextDataObject()
|
|
do.SetText(os.linesep.join(linesOfText))
|
|
wx.TheClipboard.Open()
|
|
wx.TheClipboard.SetData(do)
|
|
wx.TheClipboard.Close()
|
|
|
|
def SingleLineCopy(self, Row, bCol, eCol):
|
|
Line = self.GetTextLine(Row)
|
|
self.CopyToClipboard([Line[bCol:eCol]])
|
|
|
|
def MultipleLineCopy(self, bRow, bCol, eRow, eCol):
|
|
bLine = self.GetTextLine(bRow)[bCol:]
|
|
eLine = self.GetTextLine(eRow)[:eCol]
|
|
self.CopyToClipboard([bLine] + [l for l in self.lines[bRow + 1:eRow]] + [eLine])
|
|
|
|
def OnDeleteSelection(self, event):
|
|
selection = self.FindSelection()
|
|
if selection is None:
|
|
return
|
|
(bRow, bCol, eRow, eCol) = selection
|
|
|
|
if bRow == eRow:
|
|
self.SingleLineDelete(bRow, bCol, eCol)
|
|
else:
|
|
self.MultipleLineDelete(bRow, bCol, eRow, eCol)
|
|
|
|
self.TouchBuffer()
|
|
|
|
self.cy = bRow
|
|
self.cx = bCol
|
|
self.SelectOff()
|
|
self.UpdateView()
|
|
|
|
|
|
def SingleLineDelete(self, Row, bCol, eCol):
|
|
ModLine = self.GetTextLine(Row)
|
|
ModLine = ModLine[:bCol] + ModLine[eCol:]
|
|
self.SetTextLine(Row,ModLine)
|
|
|
|
def MultipleLineDelete(self, bRow, bCol, eRow, eCol):
|
|
bLine = self.GetTextLine(bRow)
|
|
eLine = self.GetTextLine(eRow)
|
|
ModLine = bLine[:bCol] + eLine[eCol:]
|
|
self.lines[bRow:eRow + 1] = [ModLine]
|
|
|
|
def OnPaste(self, event):
|
|
do = wx.TextDataObject()
|
|
wx.TheClipboard.Open()
|
|
success = wx.TheClipboard.GetData(do)
|
|
wx.TheClipboard.Close()
|
|
if success:
|
|
pastedLines = LineSplitter(do.GetText())
|
|
else:
|
|
wx.Bell()
|
|
return
|
|
if len(pastedLines) == 0:
|
|
wx.Bell()
|
|
return
|
|
elif len(pastedLines) == 1:
|
|
self.SingleLineInsert(pastedLines[0])
|
|
else:
|
|
self.MultipleLinePaste(pastedLines)
|
|
|
|
def SingleLineInsert(self, newText):
|
|
ModLine = self.GetTextLine(self.cy)
|
|
ModLine = ModLine[:self.cx] + newText + ModLine[self.cx:]
|
|
self.SetTextLine(self.cy, ModLine)
|
|
self.cHoriz(len(newText))
|
|
self.TouchBuffer()
|
|
self.UpdateView()
|
|
|
|
def MultipleLinePaste(self, pastedLines):
|
|
FirstLine = LastLine = self.GetTextLine(self.cy)
|
|
FirstLine = FirstLine[:self.cx] + pastedLines[0]
|
|
LastLine = pastedLines[-1] + LastLine[self.cx:]
|
|
|
|
NewSlice = [FirstLine]
|
|
NewSlice += [l for l in pastedLines[1:-1]]
|
|
NewSlice += [LastLine]
|
|
self.lines[self.cy:self.cy + 1] = NewSlice
|
|
|
|
self.cy = self.cy + len(pastedLines)-1
|
|
self.cx = len(pastedLines[-1])
|
|
self.TouchBuffer()
|
|
self.UpdateView()
|
|
|
|
def OnCutSelection(self,event):
|
|
self.CopySelection(event)
|
|
self.OnDeleteSelection(event)
|
|
|
|
#-------------- Keyboard movement implementations
|
|
|
|
def MoveDown(self, event):
|
|
self.cVert(+1)
|
|
|
|
def MoveUp(self, event):
|
|
self.cVert(-1)
|
|
|
|
def MoveLeft(self, event):
|
|
if self.cx == 0:
|
|
if self.cy == 0:
|
|
wx.Bell()
|
|
else:
|
|
self.cVert(-1)
|
|
self.cx = self.CurrentLineLength()
|
|
else:
|
|
self.cx -= 1
|
|
|
|
def MoveRight(self, event):
|
|
linelen = self.CurrentLineLength()
|
|
if self.cx == linelen:
|
|
if self.cy == len(self.lines) - 1:
|
|
wx.Bell()
|
|
else:
|
|
self.cx = 0
|
|
self.cVert(1)
|
|
else:
|
|
self.cx += 1
|
|
|
|
|
|
def MovePageDown(self, event):
|
|
self.cVert(self.sh)
|
|
|
|
def MovePageUp(self, event):
|
|
self.cVert(-self.sh)
|
|
|
|
def MoveHome(self, event):
|
|
self.cx = 0
|
|
|
|
def MoveEnd(self, event):
|
|
self.cx = self.CurrentLineLength()
|
|
|
|
def MoveStartOfFile(self, event):
|
|
self.cy = 0
|
|
self.cx = 0
|
|
|
|
def MoveEndOfFile(self, event):
|
|
self.cy = len(self.lines) - 1
|
|
self.cx = self.CurrentLineLength()
|
|
|
|
#-------------- Key handler mapping tables
|
|
|
|
def SetMoveSpecialFuncs(self, action):
|
|
action[wx.WXK_DOWN] = self.MoveDown
|
|
action[wx.WXK_UP] = self.MoveUp
|
|
action[wx.WXK_LEFT] = self.MoveLeft
|
|
action[wx.WXK_RIGHT] = self.MoveRight
|
|
action[wx.WXK_NEXT] = self.MovePageDown
|
|
action[wx.WXK_PRIOR] = self.MovePageUp
|
|
action[wx.WXK_HOME] = self.MoveHome
|
|
action[wx.WXK_END] = self.MoveEnd
|
|
|
|
def SetMoveSpecialControlFuncs(self, action):
|
|
action[wx.WXK_HOME] = self.MoveStartOfFile
|
|
action[wx.WXK_END] = self.MoveEndOfFile
|
|
|
|
def SetAltFuncs(self, action):
|
|
# subclass implements
|
|
pass
|
|
|
|
def SetControlFuncs(self, action):
|
|
action['c'] = self.OnCopySelection
|
|
action['d'] = self.OnDeleteSelection
|
|
action['v'] = self.OnPaste
|
|
action['x'] = self.OnCutSelection
|
|
|
|
def SetSpecialControlFuncs(self, action):
|
|
action[wx.WXK_INSERT] = self.OnCopySelection
|
|
|
|
def SetShiftFuncs(self, action):
|
|
action[wx.WXK_DELETE] = self.OnCutSelection
|
|
action[wx.WXK_INSERT] = self.OnPaste
|
|
|
|
def SetSpecialFuncs(self, action):
|
|
action[wx.WXK_BACK] = self.BackSpace
|
|
action[wx.WXK_DELETE] = self.Delete
|
|
action[wx.WXK_RETURN] = self.BreakLine
|
|
action[wx.WXK_ESCAPE] = self.Escape
|
|
action[wx.WXK_TAB] = self.TabKey
|
|
|
|
##-------------- Logic for key handlers
|
|
|
|
|
|
def Move(self, keySettingFunction, key, event):
|
|
action = {}
|
|
keySettingFunction(action)
|
|
|
|
if not action.has_key(key):
|
|
return False
|
|
|
|
if event.ShiftDown():
|
|
if not self.Selecting:
|
|
self.Selecting = True
|
|
self.SelectBegin = (self.cy, self.cx)
|
|
action[key](event)
|
|
self.SelectEnd = (self.cy, self.cx)
|
|
else:
|
|
action[key](event)
|
|
if self.Selecting:
|
|
self.Selecting = False
|
|
|
|
self.SelectNotify(self.Selecting, self.SelectBegin, self.SelectEnd)
|
|
self.UpdateView()
|
|
return True
|
|
|
|
def MoveSpecialKey(self, event, key):
|
|
return self.Move(self.SetMoveSpecialFuncs, key, event)
|
|
|
|
def MoveSpecialControlKey(self, event, key):
|
|
if not event.ControlDown():
|
|
return False
|
|
return self.Move(self.SetMoveSpecialControlFuncs, key, event)
|
|
|
|
def Dispatch(self, keySettingFunction, key, event):
|
|
action = {}
|
|
keySettingFunction(action)
|
|
if action.has_key(key):
|
|
action[key](event)
|
|
self.UpdateView()
|
|
return True
|
|
return False
|
|
|
|
def ModifierKey(self, key, event, modifierKeyDown, MappingFunc):
|
|
if not modifierKeyDown:
|
|
return False
|
|
|
|
key = self.UnixKeyHack(key)
|
|
try:
|
|
key = chr(key)
|
|
except:
|
|
return False
|
|
if not self.Dispatch(MappingFunc, key, event):
|
|
wx.Bell()
|
|
return True
|
|
|
|
def ControlKey(self, event, key):
|
|
return self.ModifierKey(key, event, event.ControlDown(), self.SetControlFuncs)
|
|
|
|
def AltKey(self, event, key):
|
|
return self.ModifierKey(key, event, event.AltDown(), self.SetAltFuncs)
|
|
|
|
def SpecialControlKey(self, event, key):
|
|
if not event.ControlDown():
|
|
return False
|
|
if not self.Dispatch(self.SetSpecialControlFuncs, key, event):
|
|
wx.Bell()
|
|
return True
|
|
|
|
def ShiftKey(self, event, key):
|
|
if not event.ShiftDown():
|
|
return False
|
|
return self.Dispatch(self.SetShiftFuncs, key, event)
|
|
|
|
def NormalChar(self, event, key):
|
|
self.SelectOff()
|
|
|
|
# regular ascii
|
|
if not self.Dispatch(self.SetSpecialFuncs, key, event):
|
|
if (key>31) 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
|
|
|