#---------------------------------------------------------------------------- # Name: TextEditor.py # Purpose: Text Editor for pydocview # # Author: Peter Yared # # Created: 8/15/03 # CVS-ID: $Id$ # Copyright: (c) 2003-2004 ActiveGrid, Inc. # License: wxWindows license #---------------------------------------------------------------------------- import wx import wx.lib.docview import wx.lib.pydocview import string import FindService _ = wx.GetTranslation class TextDocument(wx.lib.docview.Document): def OnSaveDocument(self, filename): view = self.GetFirstView() if not view.GetTextCtrl().SaveFile(filename): return False self.Modify(False) self.SetDocumentSaved(True) #if wx.Platform == "__WXMAC__": # fn = wx.Filename(filename) # fn.MacSetDefaultTypeAndCreator() return True def OnOpenDocument(self, filename): view = self.GetFirstView() if not view.GetTextCtrl().LoadFile(filename): return False self.SetFilename(filename, True) self.Modify(False) self.UpdateAllViews() self._savedYet = True return True def IsModified(self): view = self.GetFirstView() if view and view.GetTextCtrl(): return wx.lib.docview.Document.IsModified(self) or view.GetTextCtrl().IsModified() else: return wx.lib.docview.Document.IsModified(self) def Modify(self, mod): view = self.GetFirstView() wx.lib.docview.Document.Modify(self, mod) if not mod and view and view.GetTextCtrl(): view.GetTextCtrl().DiscardEdits() class TextView(wx.lib.docview.View): #---------------------------------------------------------------------------- # Overridden methods #---------------------------------------------------------------------------- def __init__(self): wx.lib.docview.View.__init__(self) self._textCtrl = None self._wordWrap = wx.ConfigBase_Get().ReadInt("TextEditorWordWrap", True) def OnCreate(self, doc, flags): frame = wx.GetApp().CreateDocumentFrame(self, doc, flags) sizer = wx.BoxSizer() font, color = self._GetFontAndColorFromConfig() self._textCtrl = self._BuildTextCtrl(frame, font, color = color) sizer.Add(self._textCtrl, 1, wx.EXPAND, 0) frame.SetSizer(sizer) frame.Layout() frame.Show(True) self.Activate() return True def _BuildTextCtrl(self, parent, font, color = wx.BLACK, value = "", selection = [0, 0]): if self._wordWrap: wordWrapStyle = wx.TE_WORDWRAP else: wordWrapStyle = wx.TE_DONTWRAP textCtrl = wx.TextCtrl(parent, -1, pos = wx.DefaultPosition, size = parent.GetClientSize(), style = wx.TE_MULTILINE | wordWrapStyle) textCtrl.SetFont(font) textCtrl.SetForegroundColour(color) textCtrl.SetValue(value) return textCtrl def _GetFontAndColorFromConfig(self): font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL) config = wx.ConfigBase_Get() fontData = config.Read("TextEditorFont", "") if fontData: nativeFont = wx.NativeFontInfo() nativeFont.FromString(fontData) font.SetNativeFontInfo(nativeFont) color = wx.BLACK colorData = config.Read("TextEditorColor", "") if colorData: red = int("0x" + colorData[0:2], 16) green = int("0x" + colorData[2:4], 16) blue = int("0x" + colorData[4:6], 16) color = wx.Color(red, green, blue) return font, color def OnCreateCommandProcessor(self): # Don't create a command processor, it has its own pass def OnActivateView(self, activate, activeView, deactiveView): if activate and self._textCtrl: # In MDI mode just calling set focus doesn't work and in SDI mode using CallAfter causes an endless loop if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: self._textCtrl.SetFocus() else: def SetFocusToTextCtrl(): if self._textCtrl: # Need to make sure it is there in case we are in the closeall mode of the MDI window self._textCtrl.SetFocus() wx.CallAfter(SetFocusToTextCtrl) def OnUpdate(self, sender = None, hint = None): if hint == "Word Wrap": self.SetWordWrap(wx.ConfigBase_Get().ReadInt("TextEditorWordWrap", True)) elif hint == "Font": font, color = self._GetFontAndColorFromConfig() self.SetFont(font, color) def OnClose(self, deleteWindow = True): if not wx.lib.docview.View.OnClose(self, deleteWindow): return False self.Activate(False) if deleteWindow: self.GetFrame().Destroy() return True # Since ProcessEvent is not virtual, we have to trap the relevant events using this pseudo-ProcessEvent instead of EVT_MENU def ProcessEvent(self, event): id = event.GetId() if id == wx.ID_UNDO: if not self._textCtrl: return False self._textCtrl.Undo() return True elif id == wx.ID_REDO: if not self._textCtrl: return False self._textCtrl.Redo() return True elif id == wx.ID_CUT: if not self._textCtrl: return False self._textCtrl.Cut() return True elif id == wx.ID_COPY: if not self._textCtrl: return False self._textCtrl.Copy() return True elif id == wx.ID_PASTE: if not self._textCtrl: return False self._textCtrl.Paste() return True elif id == wx.ID_CLEAR: if not self._textCtrl: return False self._textCtrl.Replace(self._textCtrl.GetSelection()[0], self._textCtrl.GetSelection()[1], '') return True elif id == wx.ID_SELECTALL: if not self._textCtrl: return False self._textCtrl.SetSelection(-1, -1) return True elif id == TextService.CHOOSE_FONT_ID: if not self._textCtrl: return False self.OnChooseFont(event) return True elif id == TextService.WORD_WRAP_ID: if not self._textCtrl: return False self.OnWordWrap(event) return True elif id == FindService.FindService.FIND_ID: self.OnFind() return True elif id == FindService.FindService.FIND_PREVIOUS_ID: self.DoFind(forceFindPrevious = True) return True elif id == FindService.FindService.FIND_NEXT_ID: self.DoFind(forceFindNext = True) return True elif id == FindService.FindService.REPLACE_ID: self.OnFind(replace = True) return True elif id == FindService.FindService.FINDONE_ID: self.DoFind() return True elif id == FindService.FindService.REPLACEONE_ID: self.DoFind(replace = True) return True elif id == FindService.FindService.REPLACEALL_ID: self.DoFind(replaceAll = True) return True elif id == FindService.FindService.GOTO_LINE_ID: self.OnGotoLine(event) return True else: return wx.lib.docview.View.ProcessEvent(self, event) def ProcessUpdateUIEvent(self, event): if not self._textCtrl: return False hasText = len(self._textCtrl.GetValue()) > 0 id = event.GetId() if id == wx.ID_UNDO: event.Enable(self._textCtrl.CanUndo()) return True elif id == wx.ID_REDO: event.Enable(self._textCtrl.CanRedo()) return True if id == wx.ID_CUT: event.Enable(self._textCtrl.CanCut()) return True elif id == wx.ID_COPY: event.Enable(self._textCtrl.CanCopy()) return True elif id == wx.ID_PASTE: event.Enable(self._textCtrl.CanPaste()) return True elif id == wx.ID_CLEAR: 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 return True elif id == wx.ID_SELECTALL: event.Enable(hasText) return True elif id == TextService.CHOOSE_FONT_ID: event.Enable(True) return True elif id == TextService.WORD_WRAP_ID: event.Enable(True) return True elif id == FindService.FindService.FIND_ID: event.Enable(hasText) return True elif id == FindService.FindService.FIND_PREVIOUS_ID: event.Enable(hasText and self._FindServiceHasString() and self._textCtrl.GetSelection()[0] > 0) return True elif id == FindService.FindService.FIND_NEXT_ID: event.Enable(hasText and self._FindServiceHasString() and self._textCtrl.GetSelection()[0] < len(self._textCtrl.GetValue())) return True elif id == FindService.FindService.REPLACE_ID: event.Enable(hasText) return True elif id == FindService.FindService.GOTO_LINE_ID: event.Enable(True) return True else: return wx.lib.docview.View.ProcessUpdateUIEvent(self, event) #---------------------------------------------------------------------------- # Methods for TextDocument to call #---------------------------------------------------------------------------- def GetTextCtrl(self): return self._textCtrl #---------------------------------------------------------------------------- # Format methods #---------------------------------------------------------------------------- def OnChooseFont(self, event): data = wx.FontData() data.EnableEffects(True) data.SetInitialFont(self._textCtrl.GetFont()) data.SetColour(self._textCtrl.GetForegroundColour()) fontDialog = wx.FontDialog(self.GetFrame(), data) if fontDialog.ShowModal() == wx.ID_OK: data = fontDialog.GetFontData() self.SetFont(data.GetChosenFont(), data.GetColour()) fontDialog.Destroy() def SetFont(self, font, color): self._textCtrl.SetFont(font) self._textCtrl.SetForegroundColour(color) self._textCtrl.Refresh() self._textCtrl.Layout() def OnWordWrap(self, event): self.SetWordWrap(not self.GetWordWrap()) def GetWordWrap(self): return self._wordWrap def SetWordWrap(self, wordWrap = True): self._wordWrap = wordWrap temp = self._textCtrl self._textCtrl = self._BuildTextCtrl(temp.GetParent(), font = temp.GetFont(), color = temp.GetForegroundColour(), value = temp.GetValue(), selection = temp.GetSelection()) self.GetDocument().Modify(temp.IsModified()) temp.Destroy() #---------------------------------------------------------------------------- # Find methods #---------------------------------------------------------------------------- def OnFind(self, replace = False): findService = wx.GetApp().GetService(FindService.FindService) if findService: findService.ShowFindReplaceDialog(findString = self._textCtrl.GetStringSelection(), replace = replace) def DoFind(self, forceFindNext = False, forceFindPrevious = False, replace = False, replaceAll = False): findService = wx.GetApp().GetService(FindService.FindService) if not findService: return findString = findService.GetFindString() if len(findString) == 0: return -1 replaceString = findService.GetReplaceString() flags = findService.GetFlags() startLoc, endLoc = self._textCtrl.GetSelection() wholeWord = flags & wx.FR_WHOLEWORD > 0 matchCase = flags & wx.FR_MATCHCASE > 0 regExp = flags & FindService.FindService.FR_REGEXP > 0 down = flags & wx.FR_DOWN > 0 wrap = flags & FindService.FindService.FR_WRAP > 0 if forceFindPrevious: # this is from function keys, not dialog box down = False wrap = False # user would want to know they're at the end of file elif forceFindNext: down = True wrap = False # user would want to know they're at the end of file # On replace dialog operations, user is allowed to replace the currently highlighted text to determine if it should be replaced or not. # 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. # If the text is a match, then replace it. if replace: result, start, end, replText = findService.DoFind(findString, replaceString, self._textCtrl.GetStringSelection(), 0, 0, True, matchCase, wholeWord, regExp, replace) if result > 0: self._textCtrl.Replace(startLoc, endLoc, replaceString) self.GetDocument().Modify(True) wx.GetApp().GetTopWindow().PushStatusText(_("1 occurrence of \"%s\" replaced") % findString) if down: startLoc += len(replText) # advance start location past replacement string to new text endLoc = startLoc text = self._textCtrl.GetValue() if wx.Platform == "__WXMSW__": text = string.replace(text, '\n', '\r\n') # Find the next matching text occurance or if it is a ReplaceAll, replace all occurances # 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 result, start, end, text = findService.DoFind(findString, replaceString, text, startLoc, endLoc, down, matchCase, wholeWord, regExp, False, replaceAll, wrap) if result > 0: self._textCtrl.SetValue(text) self.GetDocument().Modify(True) if result == 1: wx.GetApp().GetTopWindow().PushStatusText(_("1 occurrence of \"%s\" replaced") % findString) else: wx.GetApp().GetTopWindow().PushStatusText(_("%i occurrences of \"%s\" replaced") % (result, findString)) elif result == 0: self._textCtrl.SetSelection(start, end) self._textCtrl.SetFocus() wx.GetApp().GetTopWindow().PushStatusText(_("Found \"%s\"") % findString) else: wx.GetApp().GetTopWindow().PushStatusText(_("Can't find \"%s\"") % findString) def _FindServiceHasString(self): findService = wx.GetApp().GetService(FindService.FindService) if not findService or not findService.GetFindString(): return False return True def OnGotoLine(self, event): findService = wx.GetApp().GetService(FindService.FindService) if findService: line = findService.GetLineNumber(self.GetDocumentManager().FindSuitableParent()) if line > -1: pos = self._textCtrl.XYToPosition(0, line - 1) self._textCtrl.SetSelection(pos, pos) class TextService(wx.lib.pydocview.DocService): WORD_WRAP_ID = wx.NewId() CHOOSE_FONT_ID = wx.NewId() def __init__(self): wx.lib.pydocview.DocService.__init__(self) def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): if document and document.GetDocumentTemplate().GetDocumentType() != TextDocument: return config = wx.ConfigBase_Get() formatMenuIndex = menuBar.FindMenu(_("&Format")) if formatMenuIndex > -1: formatMenu = menuBar.GetMenu(formatMenuIndex) else: formatMenu = wx.Menu() formatMenu = wx.Menu() if not menuBar.FindItemById(TextService.WORD_WRAP_ID): formatMenu.AppendCheckItem(TextService.WORD_WRAP_ID, _("Word Wrap"), _("Wraps text horizontally when checked")) formatMenu.Check(TextService.WORD_WRAP_ID, config.ReadInt("TextEditorWordWrap", True)) wx.EVT_MENU(frame, TextService.WORD_WRAP_ID, frame.ProcessEvent) wx.EVT_UPDATE_UI(frame, TextService.WORD_WRAP_ID, frame.ProcessUpdateUIEvent) if not menuBar.FindItemById(TextService.CHOOSE_FONT_ID): formatMenu.Append(TextService.CHOOSE_FONT_ID, _("Font..."), _("Sets the font to use")) wx.EVT_MENU(frame, TextService.CHOOSE_FONT_ID, frame.ProcessEvent) wx.EVT_UPDATE_UI(frame, TextService.CHOOSE_FONT_ID, frame.ProcessUpdateUIEvent) if formatMenuIndex == -1: viewMenuIndex = menuBar.FindMenu(_("&View")) menuBar.Insert(viewMenuIndex + 1, formatMenu, _("&Format")) def ProcessUpdateUIEvent(self, event): id = event.GetId() if id == TextService.CHOOSE_FONT_ID: event.Enable(False) return True elif id == TextService.WORD_WRAP_ID: event.Enable(False) return True else: return False class TextOptionsPanel(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) SPACE = 10 HALF_SPACE = 5 config = wx.ConfigBase_Get() self._textFont = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL) fontData = config.Read("TextEditorFont", "") if fontData: nativeFont = wx.NativeFontInfo() nativeFont.FromString(fontData) self._textFont.SetNativeFontInfo(nativeFont) self._originalTextFont = self._textFont self._textColor = wx.BLACK colorData = config.Read("TextEditorColor", "") if colorData: red = int("0x" + colorData[0:2], 16) green = int("0x" + colorData[2:4], 16) blue = int("0x" + colorData[4:6], 16) self._textColor = wx.Color(red, green, blue) self._originalTextColor = self._textColor parent.AddPage(self, _("Text")) fontLabel = wx.StaticText(self, -1, _("Font:")) self._sampleTextCtrl = wx.TextCtrl(self, -1, "", size = (125, -1)) self._sampleTextCtrl.SetEditable(False) chooseFontButton = wx.Button(self, -1, _("Choose Font...")) wx.EVT_BUTTON(self, chooseFontButton.GetId(), self.OnChooseFont) self._wordWrapCheckBox = wx.CheckBox(self, -1, _("Wrap words inside text area")) self._wordWrapCheckBox.SetValue(wx.ConfigBase_Get().ReadInt("TextEditorWordWrap", True)) textPanelBorderSizer = wx.BoxSizer(wx.VERTICAL) textPanelSizer = wx.BoxSizer(wx.VERTICAL) textFontSizer = wx.BoxSizer(wx.HORIZONTAL) textFontSizer.Add(fontLabel, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.TOP, HALF_SPACE) textFontSizer.Add(self._sampleTextCtrl, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.RIGHT, HALF_SPACE) textFontSizer.Add(chooseFontButton, 0, wx.ALIGN_RIGHT | wx.LEFT, HALF_SPACE) textPanelSizer.Add(textFontSizer, 0, wx.ALL, HALF_SPACE) textPanelSizer.Add(self._wordWrapCheckBox, 0, wx.ALL, HALF_SPACE) textPanelBorderSizer.Add(textPanelSizer, 0, wx.ALL, SPACE) self.SetSizer(textPanelBorderSizer) self.UpdateSampleFont() def UpdateSampleFont(self): nativeFont = wx.NativeFontInfo() nativeFont.FromString(self._textFont.GetNativeFontInfoDesc()) font = wx.NullFont font.SetNativeFontInfo(nativeFont) font.SetPointSize(self._sampleTextCtrl.GetFont().GetPointSize()) # Use the standard point size self._sampleTextCtrl.SetFont(font) self._sampleTextCtrl.SetForegroundColour(self._textColor) self._sampleTextCtrl.SetValue(_("%d pt. %s") % (self._textFont.GetPointSize(), self._textFont.GetFaceName())) self._sampleTextCtrl.Refresh() self.Layout() def OnChooseFont(self, event): data = wx.FontData() data.EnableEffects(True) data.SetInitialFont(self._textFont) data.SetColour(self._textColor) fontDialog = wx.FontDialog(self, data) if fontDialog.ShowModal() == wx.ID_OK: data = fontDialog.GetFontData() self._textFont = data.GetChosenFont() self._textColor = data.GetColour() self.UpdateSampleFont() fontDialog.Destroy() def OnOK(self, optionsDialog): config = wx.ConfigBase_Get() doWordWrapUpdate = config.ReadInt("TextEditorWordWrap", True) != self._wordWrapCheckBox.GetValue() config.WriteInt("TextEditorWordWrap", self._wordWrapCheckBox.GetValue()) doFontUpdate = self._originalTextFont != self._textFont or self._originalTextColor != self._textColor config.Write("TextEditorFont", self._textFont.GetNativeFontInfoDesc()) config.Write("TextEditorColor", "%02x%02x%02x" % (self._textColor.Red(), self._textColor.Green(), self._textColor.Blue())) if doWordWrapUpdate or doFontUpdate: for document in optionsDialog.GetDocManager().GetDocuments(): if document.GetDocumentTemplate().GetDocumentType() == TextDocument: if doWordWrapUpdate: document.UpdateAllViews(hint = "Word Wrap") if doFontUpdate: document.UpdateAllViews(hint = "Font")