]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/editor/editor.py
Patch from F. Oliver Gathmann to observer enabled/disabled state of
[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 bmp = wx.EmptyBitmap(max(1,self.bw), max(1,self.bh))
245 dc = wx.BufferedDC(odc, bmp)
246 if dc.Ok():
247 dc.SetFont(self.font)
248 dc.SetBackgroundMode(wx.SOLID)
249 dc.SetTextBackground(self.bgColor)
250 dc.SetTextForeground(self.fgColor)
251 dc.Clear()
252 for line in range(self.sy, self.sy + self.sh):
253 self.DrawLine(line, dc)
254 if len(self.lines) < self.sh + self.sy:
255 self.DrawEofMarker(dc)
256 self.DrawCursor(dc)
257
258 ##------------------ eofMarker stuff
259
260 def LoadImages(self):
261 self.eofMarker = images.GetBitmap(images.EofImageData)
262
263 def DrawEofMarker(self,dc):
264 x = 0
265 y = (len(self.lines) - self.sy) * self.fh
266 hasTransparency = 1
267 dc.DrawBitmap(self.eofMarker, x, y, hasTransparency)
268
269 ##------------------ cursor-related functions
270
271 def DrawCursor(self, dc = None):
272 if not dc:
273 dc = wx.ClientDC(self)
274
275 if (self.LinesInFile())<self.cy: #-1 ?
276 self.cy = self.LinesInFile()-1
277 s = self.lines[self.cy]
278
279 x = self.cx - self.sx
280 y = self.cy - self.sy
281 self.DrawSimpleCursor(x, y, dc)
282
283
284 def DrawSimpleCursor(self, xp, yp, dc = None, old=False):
285 if not dc:
286 dc = wx.ClientDC(self)
287
288 if old:
289 xp = self.sco_x
290 yp = self.sco_y
291
292 szx = self.fw
293 szy = self.fh
294 x = xp * szx
295 y = yp * szy
296 dc.Blit(x,y, szx,szy, dc, x,y, wx.SRC_INVERT)
297 self.sco_x = xp
298 self.sco_y = yp
299
300 ##-------- Enforcing screen boundaries, cursor movement
301
302 def CalcMaxLineLen(self):
303 """get length of longest line on screen"""
304 maxlen = 0
305 for line in self.lines[self.sy:self.sy+self.sh]:
306 if len(line) >maxlen:
307 maxlen = len(line)
308 return maxlen
309
310 def KeepCursorOnScreen(self):
311 self.sy = ForceBetween(max(0, self.cy-self.sh), self.sy, self.cy)
312 self.sx = ForceBetween(max(0, self.cx-self.sw), self.sx, self.cx)
313 self.AdjustScrollbars()
314
315 def HorizBoundaries(self):
316 self.SetCharDimensions()
317 maxLineLen = self.CalcMaxLineLen()
318 self.sx = ForceBetween(0, self.sx, max(self.sw, maxLineLen - self.sw + 1))
319 self.cx = ForceBetween(self.sx, self.cx, self.sx + self.sw - 1)
320
321 def VertBoundaries(self):
322 self.SetCharDimensions()
323 self.sy = ForceBetween(0, self.sy, max(self.sh, self.LinesInFile() - self.sh + 1))
324 self.cy = ForceBetween(self.sy, self.cy, self.sy + self.sh - 1)
325
326 def cVert(self, num):
327 self.cy = self.cy + num
328 self.cy = ForceBetween(0, self.cy, self.LinesInFile() - 1)
329 self.sy = ForceBetween(self.cy - self.sh + 1, self.sy, self.cy)
330 self.cx = min(self.cx, self.CurrentLineLength())
331
332 def cHoriz(self, num):
333 self.cx = self.cx + num
334 self.cx = ForceBetween(0, self.cx, self.CurrentLineLength())
335 self.sx = ForceBetween(self.cx - self.sw + 1, self.sx, self.cx)
336
337 def AboveScreen(self, row):
338 return row < self.sy
339
340 def BelowScreen(self, row):
341 return row >= self.sy + self.sh
342
343 def LeftOfScreen(self, col):
344 return col < self.sx
345
346 def RightOfScreen(self, col):
347 return col >= self.sx + self.sw
348
349 ##----------------- data structure helper functions
350
351 def GetText(self):
352 return self.lines
353
354 def SetText(self, lines):
355 self.InitCoords()
356 self.lines = lines
357 self.UnTouchBuffer()
358 self.SelectOff()
359 self.AdjustScrollbars()
360 self.UpdateView(None)
361
362 def IsLine(self, lineNum):
363 return (0<=lineNum) and (lineNum<self.LinesInFile())
364
365 def GetTextLine(self, lineNum):
366 if self.IsLine(lineNum):
367 return self.lines[lineNum]
368 return ""
369
370 def SetTextLine(self, lineNum, text):
371 if self.IsLine(lineNum):
372 self.lines[lineNum] = text
373
374 def CurrentLineLength(self):
375 return len(self.lines[self.cy])
376
377 def LinesInFile(self):
378 return len(self.lines)
379
380 def UnTouchBuffer(self):
381 self.bufferTouched = False
382
383 def BufferWasTouched(self):
384 return self.bufferTouched
385
386 def TouchBuffer(self):
387 self.bufferTouched = True
388
389
390 ##-------------------------- Mouse scroll timing functions
391
392 def InitScrolling(self):
393 # we don't rely on the windows system to scroll for us; we just
394 # redraw the screen manually every time
395 self.EnableScrolling(False, False)
396 self.nextScrollTime = 0
397 self.SCROLLDELAY = 0.050 # seconds
398 self.scrollTimer = wx.Timer(self)
399 self.scroller = Scroller(self)
400
401 def CanScroll(self):
402 if time.time() > self.nextScrollTime:
403 self.nextScrollTime = time.time() + self.SCROLLDELAY
404 return True
405 else:
406 return False
407
408 def SetScrollTimer(self):
409 oneShot = True
410 self.scrollTimer.Start(1000*self.SCROLLDELAY/2, oneShot)
411 self.Bind(wx.EVT_TIMER, self.OnTimer)
412
413 def OnTimer(self, event):
414 screenX, screenY = wx.GetMousePosition()
415 x, y = self.ScreenToClientXY(screenX, screenY)
416 self.MouseToRow(y)
417 self.MouseToCol(x)
418 self.SelectUpdate()
419
420 ##-------------------------- Mouse off screen functions
421
422 def HandleAboveScreen(self, row):
423 self.SetScrollTimer()
424 if self.CanScroll():
425 row = self.sy - 1
426 row = max(0, row)
427 self.cy = row
428
429 def HandleBelowScreen(self, row):
430 self.SetScrollTimer()
431 if self.CanScroll():
432 row = self.sy + self.sh
433 row = min(row, self.LinesInFile() - 1)
434 self.cy = row
435
436 def HandleLeftOfScreen(self, col):
437 self.SetScrollTimer()
438 if self.CanScroll():
439 col = self.sx - 1
440 col = max(0,col)
441 self.cx = col
442
443 def HandleRightOfScreen(self, col):
444 self.SetScrollTimer()
445 if self.CanScroll():
446 col = self.sx + self.sw
447 col = min(col, self.CurrentLineLength())
448 self.cx = col
449
450 ##------------------------ mousing functions
451
452 def MouseToRow(self, mouseY):
453 row = self.sy + (mouseY/ self.fh)
454 if self.AboveScreen(row):
455 self.HandleAboveScreen(row)
456 elif self.BelowScreen(row):
457 self.HandleBelowScreen(row)
458 else:
459 self.cy = min(row, self.LinesInFile() - 1)
460
461 def MouseToCol(self, mouseX):
462 col = self.sx + (mouseX / self.fw)
463 if self.LeftOfScreen(col):
464 self.HandleLeftOfScreen(col)
465 elif self.RightOfScreen(col):
466 self.HandleRightOfScreen(col)
467 else:
468 self.cx = min(col, self.CurrentLineLength())
469
470 def MouseToCursor(self, event):
471 self.MouseToRow(event.GetY())
472 self.MouseToCol(event.GetX())
473
474 def OnMotion(self, event):
475 if event.LeftIsDown() and self.HasCapture():
476 self.Selecting = True
477 self.MouseToCursor(event)
478 self.SelectUpdate()
479
480 def OnLeftDown(self, event):
481 self.MouseToCursor(event)
482 self.SelectBegin = (self.cy, self.cx)
483 self.SelectEnd = None
484 self.UpdateView()
485 self.CaptureMouse()
486 self.SetFocus()
487
488 def OnLeftUp(self, event):
489 if not self.HasCapture():
490 return
491
492 if self.SelectEnd is None:
493 self.OnClick()
494 else:
495 self.Selecting = False
496 self.SelectNotify(False, self.SelectBegin, self.SelectEnd)
497
498 self.ReleaseMouse()
499 self.scrollTimer.Stop()
500
501
502 #------------------------- Scrolling
503
504 def HorizScroll(self, event, eventType):
505 maxLineLen = self.CalcMaxLineLen()
506
507 if eventType == wx.EVT_SCROLLWIN_LINEUP:
508 self.sx -= 1
509 elif eventType == wx.EVT_SCROLLWIN_LINEDOWN:
510 self.sx += 1
511 elif eventType == wx.EVT_SCROLLWIN_PAGEUP:
512 self.sx -= self.sw
513 elif eventType == wx.EVT_SCROLLWIN_PAGEDOWN:
514 self.sx += self.sw
515 elif eventType == wx.EVT_SCROLLWIN_TOP:
516 self.sx = self.cx = 0
517 elif eventType == wx.EVT_SCROLLWIN_BOTTOM:
518 self.sx = maxLineLen - self.sw
519 self.cx = maxLineLen
520 else:
521 self.sx = event.GetPosition()
522
523 self.HorizBoundaries()
524
525 def VertScroll(self, event, eventType):
526 if eventType == wx.EVT_SCROLLWIN_LINEUP:
527 self.sy -= 1
528 elif eventType == wx.EVT_SCROLLWIN_LINEDOWN:
529 self.sy += 1
530 elif eventType == wx.EVT_SCROLLWIN_PAGEUP:
531 self.sy -= self.sh
532 elif eventType == wx.EVT_SCROLLWIN_PAGEDOWN:
533 self.sy += self.sh
534 elif eventType == wx.EVT_SCROLLWIN_TOP:
535 self.sy = self.cy = 0
536 elif eventType == wx.EVT_SCROLLWIN_BOTTOM:
537 self.sy = self.LinesInFile() - self.sh
538 self.cy = self.LinesInFile()
539 else:
540 self.sy = event.GetPosition()
541
542 self.VertBoundaries()
543
544 def OnScroll(self, event):
545 dir = event.GetOrientation()
546 eventType = event.GetEventType()
547 if dir == wx.HORIZONTAL:
548 self.HorizScroll(event, eventType)
549 else:
550 self.VertScroll(event, eventType)
551 self.UpdateView()
552
553
554 def AdjustScrollbars(self):
555 if self:
556 for i in range(2):
557 self.SetCharDimensions()
558 self.scroller.SetScrollbars(
559 self.fw, self.fh,
560 self.CalcMaxLineLen()+3, max(self.LinesInFile()+1, self.sh),
561 self.sx, self.sy)
562
563 #------------ backspace, delete, return
564
565 def BreakLine(self, event):
566 if self.IsLine(self.cy):
567 t = self.lines[self.cy]
568 self.lines = self.lines[:self.cy] + [t[:self.cx],t[self.cx:]] + self.lines[self.cy+1:]
569 self.cVert(1)
570 self.cx = 0
571 self.TouchBuffer()
572
573 def InsertChar(self,char):
574 if self.IsLine(self.cy):
575 t = self.lines[self.cy]
576 t = t[:self.cx] + char + t[self.cx:]
577 self.SetTextLine(self.cy, t)
578 self.cHoriz(1)
579 self.TouchBuffer()
580
581 def JoinLines(self):
582 t1 = self.lines[self.cy]
583 t2 = self.lines[self.cy+1]
584 self.cx = len(t1)
585 self.lines = self.lines[:self.cy] + [t1 + t2] + self.lines[self.cy+2:]
586 self.TouchBuffer()
587
588
589 def DeleteChar(self,x,y,oldtext):
590 newtext = oldtext[:x] + oldtext[x+1:]
591 self.SetTextLine(y, newtext)
592 self.TouchBuffer()
593
594
595 def BackSpace(self, event):
596 t = self.GetTextLine(self.cy)
597 if self.cx>0:
598 self.DeleteChar(self.cx-1,self.cy,t)
599 self.cHoriz(-1)
600 self.TouchBuffer()
601 elif self.cx == 0:
602 if self.cy > 0:
603 self.cy -= 1
604 self.JoinLines()
605 self.TouchBuffer()
606 else:
607 wx.Bell()
608
609 def Delete(self, event):
610 t = self.GetTextLine(self.cy)
611 if self.cx<len(t):
612 self.DeleteChar(self.cx,self.cy,t)
613 self.TouchBuffer()
614 else:
615 if self.cy < len(self.lines) - 1:
616 self.JoinLines()
617 self.TouchBuffer()
618
619 def Escape(self, event):
620 self.SelectOff()
621
622 def TabKey(self, event):
623 numSpaces = self.SpacesPerTab - (self.cx % self.SpacesPerTab)
624 self.SingleLineInsert(' ' * numSpaces)
625
626 ##----------- selection routines
627
628 def SelectUpdate(self):
629 self.SelectEnd = (self.cy, self.cx)
630 self.SelectNotify(self.Selecting, self.SelectBegin, self.SelectEnd)
631 self.UpdateView()
632
633 def NormalizedSelect(self):
634 (begin, end) = (self.SelectBegin, self.SelectEnd)
635 (bRow, bCol) = begin
636 (eRow, eCol) = end
637 if (bRow < eRow):
638 return (begin, end)
639 elif (eRow < bRow):
640 return (end, begin)
641 else:
642 if (bCol < eCol):
643 return (begin, end)
644 else:
645 return (end, begin)
646
647 def FindSelection(self):
648 if self.SelectEnd is None or self.SelectBegin is None:
649 wx.Bell()
650 return None
651 (begin, end) = self.NormalizedSelect()
652 (bRow, bCol) = begin
653 (eRow, eCol) = end
654 return (bRow, bCol, eRow, eCol)
655
656 def SelectOff(self):
657 self.SelectBegin = None
658 self.SelectEnd = None
659 self.Selecting = False
660 self.SelectNotify(False,None,None)
661
662 def CopySelection(self, event):
663 selection = self.FindSelection()
664 if selection is None:
665 return
666 (bRow, bCol, eRow, eCol) = selection
667
668 if bRow == eRow:
669 self.SingleLineCopy(bRow, bCol, eCol)
670 else:
671 self.MultipleLineCopy(bRow, bCol, eRow, eCol)
672
673 def OnCopySelection(self, event):
674 self.CopySelection(event)
675 self.SelectOff()
676
677 def CopyToClipboard(self, linesOfText):
678 do = wx.TextDataObject()
679 do.SetText(os.linesep.join(linesOfText))
680 wx.TheClipboard.Open()
681 wx.TheClipboard.SetData(do)
682 wx.TheClipboard.Close()
683
684 def SingleLineCopy(self, Row, bCol, eCol):
685 Line = self.GetTextLine(Row)
686 self.CopyToClipboard([Line[bCol:eCol]])
687
688 def MultipleLineCopy(self, bRow, bCol, eRow, eCol):
689 bLine = self.GetTextLine(bRow)[bCol:]
690 eLine = self.GetTextLine(eRow)[:eCol]
691 self.CopyToClipboard([bLine] + [l for l in self.lines[bRow + 1:eRow]] + [eLine])
692
693 def OnDeleteSelection(self, event):
694 selection = self.FindSelection()
695 if selection is None:
696 return
697 (bRow, bCol, eRow, eCol) = selection
698
699 if bRow == eRow:
700 self.SingleLineDelete(bRow, bCol, eCol)
701 else:
702 self.MultipleLineDelete(bRow, bCol, eRow, eCol)
703
704 self.TouchBuffer()
705
706 self.cy = bRow
707 self.cx = bCol
708 self.SelectOff()
709 self.UpdateView()
710
711
712 def SingleLineDelete(self, Row, bCol, eCol):
713 ModLine = self.GetTextLine(Row)
714 ModLine = ModLine[:bCol] + ModLine[eCol:]
715 self.SetTextLine(Row,ModLine)
716
717 def MultipleLineDelete(self, bRow, bCol, eRow, eCol):
718 bLine = self.GetTextLine(bRow)
719 eLine = self.GetTextLine(eRow)
720 ModLine = bLine[:bCol] + eLine[eCol:]
721 self.lines[bRow:eRow + 1] = [ModLine]
722
723 def OnPaste(self, event):
724 do = wx.TextDataObject()
725 wx.TheClipboard.Open()
726 success = wx.TheClipboard.GetData(do)
727 wx.TheClipboard.Close()
728 if success:
729 pastedLines = LineSplitter(do.GetText())
730 else:
731 wx.Bell()
732 return
733 if len(pastedLines) == 0:
734 wx.Bell()
735 return
736 elif len(pastedLines) == 1:
737 self.SingleLineInsert(pastedLines[0])
738 else:
739 self.MultipleLinePaste(pastedLines)
740
741 def SingleLineInsert(self, newText):
742 ModLine = self.GetTextLine(self.cy)
743 ModLine = ModLine[:self.cx] + newText + ModLine[self.cx:]
744 self.SetTextLine(self.cy, ModLine)
745 self.cHoriz(len(newText))
746 self.TouchBuffer()
747 self.UpdateView()
748
749 def MultipleLinePaste(self, pastedLines):
750 FirstLine = LastLine = self.GetTextLine(self.cy)
751 FirstLine = FirstLine[:self.cx] + pastedLines[0]
752 LastLine = pastedLines[-1] + LastLine[self.cx:]
753
754 NewSlice = [FirstLine]
755 NewSlice += [l for l in pastedLines[1:-1]]
756 NewSlice += [LastLine]
757 self.lines[self.cy:self.cy + 1] = NewSlice
758
759 self.cy = self.cy + len(pastedLines)-1
760 self.cx = len(pastedLines[-1])
761 self.TouchBuffer()
762 self.UpdateView()
763
764 def OnCutSelection(self,event):
765 self.CopySelection(event)
766 self.OnDeleteSelection(event)
767
768 #-------------- Keyboard movement implementations
769
770 def MoveDown(self, event):
771 self.cVert(+1)
772
773 def MoveUp(self, event):
774 self.cVert(-1)
775
776 def MoveLeft(self, event):
777 if self.cx == 0:
778 if self.cy == 0:
779 wx.Bell()
780 else:
781 self.cVert(-1)
782 self.cx = self.CurrentLineLength()
783 else:
784 self.cx -= 1
785
786 def MoveRight(self, event):
787 linelen = self.CurrentLineLength()
788 if self.cx == linelen:
789 if self.cy == len(self.lines) - 1:
790 wx.Bell()
791 else:
792 self.cx = 0
793 self.cVert(1)
794 else:
795 self.cx += 1
796
797
798 def MovePageDown(self, event):
799 self.cVert(self.sh)
800
801 def MovePageUp(self, event):
802 self.cVert(-self.sh)
803
804 def MoveHome(self, event):
805 self.cx = 0
806
807 def MoveEnd(self, event):
808 self.cx = self.CurrentLineLength()
809
810 def MoveStartOfFile(self, event):
811 self.cy = 0
812 self.cx = 0
813
814 def MoveEndOfFile(self, event):
815 self.cy = len(self.lines) - 1
816 self.cx = self.CurrentLineLength()
817
818 #-------------- Key handler mapping tables
819
820 def SetMoveSpecialFuncs(self, action):
821 action[wx.WXK_DOWN] = self.MoveDown
822 action[wx.WXK_UP] = self.MoveUp
823 action[wx.WXK_LEFT] = self.MoveLeft
824 action[wx.WXK_RIGHT] = self.MoveRight
825 action[wx.WXK_NEXT] = self.MovePageDown
826 action[wx.WXK_PRIOR] = self.MovePageUp
827 action[wx.WXK_HOME] = self.MoveHome
828 action[wx.WXK_END] = self.MoveEnd
829
830 def SetMoveSpecialControlFuncs(self, action):
831 action[wx.WXK_HOME] = self.MoveStartOfFile
832 action[wx.WXK_END] = self.MoveEndOfFile
833
834 def SetAltFuncs(self, action):
835 # subclass implements
836 pass
837
838 def SetControlFuncs(self, action):
839 action['c'] = self.OnCopySelection
840 action['d'] = self.OnDeleteSelection
841 action['v'] = self.OnPaste
842 action['x'] = self.OnCutSelection
843
844 def SetSpecialControlFuncs(self, action):
845 action[wx.WXK_INSERT] = self.OnCopySelection
846
847 def SetShiftFuncs(self, action):
848 action[wx.WXK_DELETE] = self.OnCutSelection
849 action[wx.WXK_INSERT] = self.OnPaste
850
851 def SetSpecialFuncs(self, action):
852 action[wx.WXK_BACK] = self.BackSpace
853 action[wx.WXK_DELETE] = self.Delete
854 action[wx.WXK_RETURN] = self.BreakLine
855 action[wx.WXK_ESCAPE] = self.Escape
856 action[wx.WXK_TAB] = self.TabKey
857
858 ##-------------- Logic for key handlers
859
860
861 def Move(self, keySettingFunction, key, event):
862 action = {}
863 keySettingFunction(action)
864
865 if not action.has_key(key):
866 return False
867
868 if event.ShiftDown():
869 if not self.Selecting:
870 self.Selecting = True
871 self.SelectBegin = (self.cy, self.cx)
872 action[key](event)
873 self.SelectEnd = (self.cy, self.cx)
874 else:
875 action[key](event)
876 if self.Selecting:
877 self.Selecting = False
878
879 self.SelectNotify(self.Selecting, self.SelectBegin, self.SelectEnd)
880 self.UpdateView()
881 return True
882
883 def MoveSpecialKey(self, event, key):
884 return self.Move(self.SetMoveSpecialFuncs, key, event)
885
886 def MoveSpecialControlKey(self, event, key):
887 if not event.ControlDown():
888 return False
889 return self.Move(self.SetMoveSpecialControlFuncs, key, event)
890
891 def Dispatch(self, keySettingFunction, key, event):
892 action = {}
893 keySettingFunction(action)
894 if action.has_key(key):
895 action[key](event)
896 self.UpdateView()
897 return True
898 return False
899
900 def ModifierKey(self, key, event, modifierKeyDown, MappingFunc):
901 if not modifierKeyDown:
902 return False
903
904 key = self.UnixKeyHack(key)
905 try:
906 key = chr(key)
907 except:
908 return False
909 if not self.Dispatch(MappingFunc, key, event):
910 wx.Bell()
911 return True
912
913 def ControlKey(self, event, key):
914 return self.ModifierKey(key, event, event.ControlDown(), self.SetControlFuncs)
915
916 def AltKey(self, event, key):
917 return self.ModifierKey(key, event, event.AltDown(), self.SetAltFuncs)
918
919 def SpecialControlKey(self, event, key):
920 if not event.ControlDown():
921 return False
922 if not self.Dispatch(self.SetSpecialControlFuncs, key, event):
923 wx.Bell()
924 return True
925
926 def ShiftKey(self, event, key):
927 if not event.ShiftDown():
928 return False
929 return self.Dispatch(self.SetShiftFuncs, key, event)
930
931 def NormalChar(self, event, key):
932 self.SelectOff()
933
934 # regular ascii
935 if not self.Dispatch(self.SetSpecialFuncs, key, event):
936 if (key>31) and (key<256):
937 self.InsertChar(chr(key))
938 else:
939 wx.Bell()
940 return
941 self.UpdateView()
942 self.AdjustScrollbars()
943
944 def OnChar(self, event):
945 key = event.GetKeyCode()
946 filters = [self.AltKey,
947 self.MoveSpecialControlKey,
948 self.ControlKey,
949 self.SpecialControlKey,
950 self.MoveSpecialKey,
951 self.ShiftKey,
952 self.NormalChar]
953 for filter in filters:
954 if filter(event,key):
955 break
956 return 0
957
958 #----------------------- Eliminate memory leaks
959
960 def OnDestroy(self, event):
961 self.mdc = None
962 self.odc = None
963 self.bgColor = None
964 self.fgColor = None
965 self.font = None
966 self.selectColor = None
967 self.scrollTimer = None
968 self.eofMarker = None
969
970 #-------------------- Abstract methods for subclasses
971
972 def OnClick(self):
973 pass
974
975 def SelectNotify(self, Selecting, SelectionBegin, SelectionEnd):
976 pass
977