]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/docview/activegrid/tool/TextEditor.py
Ignore kill focus events when being destroyed
[wxWidgets.git] / wxPython / samples / docview / activegrid / tool / TextEditor.py
1 #----------------------------------------------------------------------------
2 # Name: TextEditor.py
3 # Purpose: Text Editor for pydocview
4 #
5 # Author: Peter Yared
6 #
7 # Created: 8/15/03
8 # CVS-ID: $Id$
9 # Copyright: (c) 2003-2004 ActiveGrid, Inc.
10 # License: wxWindows license
11 #----------------------------------------------------------------------------
12 import wx
13 import wx.lib.docview
14 import wx.lib.pydocview
15 import string
16 import FindService
17 _ = wx.GetTranslation
18
19 class TextDocument(wx.lib.docview.Document):
20
21
22 def OnSaveDocument(self, filename):
23 view = self.GetFirstView()
24 if not view.GetTextCtrl().SaveFile(filename):
25 return False
26 self.Modify(False)
27 self.SetDocumentSaved(True)
28 #if wx.Platform == "__WXMAC__":
29 # fn = wx.Filename(filename)
30 # fn.MacSetDefaultTypeAndCreator()
31 return True
32
33
34 def OnOpenDocument(self, filename):
35 view = self.GetFirstView()
36 if not view.GetTextCtrl().LoadFile(filename):
37 return False
38 self.SetFilename(filename, True)
39 self.Modify(False)
40 self.UpdateAllViews()
41 self._savedYet = True
42 return True
43
44
45 def IsModified(self):
46 view = self.GetFirstView()
47 if view and view.GetTextCtrl():
48 return wx.lib.docview.Document.IsModified(self) or view.GetTextCtrl().IsModified()
49 else:
50 return wx.lib.docview.Document.IsModified(self)
51
52
53 def Modify(self, mod):
54 view = self.GetFirstView()
55 wx.lib.docview.Document.Modify(self, mod)
56 if not mod and view and view.GetTextCtrl():
57 view.GetTextCtrl().DiscardEdits()
58
59
60 class TextView(wx.lib.docview.View):
61
62
63 #----------------------------------------------------------------------------
64 # Overridden methods
65 #----------------------------------------------------------------------------
66
67 def __init__(self):
68 wx.lib.docview.View.__init__(self)
69 self._textCtrl = None
70 self._wordWrap = wx.ConfigBase_Get().ReadInt("TextEditorWordWrap", True)
71
72
73 def OnCreate(self, doc, flags):
74 frame = wx.GetApp().CreateDocumentFrame(self, doc, flags)
75 sizer = wx.BoxSizer()
76 font, color = self._GetFontAndColorFromConfig()
77 self._textCtrl = self._BuildTextCtrl(frame, font, color = color)
78 sizer.Add(self._textCtrl, 1, wx.EXPAND, 0)
79 frame.SetSizer(sizer)
80 frame.Layout()
81 frame.Show(True)
82 self.Activate()
83 return True
84
85
86 def _BuildTextCtrl(self, parent, font, color = wx.BLACK, value = "", selection = [0, 0]):
87 if self._wordWrap:
88 wordWrapStyle = wx.TE_WORDWRAP
89 else:
90 wordWrapStyle = wx.TE_DONTWRAP
91 textCtrl = wx.TextCtrl(parent, -1, pos = wx.DefaultPosition, size = parent.GetClientSize(), style = wx.TE_MULTILINE | wordWrapStyle)
92 textCtrl.SetFont(font)
93 textCtrl.SetForegroundColour(color)
94 textCtrl.SetValue(value)
95 return textCtrl
96
97
98 def _GetFontAndColorFromConfig(self):
99 font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
100 config = wx.ConfigBase_Get()
101 fontData = config.Read("TextEditorFont", "")
102 if fontData:
103 nativeFont = wx.NativeFontInfo()
104 nativeFont.FromString(fontData)
105 font.SetNativeFontInfo(nativeFont)
106 color = wx.BLACK
107 colorData = config.Read("TextEditorColor", "")
108 if colorData:
109 red = int("0x" + colorData[0:2], 16)
110 green = int("0x" + colorData[2:4], 16)
111 blue = int("0x" + colorData[4:6], 16)
112 color = wx.Color(red, green, blue)
113 return font, color
114
115
116 def OnCreateCommandProcessor(self):
117 # Don't create a command processor, it has its own
118 pass
119
120
121 def OnActivateView(self, activate, activeView, deactiveView):
122 if activate and self._textCtrl:
123 # In MDI mode just calling set focus doesn't work and in SDI mode using CallAfter causes an endless loop
124 if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
125 self._textCtrl.SetFocus()
126 else:
127 def SetFocusToTextCtrl():
128 if self._textCtrl: # Need to make sure it is there in case we are in the closeall mode of the MDI window
129 self._textCtrl.SetFocus()
130 wx.CallAfter(SetFocusToTextCtrl)
131
132
133 def OnUpdate(self, sender = None, hint = None):
134 if hint == "Word Wrap":
135 self.SetWordWrap(wx.ConfigBase_Get().ReadInt("TextEditorWordWrap", True))
136 elif hint == "Font":
137 font, color = self._GetFontAndColorFromConfig()
138 self.SetFont(font, color)
139
140
141 def OnClose(self, deleteWindow = True):
142 if not wx.lib.docview.View.OnClose(self, deleteWindow):
143 return False
144 self.Activate(False)
145 if deleteWindow:
146 self.GetFrame().Destroy()
147 return True
148
149
150 # Since ProcessEvent is not virtual, we have to trap the relevant events using this pseudo-ProcessEvent instead of EVT_MENU
151 def ProcessEvent(self, event):
152 id = event.GetId()
153 if id == wx.ID_UNDO:
154 if not self._textCtrl:
155 return False
156 self._textCtrl.Undo()
157 return True
158 elif id == wx.ID_REDO:
159 if not self._textCtrl:
160 return False
161 self._textCtrl.Redo()
162 return True
163 elif id == wx.ID_CUT:
164 if not self._textCtrl:
165 return False
166 self._textCtrl.Cut()
167 return True
168 elif id == wx.ID_COPY:
169 if not self._textCtrl:
170 return False
171 self._textCtrl.Copy()
172 return True
173 elif id == wx.ID_PASTE:
174 if not self._textCtrl:
175 return False
176 self._textCtrl.Paste()
177 return True
178 elif id == wx.ID_CLEAR:
179 if not self._textCtrl:
180 return False
181 self._textCtrl.Replace(self._textCtrl.GetSelection()[0], self._textCtrl.GetSelection()[1], '')
182 return True
183 elif id == wx.ID_SELECTALL:
184 if not self._textCtrl:
185 return False
186 self._textCtrl.SetSelection(-1, -1)
187 return True
188 elif id == TextService.CHOOSE_FONT_ID:
189 if not self._textCtrl:
190 return False
191 self.OnChooseFont(event)
192 return True
193 elif id == TextService.WORD_WRAP_ID:
194 if not self._textCtrl:
195 return False
196 self.OnWordWrap(event)
197 return True
198 elif id == FindService.FindService.FIND_ID:
199 self.OnFind()
200 return True
201 elif id == FindService.FindService.FIND_PREVIOUS_ID:
202 self.DoFind(forceFindPrevious = True)
203 return True
204 elif id == FindService.FindService.FIND_NEXT_ID:
205 self.DoFind(forceFindNext = True)
206 return True
207 elif id == FindService.FindService.REPLACE_ID:
208 self.OnFind(replace = True)
209 return True
210 elif id == FindService.FindService.FINDONE_ID:
211 self.DoFind()
212 return True
213 elif id == FindService.FindService.REPLACEONE_ID:
214 self.DoFind(replace = True)
215 return True
216 elif id == FindService.FindService.REPLACEALL_ID:
217 self.DoFind(replaceAll = True)
218 return True
219 elif id == FindService.FindService.GOTO_LINE_ID:
220 self.OnGotoLine(event)
221 return True
222 else:
223 return wx.lib.docview.View.ProcessEvent(self, event)
224
225
226 def ProcessUpdateUIEvent(self, event):
227 if not self._textCtrl:
228 return False
229
230 hasText = len(self._textCtrl.GetValue()) > 0
231
232 id = event.GetId()
233 if id == wx.ID_UNDO:
234 event.Enable(self._textCtrl.CanUndo())
235 return True
236 elif id == wx.ID_REDO:
237 event.Enable(self._textCtrl.CanRedo())
238 return True
239 if id == wx.ID_CUT:
240 event.Enable(self._textCtrl.CanCut())
241 return True
242 elif id == wx.ID_COPY:
243 event.Enable(self._textCtrl.CanCopy())
244 return True
245 elif id == wx.ID_PASTE:
246 event.Enable(self._textCtrl.CanPaste())
247 return True
248 elif id == wx.ID_CLEAR:
249 event.Enable(True) # wxBug: No matter what we do, the wxTextCtrl disables the Clear menu item and the menu item traps the Del key and the wxTextCtrl doesn't get it and can't delete the next character
250 return True
251 elif id == wx.ID_SELECTALL:
252 event.Enable(hasText)
253 return True
254 elif id == TextService.CHOOSE_FONT_ID:
255 event.Enable(True)
256 return True
257 elif id == TextService.WORD_WRAP_ID:
258 event.Enable(True)
259 return True
260 elif id == FindService.FindService.FIND_ID:
261 event.Enable(hasText)
262 return True
263 elif id == FindService.FindService.FIND_PREVIOUS_ID:
264 event.Enable(hasText and
265 self._FindServiceHasString() and
266 self._textCtrl.GetSelection()[0] > 0)
267 return True
268 elif id == FindService.FindService.FIND_NEXT_ID:
269 event.Enable(hasText and
270 self._FindServiceHasString() and
271 self._textCtrl.GetSelection()[0] < len(self._textCtrl.GetValue()))
272 return True
273 elif id == FindService.FindService.REPLACE_ID:
274 event.Enable(hasText)
275 return True
276 elif id == FindService.FindService.GOTO_LINE_ID:
277 event.Enable(True)
278 return True
279 else:
280 return wx.lib.docview.View.ProcessUpdateUIEvent(self, event)
281
282
283 #----------------------------------------------------------------------------
284 # Methods for TextDocument to call
285 #----------------------------------------------------------------------------
286
287 def GetTextCtrl(self):
288 return self._textCtrl
289
290
291 #----------------------------------------------------------------------------
292 # Format methods
293 #----------------------------------------------------------------------------
294
295 def OnChooseFont(self, event):
296 data = wx.FontData()
297 data.EnableEffects(True)
298 data.SetInitialFont(self._textCtrl.GetFont())
299 data.SetColour(self._textCtrl.GetForegroundColour())
300 fontDialog = wx.FontDialog(self.GetFrame(), data)
301 if fontDialog.ShowModal() == wx.ID_OK:
302 data = fontDialog.GetFontData()
303 self.SetFont(data.GetChosenFont(), data.GetColour())
304 fontDialog.Destroy()
305
306
307 def SetFont(self, font, color):
308 self._textCtrl.SetFont(font)
309 self._textCtrl.SetForegroundColour(color)
310 self._textCtrl.Refresh()
311 self._textCtrl.Layout()
312
313
314 def OnWordWrap(self, event):
315 self.SetWordWrap(not self.GetWordWrap())
316
317
318 def GetWordWrap(self):
319 return self._wordWrap
320
321
322 def SetWordWrap(self, wordWrap = True):
323 self._wordWrap = wordWrap
324 temp = self._textCtrl
325 self._textCtrl = self._BuildTextCtrl(temp.GetParent(),
326 font = temp.GetFont(),
327 color = temp.GetForegroundColour(),
328 value = temp.GetValue(),
329 selection = temp.GetSelection())
330 self.GetDocument().Modify(temp.IsModified())
331 temp.Destroy()
332
333
334 #----------------------------------------------------------------------------
335 # Find methods
336 #----------------------------------------------------------------------------
337
338 def OnFind(self, replace = False):
339 findService = wx.GetApp().GetService(FindService.FindService)
340 if findService:
341 findService.ShowFindReplaceDialog(findString = self._textCtrl.GetStringSelection(), replace = replace)
342
343
344 def DoFind(self, forceFindNext = False, forceFindPrevious = False, replace = False, replaceAll = False):
345 findService = wx.GetApp().GetService(FindService.FindService)
346 if not findService:
347 return
348 findString = findService.GetFindString()
349 if len(findString) == 0:
350 return -1
351 replaceString = findService.GetReplaceString()
352 flags = findService.GetFlags()
353 startLoc, endLoc = self._textCtrl.GetSelection()
354
355 wholeWord = flags & wx.FR_WHOLEWORD > 0
356 matchCase = flags & wx.FR_MATCHCASE > 0
357 regExp = flags & FindService.FindService.FR_REGEXP > 0
358 down = flags & wx.FR_DOWN > 0
359 wrap = flags & FindService.FindService.FR_WRAP > 0
360
361 if forceFindPrevious: # this is from function keys, not dialog box
362 down = False
363 wrap = False # user would want to know they're at the end of file
364 elif forceFindNext:
365 down = True
366 wrap = False # user would want to know they're at the end of file
367
368 # On replace dialog operations, user is allowed to replace the currently highlighted text to determine if it should be replaced or not.
369 # Typically, it is the text from a previous find operation, but we must check to see if it isn't, user may have moved the cursor or selected some other text accidentally.
370 # If the text is a match, then replace it.
371 if replace:
372 result, start, end, replText = findService.DoFind(findString, replaceString, self._textCtrl.GetStringSelection(), 0, 0, True, matchCase, wholeWord, regExp, replace)
373 if result > 0:
374 self._textCtrl.Replace(startLoc, endLoc, replaceString)
375 self.GetDocument().Modify(True)
376 wx.GetApp().GetTopWindow().PushStatusText(_("1 occurrence of \"%s\" replaced") % findString)
377 if down:
378 startLoc += len(replText) # advance start location past replacement string to new text
379 endLoc = startLoc
380
381 text = self._textCtrl.GetValue()
382 if wx.Platform == "__WXMSW__":
383 text = string.replace(text, '\n', '\r\n')
384
385 # Find the next matching text occurance or if it is a ReplaceAll, replace all occurances
386 # Even if the user is Replacing, we should replace here, but only select the text and let the user replace it with the next Replace operation
387 result, start, end, text = findService.DoFind(findString, replaceString, text, startLoc, endLoc, down, matchCase, wholeWord, regExp, False, replaceAll, wrap)
388 if result > 0:
389 self._textCtrl.SetValue(text)
390 self.GetDocument().Modify(True)
391 if result == 1:
392 wx.GetApp().GetTopWindow().PushStatusText(_("1 occurrence of \"%s\" replaced") % findString)
393 else:
394 wx.GetApp().GetTopWindow().PushStatusText(_("%i occurrences of \"%s\" replaced") % (result, findString))
395 elif result == 0:
396 self._textCtrl.SetSelection(start, end)
397 self._textCtrl.SetFocus()
398 wx.GetApp().GetTopWindow().PushStatusText(_("Found \"%s\"") % findString)
399 else:
400 wx.GetApp().GetTopWindow().PushStatusText(_("Can't find \"%s\"") % findString)
401
402
403 def _FindServiceHasString(self):
404 findService = wx.GetApp().GetService(FindService.FindService)
405 if not findService or not findService.GetFindString():
406 return False
407 return True
408
409
410 def OnGotoLine(self, event):
411 findService = wx.GetApp().GetService(FindService.FindService)
412 if findService:
413 line = findService.GetLineNumber(self.GetDocumentManager().FindSuitableParent())
414 if line > -1:
415 pos = self._textCtrl.XYToPosition(0, line - 1)
416 self._textCtrl.SetSelection(pos, pos)
417
418
419 class TextService(wx.lib.pydocview.DocService):
420
421
422 WORD_WRAP_ID = wx.NewId()
423 CHOOSE_FONT_ID = wx.NewId()
424
425
426 def __init__(self):
427 wx.lib.pydocview.DocService.__init__(self)
428
429
430 def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
431 if document and document.GetDocumentTemplate().GetDocumentType() != TextDocument:
432 return
433 config = wx.ConfigBase_Get()
434
435 formatMenuIndex = menuBar.FindMenu(_("&Format"))
436 if formatMenuIndex > -1:
437 formatMenu = menuBar.GetMenu(formatMenuIndex)
438 else:
439 formatMenu = wx.Menu()
440 formatMenu = wx.Menu()
441 if not menuBar.FindItemById(TextService.WORD_WRAP_ID):
442 formatMenu.AppendCheckItem(TextService.WORD_WRAP_ID, _("Word Wrap"), _("Wraps text horizontally when checked"))
443 formatMenu.Check(TextService.WORD_WRAP_ID, config.ReadInt("TextEditorWordWrap", True))
444 wx.EVT_MENU(frame, TextService.WORD_WRAP_ID, frame.ProcessEvent)
445 wx.EVT_UPDATE_UI(frame, TextService.WORD_WRAP_ID, frame.ProcessUpdateUIEvent)
446 if not menuBar.FindItemById(TextService.CHOOSE_FONT_ID):
447 formatMenu.Append(TextService.CHOOSE_FONT_ID, _("Font..."), _("Sets the font to use"))
448 wx.EVT_MENU(frame, TextService.CHOOSE_FONT_ID, frame.ProcessEvent)
449 wx.EVT_UPDATE_UI(frame, TextService.CHOOSE_FONT_ID, frame.ProcessUpdateUIEvent)
450 if formatMenuIndex == -1:
451 viewMenuIndex = menuBar.FindMenu(_("&View"))
452 menuBar.Insert(viewMenuIndex + 1, formatMenu, _("&Format"))
453
454
455 def ProcessUpdateUIEvent(self, event):
456 id = event.GetId()
457 if id == TextService.CHOOSE_FONT_ID:
458 event.Enable(False)
459 return True
460 elif id == TextService.WORD_WRAP_ID:
461 event.Enable(False)
462 return True
463 else:
464 return False
465
466
467 class TextOptionsPanel(wx.Panel):
468
469
470 def __init__(self, parent, id):
471 wx.Panel.__init__(self, parent, id)
472 SPACE = 10
473 HALF_SPACE = 5
474 config = wx.ConfigBase_Get()
475 self._textFont = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
476 fontData = config.Read("TextEditorFont", "")
477 if fontData:
478 nativeFont = wx.NativeFontInfo()
479 nativeFont.FromString(fontData)
480 self._textFont.SetNativeFontInfo(nativeFont)
481 self._originalTextFont = self._textFont
482 self._textColor = wx.BLACK
483 colorData = config.Read("TextEditorColor", "")
484 if colorData:
485 red = int("0x" + colorData[0:2], 16)
486 green = int("0x" + colorData[2:4], 16)
487 blue = int("0x" + colorData[4:6], 16)
488 self._textColor = wx.Color(red, green, blue)
489 self._originalTextColor = self._textColor
490 parent.AddPage(self, _("Text"))
491 fontLabel = wx.StaticText(self, -1, _("Font:"))
492 self._sampleTextCtrl = wx.TextCtrl(self, -1, "", size = (125, -1))
493 self._sampleTextCtrl.SetEditable(False)
494 chooseFontButton = wx.Button(self, -1, _("Choose Font..."))
495 wx.EVT_BUTTON(self, chooseFontButton.GetId(), self.OnChooseFont)
496 self._wordWrapCheckBox = wx.CheckBox(self, -1, _("Wrap words inside text area"))
497 self._wordWrapCheckBox.SetValue(wx.ConfigBase_Get().ReadInt("TextEditorWordWrap", True))
498 textPanelBorderSizer = wx.BoxSizer(wx.VERTICAL)
499 textPanelSizer = wx.BoxSizer(wx.VERTICAL)
500 textFontSizer = wx.BoxSizer(wx.HORIZONTAL)
501 textFontSizer.Add(fontLabel, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.TOP, HALF_SPACE)
502 textFontSizer.Add(self._sampleTextCtrl, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.RIGHT, HALF_SPACE)
503 textFontSizer.Add(chooseFontButton, 0, wx.ALIGN_RIGHT | wx.LEFT, HALF_SPACE)
504 textPanelSizer.Add(textFontSizer, 0, wx.ALL, HALF_SPACE)
505 textPanelSizer.Add(self._wordWrapCheckBox, 0, wx.ALL, HALF_SPACE)
506 textPanelBorderSizer.Add(textPanelSizer, 0, wx.ALL, SPACE)
507 self.SetSizer(textPanelBorderSizer)
508 self.UpdateSampleFont()
509
510
511 def UpdateSampleFont(self):
512 nativeFont = wx.NativeFontInfo()
513 nativeFont.FromString(self._textFont.GetNativeFontInfoDesc())
514 font = wx.NullFont
515 font.SetNativeFontInfo(nativeFont)
516 font.SetPointSize(self._sampleTextCtrl.GetFont().GetPointSize()) # Use the standard point size
517 self._sampleTextCtrl.SetFont(font)
518 self._sampleTextCtrl.SetForegroundColour(self._textColor)
519 self._sampleTextCtrl.SetValue(_("%d pt. %s") % (self._textFont.GetPointSize(), self._textFont.GetFaceName()))
520 self._sampleTextCtrl.Refresh()
521 self.Layout()
522
523
524 def OnChooseFont(self, event):
525 data = wx.FontData()
526 data.EnableEffects(True)
527 data.SetInitialFont(self._textFont)
528 data.SetColour(self._textColor)
529 fontDialog = wx.FontDialog(self, data)
530 if fontDialog.ShowModal() == wx.ID_OK:
531 data = fontDialog.GetFontData()
532 self._textFont = data.GetChosenFont()
533 self._textColor = data.GetColour()
534 self.UpdateSampleFont()
535 fontDialog.Destroy()
536
537
538 def OnOK(self, optionsDialog):
539 config = wx.ConfigBase_Get()
540 doWordWrapUpdate = config.ReadInt("TextEditorWordWrap", True) != self._wordWrapCheckBox.GetValue()
541 config.WriteInt("TextEditorWordWrap", self._wordWrapCheckBox.GetValue())
542 doFontUpdate = self._originalTextFont != self._textFont or self._originalTextColor != self._textColor
543 config.Write("TextEditorFont", self._textFont.GetNativeFontInfoDesc())
544 config.Write("TextEditorColor", "%02x%02x%02x" % (self._textColor.Red(), self._textColor.Green(), self._textColor.Blue()))
545 if doWordWrapUpdate or doFontUpdate:
546 for document in optionsDialog.GetDocManager().GetDocuments():
547 if document.GetDocumentTemplate().GetDocumentType() == TextDocument:
548 if doWordWrapUpdate:
549 document.UpdateAllViews(hint = "Word Wrap")
550 if doFontUpdate:
551 document.UpdateAllViews(hint = "Font")