]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/editor/editor.py
1.
[wxWidgets.git] / wxPython / wx / lib / editor / editor.py
CommitLineData
d14a1e28
RD
1#----------------------------------------------------------------------
2# Name: wxPython.lib.editor.wxEditor
3# Purpose: An intelligent text editor with colorization capabilities.
4#
5# Original
6# Authors: Dirk Holtwic, Robin Dunn
7#
8# New
9# Authors: Adam Feuer, Steve Howell
10#
11# History:
12# This code used to support a fairly complex subclass that did
13# syntax coloring and outliner collapse mode. Adam and Steve
14# inherited the code, and added a lot of basic editor
15# functionality that had not been there before, such as cut-and-paste.
16#
17#
18# Created: 15-Dec-1999
19# RCS-ID: $Id$
20# Copyright: (c) 1999 by Dirk Holtwick, 1999
21# Licence: wxWindows license
22#----------------------------------------------------------------------
b881fc78
RD
23# 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net)
24#
25# o 2.5 compatability update.
26#
1fded56b 27
b881fc78
RD
28import os
29import time
d14a1e28 30
b881fc78 31import wx
d14a1e28 32
b881fc78
RD
33import selection
34import images
d14a1e28
RD
35
36#----------------------------
37
38def ForceBetween(min, val, max):
39 if val > max:
40 return max
41 if val < min:
42 return min
43 return val
44
45
46def LineTrimmer(lineOfText):
47 if len(lineOfText) == 0:
48 return ""
49 elif lineOfText[-1] == '\r':
50 return lineOfText[:-1]
51 else:
52 return lineOfText
53
54def LineSplitter(text):
55 return map (LineTrimmer, text.split('\n'))
56
57
58#----------------------------
59
60class Scroller:
61 def __init__(self, parent):
62 self.parent = parent
63 self.ow = 0
64 self.oh = 0
65 self.ox = 0
66 self.oy = 0
67
68 def SetScrollbars(self, fw, fh, w, h, x, y):
69 if (self.ow != w or self.oh != h or self.ox != x or self.oy != y):
70 self.parent.SetScrollbars(fw, fh, w, h, x, y)
71 self.ow = w
72 self.oh = h
73 self.ox = x
74 self.oy = y
75
76#----------------------------------------------------------------------
77
b881fc78 78class wxEditor(wx.ScrolledWindow):
d14a1e28
RD
79
80 def __init__(self, parent, id,
b881fc78 81 pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
d14a1e28 82
b881fc78 83 wx.ScrolledWindow.__init__(self, parent, id,
d14a1e28 84 pos, size,
b881fc78 85 style|wx.WANTS_CHARS)
d14a1e28
RD
86
87 self.isDrawing = False
88
89 self.InitCoords()
90 self.InitFonts()
91 self.SetColors()
92 self.MapEvents()
93 self.LoadImages()
94 self.InitDoubleBuffering()
95 self.InitScrolling()
96 self.SelectOff()
97 self.SetFocus()
98 self.SetText([""])
99 self.SpacesPerTab = 4
100
101##------------------ Init stuff
102
103 def InitCoords(self):
104 self.cx = 0
105 self.cy = 0
106 self.oldCx = 0
107 self.oldCy = 0
108 self.sx = 0
109 self.sy = 0
110 self.sw = 0
111 self.sh = 0
112 self.sco_x = 0
113 self.sco_y = 0
114
115 def MapEvents(self):
b881fc78
RD
116 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
117 self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
118 self.Bind(wx.EVT_MOTION, self.OnMotion)
119 self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
120 self.Bind(wx.EVT_CHAR, self.OnChar)
121 self.Bind(wx.EVT_PAINT, self.OnPaint)
122 self.Bind(wx.EVT_SIZE, self.OnSize)
123 self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
124 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
d14a1e28
RD
125
126##------------------- Platform-specific stuff
127
128 def NiceFontForPlatform(self):
b881fc78
RD
129 if wx.Platform == "__WXMSW__":
130 return wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)
d14a1e28 131 else:
b881fc78 132 return wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL, False)
d14a1e28
RD
133
134 def UnixKeyHack(self, key):
b881fc78 135 #
d14a1e28 136 # this will be obsolete when we get the new wxWindows patch
b881fc78
RD
137 #
138 # 12/14/03 - jmg
139 #
140 # Which patch? I don't know if this is needed, but I don't know
141 # why it's here either. Play it safe; leave it in.
142 #
d14a1e28
RD
143 if key <= 26:
144 key += ord('a') - 1
145 return key
146
147##-------------------- UpdateView/Cursor code
148
149 def OnSize(self, event):
150 self.AdjustScrollbars()
151 self.SetFocus()
152
153 def SetCharDimensions(self):
154 # TODO: We need a code review on this. It appears that Linux
155 # improperly reports window dimensions when the scrollbar's there.
b881fc78 156 self.bw, self.bh = self.GetClientSize()
d14a1e28 157
b881fc78 158 if wx.Platform == "__WXMSW__":
d14a1e28
RD
159 self.sh = self.bh / self.fh
160 self.sw = (self.bw / self.fw) - 1
161 else:
162 self.sh = self.bh / self.fh
163 if self.LinesInFile() >= self.sh:
b881fc78 164 self.bw = self.bw - wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
d14a1e28
RD
165 self.sw = (self.bw / self.fw) - 1
166
167 self.sw = (self.bw / self.fw) - 1
168 if self.CalcMaxLineLen() >= self.sw:
b881fc78 169 self.bh = self.bh - wx.SystemSettings_GetMetric(wx.SYS_HSCROLL_Y)
d14a1e28
RD
170 self.sh = self.bh / self.fh
171
172
173 def UpdateView(self, dc = None):
174 if dc is None:
b881fc78 175 dc = wx.ClientDC(self)
d14a1e28
RD
176 if dc.Ok():
177 self.SetCharDimensions()
178 self.KeepCursorOnScreen()
b881fc78 179 self.DrawSimpleCursor(0,0, dc, True)
d14a1e28
RD
180 self.Draw(dc)
181
182 def OnPaint(self, event):
b881fc78 183 dc = wx.PaintDC(self)
d14a1e28
RD
184 if self.isDrawing:
185 return
186 self.isDrawing = True
187 self.UpdateView(dc)
b881fc78 188 wx.CallAfter(self.AdjustScrollbars)
d14a1e28
RD
189 self.isDrawing = False
190
191 def OnEraseBackground(self, evt):
192 pass
193
194##-------------------- Drawing code
195
196 def InitFonts(self):
b881fc78 197 dc = wx.ClientDC(self)
d14a1e28
RD
198 self.font = self.NiceFontForPlatform()
199 dc.SetFont(self.font)
200 self.fw = dc.GetCharWidth()
201 self.fh = dc.GetCharHeight()
202
203 def SetColors(self):
b881fc78
RD
204 self.fgColor = wx.NamedColour('black')
205 self.bgColor = wx.NamedColour('white')
206 self.selectColor = wx.Colour(238, 220, 120) # r, g, b = emacsOrange
d14a1e28
RD
207
208 def InitDoubleBuffering(self):
209 pass
210
211 def DrawEditText(self, t, x, y, dc):
7722248d 212 dc.DrawText(t, (x * self.fw, y * self.fh))
d14a1e28
RD
213
214 def DrawLine(self, line, dc):
215 if self.IsLine(line):
216 l = line
217 t = self.lines[l]
218 dc.SetTextForeground(self.fgColor)
219 fragments = selection.Selection(
220 self.SelectBegin, self.SelectEnd,
221 self.sx, self.sw, line, t)
222 x = 0
223 for (data, selected) in fragments:
224 if selected:
225 dc.SetTextBackground(self.selectColor)
226 if x == 0 and len(data) == 0 and len(fragments) == 1:
227 data = ' '
228 else:
229 dc.SetTextBackground(self.bgColor)
230 self.DrawEditText(data, x, line - self.sy, dc)
231 x += len(data)
232
233 def Draw(self, odc=None):
234 if not odc:
b881fc78 235 odc = wx.ClientDC(self)
d14a1e28 236
b881fc78
RD
237 bmp = wx.EmptyBitmap(max(1,self.bw), max(1,self.bh))
238 dc = wx.BufferedDC(odc, bmp)
d14a1e28
RD
239 if dc.Ok():
240 dc.SetFont(self.font)
b881fc78 241 dc.SetBackgroundMode(wx.SOLID)
d14a1e28
RD
242 dc.SetTextBackground(self.bgColor)
243 dc.SetTextForeground(self.fgColor)
244 dc.Clear()
245 for line in range(self.sy, self.sy + self.sh):
246 self.DrawLine(line, dc)
247 if len(self.lines) < self.sh + self.sy:
248 self.DrawEofMarker(dc)
249 self.DrawCursor(dc)
250
251##------------------ eofMarker stuff
252
253 def LoadImages(self):
254 self.eofMarker = images.GetBitmap(images.EofImageData)
255
256 def DrawEofMarker(self,dc):
257 x = 0
258 y = (len(self.lines) - self.sy) * self.fh
259 hasTransparency = 1
7722248d 260 dc.DrawBitmap(self.eofMarker, (x, y), hasTransparency)
d14a1e28
RD
261
262##------------------ cursor-related functions
263
264 def DrawCursor(self, dc = None):
265 if not dc:
b881fc78 266 dc = wx.ClientDC(self)
d14a1e28
RD
267
268 if (self.LinesInFile())<self.cy: #-1 ?
269 self.cy = self.LinesInFile()-1
270 s = self.lines[self.cy]
271
272 x = self.cx - self.sx
273 y = self.cy - self.sy
274 self.DrawSimpleCursor(x, y, dc)
275
276
277 def DrawSimpleCursor(self, xp, yp, dc = None, old=False):
278 if not dc:
b881fc78 279 dc = wx.ClientDC(self)
d14a1e28
RD
280
281 if old:
282 xp = self.sco_x
283 yp = self.sco_y
284
285 szx = self.fw
286 szy = self.fh
287 x = xp * szx
288 y = yp * szy
b881fc78 289 dc.Blit((x,y), (szx,szy), dc, (x,y), wx.SRC_INVERT)
d14a1e28
RD
290 self.sco_x = xp
291 self.sco_y = yp
292
293##-------- Enforcing screen boundaries, cursor movement
294
295 def CalcMaxLineLen(self):
296 """get length of longest line on screen"""
297 maxlen = 0
298 for line in self.lines[self.sy:self.sy+self.sh]:
299 if len(line) >maxlen:
300 maxlen = len(line)
301 return maxlen
302
303 def KeepCursorOnScreen(self):
304 self.sy = ForceBetween(max(0, self.cy-self.sh), self.sy, self.cy)
305 self.sx = ForceBetween(max(0, self.cx-self.sw), self.sx, self.cx)
306 self.AdjustScrollbars()
307
308 def HorizBoundaries(self):
309 self.SetCharDimensions()
310 maxLineLen = self.CalcMaxLineLen()
311 self.sx = ForceBetween(0, self.sx, max(self.sw, maxLineLen - self.sw + 1))
312 self.cx = ForceBetween(self.sx, self.cx, self.sx + self.sw - 1)
313
314 def VertBoundaries(self):
315 self.SetCharDimensions()
316 self.sy = ForceBetween(0, self.sy, max(self.sh, self.LinesInFile() - self.sh + 1))
317 self.cy = ForceBetween(self.sy, self.cy, self.sy + self.sh - 1)
318
319 def cVert(self, num):
320 self.cy = self.cy + num
321 self.cy = ForceBetween(0, self.cy, self.LinesInFile() - 1)
322 self.sy = ForceBetween(self.cy - self.sh + 1, self.sy, self.cy)
323 self.cx = min(self.cx, self.CurrentLineLength())
324
325 def cHoriz(self, num):
326 self.cx = self.cx + num
327 self.cx = ForceBetween(0, self.cx, self.CurrentLineLength())
328 self.sx = ForceBetween(self.cx - self.sw + 1, self.sx, self.cx)
329
330 def AboveScreen(self, row):
331 return row < self.sy
332
333 def BelowScreen(self, row):
334 return row >= self.sy + self.sh
335
336 def LeftOfScreen(self, col):
337 return col < self.sx
338
339 def RightOfScreen(self, col):
340 return col >= self.sx + self.sw
341
342##----------------- data structure helper functions
343
344 def GetText(self):
345 return self.lines
346
347 def SetText(self, lines):
348 self.InitCoords()
349 self.lines = lines
350 self.UnTouchBuffer()
351 self.SelectOff()
352 self.AdjustScrollbars()
353 self.UpdateView(None)
354
355 def IsLine(self, lineNum):
356 return (0<=lineNum) and (lineNum<self.LinesInFile())
357
358 def GetTextLine(self, lineNum):
359 if self.IsLine(lineNum):
360 return self.lines[lineNum]
361 return ""
362
363 def SetTextLine(self, lineNum, text):
364 if self.IsLine(lineNum):
365 self.lines[lineNum] = text
366
367 def CurrentLineLength(self):
368 return len(self.lines[self.cy])
369
370 def LinesInFile(self):
371 return len(self.lines)
372
373 def UnTouchBuffer(self):
374 self.bufferTouched = False
375
376 def BufferWasTouched(self):
377 return self.bufferTouched
378
379 def TouchBuffer(self):
380 self.bufferTouched = True
381
382
383##-------------------------- Mouse scroll timing functions
384
385 def InitScrolling(self):
386 # we don't rely on the windows system to scroll for us; we just
387 # redraw the screen manually every time
388 self.EnableScrolling(False, False)
389 self.nextScrollTime = 0
390 self.SCROLLDELAY = 0.050 # seconds
b881fc78 391 self.scrollTimer = wx.Timer(self)
d14a1e28
RD
392 self.scroller = Scroller(self)
393
394 def CanScroll(self):
395 if time.time() > self.nextScrollTime:
396 self.nextScrollTime = time.time() + self.SCROLLDELAY
397 return True
398 else:
399 return False
400
401 def SetScrollTimer(self):
402 oneShot = True
403 self.scrollTimer.Start(1000*self.SCROLLDELAY/2, oneShot)
b881fc78 404 self.Bind(wx.EVT_TIMER, self.OnTimer)
d14a1e28
RD
405
406 def OnTimer(self, event):
b881fc78 407 screenX, screenY = wx.GetMousePosition()
d14a1e28
RD
408 x, y = self.ScreenToClientXY(screenX, screenY)
409 self.MouseToRow(y)
410 self.MouseToCol(x)
411 self.SelectUpdate()
412
413##-------------------------- Mouse off screen functions
414
415 def HandleAboveScreen(self, row):
416 self.SetScrollTimer()
417 if self.CanScroll():
418 row = self.sy - 1
419 row = max(0, row)
420 self.cy = row
421
422 def HandleBelowScreen(self, row):
423 self.SetScrollTimer()
424 if self.CanScroll():
425 row = self.sy + self.sh
426 row = min(row, self.LinesInFile() - 1)
427 self.cy = row
428
429 def HandleLeftOfScreen(self, col):
430 self.SetScrollTimer()
431 if self.CanScroll():
432 col = self.sx - 1
433 col = max(0,col)
434 self.cx = col
435
436 def HandleRightOfScreen(self, col):
437 self.SetScrollTimer()
438 if self.CanScroll():
439 col = self.sx + self.sw
440 col = min(col, self.CurrentLineLength())
441 self.cx = col
442
443##------------------------ mousing functions
444
445 def MouseToRow(self, mouseY):
446 row = self.sy + (mouseY/ self.fh)
447 if self.AboveScreen(row):
448 self.HandleAboveScreen(row)
449 elif self.BelowScreen(row):
450 self.HandleBelowScreen(row)
451 else:
452 self.cy = min(row, self.LinesInFile() - 1)
453
454 def MouseToCol(self, mouseX):
455 col = self.sx + (mouseX / self.fw)
456 if self.LeftOfScreen(col):
457 self.HandleLeftOfScreen(col)
458 elif self.RightOfScreen(col):
459 self.HandleRightOfScreen(col)
460 else:
461 self.cx = min(col, self.CurrentLineLength())
462
463 def MouseToCursor(self, event):
464 self.MouseToRow(event.GetY())
465 self.MouseToCol(event.GetX())
466
467 def OnMotion(self, event):
468 if event.LeftIsDown() and self.HasCapture():
469 self.Selecting = True
470 self.MouseToCursor(event)
471 self.SelectUpdate()
472
473 def OnLeftDown(self, event):
474 self.MouseToCursor(event)
475 self.SelectBegin = (self.cy, self.cx)
476 self.SelectEnd = None
477 self.UpdateView()
478 self.CaptureMouse()
479
480 def OnLeftUp(self, event):
481 if not self.HasCapture():
482 return
483
484 if self.SelectEnd is None:
485 self.OnClick()
486 else:
487 self.Selecting = False
488 self.SelectNotify(False, self.SelectBegin, self.SelectEnd)
489
490 self.ReleaseMouse()
491 self.scrollTimer.Stop()
492
493
494#------------------------- Scrolling
495
496 def HorizScroll(self, event, eventType):
497 maxLineLen = self.CalcMaxLineLen()
498
b881fc78 499 if eventType == wx.EVT_SCROLLWIN_LINEUP:
d14a1e28 500 self.sx -= 1
b881fc78 501 elif eventType == wx.EVT_SCROLLWIN_LINEDOWN:
d14a1e28 502 self.sx += 1
b881fc78 503 elif eventType == wx.EVT_SCROLLWIN_PAGEUP:
d14a1e28 504 self.sx -= self.sw
b881fc78 505 elif eventType == wx.EVT_SCROLLWIN_PAGEDOWN:
d14a1e28 506 self.sx += self.sw
b881fc78 507 elif eventType == wx.EVT_SCROLLWIN_TOP:
d14a1e28 508 self.sx = self.cx = 0
b881fc78 509 elif eventType == wx.EVT_SCROLLWIN_BOTTOM:
d14a1e28
RD
510 self.sx = maxLineLen - self.sw
511 self.cx = maxLineLen
512 else:
513 self.sx = event.GetPosition()
514
515 self.HorizBoundaries()
516
517 def VertScroll(self, event, eventType):
b881fc78 518 if eventType == wx.EVT_SCROLLWIN_LINEUP:
d14a1e28 519 self.sy -= 1
b881fc78 520 elif eventType == wx.EVT_SCROLLWIN_LINEDOWN:
d14a1e28 521 self.sy += 1
b881fc78 522 elif eventType == wx.EVT_SCROLLWIN_PAGEUP:
d14a1e28 523 self.sy -= self.sh
b881fc78 524 elif eventType == wx.EVT_SCROLLWIN_PAGEDOWN:
d14a1e28 525 self.sy += self.sh
b881fc78 526 elif eventType == wx.EVT_SCROLLWIN_TOP:
d14a1e28 527 self.sy = self.cy = 0
b881fc78 528 elif eventType == wx.EVT_SCROLLWIN_BOTTOM:
d14a1e28
RD
529 self.sy = self.LinesInFile() - self.sh
530 self.cy = self.LinesInFile()
531 else:
532 self.sy = event.GetPosition()
533
534 self.VertBoundaries()
535
536 def OnScroll(self, event):
537 dir = event.GetOrientation()
538 eventType = event.GetEventType()
b881fc78 539 if dir == wx.HORIZONTAL:
d14a1e28
RD
540 self.HorizScroll(event, eventType)
541 else:
542 self.VertScroll(event, eventType)
543 self.UpdateView()
544
545
546 def AdjustScrollbars(self):
547 for i in range(2):
548 self.SetCharDimensions()
549 self.scroller.SetScrollbars(
550 self.fw, self.fh,
551 self.CalcMaxLineLen()+3, max(self.LinesInFile()+1, self.sh),
552 self.sx, self.sy)
553
554#------------ backspace, delete, return
555
556 def BreakLine(self, event):
557 if self.IsLine(self.cy):
558 t = self.lines[self.cy]
559 self.lines = self.lines[:self.cy] + [t[:self.cx],t[self.cx:]] + self.lines[self.cy+1:]
560 self.cVert(1)
561 self.cx = 0
562 self.TouchBuffer()
563
564 def InsertChar(self,char):
565 if self.IsLine(self.cy):
566 t = self.lines[self.cy]
567 t = t[:self.cx] + char + t[self.cx:]
568 self.SetTextLine(self.cy, t)
569 self.cHoriz(1)
570 self.TouchBuffer()
571
572 def JoinLines(self):
573 t1 = self.lines[self.cy]
574 t2 = self.lines[self.cy+1]
575 self.cx = len(t1)
576 self.lines = self.lines[:self.cy] + [t1 + t2] + self.lines[self.cy+2:]
577 self.TouchBuffer()
578
579
580 def DeleteChar(self,x,y,oldtext):
581 newtext = oldtext[:x] + oldtext[x+1:]
582 self.SetTextLine(y, newtext)
583 self.TouchBuffer()
584
585
586 def BackSpace(self, event):
587 t = self.GetTextLine(self.cy)
588 if self.cx>0:
589 self.DeleteChar(self.cx-1,self.cy,t)
590 self.cHoriz(-1)
591 self.TouchBuffer()
592 elif self.cx == 0:
593 if self.cy > 0:
594 self.cy -= 1
595 self.JoinLines()
596 self.TouchBuffer()
597 else:
b881fc78 598 wx.Bell()
d14a1e28
RD
599
600 def Delete(self, event):
601 t = self.GetTextLine(self.cy)
602 if self.cx<len(t):
603 self.DeleteChar(self.cx,self.cy,t)
604 self.TouchBuffer()
605 else:
606 if self.cy < len(self.lines) - 1:
607 self.JoinLines()
608 self.TouchBuffer()
609
610 def Escape(self, event):
611 self.SelectOff()
612
613 def TabKey(self, event):
614 numSpaces = self.SpacesPerTab - (self.cx % self.SpacesPerTab)
615 self.SingleLineInsert(' ' * numSpaces)
616
617##----------- selection routines
618
619 def SelectUpdate(self):
620 self.SelectEnd = (self.cy, self.cx)
621 self.SelectNotify(self.Selecting, self.SelectBegin, self.SelectEnd)
622 self.UpdateView()
623
624 def NormalizedSelect(self):
625 (begin, end) = (self.SelectBegin, self.SelectEnd)
626 (bRow, bCol) = begin
627 (eRow, eCol) = end
628 if (bRow < eRow):
629 return (begin, end)
630 elif (eRow < bRow):
631 return (end, begin)
632 else:
633 if (bCol < eCol):
634 return (begin, end)
635 else:
636 return (end, begin)
637
638 def FindSelection(self):
639 if self.SelectEnd is None or self.SelectBegin is None:
b881fc78 640 wx.Bell()
d14a1e28
RD
641 return None
642 (begin, end) = self.NormalizedSelect()
643 (bRow, bCol) = begin
644 (eRow, eCol) = end
645 return (bRow, bCol, eRow, eCol)
646
647 def SelectOff(self):
648 self.SelectBegin = None
649 self.SelectEnd = None
650 self.Selecting = False
651 self.SelectNotify(False,None,None)
652
653 def CopySelection(self, event):
654 selection = self.FindSelection()
655 if selection is None:
656 return
657 (bRow, bCol, eRow, eCol) = selection
658
659 if bRow == eRow:
660 self.SingleLineCopy(bRow, bCol, eCol)
661 else:
662 self.MultipleLineCopy(bRow, bCol, eRow, eCol)
663
664 def OnCopySelection(self, event):
665 self.CopySelection(event)
666 self.SelectOff()
667
668 def CopyToClipboard(self, linesOfText):
b881fc78 669 do = wx.TextDataObject()
d14a1e28 670 do.SetText(os.linesep.join(linesOfText))
b881fc78
RD
671 wx.TheClipboard.Open()
672 wx.TheClipboard.SetData(do)
673 wx.TheClipboard.Close()
d14a1e28
RD
674
675 def SingleLineCopy(self, Row, bCol, eCol):
676 Line = self.GetTextLine(Row)
677 self.CopyToClipboard([Line[bCol:eCol]])
678
679 def MultipleLineCopy(self, bRow, bCol, eRow, eCol):
680 bLine = self.GetTextLine(bRow)[bCol:]
681 eLine = self.GetTextLine(eRow)[:eCol]
682 self.CopyToClipboard([bLine] + [l for l in self.lines[bRow + 1:eRow]] + [eLine])
683
684 def OnDeleteSelection(self, event):
685 selection = self.FindSelection()
686 if selection is None:
687 return
688 (bRow, bCol, eRow, eCol) = selection
689
690 if bRow == eRow:
691 self.SingleLineDelete(bRow, bCol, eCol)
692 else:
693 self.MultipleLineDelete(bRow, bCol, eRow, eCol)
694
695 self.TouchBuffer()
696
697 self.cy = bRow
698 self.cx = bCol
699 self.SelectOff()
700 self.UpdateView()
701
702
703 def SingleLineDelete(self, Row, bCol, eCol):
704 ModLine = self.GetTextLine(Row)
705 ModLine = ModLine[:bCol] + ModLine[eCol:]
706 self.SetTextLine(Row,ModLine)
707
708 def MultipleLineDelete(self, bRow, bCol, eRow, eCol):
709 bLine = self.GetTextLine(bRow)
710 eLine = self.GetTextLine(eRow)
711 ModLine = bLine[:bCol] + eLine[eCol:]
712 self.lines[bRow:eRow + 1] = [ModLine]
713
714 def OnPaste(self, event):
b881fc78
RD
715 do = wx.TextDataObject()
716 wx.TheClipboard.Open()
717 success = wx.TheClipboard.GetData(do)
718 wx.TheClipboard.Close()
d14a1e28
RD
719 if success:
720 pastedLines = LineSplitter(do.GetText())
721 else:
b881fc78 722 wx.Bell()
d14a1e28
RD
723 return
724 if len(pastedLines) == 0:
b881fc78 725 wx.Bell()
d14a1e28
RD
726 return
727 elif len(pastedLines) == 1:
728 self.SingleLineInsert(pastedLines[0])
729 else:
730 self.MultipleLinePaste(pastedLines)
731
732 def SingleLineInsert(self, newText):
733 ModLine = self.GetTextLine(self.cy)
734 ModLine = ModLine[:self.cx] + newText + ModLine[self.cx:]
735 self.SetTextLine(self.cy, ModLine)
736 self.cHoriz(len(newText))
737 self.TouchBuffer()
738 self.UpdateView()
739
740 def MultipleLinePaste(self, pastedLines):
741 FirstLine = LastLine = self.GetTextLine(self.cy)
742 FirstLine = FirstLine[:self.cx] + pastedLines[0]
743 LastLine = pastedLines[-1] + LastLine[self.cx:]
744
745 NewSlice = [FirstLine]
746 NewSlice += [l for l in pastedLines[1:-1]]
747 NewSlice += [LastLine]
748 self.lines[self.cy:self.cy + 1] = NewSlice
749
750 self.cy = self.cy + len(pastedLines)-1
751 self.cx = len(pastedLines[-1])
752 self.TouchBuffer()
753 self.UpdateView()
754
755 def OnCutSelection(self,event):
756 self.CopySelection(event)
757 self.OnDeleteSelection(event)
758
759#-------------- Keyboard movement implementations
760
761 def MoveDown(self, event):
762 self.cVert(+1)
763
764 def MoveUp(self, event):
765 self.cVert(-1)
766
767 def MoveLeft(self, event):
768 if self.cx == 0:
769 if self.cy == 0:
770 wxBell()
771 else:
772 self.cVert(-1)
773 self.cx = self.CurrentLineLength()
774 else:
775 self.cx -= 1
776
777 def MoveRight(self, event):
778 linelen = self.CurrentLineLength()
779 if self.cx == linelen:
780 if self.cy == len(self.lines) - 1:
781 wxBell()
782 else:
783 self.cx = 0
784 self.cVert(1)
785 else:
786 self.cx += 1
787
788
789 def MovePageDown(self, event):
790 self.cVert(self.sh)
791
792 def MovePageUp(self, event):
793 self.cVert(-self.sh)
794
795 def MoveHome(self, event):
796 self.cx = 0
797
798 def MoveEnd(self, event):
799 self.cx = self.CurrentLineLength()
800
801 def MoveStartOfFile(self, event):
802 self.cy = 0
803 self.cx = 0
804
805 def MoveEndOfFile(self, event):
806 self.cy = len(self.lines) - 1
807 self.cx = self.CurrentLineLength()
808
809#-------------- Key handler mapping tables
810
811 def SetMoveSpecialFuncs(self, action):
b881fc78
RD
812 action[wx.WXK_DOWN] = self.MoveDown
813 action[wx.WXK_UP] = self.MoveUp
814 action[wx.WXK_LEFT] = self.MoveLeft
815 action[wx.WXK_RIGHT] = self.MoveRight
816 action[wx.WXK_NEXT] = self.MovePageDown
817 action[wx.WXK_PRIOR] = self.MovePageUp
818 action[wx.WXK_HOME] = self.MoveHome
819 action[wx.WXK_END] = self.MoveEnd
d14a1e28
RD
820
821 def SetMoveSpecialControlFuncs(self, action):
b881fc78
RD
822 action[wx.WXK_HOME] = self.MoveStartOfFile
823 action[wx.WXK_END] = self.MoveEndOfFile
d14a1e28
RD
824
825 def SetAltFuncs(self, action):
826 # subclass implements
827 pass
828
829 def SetControlFuncs(self, action):
830 action['c'] = self.OnCopySelection
831 action['d'] = self.OnDeleteSelection
832 action['v'] = self.OnPaste
833 action['x'] = self.OnCutSelection
834
835 def SetSpecialControlFuncs(self, action):
b881fc78 836 action[wx.WXK_INSERT] = self.OnCopySelection
d14a1e28
RD
837
838 def SetShiftFuncs(self, action):
b881fc78
RD
839 action[wx.WXK_DELETE] = self.OnCutSelection
840 action[wx.WXK_INSERT] = self.OnPaste
d14a1e28
RD
841
842 def SetSpecialFuncs(self, action):
b881fc78
RD
843 action[wx.WXK_BACK] = self.BackSpace
844 action[wx.WXK_DELETE] = self.Delete
845 action[wx.WXK_RETURN] = self.BreakLine
846 action[wx.WXK_ESCAPE] = self.Escape
847 action[wx.WXK_TAB] = self.TabKey
d14a1e28
RD
848
849##-------------- Logic for key handlers
850
851
852 def Move(self, keySettingFunction, key, event):
853 action = {}
854 keySettingFunction(action)
855
856 if not action.has_key(key):
857 return False
858
859 if event.ShiftDown():
860 if not self.Selecting:
861 self.Selecting = True
862 self.SelectBegin = (self.cy, self.cx)
863 action[key](event)
864 self.SelectEnd = (self.cy, self.cx)
865 else:
866 action[key](event)
867 if self.Selecting:
868 self.Selecting = False
869
870 self.SelectNotify(self.Selecting, self.SelectBegin, self.SelectEnd)
871 self.UpdateView()
872 return True
873
874 def MoveSpecialKey(self, event, key):
875 return self.Move(self.SetMoveSpecialFuncs, key, event)
876
877 def MoveSpecialControlKey(self, event, key):
878 if not event.ControlDown():
879 return False
880 return self.Move(self.SetMoveSpecialControlFuncs, key, event)
881
882 def Dispatch(self, keySettingFunction, key, event):
883 action = {}
884 keySettingFunction(action)
885 if action.has_key(key):
886 action[key](event)
887 self.UpdateView()
888 return True
889 return False
890
891 def ModifierKey(self, key, event, modifierKeyDown, MappingFunc):
892 if not modifierKeyDown:
893 return False
894
895 key = self.UnixKeyHack(key)
896 try:
897 key = chr(key)
898 except:
899 return False
900 if not self.Dispatch(MappingFunc, key, event):
b881fc78 901 wx.Bell()
d14a1e28
RD
902 return True
903
904 def ControlKey(self, event, key):
905 return self.ModifierKey(key, event, event.ControlDown(), self.SetControlFuncs)
906
907 def AltKey(self, event, key):
908 return self.ModifierKey(key, event, event.AltDown(), self.SetAltFuncs)
909
910 def SpecialControlKey(self, event, key):
911 if not event.ControlDown():
912 return False
913 if not self.Dispatch(self.SetSpecialControlFuncs, key, event):
b881fc78 914 wx.Bell()
d14a1e28
RD
915 return True
916
917 def ShiftKey(self, event, key):
918 if not event.ShiftDown():
919 return False
920 return self.Dispatch(self.SetShiftFuncs, key, event)
921
922 def NormalChar(self, event, key):
923 self.SelectOff()
924
925 # regular ascii
926 if not self.Dispatch(self.SetSpecialFuncs, key, event):
927 if (key>31) and (key<256):
928 self.InsertChar(chr(key))
929 else:
b881fc78 930 wx.Bell()
d14a1e28
RD
931 return
932 self.UpdateView()
933 self.AdjustScrollbars()
934
935 def OnChar(self, event):
936 key = event.KeyCode()
937 filters = [self.AltKey,
938 self.MoveSpecialControlKey,
939 self.ControlKey,
940 self.SpecialControlKey,
941 self.MoveSpecialKey,
942 self.ShiftKey,
943 self.NormalChar]
944 for filter in filters:
945 if filter(event,key):
946 break
947 return 0
948
949#----------------------- Eliminate memory leaks
950
951 def OnDestroy(self, event):
952 self.mdc = None
953 self.odc = None
954 self.bgColor = None
955 self.fgColor = None
956 self.font = None
957 self.selectColor = None
958 self.scrollTimer = None
959 self.eofMarker = None
960
961#-------------------- Abstract methods for subclasses
962
963 def OnClick(self):
964 pass
965
966 def SelectNotify(self, Selecting, SelectionBegin, SelectionEnd):
967 pass
1fded56b 968