📄 editor.py
字号:
#----------------------------------------------------------------------
# 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: editor.py,v 1.12 2006/01/29 02:36:29 RD Exp $
# 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()
self.SetFocus()
def OnLeftUp(self, event):
if not self.HasCapture():
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -