codekingpro/portable-devtools
115k
1##################################################################2##3## Interactive Shell Window4##5 6import array7import code8import os9import string10import sys11import traceback12 13import __main__14import afxres15import pywin.framework.app16import pywin.scintilla.control17import pywin.scintilla.formatter18import pywin.scintilla.IDLEenvironment19import win32api20import win32clipboard21import win32con22import win32ui23 24## sequential after ID_GOTO_LINE defined in editor.py25ID_EDIT_COPY_CODE = 0xE200226ID_EDIT_EXEC_CLIPBOARD = 0x200327 28trace = pywin.scintilla.formatter.trace29 30import re31 32from . import winout33 34# from IDLE.35_is_block_opener = re.compile(r":\s*(#.*)?$").search36_is_block_closer = re.compile(37 r"""38 \s*39 ( return40 | break41 | continue42 | raise43 | pass44 )45 \b46""",47 re.VERBOSE,48).match49 50tracebackHeader = "Traceback (".encode("ascii")51 52sectionProfile = "Interactive Window"53valueFormatTitle = "FormatTitle"54valueFormatInput = "FormatInput"55valueFormatOutput = "FormatOutput"56valueFormatOutputError = "FormatOutputError"57 58# These are defaults only. Values are read from the registry.59formatTitle = (-536870897, 0, 220, 0, 16711680, 184, 34, "Arial")60formatInput = (-402653169, 0, 200, 0, 0, 0, 49, "Courier New")61formatOutput = (-402653169, 0, 200, 0, 8421376, 0, 49, "Courier New")62formatOutputError = (-402653169, 0, 200, 0, 255, 0, 49, "Courier New")63 64try:65 sys.ps166except AttributeError:67 sys.ps1 = ">>> "68 sys.ps2 = "... "69 70 71def LoadPreference(preference, default=""):72 return win32ui.GetProfileVal(sectionProfile, preference, default)73 74 75def SavePreference(prefName, prefValue):76 win32ui.WriteProfileVal(sectionProfile, prefName, prefValue)77 78 79def GetPromptPrefix(line):80 ps1 = sys.ps181 if line[: len(ps1)] == ps1:82 return ps183 ps2 = sys.ps284 if line[: len(ps2)] == ps2:85 return ps286 87 88#############################################################89#90# Colorizer related code.91#92#############################################################93STYLE_INTERACTIVE_EOL = "Interactive EOL"94STYLE_INTERACTIVE_OUTPUT = "Interactive Output"95STYLE_INTERACTIVE_PROMPT = "Interactive Prompt"96STYLE_INTERACTIVE_BANNER = "Interactive Banner"97STYLE_INTERACTIVE_ERROR = "Interactive Error"98STYLE_INTERACTIVE_ERROR_FINALLINE = "Interactive Error (final line)"99 100INTERACTIVE_STYLES = [101 STYLE_INTERACTIVE_EOL,102 STYLE_INTERACTIVE_OUTPUT,103 STYLE_INTERACTIVE_PROMPT,104 STYLE_INTERACTIVE_BANNER,105 STYLE_INTERACTIVE_ERROR,106 STYLE_INTERACTIVE_ERROR_FINALLINE,107]108 109FormatterParent = pywin.scintilla.formatter.PythonSourceFormatter110 111 112class InteractiveFormatter(FormatterParent):113 def __init__(self, scintilla):114 FormatterParent.__init__(self, scintilla)115 self.bannerDisplayed = False116 117 def SetStyles(self):118 FormatterParent.SetStyles(self)119 Style = pywin.scintilla.formatter.Style120 self.RegisterStyle(Style(STYLE_INTERACTIVE_EOL, STYLE_INTERACTIVE_PROMPT))121 self.RegisterStyle(Style(STYLE_INTERACTIVE_PROMPT, formatInput))122 self.RegisterStyle(Style(STYLE_INTERACTIVE_OUTPUT, formatOutput))123 self.RegisterStyle(Style(STYLE_INTERACTIVE_BANNER, formatTitle))124 self.RegisterStyle(Style(STYLE_INTERACTIVE_ERROR, formatOutputError))125 self.RegisterStyle(126 Style(STYLE_INTERACTIVE_ERROR_FINALLINE, STYLE_INTERACTIVE_ERROR)127 )128 129 def LoadPreference(self, name, default):130 rc = win32ui.GetProfileVal("Format", name, default)131 if rc == default:132 rc = win32ui.GetProfileVal(sectionProfile, name, default)133 return rc134 135 def ColorizeInteractiveCode(self, cdoc, styleStart, stylePyStart):136 lengthDoc = len(cdoc)137 if lengthDoc == 0:138 return139 state = styleStart140 # As per comments in Colorize(), we work with the raw utf8141 # bytes. To avoid too muych py3k pain, we treat each utf8 byte142 # as a latin-1 unicode character - we only use it to compare143 # against ascii chars anyway...144 chNext = cdoc[0:1].decode("latin-1")145 startSeg = 0146 i = 0147 lastState = state # debug only148 while i < lengthDoc:149 ch = chNext150 chNext = cdoc[i + 1 : i + 2].decode("latin-1")151 152 # trace("ch=%r, i=%d, next=%r, state=%s" % (ch, i, chNext, state))153 if state == STYLE_INTERACTIVE_EOL:154 if ch not in "\r\n":155 self.ColorSeg(startSeg, i - 1, state)156 startSeg = i157 if ch in (sys.ps1[0], sys.ps2[0]):158 state = STYLE_INTERACTIVE_PROMPT159 elif cdoc[i : i + len(tracebackHeader)] == tracebackHeader:160 state = STYLE_INTERACTIVE_ERROR161 else:162 state = STYLE_INTERACTIVE_OUTPUT163 elif state == STYLE_INTERACTIVE_PROMPT:164 if ch not in sys.ps1 + sys.ps2 + " ":165 self.ColorSeg(startSeg, i - 1, state)166 startSeg = i167 if ch in "\r\n":168 state = STYLE_INTERACTIVE_EOL169 else:170 state = stylePyStart # Start coloring Python code.171 elif state in (STYLE_INTERACTIVE_OUTPUT,):172 if ch in "\r\n":173 self.ColorSeg(startSeg, i - 1, state)174 startSeg = i175 state = STYLE_INTERACTIVE_EOL176 elif state == STYLE_INTERACTIVE_ERROR:177 if ch in "\r\n" and chNext and chNext not in string.whitespace:178 # Everything including me179 self.ColorSeg(startSeg, i, state)180 startSeg = i + 1181 state = STYLE_INTERACTIVE_ERROR_FINALLINE182 elif i == 0 and ch not in string.whitespace:183 # If we are coloring from the start of a line,184 # we need this better check for the last line185 # Color up to not including me186 self.ColorSeg(startSeg, i - 1, state)187 startSeg = i188 state = STYLE_INTERACTIVE_ERROR_FINALLINE189 elif state == STYLE_INTERACTIVE_ERROR_FINALLINE:190 if ch in "\r\n":191 self.ColorSeg(startSeg, i - 1, state)192 startSeg = i193 state = STYLE_INTERACTIVE_EOL194 elif state == STYLE_INTERACTIVE_BANNER:195 if ch in "\r\n" and (chNext == "" or chNext in ">["):196 # Everything including me197 self.ColorSeg(startSeg, i - 1, state)198 startSeg = i199 state = STYLE_INTERACTIVE_EOL200 else:201 # It is a PythonColorizer state - seek past the end of the line202 # and ask the Python colorizer to color that.203 end = startSeg204 while end < lengthDoc and cdoc[end] not in "\r\n".encode("ascii"):205 end = end + 1206 self.ColorizePythonCode(cdoc[:end], startSeg, state)207 stylePyStart = self.GetStringStyle(end - 1)208 if stylePyStart is None:209 stylePyStart = pywin.scintilla.formatter.STYLE_DEFAULT210 else:211 stylePyStart = stylePyStart.name212 startSeg = end213 i = end - 1 # ready for increment.214 chNext = cdoc[end : end + 1].decode("latin-1")215 state = STYLE_INTERACTIVE_EOL216 if lastState != state:217 lastState = state218 i = i + 1219 # and the rest220 if startSeg < i:221 self.ColorSeg(startSeg, i - 1, state)222 223 def Colorize(self, start=0, end=-1):224 # scintilla's formatting is all done in terms of utf, so225 # we work with utf8 bytes instead of unicode. This magically226 # works as any extended chars found in the utf8 don't change227 # the semantics.228 stringVal = self.scintilla.GetTextRange(start, end, decode=False)229 styleStart = None230 stylePyStart = None231 if start > 1:232 # Likely we are being asked to color from the start of the line.233 # We find the last formatted character on the previous line.234 # If TQString, we continue it. Otherwise, we reset.235 look = start - 1236 while look and self.scintilla.SCIGetCharAt(look) in "\n\r":237 look = look - 1238 if look and look < start - 1: # Did we find a char before the \n\r sets?239 strstyle = self.GetStringStyle(look)240 quote_char = None241 if strstyle is not None:242 if strstyle.name == pywin.scintilla.formatter.STYLE_TQSSTRING:243 quote_char = "'"244 elif strstyle.name == pywin.scintilla.formatter.STYLE_TQDSTRING:245 quote_char = '"'246 if quote_char is not None:247 # It is a TQS. If the TQS is not terminated, we248 # carry the style through.249 if look > 2:250 look_str = (251 self.scintilla.SCIGetCharAt(look - 2)252 + self.scintilla.SCIGetCharAt(look - 1)253 + self.scintilla.SCIGetCharAt(look)254 )255 if look_str != quote_char * 3:256 stylePyStart = strstyle.name257 if stylePyStart is None:258 stylePyStart = pywin.scintilla.formatter.STYLE_DEFAULT259 260 if start > 0:261 stylenum = self.scintilla.SCIGetStyleAt(start - 1)262 styleStart = self.GetStyleByNum(stylenum).name263 elif self.bannerDisplayed:264 styleStart = STYLE_INTERACTIVE_EOL265 else:266 styleStart = STYLE_INTERACTIVE_BANNER267 self.bannerDisplayed = True268 self.scintilla.SCIStartStyling(start, 31)269 self.style_buffer = array.array("b", (0,) * len(stringVal))270 self.ColorizeInteractiveCode(stringVal, styleStart, stylePyStart)271 self.scintilla.SCISetStylingEx(self.style_buffer)272 self.style_buffer = None273 274 275###############################################################276#277# This class handles the Python interactive interpreter.278#279# It uses a basic EditWindow, and does all the magic.280# This is triggered by the enter key hander attached by the281# start-up code. It determines if a command is to be executed282# or continued (ie, emit "... ") by snooping around the current283# line, looking for the prompts284#285class PythonwinInteractiveInterpreter(code.InteractiveInterpreter):286 def __init__(self, locals=None, globals=None):287 if locals is None:288 locals = __main__.__dict__289 if globals is None:290 globals = locals291 self.globals = globals292 code.InteractiveInterpreter.__init__(self, locals)293 294 def showsyntaxerror(self, filename=None):295 sys.stderr.write(296 tracebackHeader.decode("ascii")297 ) # So the color syntaxer recognises it.298 code.InteractiveInterpreter.showsyntaxerror(self, filename)299 300 def runcode(self, code):301 try:302 exec(code, self.globals, self.locals)303 except SystemExit:304 raise305 except:306 self.showtraceback()307 308 309class InteractiveCore:310 def __init__(self, banner=None):311 self.banner = banner312 313 # LoadFontPreferences()314 def Init(self):315 self.oldStdOut = self.oldStdErr = None316 317 # self.SetWordWrap(win32ui.CRichEditView_WrapNone)318 self.interp = PythonwinInteractiveInterpreter()319 320 self.OutputGrab() # Release at cleanup.321 322 if self.GetTextLength() == 0:323 if self.banner is None:324 suffix = ""325 if win32ui.debug:326 suffix = ", debug build"327 sys.stderr.write(328 "PythonWin %s on %s%s.\n" % (sys.version, sys.platform, suffix)329 )330 sys.stderr.write(331 "Portions %s - see 'Help/About PythonWin' for further copyright information.\n"332 % (win32ui.copyright,)333 )334 else:335 sys.stderr.write(banner)336 rcfile = os.environ.get("PYTHONSTARTUP")337 if rcfile:338 import __main__339 340 try:341 exec(342 compile(343 open(rcfile, "rb").read(), rcfile, "exec", dont_inherit=True344 ),345 __main__.__dict__,346 __main__.__dict__,347 )348 except:349 sys.stderr.write(350 ">>> \nError executing PYTHONSTARTUP script %r\n" % (rcfile)351 )352 traceback.print_exc(file=sys.stderr)353 self.AppendToPrompt([])354 355 def SetContext(self, globals, locals, name="Dbg"):356 oldPrompt = sys.ps1357 if globals is None:358 # Reset359 sys.ps1 = ">>> "360 sys.ps2 = "... "361 locals = globals = __main__.__dict__362 else:363 sys.ps1 = "[%s]>>> " % name364 sys.ps2 = "[%s]... " % name365 self.interp.locals = locals366 self.interp.globals = globals367 self.AppendToPrompt([], oldPrompt)368 369 def GetContext(self):370 return self.interp.globals, self.interp.locals371 372 def DoGetLine(self, line=-1):373 if line == -1:374 line = self.LineFromChar()375 line = self.GetLine(line)376 while line and line[-1] in ("\r", "\n"):377 line = line[:-1]378 return line379 380 def AppendToPrompt(self, bufLines, oldPrompt=None):381 "Take a command and stick it at the end of the buffer (with python prompts inserted if required)."382 self.flush()383 lastLineNo = self.GetLineCount() - 1384 line = self.DoGetLine(lastLineNo)385 if oldPrompt and line == oldPrompt:386 self.SetSel(self.GetTextLength() - len(oldPrompt), self.GetTextLength())387 self.ReplaceSel(sys.ps1)388 elif line != str(sys.ps1):389 if len(line) != 0:390 self.write("\n")391 self.write(sys.ps1)392 self.flush()393 self.idle.text.mark_set("iomark", "end-1c")394 if not bufLines:395 return396 terms = (["\n" + sys.ps2] * (len(bufLines) - 1)) + [""]397 for bufLine, term in zip(bufLines, terms):398 if bufLine.strip():399 self.write(bufLine + term)400 self.flush()401 402 def EnsureNoPrompt(self):403 # Get ready to write some text NOT at a Python prompt.404 self.flush()405 lastLineNo = self.GetLineCount() - 1406 line = self.DoGetLine(lastLineNo)407 if not line or line in (sys.ps1, sys.ps2):408 self.SetSel(self.GetTextLength() - len(line), self.GetTextLength())409 self.ReplaceSel("")410 else:411 # Just add a new line.412 self.write("\n")413 414 def _GetSubConfigNames(self):415 return ["interactive"] # Allow [Keys:Interactive] sections to be specific416 417 def HookHandlers(self):418 # Hook menu command (executed when a menu item with that ID is selected from a menu/toolbar419 self.HookCommand(self.OnSelectBlock, win32ui.ID_EDIT_SELECT_BLOCK)420 self.HookCommand(self.OnEditCopyCode, ID_EDIT_COPY_CODE)421 self.HookCommand(self.OnEditExecClipboard, ID_EDIT_EXEC_CLIPBOARD)422 mod = pywin.scintilla.IDLEenvironment.GetIDLEModule("IdleHistory")423 if mod is not None:424 self.history = mod.History(self.idle.text, "\n" + sys.ps2)425 else:426 self.history = None427 # hack for now for event handling.428 429 # GetBlockBoundary takes a line number, and will return the430 # start and and line numbers of the block, and a flag indicating if the431 # block is a Python code block.432 # If the line specified has a Python prompt, then the lines are parsed433 # backwards and forwards, and the flag is true.434 # If the line does not start with a prompt, the block is searched forward435 # and backward until a prompt _is_ found, and all lines in between without436 # prompts are returned, and the flag is false.437 def GetBlockBoundary(self, lineNo):438 line = self.DoGetLine(lineNo)439 maxLineNo = self.GetLineCount() - 1440 prefix = GetPromptPrefix(line)441 if prefix is None: # Non code block442 flag = 0443 startLineNo = lineNo444 while startLineNo > 0:445 if GetPromptPrefix(self.DoGetLine(startLineNo - 1)) is not None:446 break # there _is_ a prompt447 startLineNo = startLineNo - 1448 endLineNo = lineNo449 while endLineNo < maxLineNo:450 if GetPromptPrefix(self.DoGetLine(endLineNo + 1)) is not None:451 break # there _is_ a prompt452 endLineNo = endLineNo + 1453 else: # Code block454 flag = 1455 startLineNo = lineNo456 while startLineNo > 0 and prefix != str(sys.ps1):457 prefix = GetPromptPrefix(self.DoGetLine(startLineNo - 1))458 if prefix is None:459 break460 # there is no prompt.461 startLineNo = startLineNo - 1462 endLineNo = lineNo463 while endLineNo < maxLineNo:464 prefix = GetPromptPrefix(self.DoGetLine(endLineNo + 1))465 if prefix is None:466 break # there is no prompt467 if prefix == str(sys.ps1):468 break # this is another command469 endLineNo = endLineNo + 1470 # continue until end of buffer, or no prompt471 return (startLineNo, endLineNo, flag)472 473 def ExtractCommand(self, lines):474 start, end = lines475 retList = []476 while end >= start:477 thisLine = self.DoGetLine(end)478 promptLen = len(GetPromptPrefix(thisLine))479 retList = [thisLine[promptLen:]] + retList480 end = end - 1481 return retList482 483 def OutputGrab(self):484 # import win32traceutil; return485 self.oldStdOut = sys.stdout486 self.oldStdErr = sys.stderr487 sys.stdout = self488 sys.stderr = self489 self.flush()490 491 def OutputRelease(self):492 # a command may have overwritten these - only restore if not.493 if self.oldStdOut is not None:494 if sys.stdout == self:495 sys.stdout = self.oldStdOut496 if self.oldStdErr is not None:497 if sys.stderr == self:498 sys.stderr = self.oldStdErr499 self.oldStdOut = None500 self.oldStdErr = None501 self.flush()502 503 ###################################504 #505 # Message/Command/Key Hooks.506 #507 # Enter key handler508 #509 def ProcessEnterEvent(self, event):510 # If autocompletion has been triggered, complete and do not process event511 if self.SCIAutoCActive():512 self.SCIAutoCComplete()513 self.SCICancel()514 return515 516 self.SCICancel()517 # First, check for an error message518 haveGrabbedOutput = 0519 if self.HandleSpecialLine():520 return 0521 522 lineNo = self.LineFromChar()523 start, end, isCode = self.GetBlockBoundary(lineNo)524 # If we are not in a code block just go to the prompt (or create a new one)525 if not isCode:526 self.AppendToPrompt([])527 win32ui.SetStatusText(win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE))528 return529 530 lines = self.ExtractCommand((start, end))531 532 # If we are in a code-block, but it isnt at the end of the buffer533 # then copy it to the end ready for editing and subsequent execution534 if end != self.GetLineCount() - 1:535 win32ui.SetStatusText("Press ENTER to execute command")536 self.AppendToPrompt(lines)537 self.SetSel(-2)538 return539 540 # If SHIFT held down, we want new code here and now!541 bNeedIndent = (542 win32api.GetKeyState(win32con.VK_SHIFT) < 0543 or win32api.GetKeyState(win32con.VK_CONTROL) < 0544 )545 if bNeedIndent:546 self.ReplaceSel("\n")547 else:548 self.SetSel(-2)549 self.ReplaceSel("\n")550 source = "\n".join(lines)551 while source and source[-1] in "\t ":552 source = source[:-1]553 self.OutputGrab() # grab the output for the command exec.554 try:555 if self.interp.runsource(556 source, "<interactive input>"557 ): # Need more input!558 bNeedIndent = 1559 else:560 # If the last line isnt empty, append a newline561 if self.history is not None:562 self.history.history_store(source)563 self.AppendToPrompt([])564 win32ui.SetStatusText(565 win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE)566 )567 # win32ui.SetStatusText('Successfully executed statement')568 finally:569 self.OutputRelease()570 if bNeedIndent:571 win32ui.SetStatusText("Ready to continue the command")572 # Now attempt correct indentation (should use IDLE?)573 curLine = self.DoGetLine(lineNo)[len(sys.ps2) :]574 pos = 0575 indent = ""576 while len(curLine) > pos and curLine[pos] in string.whitespace:577 indent = indent + curLine[pos]578 pos = pos + 1579 if _is_block_opener(curLine):580 indent = indent + "\t"581 elif _is_block_closer(curLine):582 indent = indent[:-1]583 # use ReplaceSel to ensure it goes at the cursor rather than end of buffer.584 self.ReplaceSel(sys.ps2 + indent)585 return 0586 587 # ESC key handler588 def ProcessEscEvent(self, event):589 # Implement a cancel.590 if self.SCIAutoCActive() or self.SCICallTipActive():591 self.SCICancel()592 else:593 win32ui.SetStatusText("Cancelled.")594 self.AppendToPrompt(("",))595 return 0596 597 def OnSelectBlock(self, command, code):598 lineNo = self.LineFromChar()599 start, end, isCode = self.GetBlockBoundary(lineNo)600 startIndex = self.LineIndex(start)601 endIndex = self.LineIndex(end + 1) - 2 # skip \r + \n602 if endIndex < 0: # must be beyond end of buffer603 endIndex = -2 # self.Length()604 self.SetSel(startIndex, endIndex)605 606 def OnEditCopyCode(self, command, code):607 """Sanitizes code from interactive window, removing prompts and output,608 and inserts it in the clipboard."""609 code = self.GetSelText()610 lines = code.splitlines()611 out_lines = []612 for line in lines:613 if line.startswith(sys.ps1):614 line = line[len(sys.ps1) :]615 out_lines.append(line)616 elif line.startswith(sys.ps2):617 line = line[len(sys.ps2) :]618 out_lines.append(line)619 out_code = os.linesep.join(out_lines)620 win32clipboard.OpenClipboard()621 try:622 win32clipboard.SetClipboardData(623 win32clipboard.CF_UNICODETEXT, str(out_code)624 )625 finally:626 win32clipboard.CloseClipboard()627 628 def OnEditExecClipboard(self, command, code):629 """Executes python code directly from the clipboard."""630 win32clipboard.OpenClipboard()631 try:632 code = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)633 finally:634 win32clipboard.CloseClipboard()635 636 code = code.replace("\r\n", "\n") + "\n"637 try:638 o = compile(code, "<clipboard>", "exec")639 exec(o, __main__.__dict__)640 except:641 traceback.print_exc()642 643 def GetRightMenuItems(self):644 # Just override parents645 ret = []646 flags = 0647 ret.append((flags, win32ui.ID_EDIT_UNDO, "&Undo"))648 ret.append(win32con.MF_SEPARATOR)649 ret.append((flags, win32ui.ID_EDIT_CUT, "Cu&t"))650 ret.append((flags, win32ui.ID_EDIT_COPY, "&Copy"))651 652 start, end = self.GetSel()653 if start != end:654 ret.append((flags, ID_EDIT_COPY_CODE, "Copy code without prompts"))655 if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_UNICODETEXT):656 ret.append(657 (flags, ID_EDIT_EXEC_CLIPBOARD, "Execute python code from clipboard")658 )659 660 ret.append((flags, win32ui.ID_EDIT_PASTE, "&Paste"))661 ret.append(win32con.MF_SEPARATOR)662 ret.append((flags, win32ui.ID_EDIT_SELECT_ALL, "&Select all"))663 ret.append((flags, win32ui.ID_EDIT_SELECT_BLOCK, "Select &block"))664 ret.append((flags, win32ui.ID_VIEW_WHITESPACE, "View &Whitespace"))665 return ret666 667 def MDINextEvent(self, event):668 win32ui.GetMainFrame().MDINext(0)669 670 def MDIPrevEvent(self, event):671 win32ui.GetMainFrame().MDINext(0)672 673 def WindowBackEvent(self, event):674 parent = self.GetParentFrame()675 if parent == win32ui.GetMainFrame():676 # It is docked.677 try:678 wnd, isactive = parent.MDIGetActive()679 wnd.SetFocus()680 except win32ui.error:681 # No MDI window active!682 pass683 else:684 # Normal Window685 try:686 lastActive = self.GetParentFrame().lastActive687 # If the window is invalid, reset it.688 if lastActive is not None and (689 lastActive._obj_ is None or lastActive.GetSafeHwnd() == 0690 ):691 lastActive = self.GetParentFrame().lastActive = None692 win32ui.SetStatusText("The last active Window has been closed.")693 except AttributeError:694 print("Can't find the last active window!")695 lastActive = None696 if lastActive is not None:697 lastActive.MDIActivate()698 699 700class InteractiveView(InteractiveCore, winout.WindowOutputView):701 def __init__(self, doc):702 InteractiveCore.__init__(self)703 winout.WindowOutputView.__init__(self, doc)704 self.encoding = pywin.default_scintilla_encoding705 706 def _MakeColorizer(self):707 return InteractiveFormatter(self)708 709 def OnInitialUpdate(self):710 winout.WindowOutputView.OnInitialUpdate(self)711 self.SetWordWrap()712 self.Init()713 714 def HookHandlers(self):715 winout.WindowOutputView.HookHandlers(self)716 InteractiveCore.HookHandlers(self)717 718 719class CInteractivePython(winout.WindowOutput):720 def __init__(self, makeDoc=None, makeFrame=None):721 self.IsFinalDestroy = 0722 winout.WindowOutput.__init__(723 self,724 sectionProfile,725 sectionProfile,726 winout.flags.WQ_LINE,727 1,728 None,729 makeDoc,730 makeFrame,731 InteractiveView,732 )733 self.Create()734 735 def OnViewDestroy(self, view):736 if self.IsFinalDestroy:737 view.OutputRelease()738 winout.WindowOutput.OnViewDestroy(self, view)739 740 def Close(self):741 self.IsFinalDestroy = 1742 winout.WindowOutput.Close(self)743 744 745class InteractiveFrame(winout.WindowOutputFrame):746 def __init__(self):747 self.lastActive = None748 winout.WindowOutputFrame.__init__(self)749 750 def OnMDIActivate(self, bActive, wndActive, wndDeactive):751 if bActive:752 self.lastActive = wndDeactive753 754 755######################################################################756##757## Dockable Window Support758##759######################################################################760ID_DOCKED_INTERACTIVE_CONTROLBAR = 0xE802761 762DockedInteractiveViewParent = InteractiveView763 764 765class DockedInteractiveView(DockedInteractiveViewParent):766 def HookHandlers(self):767 DockedInteractiveViewParent.HookHandlers(self)768 self.HookMessage(self.OnSetFocus, win32con.WM_SETFOCUS)769 self.HookMessage(self.OnKillFocus, win32con.WM_KILLFOCUS)770 771 def OnSetFocus(self, msg):772 self.GetParentFrame().SetActiveView(self)773 return 1774 775 def OnKillFocus(self, msg):776 # If we are losing focus to another in this app, reset the main frame's active view.777 hwnd = wparam = msg[2]778 try:779 wnd = win32ui.CreateWindowFromHandle(hwnd)780 reset = wnd.GetTopLevelFrame() == self.GetTopLevelFrame()781 except win32ui.error:782 reset = 0 # Not my window783 if reset:784 self.GetParentFrame().SetActiveView(None)785 return 1786 787 def OnDestroy(self, msg):788 newSize = self.GetWindowPlacement()[4]789 pywin.framework.app.SaveWindowSize("Interactive Window", newSize, "docked")790 try:791 if self.GetParentFrame().GetActiveView == self:792 self.GetParentFrame().SetActiveView(None)793 except win32ui.error:794 pass795 try:796 if win32ui.GetMainFrame().GetActiveView() == self:797 win32ui.GetMainFrame().SetActiveView(None)798 except win32ui.error:799 pass800 return DockedInteractiveViewParent.OnDestroy(self, msg)801 802 803class CDockedInteractivePython(CInteractivePython):804 def __init__(self, dockbar):805 self.bFirstCreated = 0806 self.dockbar = dockbar807 CInteractivePython.__init__(self)808 809 def NeedRecreateWindow(self):810 if self.bCreating:811 return 0812 try:813 frame = win32ui.GetMainFrame()814 if frame.closing:815 return 0 # Dieing!816 except (win32ui.error, AttributeError):817 return 0 # The app is dieing!818 try:819 cb = frame.GetControlBar(ID_DOCKED_INTERACTIVE_CONTROLBAR)820 return not cb.IsWindowVisible()821 except win32ui.error:822 return 1 # Control bar does not exist!823 824 def RecreateWindow(self):825 try:826 dockbar = win32ui.GetMainFrame().GetControlBar(827 ID_DOCKED_INTERACTIVE_CONTROLBAR828 )829 win32ui.GetMainFrame().ShowControlBar(dockbar, 1, 1)830 except win32ui.error:831 CreateDockedInteractiveWindow()832 833 def Create(self):834 self.bCreating = 1835 doc = InteractiveDocument(None, self.DoCreateDoc())836 view = DockedInteractiveView(doc)837 defRect = pywin.framework.app.LoadWindowSize("Interactive Window", "docked")838 if defRect[2] - defRect[0] == 0:839 defRect = 0, 0, 500, 200840 style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.WS_BORDER841 id = 1050 # win32ui.AFX_IDW_PANE_FIRST842 view.CreateWindow(self.dockbar, id, style, defRect)843 view.OnInitialUpdate()844 self.bFirstCreated = 1845 846 self.currentView = doc.GetFirstView()847 self.bCreating = 0848 if self.title:849 doc.SetTitle(self.title)850 851 852# The factory we pass to the dockable window support.853def InteractiveViewCreator(parent):854 global edit855 edit = CDockedInteractivePython(parent)856 return edit.currentView857 858 859def CreateDockedInteractiveWindow():860 # Later, the DockingBar should be capable of hosting multiple861 # children.862 from pywin.docking.DockingBar import DockingBar863 864 bar = DockingBar()865 creator = InteractiveViewCreator866 bar.CreateWindow(867 win32ui.GetMainFrame(),868 creator,869 "Interactive Window",870 ID_DOCKED_INTERACTIVE_CONTROLBAR,871 )872 bar.SetBarStyle(873 bar.GetBarStyle()874 | afxres.CBRS_TOOLTIPS875 | afxres.CBRS_FLYBY876 | afxres.CBRS_SIZE_DYNAMIC877 )878 bar.EnableDocking(afxres.CBRS_ALIGN_ANY)879 win32ui.GetMainFrame().DockControlBar(bar, afxres.AFX_IDW_DOCKBAR_BOTTOM)880 881 882######################################################################883#884# The public interface to this module.885#886######################################################################887# No extra functionality now, but maybe later, so888# publicize these names.889InteractiveDocument = winout.WindowOutputDocument890 891# We remember our one and only interactive window in the "edit" variable.892edit = None893 894 895def CreateInteractiveWindowUserPreference(makeDoc=None, makeFrame=None):896 """Create some sort of interactive window if the user's preference say we should."""897 bCreate = LoadPreference("Show at startup", 1)898 if bCreate:899 CreateInteractiveWindow(makeDoc, makeFrame)900 901 902def CreateInteractiveWindow(makeDoc=None, makeFrame=None):903 """Create a standard or docked interactive window unconditionally"""904 assert edit is None, "Creating second interactive window!"905 bDocking = LoadPreference("Docking", 0)906 if bDocking:907 CreateDockedInteractiveWindow()908 else:909 CreateMDIInteractiveWindow(makeDoc, makeFrame)910 assert edit is not None, "Created interactive window, but did not set the global!"911 edit.currentView.SetFocus()912 913 914def CreateMDIInteractiveWindow(makeDoc=None, makeFrame=None):915 """Create a standard (non-docked) interactive window unconditionally"""916 global edit917 if makeDoc is None:918 makeDoc = InteractiveDocument919 if makeFrame is None:920 makeFrame = InteractiveFrame921 edit = CInteractivePython(makeDoc=makeDoc, makeFrame=makeFrame)922 923 924def DestroyInteractiveWindow():925 """Destroy the interactive window.926 This is different to Closing the window,927 which may automatically re-appear. Once destroyed, it can never be recreated,928 and a complete new instance must be created (which the various other helper929 functions will then do after making this call930 """931 global edit932 if edit is not None and edit.currentView is not None:933 if edit.currentView.GetParentFrame() == win32ui.GetMainFrame():934 # It is docked - do nothing now (this is only called at shutdown!)935 pass936 else:937 # It is a standard window - call Close on the container.938 edit.Close()939 edit = None940 941 942def CloseInteractiveWindow():943 """Close the interactive window, allowing it to be re-created on demand."""944 global edit945 if edit is not None and edit.currentView is not None:946 if edit.currentView.GetParentFrame() == win32ui.GetMainFrame():947 # It is docked, just hide the dock bar.948 frame = win32ui.GetMainFrame()949 cb = frame.GetControlBar(ID_DOCKED_INTERACTIVE_CONTROLBAR)950 frame.ShowControlBar(cb, 0, 1)951 else:952 # It is a standard window - destroy the frame/view, allowing the object itself to remain.953 edit.currentView.GetParentFrame().DestroyWindow()954 955 956def ToggleInteractiveWindow():957 """If the interactive window is visible, hide it, otherwise show it."""958 if edit is None:959 CreateInteractiveWindow()960 else:961 if edit.NeedRecreateWindow():962 edit.RecreateWindow()963 else:964 # Close it, allowing a reopen.965 CloseInteractiveWindow()966 967 968def ShowInteractiveWindow():969 """Shows (or creates if necessary) an interactive window"""970 if edit is None:971 CreateInteractiveWindow()972 else:973 if edit.NeedRecreateWindow():974 edit.RecreateWindow()975 else:976 parent = edit.currentView.GetParentFrame()977 if parent == win32ui.GetMainFrame(): # It is docked.978 edit.currentView.SetFocus()979 else: # It is a "normal" window980 edit.currentView.GetParentFrame().AutoRestore()981 win32ui.GetMainFrame().MDIActivate(edit.currentView.GetParentFrame())982 983 984def IsInteractiveWindowVisible():985 return edit is not None and not edit.NeedRecreateWindow()986 