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