demo to match. Also fixed some deprecation warnings. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@27049 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
 | |
| 
 |