| 1 | """EditWindow class.""" |
| 2 | |
| 3 | __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" |
| 4 | __cvsid__ = "$Id$" |
| 5 | __revision__ = "$Revision$"[11:-2] |
| 6 | |
| 7 | import wx |
| 8 | from wx import stc |
| 9 | |
| 10 | import keyword |
| 11 | import os |
| 12 | import sys |
| 13 | import time |
| 14 | |
| 15 | import dispatcher |
| 16 | from version import VERSION |
| 17 | |
| 18 | |
| 19 | if 'wxMSW' in wx.PlatformInfo: |
| 20 | FACES = { 'times' : 'Times New Roman', |
| 21 | 'mono' : 'Courier New', |
| 22 | 'helv' : 'Arial', |
| 23 | 'lucida' : 'Lucida Console', |
| 24 | 'other' : 'Comic Sans MS', |
| 25 | 'size' : 10, |
| 26 | 'lnsize' : 8, |
| 27 | 'backcol' : '#FFFFFF', |
| 28 | 'calltipbg' : '#FFFFB8', |
| 29 | 'calltipfg' : '#404040', |
| 30 | } |
| 31 | |
| 32 | elif 'wxGTK' in wx.PlatformInfo and 'gtk2' in wx.PlatformInfo: |
| 33 | FACES = { 'times' : 'Serif', |
| 34 | 'mono' : 'Monospace', |
| 35 | 'helv' : 'Sans', |
| 36 | 'other' : 'new century schoolbook', |
| 37 | 'size' : 10, |
| 38 | 'lnsize' : 9, |
| 39 | 'backcol' : '#FFFFFF', |
| 40 | 'calltipbg' : '#FFFFB8', |
| 41 | 'calltipfg' : '#404040', |
| 42 | } |
| 43 | |
| 44 | elif 'wxMac' in wx.PlatformInfo: |
| 45 | FACES = { 'times' : 'Lucida Grande', |
| 46 | 'mono' : 'Courier New', |
| 47 | 'helv' : 'Geneva', |
| 48 | 'other' : 'new century schoolbook', |
| 49 | 'size' : 13, |
| 50 | 'lnsize' : 10, |
| 51 | 'backcol' : '#FFFFFF', |
| 52 | 'calltipbg' : '#FFFFB8', |
| 53 | 'calltipfg' : '#404040', |
| 54 | } |
| 55 | |
| 56 | else: # GTK1, etc. |
| 57 | FACES = { 'times' : 'Times', |
| 58 | 'mono' : 'Courier', |
| 59 | 'helv' : 'Helvetica', |
| 60 | 'other' : 'new century schoolbook', |
| 61 | 'size' : 12, |
| 62 | 'lnsize' : 10, |
| 63 | 'backcol' : '#FFFFFF', |
| 64 | 'calltipbg' : '#FFFFB8', |
| 65 | 'calltipfg' : '#404040', |
| 66 | } |
| 67 | |
| 68 | |
| 69 | class EditWindow(stc.StyledTextCtrl): |
| 70 | """EditWindow based on StyledTextCtrl.""" |
| 71 | |
| 72 | revision = __revision__ |
| 73 | |
| 74 | def __init__(self, parent, id=-1, pos=wx.DefaultPosition, |
| 75 | size=wx.DefaultSize, style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER): |
| 76 | """Create EditWindow instance.""" |
| 77 | stc.StyledTextCtrl.__init__(self, parent, id, pos, size, style) |
| 78 | self.__config() |
| 79 | stc.EVT_STC_UPDATEUI(self, id, self.OnUpdateUI) |
| 80 | dispatcher.connect(receiver=self._fontsizer, signal='FontIncrease') |
| 81 | dispatcher.connect(receiver=self._fontsizer, signal='FontDecrease') |
| 82 | dispatcher.connect(receiver=self._fontsizer, signal='FontDefault') |
| 83 | |
| 84 | def _fontsizer(self, signal): |
| 85 | """Receiver for Font* signals.""" |
| 86 | size = self.GetZoom() |
| 87 | if signal == 'FontIncrease': |
| 88 | size += 1 |
| 89 | elif signal == 'FontDecrease': |
| 90 | size -= 1 |
| 91 | elif signal == 'FontDefault': |
| 92 | size = 0 |
| 93 | self.SetZoom(size) |
| 94 | |
| 95 | |
| 96 | def __config(self): |
| 97 | self.setDisplayLineNumbers(False) |
| 98 | |
| 99 | self.SetLexer(stc.STC_LEX_PYTHON) |
| 100 | self.SetKeyWords(0, ' '.join(keyword.kwlist)) |
| 101 | |
| 102 | self.setStyles(FACES) |
| 103 | self.SetViewWhiteSpace(False) |
| 104 | self.SetTabWidth(4) |
| 105 | self.SetUseTabs(False) |
| 106 | # Do we want to automatically pop up command completion options? |
| 107 | self.autoComplete = True |
| 108 | self.autoCompleteIncludeMagic = True |
| 109 | self.autoCompleteIncludeSingle = True |
| 110 | self.autoCompleteIncludeDouble = True |
| 111 | self.autoCompleteCaseInsensitive = True |
| 112 | self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive) |
| 113 | self.autoCompleteAutoHide = False |
| 114 | self.AutoCompSetAutoHide(self.autoCompleteAutoHide) |
| 115 | self.AutoCompStops(' .,;:([)]}\'"\\<>%^&+-=*/|`') |
| 116 | # Do we want to automatically pop up command argument help? |
| 117 | self.autoCallTip = True |
| 118 | self.callTipInsert = True |
| 119 | self.CallTipSetBackground(FACES['calltipbg']) |
| 120 | self.CallTipSetForeground(FACES['calltipfg']) |
| 121 | self.SetWrapMode(False) |
| 122 | try: |
| 123 | self.SetEndAtLastLine(False) |
| 124 | except AttributeError: |
| 125 | pass |
| 126 | |
| 127 | def setDisplayLineNumbers(self, state): |
| 128 | self.lineNumbers = state |
| 129 | if state: |
| 130 | self.SetMarginType(1, stc.STC_MARGIN_NUMBER) |
| 131 | self.SetMarginWidth(1, 40) |
| 132 | else: |
| 133 | # Leave a small margin so the feature hidden lines marker can be seen |
| 134 | self.SetMarginType(1, 0) |
| 135 | self.SetMarginWidth(1, 10) |
| 136 | |
| 137 | def setStyles(self, faces): |
| 138 | """Configure font size, typeface and color for lexer.""" |
| 139 | |
| 140 | # Default style |
| 141 | self.StyleSetSpec(stc.STC_STYLE_DEFAULT, |
| 142 | "face:%(mono)s,size:%(size)d,back:%(backcol)s" % \ |
| 143 | faces) |
| 144 | |
| 145 | self.StyleClearAll() |
| 146 | |
| 147 | # Built in styles |
| 148 | self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, |
| 149 | "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % FACES) |
| 150 | self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, |
| 151 | "face:%(mono)s" % faces) |
| 152 | self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, |
| 153 | "fore:#0000FF,back:#FFFF88") |
| 154 | self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, |
| 155 | "fore:#FF0000,back:#FFFF88") |
| 156 | |
| 157 | # Python styles |
| 158 | self.StyleSetSpec(stc.STC_P_DEFAULT, |
| 159 | "face:%(mono)s" % faces) |
| 160 | self.StyleSetSpec(stc.STC_P_COMMENTLINE, |
| 161 | "fore:#007F00,face:%(mono)s" % faces) |
| 162 | self.StyleSetSpec(stc.STC_P_NUMBER, |
| 163 | "") |
| 164 | self.StyleSetSpec(stc.STC_P_STRING, |
| 165 | "fore:#7F007F,face:%(mono)s" % faces) |
| 166 | self.StyleSetSpec(stc.STC_P_CHARACTER, |
| 167 | "fore:#7F007F,face:%(mono)s" % faces) |
| 168 | self.StyleSetSpec(stc.STC_P_WORD, |
| 169 | "fore:#00007F,bold") |
| 170 | self.StyleSetSpec(stc.STC_P_TRIPLE, |
| 171 | "fore:#7F0000") |
| 172 | self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, |
| 173 | "fore:#000033,back:#FFFFE8") |
| 174 | self.StyleSetSpec(stc.STC_P_CLASSNAME, |
| 175 | "fore:#0000FF,bold") |
| 176 | self.StyleSetSpec(stc.STC_P_DEFNAME, |
| 177 | "fore:#007F7F,bold") |
| 178 | self.StyleSetSpec(stc.STC_P_OPERATOR, |
| 179 | "") |
| 180 | self.StyleSetSpec(stc.STC_P_IDENTIFIER, |
| 181 | "") |
| 182 | self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, |
| 183 | "fore:#7F7F7F") |
| 184 | self.StyleSetSpec(stc.STC_P_STRINGEOL, |
| 185 | "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces) |
| 186 | |
| 187 | def OnUpdateUI(self, event): |
| 188 | """Check for matching braces.""" |
| 189 | # If the auto-complete window is up let it do its thing. |
| 190 | if self.AutoCompActive() or self.CallTipActive(): |
| 191 | return |
| 192 | braceAtCaret = -1 |
| 193 | braceOpposite = -1 |
| 194 | charBefore = None |
| 195 | caretPos = self.GetCurrentPos() |
| 196 | if caretPos > 0: |
| 197 | charBefore = self.GetCharAt(caretPos - 1) |
| 198 | styleBefore = self.GetStyleAt(caretPos - 1) |
| 199 | |
| 200 | # Check before. |
| 201 | if charBefore and chr(charBefore) in '[]{}()' \ |
| 202 | and styleBefore == stc.STC_P_OPERATOR: |
| 203 | braceAtCaret = caretPos - 1 |
| 204 | |
| 205 | # Check after. |
| 206 | if braceAtCaret < 0: |
| 207 | charAfter = self.GetCharAt(caretPos) |
| 208 | styleAfter = self.GetStyleAt(caretPos) |
| 209 | if charAfter and chr(charAfter) in '[]{}()' \ |
| 210 | and styleAfter == stc.STC_P_OPERATOR: |
| 211 | braceAtCaret = caretPos |
| 212 | |
| 213 | if braceAtCaret >= 0: |
| 214 | braceOpposite = self.BraceMatch(braceAtCaret) |
| 215 | |
| 216 | if braceAtCaret != -1 and braceOpposite == -1: |
| 217 | self.BraceBadLight(braceAtCaret) |
| 218 | else: |
| 219 | self.BraceHighlight(braceAtCaret, braceOpposite) |
| 220 | |
| 221 | def CanCopy(self): |
| 222 | """Return True if text is selected and can be copied.""" |
| 223 | return self.GetSelectionStart() != self.GetSelectionEnd() |
| 224 | |
| 225 | def CanCut(self): |
| 226 | """Return True if text is selected and can be cut.""" |
| 227 | return self.CanCopy() and self.CanEdit() |
| 228 | |
| 229 | def CanEdit(self): |
| 230 | """Return True if editing should succeed.""" |
| 231 | return not self.GetReadOnly() |
| 232 | |
| 233 | def CanPaste(self): |
| 234 | """Return True if pasting should succeed.""" |
| 235 | return stc.StyledTextCtrl.CanPaste(self) and self.CanEdit() |
| 236 | |
| 237 | |
| 238 | def GetLastPosition(self): |
| 239 | return self.GetLength() |
| 240 | |
| 241 | def GetRange(self, start, end): |
| 242 | return self.GetTextRange(start, end) |
| 243 | |
| 244 | def GetSelection(self): |
| 245 | return self.GetAnchor(), self.GetCurrentPos() |
| 246 | |
| 247 | def ShowPosition(self, pos): |
| 248 | line = self.LineFromPosition(pos) |
| 249 | #self.EnsureVisible(line) |
| 250 | self.GotoLine(line) |
| 251 | |
| 252 | def DoFindNext(self, findData, findDlg=None): |
| 253 | backward = not (findData.GetFlags() & wx.FR_DOWN) |
| 254 | matchcase = (findData.GetFlags() & wx.FR_MATCHCASE) != 0 |
| 255 | end = self.GetLastPosition() |
| 256 | textstring = self.GetRange(0, end) |
| 257 | findstring = findData.GetFindString() |
| 258 | if not matchcase: |
| 259 | textstring = textstring.lower() |
| 260 | findstring = findstring.lower() |
| 261 | if backward: |
| 262 | start = self.GetSelection()[0] |
| 263 | loc = textstring.rfind(findstring, 0, start) |
| 264 | else: |
| 265 | start = self.GetSelection()[1] |
| 266 | loc = textstring.find(findstring, start) |
| 267 | |
| 268 | # if it wasn't found then restart at begining |
| 269 | if loc == -1 and start != 0: |
| 270 | if backward: |
| 271 | start = end |
| 272 | loc = textstring.rfind(findstring, 0, start) |
| 273 | else: |
| 274 | start = 0 |
| 275 | loc = textstring.find(findstring, start) |
| 276 | |
| 277 | # was it still not found? |
| 278 | if loc == -1: |
| 279 | dlg = wx.MessageDialog(self, 'Unable to find the search text.', |
| 280 | 'Not found!', |
| 281 | wx.OK | wx.ICON_INFORMATION) |
| 282 | dlg.ShowModal() |
| 283 | dlg.Destroy() |
| 284 | if findDlg: |
| 285 | if loc == -1: |
| 286 | wx.CallAfter(findDlg.SetFocus) |
| 287 | return |
| 288 | else: |
| 289 | findDlg.Close() |
| 290 | |
| 291 | # show and select the found text |
| 292 | self.ShowPosition(loc) |
| 293 | self.SetSelection(loc, loc + len(findstring)) |