]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/editor/editor.py
bfae39281720bb9b1bd4f2c173c926611c28d38d
[wxWidgets.git] / wxPython / wxPython / lib / editor / editor.py
1 #----------------------------------------------------------------------
2 # Name: wxPython.lib.editor.wxEditor
3 # Purpose: An intelligent text editor with colorization capabilities.
4 #
5 # Author: Dirk Holtwic, Robin Dunn
6 #
7 # Created: 15-Dec-1999
8 # RCS-ID: $Id$
9 # Copyright: (c) 1999 by Dirk Holtwick, 1999
10 # Licence: wxWindows license
11 #----------------------------------------------------------------------
12
13
14 # PLEASE NOTE: This is experimental code. It needs an overhall in the
15 # drawing and update code, and there is occasionally a
16 # mysteriously disappearing line...
17 #
18 # I am working on a StyledTextEditor that will likely
19 # render this editor obsolete... But this one is at
20 # least somewhat functional now while the other is still
21 # vapor.
22 #
23 # - Robin
24
25
26 from wxPython.wx import *
27 from string import *
28 from keyword import *
29 from regsub import *
30 from tokenizer import *
31
32 #---------------------------------------------------------------------------
33
34
35 class Line:
36 def __init__(self, text=""):
37 self.text = text # the string itself
38 self.syntax = [] # the colors of the line
39 self.editable = true # edit?
40 self.visible = 0 # will be incremented if not
41 self.indent = 0 # not used yet
42
43 #----------------------------------------------------------------------
44
45 class wxEditor(wxScrolledWindow):
46
47 def __init__(self, parent, id,
48 pos=wxDefaultPosition, size=wxDefaultSize, style=0):
49 ###############################################################
50 """
51 Alles hat einen Anfang
52 """
53
54 wxScrolledWindow.__init__(self, parent, id,
55 pos, size,
56 style|wxWANTS_CHARS)
57
58 # the syntax informations, if they don't exist,
59 # all syntax stuff will be ignored
60
61 # cursor pos
62 self.cx = 0
63 self.cy = 0
64
65 # the lines that are visible
66 self.lines = []
67 self.line = 0
68 self.len = 0
69
70 self.ocy = 0
71
72 # border pos
73 #self.bx = 0
74 #self.by = 0
75
76 # screen
77 self.sx = 0
78 self.sy = 0
79 self.sw = 0
80 self.sh = 0
81 self.osx= 0
82 self.osy= 0
83
84 # font
85 dc = wxClientDC(self)
86
87 if wxPlatform == "__WXMSW__":
88 self.font = wxFont(10, wxMODERN, wxNORMAL, wxNORMAL)
89 else:
90 self.font = wxFont(12, wxMODERN, wxNORMAL, wxNORMAL, false)
91 dc.SetFont(self.font)
92
93 # font weight, height
94 self.fw = dc.GetCharWidth()
95 self.fh = dc.GetCharHeight()
96
97 # back, for colour
98 self.bcol = wxNamedColour('white')
99 self.fcol = wxNamedColour('black')
100
101 self.cfcol = wxNamedColour('black')
102 self.cbcol = wxNamedColour('red')
103
104 # nicht edierbare zeile (hintergrund)
105 self.nedcol = wxNamedColour('grey')
106
107 self.SetBackgroundColour(self.bcol)
108 #dc.SetForegroundColour(self.fcol)
109
110 # events
111 EVT_LEFT_DOWN(self, self.OnMouseClick)
112 EVT_RIGHT_DOWN(self, self.OnMouseClick)
113 EVT_SCROLLWIN(self, self.OnScroll)
114 EVT_CHAR(self, self.OnChar)
115 EVT_PAINT(self, self.OnPaint)
116
117 self.o_cx = self.cx
118 self.o_cy = self.cy
119 self.o_sx = self.sx
120 self.o_sy = self.sy
121 self.o_line = self.line
122 self.sco_x = 0
123 self.sco_y = 0
124
125 self.tabsize = 4
126
127 self.update = true
128 self.in_scroll =FALSE
129 self.inUpdate = FALSE
130
131
132 bw,bh = self.GetSizeTuple()
133 # double buffering
134 self.mdc = wxMemoryDC()
135 self.mdc.SelectObject(wxEmptyBitmap(bw,bh))
136 # disable physical scrolling because invisible parts are not drawn
137 self.EnableScrolling(FALSE, FALSE)
138
139 # the ordinary text as it is
140 self.SetText()
141 self.SetFocus()
142
143
144 #---------------------------------------------------------------------------
145
146 def CalcLines(self):
147 ###############################################################
148 self.lines = []
149 x =maxlen =0
150 for line in self.text:
151 if line.visible==0:
152 self.lines.append(x)
153 else:
154 if len(line.text) >maxlen:
155 maxlen =len(line.text)
156 x = x + 1
157 self.len = len(self.lines)
158 self.max_linelength =maxlen
159
160
161 def SetFontTab(self, fonttab):
162 ###############################################################
163 """ Fonttabelle zum schnellen Zugriff """
164 self.ftab = fonttab
165
166
167 def SetText(self, text = [""]):
168 ###############################################################
169 """ Text mittels Liste setzen """
170 self.cx = 0
171 self.cy = 0
172 self.text = []
173
174 for t in text:
175 self.text.append(Line(t))
176
177 for l in range(0,len(text)-1):
178 #self.UpdateSyntax(l)
179 self.OnUpdateHighlight(l)
180
181 self.OnInit()
182
183 self.update = true
184 self.UpdateView(None, true)
185
186
187 # show new text
188 def GetText(self):
189 ###############################################################
190 """ Der gesamte Text als Liste """
191 text = []
192 for line in self.text:
193 text.append(line.text)
194 return text
195
196
197 def IsEmpty(self):
198 ###############################################################
199 """see if at least one text line is not empty"""
200 for line in self.text:
201 if line.text: return 0
202 return 1
203
204
205 def IsLine(self, line):
206 ###############################################################
207 """ Schauen, ob alles im grünen Bereich ist """
208 return (line>=0) and (line<self.len)
209
210
211 def IsEditable(self, line):
212 ###############################################################
213 return self.text[self.GetLine(line)].editable
214
215
216 def GetLine(self, line):
217 ###############################################################
218 return self.lines[line]
219
220
221 def GetTextLine(self, line):
222 ###############################################################
223 """ Text holen """
224 if self.IsLine(line):
225 return self.text[self.GetLine(line)].text
226 return ""
227
228
229 def SetTextLine(self, line, text):
230 ###############################################################
231 """ Nur den Text ändern """
232 if self.IsLine(line):
233 l = self.GetLine(line)
234 self.text[l].text = text
235 #self.UpdateSyntax(l)
236 self.OnUpdateHighlight(l)
237 self.update = true
238
239
240 #---------------------------------------------------------------------------
241
242 def OnMouseClick(self, event):
243 ###############################################################
244 """
245 Wenn es Click gemacht hat => Cursor setzen
246 """
247 self.SetFocus()
248
249 self.cy = self.sy + (event.GetY() / self.fh)
250 if self.cy >= self.len: self.cy =max(self.len -1, 0)
251 linelen =len(self.text[self.GetLine(self.cy)].text)
252 self.cx = self.sx + (event.GetX() / self.fw)
253 # allow positioning right behind the last character
254 if self.cx > linelen: self.cx =linelen
255 if event.GetEventType() ==wxEVT_RIGHT_DOWN:
256 self.update = true
257 self.OnFold()
258 self.UpdateView()
259
260
261 def DrawCursor(self, dc = None):
262 ###############################################################
263 """
264 Auch der Cursor muß ja irgendwie gezeichnet werden
265 """
266 if not dc:
267 dc = wxClientDC(self)
268
269 if (self.len)<self.cy: #-1 ?
270 self.cy = self.len-1
271 s = self.text[self.GetLine(self.cy)].text
272
273 x = self.cx - self.sx
274 y = self.cy - self.sy
275 self.DrawSimpleCursor(x, y, dc)
276
277
278 def DrawSimpleCursor(self, xp, yp, dc = None, old=false):
279 ###############################################################
280 """
281 Auch der Cursor muß ja irgendwie gezeichnet werden
282 """
283 if not dc:
284 dc = wxClientDC(self)
285
286 if old:
287 xp = self.sco_x
288 yp = self.sco_y
289
290 szx = self.fw
291 szy = self.fh
292 x = xp * szx
293 y = yp * szy
294 dc.Blit(x,y,szx,szy,dc,x,y,wxSRC_INVERT)
295 self.sco_x = xp
296 self.sco_y = yp
297
298
299 def OnScroll(self, event):
300 dir =event.GetOrientation()
301 evt =event.GetEventType()
302 if dir ==wxHORIZONTAL:
303 if evt ==wxEVT_SCROLLWIN_LINEUP: self.sx =self.sx -1
304 elif evt ==wxEVT_SCROLLWIN_LINEDOWN: self.sx =self.sx +1
305 elif evt ==wxEVT_SCROLLWIN_PAGEUP: self.sx =self.sx -self.sw
306 elif evt ==wxEVT_SCROLLWIN_PAGEDOWN: self.sx =self.sx +self.sw
307 elif evt ==wxEVT_SCROLLWIN_TOP: self.sx =self.cx =0
308 elif evt ==wxEVT_SCROLLWIN_BOTTOM:
309 self.sx =self.max_linelength -self.sw
310 self.cx =self.max_linelength
311 else:
312 self.sx =event.GetPosition()
313
314 if self.sx >(self.max_linelength -self.sw +1):
315 self.sx =self.max_linelength -self.sw +1
316 if self.sx <0: self.sx =0
317 if self.cx >(self.sx +self.sw -1): self.cx =self.sx +self.sw -1
318 if self.cx <self.sx: self.cx =self.sx
319
320 else:
321 if evt ==wxEVT_SCROLLWIN_LINEUP: self.sy =self.sy -1
322 elif evt ==wxEVT_SCROLLWIN_LINEDOWN: self.sy =self.sy +1
323 elif evt ==wxEVT_SCROLLWIN_PAGEUP: self.sy =self.sy -self.sh
324 elif evt ==wxEVT_SCROLLWIN_PAGEDOWN: self.sy =self.sy +self.sh
325 elif evt ==wxEVT_SCROLLWIN_TOP: self.sy =self.cy =0
326 elif evt ==wxEVT_SCROLLWIN_BOTTOM:
327 self.sy =self.len -self.sh
328 self.cy =self.len
329 else:
330 self.sy =event.GetPosition()
331
332 if self.sy >(self.len -self.sh +1):
333 self.sy =self.len -self.sh +1
334 if self.sy <0: self.sy =0
335 if self.cy >(self.sy +self.sh -1): self.cy =self.sy +self.sh -1
336 if self.cy <self.sy: self.cy =self.sy
337
338 self.UpdateView()
339
340
341 def AdjustScrollbars(self):
342 # there appears to be endless recursion:
343 # SetScrollbars issue EvtPaint which calls UpdateView
344 # which calls AdjustScrollbars
345 if not self.in_scroll:
346 self.in_scroll =TRUE
347 self.SetScrollbars(self.fw, self.fh, self.max_linelength +1,
348 # it seem to be a bug in scrollbars:
349 # the scrollbar is hidden
350 # even if current position >0
351 max(self.len +1, self.sy +self.sh),
352 self.sx, self.sy)
353 self.osx, self.osy = self.sx, self.sy
354 self.in_scroll =FALSE
355
356
357 # adapts the output to what it should be
358 def UpdateView(self, dc = None, doup=false):
359 ###############################################################
360 """
361 Diese Routine wird immer dann aufgerufen, wenn
362 sich etwas verändert hat
363 """
364 if self.inUpdate:
365 return
366 self.inUpdate = true
367
368 self.CalcLines()
369
370 if not dc:
371 dc = wxClientDC(self)
372
373 self.bw,self.bh = self.GetSizeTuple()
374 self.sw = self.bw / self.fw
375 self.sh = self.bh / self.fh
376
377 if self.cy<self.sy:
378 self.sy = self.cy
379 elif self.cy>(self.sy+self.sh-1):
380 self.sy = self.cy-self.sh+1
381
382 if self.cx<self.sx:
383 self.sx = self.cx
384 elif self.cx>(self.sx+self.sw-1):
385 self.sx = self.cx-self.sw+1
386
387 # left line? change syntax!
388 if self.ocy!=self.cy:
389 self.OnUpdateSyntax(self.ocy)
390 self.ocy = self.cy
391
392 # alles beim alten
393 if self.osx != self.sx or self.osy != self.sy:
394 self.AdjustScrollbars()
395
396 self.DrawSimpleCursor(0,0,dc, true)
397 # [als] i don't really understand how the following condition works
398 #if self.update or doup:
399 self.Draw(dc)
400 # self.update = false
401 #else:
402 # self.DrawCursor(dc)
403
404 self.o_cx = self.cx
405 self.o_cy = self.cy
406 self.o_sx = self.sx
407 self.o_sy = self.sy
408 self.o_line = self.line
409 self.inUpdate = false
410
411
412
413
414 def DrawEditText(self, t, x, y, dc = None):
415 ###############################################################
416 """ Einfache Hilfsroutine um Text zu schreiben
417 """
418 if not dc:
419 dc = wxClientDC(self)
420 dc.SetFont(self.font)
421 dc.DrawText(t, x * self.fw, y * self.fh)
422
423
424 def DrawLine(self, line, dc=None):
425 ###############################################################
426 """
427 Hier wird einfach die Ansicht der ganzen Seite
428 wiederhergestellt.
429 !!! Kann modifiziert werden !!!
430 """
431
432 if not dc:
433 dc = wxClientDC(self)
434
435 dc.SetBackgroundMode(wxSOLID)
436 dc.SetTextBackground(self.bcol)
437 dc.SetTextForeground(self.fcol)
438 #dc.Clear()
439
440 # delimiter
441 ll = self.sx
442 lr = self.sx + self.sw
443 y = line - self.sy
444
445 # text + syntax
446 if self.IsLine(line):
447 l = self.GetLine(line)
448 t = self.text[l].text
449 syn = self.text[l].syntax
450
451 if not self.text[l].editable:
452 dc.SetTextBackground(self.nedcol)
453 else:
454 dc.SetTextBackground(self.bcol)
455
456 dc.SetTextForeground(self.fcol)
457
458 pos = ll
459 for h in syn:
460 xp, col = h
461 if xp>=ll:
462 self.DrawEditText(t[pos:xp], (pos-ll), y, dc)
463 pos = xp
464 dc.SetTextForeground(self.ftab[col])
465 self.DrawEditText(t[pos:], (pos-ll), y, dc)
466
467
468 def Draw(self, odc=None):
469 ###############################################################
470 """
471 Hier wird einfach die Ansicht der ganzen Seite
472 wiederhergestellt.
473 !!! Kann modifiziert werden !!!
474 """
475
476 if not odc:
477 odc = wxClientDC(self)
478
479 dc = self.mdc
480 dc.SelectObject(wxEmptyBitmap(self.bw,self.bh))
481 dc.SetBackgroundMode(wxSOLID)
482 dc.SetTextBackground(self.bcol)
483 dc.SetTextForeground(self.fcol)
484 dc.Clear()
485 for line in range(self.sy, self.sy + self.sh): self.DrawLine(line, dc)
486 odc.Blit(0,0,self.bw,self.bh,dc,0,0,wxCOPY)
487 self.DrawCursor(odc)
488
489
490 def cVert(self, num):
491 ###############################################################
492 """ Vertikale Cursorverschiebung
493 """
494 cy = self.cy + num
495 if cy <0: cy =0
496 elif cy >(self.len -1): cy =self.len -1
497 # scroll when edge hit
498 if cy >(self.sy +self.sh -1): self.sy =cy -self.sh +1
499 elif cy <self.sy: self.sy =cy
500 self.cy =cy
501 # disallow positioning behind the end of the line
502 linelen =len(self.text[self.GetLine(cy)].text)
503 if self.cx >linelen: self.cx =linelen
504
505
506 def cHoriz(self, num):
507 ###############################################################
508 """ Horizontale Cursorverschiebung
509 """
510 cx = self.cx + num
511 linelen =len(self.text[self.GetLine(self.cy)].text)
512 if cx <0: cx =0
513 elif cx >linelen: cx =linelen
514 # scroll when edge hit
515 if cx >(self.sx +self.sw -2): self.sx =cx -self.sw +2
516 elif cx <self.sx: self.sx =cx
517 self.cx =cx
518
519
520 def InsertText(self, text):
521 ###############################################################
522 """
523 Simple Routine um Text - auch über mehrere
524 Zeilen - einzufügen
525 """
526
527 if self.IsEditable(self.cy):
528 tis = split(text, "\n")
529
530 t = self.GetTextLine(self.cy)
531
532 if len(tis)==1:
533 t = t[:self.cx] + text + t[self.cx:]
534 self.SetTextLine(self.cy, t)
535 self.cHoriz(len(text))
536 else:
537 rest = t[self.cx:]
538 t = t[:self.cx] + tis[0]
539 self.SetTextLine(self.cy, t)
540 for i in range(1,len(tis)):
541 self.text.insert(self.GetLine(self.cy)+1, Line())
542 self.lines.insert(self.cy+1,self.GetLine(self.cy)+1)
543 self.cVert(+1)
544 self.SetTextLine(self.cy, tis[i])
545 t = self.GetTextLine(self.cy)
546 self.cx = len(t)
547 t = t + rest
548 self.SetTextLine(self.cy, t)
549 self.update = true
550 #self.UpdateView()
551
552 #-----------------------------------------------------------------------------------------
553
554 def RemoveLine(self, line):
555 pass
556
557
558 def OnChar(self, event):
559 ###############################################################
560 """
561 Wenn eine Taste gedrückt wird,
562 kann an dieser Stelle die Auswertung stattfinden
563 """
564
565 # get code
566 key = event.KeyCode()
567
568 # if event.ControlDown:
569 # if chr(key)=="k":
570 # print "weg"
571
572
573 # movements
574 if key==WXK_DOWN:
575 self.cVert(+1)
576 elif key==WXK_UP:
577 self.cVert(-1)
578 elif key==WXK_LEFT:
579 self.cHoriz(-1)
580 elif key==WXK_RIGHT:
581 self.cHoriz(+1)
582
583 elif key==WXK_NEXT:
584 self.cVert(self.sh)
585 elif key==WXK_PRIOR:
586 self.cVert(-self.sh)
587
588 elif key==WXK_HOME:
589 self.cx = 0
590 elif key==WXK_END:
591 self.cx = len(self.GetTextLine(self.cy))
592
593 elif key==WXK_BACK:
594 t = self.GetTextLine(self.cy)
595 if self.cx>0:
596 t = t[:self.cx-1] + t[self.cx:]
597 self.SetTextLine(self.cy, t)
598 self.cHoriz(-1)
599
600 elif key==WXK_DELETE:
601 t = self.GetTextLine(self.cy)
602 if self.cx<len(t):
603 t = t[:self.cx] + t[self.cx+1:]
604 self.SetTextLine(self.cy, t)
605
606 elif key==WXK_RETURN:
607 self.InsertText("\n")
608
609 elif key==WXK_TAB:
610 self.OnTabulator(event)
611
612 # clipboard (buggy)
613 elif key==WXK_F10:
614 if wxTheClipboard.Open():
615 data = wxTheClipboard.GetData()
616 wxTheClipboard.Close()
617 print data
618
619 # folding (buggy)
620 elif key==WXK_F12:
621 self.update = true
622 self.OnFold()
623
624 # regular ascii
625 elif (key>31) and (key<256):
626 self.InsertText(chr(key))
627
628 self.UpdateView()
629 return 0
630
631
632 def OnPaint(self, event):
633 dc = wxPaintDC(self)
634 self.bw,self.bh = self.GetSizeTuple()
635 self.UpdateView(dc, true)
636
637
638 #-----------------------------------------------------------------------------------------
639
640 def GetIndent(self, line):
641 p = 0
642 for c in line:
643 if c==" ": p = p + 1
644 elif c=="\t": p =(p /self.tabsize +1) *self.tabsize
645 else: break
646 return p
647
648
649 def Goto(self, pos):
650 self.cVert(pos-self.cy-1)
651 self.UpdateView()
652
653 # --------------------------------------------------------
654
655 # to be overloaded
656 def OnUpdateHighlight(self, line = -1):
657 pass
658
659 def OnUpdateSyntax(self, line = -1):
660 pass
661
662 def OnTabulator(self, event):
663 pass
664
665 def OnInit(self):
666 pass
667
668 def OnFold(self):
669 pass
670