]>
Commit | Line | Data |
---|---|---|
1f780e48 RD |
1 | #---------------------------------------------------------------------------- |
2 | # Name: PythonEditor.py | |
3 | # Purpose: PythonEditor for wx.lib.pydocview tbat uses the Styled Text Control | |
4 | # | |
5 | # Author: Peter Yared | |
6 | # | |
7 | # Created: 8/15/03 | |
8 | # CVS-ID: $Id$ | |
9 | # Copyright: (c) 2004-2005 ActiveGrid, Inc. | |
10 | # License: wxWindows License | |
11 | #---------------------------------------------------------------------------- | |
12 | ||
13 | import CodeEditor | |
14 | import wx | |
15 | import wx.lib.docview | |
16 | import wx.lib.pydocview | |
17 | import string | |
18 | import keyword # So it knows what to hilite | |
19 | import wx.py # For the Python interpreter | |
20 | import wx.stc # For the Python interpreter | |
21 | import cStringIO # For indent | |
22 | import OutlineService | |
23 | import STCTextEditor | |
24 | import keyword # for GetAutoCompleteKeywordList | |
25 | import sys # for GetAutoCompleteKeywordList | |
26 | import MessageService # for OnCheckCode | |
27 | import OutlineService | |
02b800ce | 28 | from UICommon import CaseInsensitiveCompare |
1f780e48 RD |
29 | try: |
30 | import checker # for pychecker | |
31 | _CHECKER_INSTALLED = True | |
32 | except ImportError: | |
33 | _CHECKER_INSTALLED = False | |
34 | import os.path # for pychecker | |
35 | _ = wx.GetTranslation | |
36 | ||
37 | if wx.Platform == '__WXMSW__': | |
38 | _WINDOWS = True | |
39 | else: | |
40 | _WINDOWS = False | |
41 | ||
42 | ||
43 | VIEW_PYTHON_INTERPRETER_ID = wx.NewId() | |
44 | ||
45 | ||
46 | class PythonDocument(CodeEditor.CodeDocument): | |
47 | pass | |
48 | ||
49 | ||
50 | class PythonView(CodeEditor.CodeView): | |
51 | ||
52 | ||
26ee3a06 RD |
53 | def GetCtrlClass(self): |
54 | """ Used in split window to instantiate new instances """ | |
55 | return PythonCtrl | |
56 | ||
57 | ||
1f780e48 RD |
58 | def ProcessUpdateUIEvent(self, event): |
59 | if not self.GetCtrl(): | |
60 | return False | |
61 | ||
62 | id = event.GetId() | |
63 | if id == CodeEditor.CHECK_CODE_ID: | |
64 | hasText = self.GetCtrl().GetTextLength() > 0 | |
65 | event.Enable(hasText) | |
66 | return True | |
67 | ||
68 | return CodeEditor.CodeView.ProcessUpdateUIEvent(self, event) | |
69 | ||
70 | ||
1f780e48 RD |
71 | def OnActivateView(self, activate, activeView, deactiveView): |
72 | STCTextEditor.TextView.OnActivateView(self, activate, activeView, deactiveView) | |
02b800ce RD |
73 | if activate and self.GetCtrl(): |
74 | if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: | |
75 | self.LoadOutline() | |
76 | else: | |
77 | wx.CallAfter(self.LoadOutline) # need CallAfter because document isn't loaded yet | |
1f780e48 RD |
78 | |
79 | ||
80 | def OnClose(self, deleteWindow = True): | |
81 | status = STCTextEditor.TextView.OnClose(self, deleteWindow) | |
82 | wx.CallAfter(self.ClearOutline) # need CallAfter because when closing the document, it is Activated and then Close, so need to match OnActivateView's CallAfter | |
83 | return status | |
84 | ||
85 | ||
86 | def GetAutoCompleteKeywordList(self, context, hint): | |
87 | obj = None | |
88 | try: | |
89 | if context and len(context): | |
90 | obj = eval(context, globals(), locals()) | |
91 | except: | |
92 | if not hint or len(hint) == 0: # context isn't valid, maybe it was the hint | |
93 | hint = context | |
94 | ||
95 | if obj is None: | |
96 | kw = keyword.kwlist[:] | |
97 | else: | |
98 | symTbl = dir(obj) | |
99 | kw = filter(lambda item: item[0] != '_', symTbl) # remove local variables and methods | |
100 | ||
101 | if hint and len(hint): | |
102 | lowerHint = hint.lower() | |
103 | filterkw = filter(lambda item: item.lower().startswith(lowerHint), kw) # remove variables and methods that don't match hint | |
104 | kw = filterkw | |
105 | ||
02b800ce | 106 | kw.sort(CaseInsensitiveCompare) |
1f780e48 RD |
107 | |
108 | if hint: | |
109 | replaceLen = len(hint) | |
110 | else: | |
111 | replaceLen = 0 | |
112 | ||
113 | return " ".join(kw), replaceLen | |
114 | ||
115 | ||
116 | def OnCheckCode(self): | |
117 | if not _CHECKER_INSTALLED: | |
118 | wx.MessageBox(_("pychecker not found. Please install pychecker."), _("Check Code")) | |
119 | return | |
120 | ||
121 | filename = os.path.basename(self.GetDocument().GetFilename()) | |
122 | ||
123 | # pychecker only works on files, doesn't take a stream or string input | |
124 | if self.GetDocument().IsModified(): | |
125 | dlg = wx.MessageDialog(self.GetFrame(), _("'%s' has been modfied and must be saved first. Save file and check code?") % filename, _("Check Code")) | |
02b800ce | 126 | dlg.CenterOnParent() |
1f780e48 RD |
127 | val = dlg.ShowModal() |
128 | dlg.Destroy() | |
129 | if val == wx.ID_OK: | |
130 | self.GetDocument().Save() | |
131 | else: | |
132 | return | |
133 | ||
134 | messageService = wx.GetApp().GetService(MessageService.MessageService) | |
135 | messageService.ShowWindow() | |
136 | view = messageService.GetView() | |
137 | if not view: | |
138 | return | |
139 | ||
140 | view.ClearLines() | |
141 | view.SetCallback(self.OnJumpToFoundLine) | |
142 | ||
143 | # Set cursor to Wait cursor | |
144 | wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT)) | |
145 | ||
146 | # This takes a while for involved code | |
147 | checker.checkSyntax(self.GetDocument().GetFilename(), view) | |
148 | ||
149 | # Set cursor to Default cursor | |
150 | wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) | |
151 | ||
152 | ||
153 | def OnJumpToFoundLine(self, event): | |
154 | messageService = wx.GetApp().GetService(MessageService.MessageService) | |
155 | lineText, pos = messageService.GetView().GetCurrLine() | |
156 | ||
157 | lineEnd = lineText.find(".py:") | |
158 | if lineEnd == -1: | |
159 | return | |
160 | ||
161 | lineStart = lineEnd + len(".py:") | |
162 | lineEnd = lineText.find(":", lineStart) | |
163 | lineNum = int(lineText[lineStart:lineEnd]) | |
164 | ||
165 | filename = lineText[0:lineStart - 1] | |
166 | ||
167 | foundView = None | |
168 | openDocs = wx.GetApp().GetDocumentManager().GetDocuments() | |
169 | for openDoc in openDocs: | |
170 | if openDoc.GetFilename() == filename: | |
171 | foundView = openDoc.GetFirstView() | |
172 | break | |
173 | ||
174 | if not foundView: | |
02b800ce | 175 | doc = wx.GetApp().GetDocumentManager().CreateDocument(filename, wx.lib.docview.DOC_SILENT|wx.lib.docview.DOC_OPEN_ONCE) |
1f780e48 RD |
176 | foundView = doc.GetFirstView() |
177 | ||
178 | if foundView: | |
179 | foundView.GetFrame().SetFocus() | |
180 | foundView.Activate() | |
181 | foundView.GotoLine(lineNum) | |
182 | startPos = foundView.PositionFromLine(lineNum) | |
183 | endPos = foundView.GetLineEndPosition(lineNum) | |
184 | # wxBug: Need to select in reverse order, (end, start) to put cursor at head of line so positioning is correct | |
185 | # Also, if we use the correct positioning order (start, end), somehow, when we open a edit window for the first | |
186 | # time, we don't see the selection, it is scrolled off screen | |
187 | foundView.SetSelection(endPos, startPos) | |
188 | wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(foundView, position=startPos) | |
189 | ||
190 | ||
191 | ||
192 | class PythonInterpreterView(wx.lib.docview.View): | |
193 | ||
194 | ||
195 | def OnCreate(self, doc, flags): | |
196 | frame = wx.GetApp().CreateDocumentFrame(self, doc, flags) | |
197 | sizer = wx.BoxSizer() | |
198 | self._pyCrust = wx.py.crust.Crust(frame) | |
199 | sizer.Add(self._pyCrust, 1, wx.EXPAND, 0) | |
200 | frame.SetSizer(sizer) | |
201 | frame.Layout() | |
202 | self.Activate() | |
203 | frame.Show() | |
204 | return True | |
205 | ||
206 | ||
207 | def ProcessEvent(self, event): | |
208 | if not hasattr(self, "_pyCrust") or not self._pyCrust: | |
209 | return wx.lib.docview.View.ProcessEvent(self, event) | |
210 | stcControl = wx.Window_FindFocus() | |
211 | if not isinstance(stcControl, wx.stc.StyledTextCtrl): | |
212 | return wx.lib.docview.View.ProcessEvent(self, event) | |
213 | id = event.GetId() | |
214 | if id == wx.ID_UNDO: | |
215 | stcControl.Undo() | |
216 | return True | |
217 | elif id == wx.ID_REDO: | |
218 | stcControl.Redo() | |
219 | return True | |
220 | elif id == wx.ID_CUT: | |
221 | stcControl.Cut() | |
222 | return True | |
223 | elif id == wx.ID_COPY: | |
224 | stcControl.Copy() | |
225 | return True | |
226 | elif id == wx.ID_PASTE: | |
227 | stcControl.Paste() | |
228 | return True | |
229 | elif id == wx.ID_CLEAR: | |
230 | stcControl.Clear() | |
231 | return True | |
232 | elif id == wx.ID_SELECTALL: | |
233 | stcControl.SetSelection(0, -1) | |
234 | return True | |
235 | else: | |
236 | return wx.lib.docview.View.ProcessEvent(self, event) | |
237 | ||
238 | ||
239 | def ProcessUpdateUIEvent(self, event): | |
240 | if not hasattr(self, "_pyCrust") or not self._pyCrust: | |
241 | return wx.lib.docview.View.ProcessUpdateUIEvent(self, event) | |
242 | stcControl = wx.Window_FindFocus() | |
243 | if not isinstance(stcControl, wx.stc.StyledTextCtrl): | |
244 | return wx.lib.docview.View.ProcessUpdateUIEvent(self, event) | |
245 | id = event.GetId() | |
246 | if id == wx.ID_UNDO: | |
247 | event.Enable(stcControl.CanUndo()) | |
248 | return True | |
249 | elif id == wx.ID_REDO: | |
250 | event.Enable(stcControl.CanRedo()) | |
251 | return True | |
252 | elif id == wx.ID_CUT: | |
253 | event.Enable(stcControl.CanCut()) | |
254 | return True | |
255 | elif id == wx.ID_COPY: | |
256 | event.Enable(stcControl.CanCopy()) | |
257 | return True | |
258 | elif id == wx.ID_PASTE: | |
259 | event.Enable(stcControl.CanPaste()) | |
260 | return True | |
261 | elif id == wx.ID_CLEAR: | |
262 | event.Enable(True) # wxBug: should be stcControl.CanCut()) but disabling clear item means del key doesn't work in control as expected | |
263 | return True | |
264 | elif id == wx.ID_SELECTALL: | |
265 | event.Enable(stcControl.GetTextLength() > 0) | |
266 | return True | |
267 | else: | |
268 | return wx.lib.docview.View.ProcessUpdateUIEvent(self, event) | |
269 | ||
270 | ||
271 | def OnClose(self, deleteWindow=True): | |
272 | if deleteWindow and self.GetFrame(): | |
273 | self.GetFrame().Destroy() | |
274 | return True | |
275 | ||
276 | ||
02b800ce RD |
277 | class PythonInterpreterDocument(wx.lib.docview.Document): |
278 | """ Generate Unique Doc Type """ | |
279 | pass | |
280 | ||
281 | ||
1f780e48 RD |
282 | class PythonService(CodeEditor.CodeService): |
283 | ||
284 | ||
285 | def __init__(self): | |
286 | CodeEditor.CodeService.__init__(self) | |
02b800ce RD |
287 | docManager = wx.GetApp().GetDocumentManager() |
288 | pythonInterpreterTemplate = wx.lib.docview.DocTemplate(docManager, | |
289 | _("Python Interpreter"), | |
290 | "*.Foobar", | |
291 | "Foobar", | |
292 | ".Foobar", | |
293 | _("Python Interpreter Document"), | |
294 | _("Python Interpreter View"), | |
295 | PythonInterpreterDocument, | |
296 | PythonInterpreterView, | |
297 | flags = wx.lib.docview.TEMPLATE_INVISIBLE, | |
298 | icon = getPythonIcon()) | |
299 | docManager.AssociateTemplate(pythonInterpreterTemplate) | |
1f780e48 RD |
300 | |
301 | ||
302 | def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): | |
303 | CodeEditor.CodeService.InstallControls(self, frame, menuBar, toolBar, statusBar, document) | |
304 | ||
305 | if document and document.GetDocumentTemplate().GetDocumentType() != PythonDocument: | |
306 | return | |
307 | if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: | |
308 | return | |
309 | ||
310 | viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View"))) | |
311 | ||
312 | viewStatusBarItemPos = self.GetMenuItemPos(viewMenu, wx.lib.pydocview.VIEW_STATUSBAR_ID) | |
313 | viewMenu.InsertCheckItem(viewStatusBarItemPos + 1, VIEW_PYTHON_INTERPRETER_ID, _("Python &Interpreter"), _("Shows or hides the Python interactive window")) | |
314 | wx.EVT_MENU(frame, VIEW_PYTHON_INTERPRETER_ID, frame.ProcessEvent) | |
315 | wx.EVT_UPDATE_UI(frame, VIEW_PYTHON_INTERPRETER_ID, frame.ProcessUpdateUIEvent) | |
316 | ||
317 | ||
318 | def ProcessEvent(self, event): | |
319 | id = event.GetId() | |
320 | if id == VIEW_PYTHON_INTERPRETER_ID: | |
321 | self.OnViewPythonInterpreter(event) | |
322 | return True | |
323 | else: | |
324 | return CodeEditor.CodeService.ProcessEvent(self, event) | |
325 | ||
326 | ||
327 | def ProcessUpdateUIEvent(self, event): | |
328 | id = event.GetId() | |
329 | if id == VIEW_PYTHON_INTERPRETER_ID: | |
330 | event.Enable(True) | |
331 | docManager = wx.GetApp().GetDocumentManager() | |
332 | event.Check(False) | |
333 | for doc in docManager.GetDocuments(): | |
02b800ce | 334 | if isinstance(doc, PythonInterpreterDocument): |
1f780e48 RD |
335 | event.Check(True) |
336 | break | |
337 | return True | |
338 | else: | |
339 | return CodeEditor.CodeService.ProcessUpdateUIEvent(self, event) | |
340 | ||
341 | ||
342 | def OnViewPythonInterpreter(self, event): | |
343 | for doc in wx.GetApp().GetDocumentManager().GetDocuments(): | |
02b800ce RD |
344 | if isinstance(doc, PythonInterpreterDocument): |
345 | doc.DeleteAllViews() | |
1f780e48 RD |
346 | return |
347 | ||
02b800ce RD |
348 | for template in self.GetDocumentManager().GetTemplates(): |
349 | if template.GetDocumentType() == PythonInterpreterDocument: | |
350 | newDoc = template.CreateDocument('', wx.lib.docview.DOC_SILENT|wx.lib.docview.DOC_OPEN_ONCE) | |
351 | if newDoc: | |
352 | newDoc.SetDocumentName(template.GetDocumentName()) | |
353 | newDoc.SetDocumentTemplate(template) | |
354 | newDoc.OnNewDocument() | |
355 | newDoc.SetWriteable(False) | |
356 | newDoc.GetFirstView().GetFrame().SetTitle(_("Python Interpreter")) | |
357 | break | |
1f780e48 RD |
358 | |
359 | ||
360 | class PythonCtrl(CodeEditor.CodeCtrl): | |
361 | ||
362 | ||
26ee3a06 RD |
363 | def __init__(self, parent, id=-1, style=wx.NO_FULL_REPAINT_ON_RESIZE): |
364 | CodeEditor.CodeCtrl.__init__(self, parent, id, style) | |
1f780e48 RD |
365 | self.SetProperty("tab.timmy.whinge.level", "1") |
366 | self.SetProperty("fold.comment.python", "1") | |
367 | self.SetProperty("fold.quotes.python", "1") | |
368 | self.SetLexer(wx.stc.STC_LEX_PYTHON) | |
369 | self.SetKeyWords(0, string.join(keyword.kwlist)) | |
370 | ||
371 | ||
372 | def SetViewDefaults(self): | |
02b800ce | 373 | CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Python", hasWordWrap = True, hasTabs = True) |
1f780e48 RD |
374 | |
375 | ||
376 | def GetFontAndColorFromConfig(self): | |
377 | return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "Python") | |
378 | ||
379 | ||
380 | def UpdateStyles(self): | |
381 | CodeEditor.CodeCtrl.UpdateStyles(self) | |
382 | ||
383 | if not self.GetFont(): | |
384 | return | |
385 | ||
386 | faces = { 'font' : self.GetFont().GetFaceName(), | |
387 | 'size' : self.GetFont().GetPointSize(), | |
388 | 'size2': self.GetFont().GetPointSize() - 2, | |
389 | 'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue()) | |
390 | } | |
391 | ||
392 | # Python styles | |
393 | # White space | |
394 | self.StyleSetSpec(wx.stc.STC_P_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) | |
395 | # Comment | |
396 | self.StyleSetSpec(wx.stc.STC_P_COMMENTLINE, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces) | |
397 | # Number | |
398 | self.StyleSetSpec(wx.stc.STC_P_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces) | |
399 | # String | |
400 | self.StyleSetSpec(wx.stc.STC_P_STRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) | |
401 | # Single quoted string | |
402 | self.StyleSetSpec(wx.stc.STC_P_CHARACTER, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) | |
403 | # Keyword | |
404 | self.StyleSetSpec(wx.stc.STC_P_WORD, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) | |
405 | # Triple quotes | |
406 | self.StyleSetSpec(wx.stc.STC_P_TRIPLE, "face:%(font)s,fore:#7F0000,size:%(size)d" % faces) | |
407 | # Triple double quotes | |
408 | self.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, "face:%(font)s,fore:#7F0000,size:%(size)d" % faces) | |
409 | # Class name definition | |
410 | self.StyleSetSpec(wx.stc.STC_P_CLASSNAME, "face:%(font)s,fore:#0000FF,bold,size:%(size)d" % faces) | |
411 | # Function or method name definition | |
412 | self.StyleSetSpec(wx.stc.STC_P_DEFNAME, "face:%(font)s,fore:#007F7F,bold,size:%(size)d" % faces) | |
413 | # Operators | |
414 | self.StyleSetSpec(wx.stc.STC_P_OPERATOR, "face:%(font)s,size:%(size)d" % faces) | |
415 | # Identifiers | |
416 | self.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, "face:%(font)s,fore:#%(color)s,face:%(font)s,size:%(size)d" % faces) | |
417 | # Comment-blocks | |
418 | self.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, "face:%(font)s,fore:#7F7F7F,size:%(size)d" % faces) | |
419 | # End of line where string is not closed | |
420 | self.StyleSetSpec(wx.stc.STC_P_STRINGEOL, "face:%(font)s,fore:#000000,face:%(font)s,back:#E0C0E0,eol,size:%(size)d" % faces) | |
421 | ||
422 | ||
423 | def OnUpdateUI(self, evt): | |
424 | braces = self.GetMatchingBraces() | |
425 | ||
426 | # check for matching braces | |
427 | braceAtCaret = -1 | |
428 | braceOpposite = -1 | |
429 | charBefore = None | |
430 | caretPos = self.GetCurrentPos() | |
431 | if caretPos > 0: | |
432 | charBefore = self.GetCharAt(caretPos - 1) | |
433 | styleBefore = self.GetStyleAt(caretPos - 1) | |
434 | ||
435 | # check before | |
436 | if charBefore and chr(charBefore) in braces and styleBefore == wx.stc.STC_P_OPERATOR: | |
437 | braceAtCaret = caretPos - 1 | |
438 | ||
439 | # check after | |
440 | if braceAtCaret < 0: | |
441 | charAfter = self.GetCharAt(caretPos) | |
442 | styleAfter = self.GetStyleAt(caretPos) | |
443 | if charAfter and chr(charAfter) in braces and styleAfter == wx.stc.STC_P_OPERATOR: | |
444 | braceAtCaret = caretPos | |
445 | ||
446 | if braceAtCaret >= 0: | |
447 | braceOpposite = self.BraceMatch(braceAtCaret) | |
448 | ||
449 | if braceAtCaret != -1 and braceOpposite == -1: | |
450 | self.BraceBadLight(braceAtCaret) | |
451 | else: | |
452 | self.BraceHighlight(braceAtCaret, braceOpposite) | |
453 | ||
454 | evt.Skip() | |
455 | ||
456 | ||
457 | def DoIndent(self): | |
458 | (text, caretPos) = self.GetCurLine() | |
459 | ||
460 | self._tokenizerChars = {} # This is really too much, need to find something more like a C array | |
461 | for i in range(len(text)): | |
462 | self._tokenizerChars[i] = 0 | |
463 | ||
464 | ctext = cStringIO.StringIO(text) | |
465 | try: | |
466 | tokenize.tokenize(ctext.readline, self) | |
467 | except: | |
468 | pass | |
469 | ||
470 | # Left in for debugging purposes: | |
471 | #for i in range(len(text)): | |
472 | # print i, text[i], self._tokenizerChars[i] | |
473 | ||
474 | if caretPos == 0 or len(string.strip(text)) == 0: # At beginning of line or within an empty line | |
475 | self.AddText('\n') | |
476 | else: | |
477 | doExtraIndent = False | |
478 | brackets = False | |
479 | commentStart = -1 | |
480 | if caretPos > 1: | |
481 | startParenCount = 0 | |
482 | endParenCount = 0 | |
483 | startSquareBracketCount = 0 | |
484 | endSquareBracketCount = 0 | |
485 | startCurlyBracketCount = 0 | |
486 | endCurlyBracketCount = 0 | |
487 | startQuoteCount = 0 | |
488 | endQuoteCount = 0 | |
489 | for i in range(caretPos - 1, -1, -1): # Go through each character before the caret | |
490 | if i >= len(text): # Sometimes the caret is at the end of the text if there is no LF | |
491 | continue | |
492 | if self._tokenizerChars[i] == 1: | |
493 | continue | |
494 | elif self._tokenizerChars[i] == 2: | |
495 | startQuoteCount = startQuoteCount + 1 | |
496 | elif self._tokenizerChars[i] == 3: | |
497 | endQuoteCount = endQuoteCount + 1 | |
498 | elif text[i] == '(': # Would be nice to use a dict for this, but the code is much more readable this way | |
499 | startParenCount = startParenCount + 1 | |
500 | elif text[i] == ')': | |
501 | endParenCount = endParenCount + 1 | |
502 | elif text[i] == "[": | |
503 | startSquareBracketCount = startSquareBracketCount + 1 | |
504 | elif text[i] == "]": | |
505 | endSquareBracketCount = endSquareBracketCount + 1 | |
506 | elif text[i] == "{": | |
507 | startCurlyBracketCount = startCurlyBracketCount + 1 | |
508 | elif text[i] == "}": | |
509 | endCurlyBracketCount = endCurlyBracketCount + 1 | |
510 | elif text[i] == "#": | |
511 | commentStart = i | |
512 | break | |
513 | if startQuoteCount > endQuoteCount or startParenCount > endParenCount or startSquareBracketCount > endSquareBracketCount or startCurlyBracketCount > endCurlyBracketCount: | |
514 | if i + 1 >= caretPos: # Caret is right at the open paren, so just do indent as if colon was there | |
515 | doExtraIndent = True | |
516 | break | |
517 | else: | |
518 | spaces = " " * (i + 1) | |
519 | brackets = True | |
520 | break | |
521 | if not brackets: | |
522 | spaces = text[0:len(text) - len(string.lstrip(text))] | |
523 | if caretPos < len(spaces): # If within the opening spaces of a line | |
524 | spaces = spaces[:caretPos] | |
525 | ||
526 | # strip comment off | |
527 | if commentStart != -1: | |
528 | text = text[0:commentStart] | |
529 | ||
530 | textNoTrailingSpaces = text[0:caretPos].rstrip() | |
531 | if doExtraIndent or len(textNoTrailingSpaces) and textNoTrailingSpaces[-1] == ':': | |
532 | spaces = spaces + ' ' * self.GetIndent() | |
533 | self.AddText('\n' + spaces) | |
26ee3a06 | 534 | self.EnsureCaretVisible() |
1f780e48 RD |
535 | |
536 | ||
537 | # Callback for tokenizer in self.DoIndent | |
538 | def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line): | |
539 | if toktype == tokenize.COMMENT: | |
540 | for i in range(scol, ecol + 1): | |
541 | self._validChars[i] = False | |
542 | elif toktype == token.STRING: | |
543 | self._tokenizerChars[scol] = 2 # Open quote | |
544 | self._tokenizerChars[ecol - 1] = 3 # Close quote | |
545 | for i in range(scol + 1, ecol - 2): | |
546 | self._tokenizerChars[i] = 1 # Part of string, 1 == ignore the char | |
547 | ||
548 | ||
549 | class PythonOptionsPanel(wx.Panel): | |
550 | ||
551 | def __init__(self, parent, id): | |
552 | wx.Panel.__init__(self, parent, id) | |
553 | pathLabel = wx.StaticText(self, -1, _("python.exe Path:")) | |
554 | config = wx.ConfigBase_Get() | |
555 | path = config.Read("ActiveGridPythonLocation") | |
556 | self._pathTextCtrl = wx.TextCtrl(self, -1, path, size = (150, -1)) | |
557 | self._pathTextCtrl.SetToolTipString(self._pathTextCtrl.GetValue()) | |
558 | self._pathTextCtrl.SetInsertionPointEnd() | |
559 | choosePathButton = wx.Button(self, -1, _("Browse...")) | |
560 | pathSizer = wx.BoxSizer(wx.HORIZONTAL) | |
561 | HALF_SPACE = 5 | |
02b800ce RD |
562 | SPACE = 10 |
563 | pathSizer.Add(pathLabel, 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.TOP, HALF_SPACE) | |
564 | pathSizer.Add(self._pathTextCtrl, 1, wx.EXPAND|wx.LEFT|wx.TOP, HALF_SPACE) | |
565 | pathSizer.Add(choosePathButton, 0, wx.ALIGN_RIGHT|wx.LEFT|wx.RIGHT|wx.TOP, HALF_SPACE) | |
1f780e48 RD |
566 | wx.EVT_BUTTON(self, choosePathButton.GetId(), self.OnChoosePath) |
567 | mainSizer = wx.BoxSizer(wx.VERTICAL) | |
02b800ce | 568 | mainSizer.Add(pathSizer, 0, wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, SPACE) |
1f780e48 | 569 | |
02b800ce RD |
570 | self._otherOptions = STCTextEditor.TextOptionsPanel(self, -1, configPrefix = "Python", label = "Python", hasWordWrap = True, hasTabs = True, addPage=False) |
571 | mainSizer.Add(self._otherOptions, 0, wx.EXPAND|wx.BOTTOM, SPACE) | |
1f780e48 RD |
572 | self.SetSizer(mainSizer) |
573 | parent.AddPage(self, _("Python")) | |
574 | ||
02b800ce | 575 | |
1f780e48 | 576 | def OnChoosePath(self, event): |
02b800ce RD |
577 | defaultDir = os.path.dirname(self._pathTextCtrl.GetValue().strip()) |
578 | defaultFile = os.path.basename(self._pathTextCtrl.GetValue().strip()) | |
1f780e48 | 579 | if _WINDOWS: |
02b800ce RD |
580 | wildcard = _("Executable (*.exe)|*.exe|All (*.*)|*.*") |
581 | if not defaultFile: | |
582 | defaultFile = "python.exe" | |
1f780e48 RD |
583 | else: |
584 | wildcard = _("*") | |
02b800ce RD |
585 | dlg = wx.FileDialog(wx.GetApp().GetTopWindow(), |
586 | _("Select a File"), | |
587 | defaultDir=defaultDir, | |
588 | defaultFile=defaultFile, | |
589 | wildcard=wildcard, | |
590 | style=wx.OPEN|wx.FILE_MUST_EXIST|wx.HIDE_READONLY) | |
591 | # dlg.CenterOnParent() # wxBug: caused crash with wx.FileDialog | |
592 | if dlg.ShowModal() == wx.ID_OK: | |
593 | path = dlg.GetPath() | |
594 | if path: | |
595 | self._pathTextCtrl.SetValue(path) | |
596 | self._pathTextCtrl.SetToolTipString(self._pathTextCtrl.GetValue()) | |
597 | self._pathTextCtrl.SetInsertionPointEnd() | |
598 | dlg.Destroy() | |
599 | ||
1f780e48 RD |
600 | |
601 | def OnOK(self, optionsDialog): | |
02b800ce RD |
602 | config = wx.ConfigBase_Get() |
603 | config.Write("ActiveGridPythonLocation", self._pathTextCtrl.GetValue().strip()) | |
1f780e48 RD |
604 | |
605 | self._otherOptions.OnOK(optionsDialog) | |
02b800ce RD |
606 | |
607 | ||
608 | def GetIcon(self): | |
609 | return getPythonIcon() | |
610 | ||
611 | ||
1f780e48 RD |
612 | #---------------------------------------------------------------------------- |
613 | # Icon Bitmaps - generated by encode_bitmaps.py | |
614 | #---------------------------------------------------------------------------- | |
615 | from wx import ImageFromStream, BitmapFromImage | |
1f780e48 RD |
616 | import cStringIO |
617 | ||
618 | ||
619 | def getPythonData(): | |
620 | return \ | |
02b800ce | 621 | '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ |
1f780e48 | 622 | \x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ |
02b800ce RD |
623 | \x00\x01\xe7IDAT8\x8d}\x921h\x13Q\x18\xc7\x7fw\xb9\x0ei\x9d*\xbd\xeb\x10\x8f\ |
624 | ,\x99\x1c*A[\xaa\x19B\xe8\xd0\xb1\x0e%K\x87\x88T2\x88Cqp\tD\x14i\xe9\xe0V\ | |
625 | \xdaQ\xb7\xe0P\xa1\x8b\xa0(\x95$z\xd5Q1\x90\xa2\xd7\x9a4^\x87\xa0`\x92!w9\ | |
626 | \x87\xf8.\xb9\xa6\xc97\xbd\xef{\xef\xfb\xbd\xff\xfb\xbfO*~;v\xf9\x1f\xad\xba\ | |
627 | \x05@\xf9\xd4\x06\xc0::$\xbb\x96\x92\x18\x11\n@(4\xdd\xcdB\xd3\xd4\x1d\x85\ | |
628 | \x8b\x97\xe1\xe3;\x83\x99\xe5\x15\xb2\xe0\x8e\x82\xc8\xa3\xe8\x003\xcb+\xac\ | |
629 | \xaee\xdda\xfb\xb2\x90\rPw\x14\x00\x9a\xb5\n\xbf\xfflSz\x9d\xa2Y\xdc"zca\xe8\ | |
630 | \x05\xb2h\x14\xcd\xd0\xf3B\x9f\x98\xe5\xf9\xde\x13"\xaaB\xc7\xb1\xcfU!\x0b\ | |
631 | \xc3D4k\x15\xac\x93\x03\xf4\x89Y\xaf\x96\xffT\x028\x17\xa2\xf4\'\xcdZ\x85\ | |
632 | \xf7F\x06{\xaa\x80ev\xc1\x91\xb91>\x18\x0f\xb8\xb7\x95a\xe9\xca\x0b:\x8e\xed\ | |
633 | \xca\x01E\x1a\x00\x98\r\x89\x92\x91\xa1\xda\xd8\x87\x06ha\x1f\x1b\x80\xcd\ | |
634 | \x9d%\xe0\xa5\x0f"[G\x87\x98\x8d\xde/ia\x05-\xac`\x996\xf9\\\x0b\xcb\xb4)\ | |
635 | \x1bmOMn\xf7\xd5\xf0\'\\\x8b\xdces\xe7\x8d\xef\x80h\xd6\xc2\n\xf9\\\x0b]\xf5\ | |
636 | \xab\xf2\xcdApR#\xf1kp4b\xc9 \xf9\\\x0b\x80\xe4\xcdE\xaf\xdeqlW\xaeVL\xaf`~\ | |
637 | \xd9\x03@W\xd3\x00\xc4\x13\x0b\xc4\x92A\xcf\xd0\xf9\xe8:\x89\xebW\x01(|\xfd\ | |
638 | \xe1\xbe-~F\xbas\xff\x91\xf75\x82n\x9d\x1c\xf0}\xfciw\xdd\xe7A<\xd1\x1b\xa8j\ | |
639 | c\x9f\xb2\xd1F\x92\xe4\x80O\x12\xc0\xc6\xb3\x14\xf6Ta\xe0)g\x81\xba\x9a\xf6\ | |
640 | \x9b(\x07\x14I@\x84lq\xb8?\xe6\xa3\xeb\x00\xdc\xba\x9d\xf4+\x10*~\xfem\xf3\ | |
641 | \xf8\xe1\x06\xc7\xa7\xdb\xe8j\x9a\xf8\xdc\xa4\xb7\x1f[\\\xe5\xd2\x851/\xff\ | |
642 | \x07\xac\x9b\xd1e\x12\x96\x0f\xfd\x00\x00\x00\x00IEND\xaeB`\x82' | |
1f780e48 RD |
643 | |
644 | ||
645 | def getPythonBitmap(): | |
646 | return BitmapFromImage(getPythonImage()) | |
647 | ||
648 | def getPythonImage(): | |
649 | stream = cStringIO.StringIO(getPythonData()) | |
650 | return ImageFromStream(stream) | |
651 | ||
652 | def getPythonIcon(): | |
bbf7159c | 653 | return wx.IconFromBitmap(getPythonBitmap()) |