1# pyenchant 2# 3# Copyright (C) 2004-2008, Ryan Kelly 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the 17# Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18# Boston, MA 02111-1307, USA. 19# 20# In addition, as a special exception, you are 21# given permission to link the code of this program with 22# non-LGPL Spelling Provider libraries (eg: a MSFT Office 23# spell checker backend) and distribute linked combinations including 24# the two. You must obey the GNU Lesser General Public License in all 25# respects for all of the code used other than said providers. If you modify 26# this file, you may extend this exception to your version of the 27# file, but you are not obligated to do so. If you do not wish to 28# do so, delete this exception statement from your version. 29# 30# Major code cleanup and re-write thanks to Phil Mayes, 2007 31# 32""" 33 34 enchant.checker.wxSpellCheckerDialog: wxPython spellchecker interface 35 36 This module provides the class wxSpellCheckerDialog, which provides 37 a wxPython dialog that can be used as an interface to a spell checking 38 session. Currently it is intended as a proof-of-concept and demonstration 39 class, but it should be suitable for general-purpose use in a program. 40 41 The class must be given an enchant.checker.SpellChecker object with 42 which to operate. It can (in theory...) be used in modal and non-modal 43 modes. Use Show() when operating on an array of characters as it will 44 modify the array in place, meaning other work can be done at the same 45 time. Use ShowModal() when operating on a static string. 46 47""" 48_DOC_ERRORS = ["ShowModal"] 49 50import wx 51 52 53class wxSpellCheckerDialog(wx.Dialog): 54 """Simple spellcheck dialog for wxPython 55 56 This class implements a simple spellcheck interface for wxPython, 57 in the form of a dialog. It's intended mainly of an example of 58 how to do this, although it should be useful for applications that 59 just need a simple graphical spellchecker. 60 61 To use, a SpellChecker instance must be created and passed to the 62 dialog before it is shown: 63 64 >>> dlg = wxSpellCheckerDialog(None,-1,"") 65 >>> chkr = SpellChecker("en_AU",text) 66 >>> dlg.SetSpellChecker(chkr) 67 >>> dlg.Show() 68 69 This is most useful when the text to be checked is in the form of 70 a character array, as it will be modified in place as the user 71 interacts with the dialog. For checking strings, the final result 72 will need to be obtained from the SpellChecker object: 73 74 >>> dlg = wxSpellCheckerDialog(None,-1,"") 75 >>> chkr = SpellChecker("en_AU",text) 76 >>> dlg.SetSpellChecker(chkr) 77 >>> dlg.ShowModal() 78 >>> text = dlg.GetSpellChecker().get_text() 79 80 Currently the checker must deal with strings of the same type as 81 returned by wxPython - unicode or normal string depending on the 82 underlying system. This needs to be fixed, somehow... 83 """ 84 85 _DOC_ERRORS = [ 86 "dlg", 87 "chkr", 88 "dlg", 89 "SetSpellChecker", 90 "chkr", 91 "dlg", 92 "dlg", 93 "chkr", 94 "dlg", 95 "SetSpellChecker", 96 "chkr", 97 "dlg", 98 "ShowModal", 99 "dlg", 100 "GetSpellChecker", 101 ] 102 103 # Remember dialog size across invocations by storing it on the class 104 sz = (300, 70) 105 106 def __init__(self, parent=None, id=-1, title="Checking Spelling..."): 107 super().__init__( 108 parent, 109 id, 110 title, 111 size=wxSpellCheckerDialog.sz, 112 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, 113 ) 114 self._numContext = 40 115 self._checker = None 116 self._buttonsEnabled = True 117 self.error_text = wx.TextCtrl( 118 self, -1, "", style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH 119 ) 120 self.replace_text = wx.TextCtrl(self, -1, "", style=wx.TE_PROCESS_ENTER) 121 self.replace_list = wx.ListBox(self, -1, style=wx.LB_SINGLE) 122 self.InitLayout() 123 wx.EVT_LISTBOX(self, self.replace_list.GetId(), self.OnReplSelect) 124 wx.EVT_LISTBOX_DCLICK(self, self.replace_list.GetId(), self.OnReplace) 125 126 def InitLayout(self): 127 """Lay out controls and add buttons.""" 128 sizer = wx.BoxSizer(wx.HORIZONTAL) 129 txtSizer = wx.BoxSizer(wx.VERTICAL) 130 btnSizer = wx.BoxSizer(wx.VERTICAL) 131 replaceSizer = wx.BoxSizer(wx.HORIZONTAL) 132 txtSizer.Add( 133 wx.StaticText(self, -1, "Unrecognised Word:"), 0, wx.LEFT | wx.TOP, 5 134 ) 135 txtSizer.Add(self.error_text, 1, wx.ALL | wx.EXPAND, 5) 136 replaceSizer.Add( 137 wx.StaticText(self, -1, "Replace with:"), 138 0, 139 wx.ALL | wx.ALIGN_CENTER_VERTICAL, 140 5, 141 ) 142 replaceSizer.Add(self.replace_text, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) 143 txtSizer.Add(replaceSizer, 0, wx.EXPAND, 0) 144 txtSizer.Add(self.replace_list, 2, wx.ALL | wx.EXPAND, 5) 145 sizer.Add(txtSizer, 1, wx.EXPAND, 0) 146 self.buttons = [] 147 for label, action, tip in ( 148 ("Ignore", self.OnIgnore, "Ignore this word and continue"), 149 ( 150 "Ignore All", 151 self.OnIgnoreAll, 152 "Ignore all instances of this word and continue", 153 ), 154 ("Replace", self.OnReplace, "Replace this word"), 155 ("Replace All", self.OnReplaceAll, "Replace all instances of this word"), 156 ("Add", self.OnAdd, "Add this word to the dictionary"), 157 ("Done", self.OnDone, "Finish spell-checking and accept changes"), 158 ): 159 btn = wx.Button(self, -1, label) 160 btn.SetToolTip(wx.ToolTip(tip)) 161 btnSizer.Add(btn, 0, wx.ALIGN_RIGHT | wx.ALL, 4) 162 btn.Bind(wx.EVT_BUTTON, action) 163 self.buttons.append(btn) 164 sizer.Add(btnSizer, 0, wx.ALL | wx.EXPAND, 5) 165 self.SetAutoLayout(True) 166 self.SetSizer(sizer) 167 sizer.Fit(self) 168 169 def Advance(self): 170 """Advance to the next error. 171 172 This method advances the SpellChecker to the next error, if 173 any. It then displays the error and some surrounding context, 174 and well as listing the suggested replacements. 175 """ 176 # Disable interaction if no checker 177 if self._checker is None: 178 self.EnableButtons(False) 179 return False 180 # Advance to next error, disable if not available 181 try: 182 self._checker.next() 183 except StopIteration: 184 self.EnableButtons(False) 185 self.error_text.SetValue("") 186 self.replace_list.Clear() 187 self.replace_text.SetValue("") 188 if self.IsModal(): # test needed for SetSpellChecker call 189 # auto-exit when checking complete 190 self.EndModal(wx.ID_OK) 191 return False 192 self.EnableButtons() 193 # Display error context with erroneous word in red. 194 # Restoring default style was misbehaving under win32, so 195 # I am forcing the rest of the text to be black. 196 self.error_text.SetValue("") 197 self.error_text.SetDefaultStyle(wx.TextAttr(wx.BLACK)) 198 lContext = self._checker.leading_context(self._numContext) 199 self.error_text.AppendText(lContext) 200 self.error_text.SetDefaultStyle(wx.TextAttr(wx.RED)) 201 self.error_text.AppendText(self._checker.word) 202 self.error_text.SetDefaultStyle(wx.TextAttr(wx.BLACK)) 203 tContext = self._checker.trailing_context(self._numContext) 204 self.error_text.AppendText(tContext) 205 # Display suggestions in the replacements list 206 suggs = self._checker.suggest() 207 self.replace_list.Set(suggs) 208 self.replace_text.SetValue(suggs and suggs[0] or "") 209 return True 210 211 def EnableButtons(self, state=True): 212 """Enable the checking-related buttons""" 213 if state != self._buttonsEnabled: 214 for btn in self.buttons[:-1]: 215 btn.Enable(state) 216 self._buttonsEnabled = state 217 218 def GetRepl(self): 219 """Get the chosen replacement string.""" 220 repl = self.replace_text.GetValue() 221 return repl 222 223 def OnAdd(self, evt): 224 """Callback for the "add" button.""" 225 self._checker.add() 226 self.Advance() 227 228 def OnDone(self, evt): 229 """Callback for the "close" button.""" 230 wxSpellCheckerDialog.sz = self.error_text.GetSizeTuple() 231 if self.IsModal(): 232 self.EndModal(wx.ID_OK) 233 else: 234 self.Close() 235 236 def OnIgnore(self, evt): 237 """Callback for the "ignore" button. 238 This simply advances to the next error. 239 """ 240 self.Advance() 241 242 def OnIgnoreAll(self, evt): 243 """Callback for the "ignore all" button.""" 244 self._checker.ignore_always() 245 self.Advance() 246 247 def OnReplace(self, evt): 248 """Callback for the "replace" button.""" 249 repl = self.GetRepl() 250 if repl: 251 self._checker.replace(repl) 252 self.Advance() 253 254 def OnReplaceAll(self, evt): 255 """Callback for the "replace all" button.""" 256 repl = self.GetRepl() 257 self._checker.replace_always(repl) 258 self.Advance() 259 260 def OnReplSelect(self, evt): 261 """Callback when a new replacement option is selected.""" 262 sel = self.replace_list.GetSelection() 263 if sel == -1: 264 return 265 opt = self.replace_list.GetString(sel) 266 self.replace_text.SetValue(opt) 267 268 def GetSpellChecker(self): 269 """Get the spell checker object.""" 270 return self._checker 271 272 def SetSpellChecker(self, chkr): 273 """Set the spell checker, advancing to the first error. 274 Return True if error(s) to correct, else False.""" 275 self._checker = chkr 276 return self.Advance() 277 278 279def _test(): 280 class TestDialog(wxSpellCheckerDialog): 281 def __init__(self, *args): 282 super().__init__(*args) 283 wx.EVT_CLOSE(self, self.OnClose) 284 285 def OnClose(self, evnt): 286 chkr = dlg.GetSpellChecker() 287 if chkr is not None: 288 print(["AFTER:", chkr.get_text()]) 289 self.Destroy() 290 291 from enchant.checker import SpellChecker 292 293 text = "This is sme text with a fw speling errors in it. Here are a fw more to tst it ut." 294 print(["BEFORE:", text]) 295 app = wx.App(False) 296 dlg = TestDialog() 297 chkr = SpellChecker("en_US", text) 298 dlg.SetSpellChecker(chkr) 299 dlg.Show() 300 app.MainLoop() 301 302 303if __name__ == "__main__": 304 _test() 305