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