Spaces:
Sleeping
Sleeping
# intpyapp.py - Interactive Python application class | |
# | |
import os | |
import sys | |
import traceback | |
import __main__ | |
import commctrl | |
import win32api | |
import win32con | |
import win32ui | |
from pywin.mfc import afxres, dialog | |
from . import app, dbgcommands | |
lastLocateFileName = ".py" # used in the "File/Locate" dialog... | |
# todo - _SetupSharedMenu should be moved to a framework class. | |
def _SetupSharedMenu_(self): | |
sharedMenu = self.GetSharedMenu() | |
from pywin.framework import toolmenu | |
toolmenu.SetToolsMenu(sharedMenu) | |
from pywin.framework import help | |
help.SetHelpMenuOtherHelp(sharedMenu) | |
from pywin.mfc import docview | |
docview.DocTemplate._SetupSharedMenu_ = _SetupSharedMenu_ | |
class MainFrame(app.MainFrame): | |
def OnCreate(self, createStruct): | |
self.closing = 0 | |
if app.MainFrame.OnCreate(self, createStruct) == -1: | |
return -1 | |
style = ( | |
win32con.WS_CHILD | |
| afxres.CBRS_SIZE_DYNAMIC | |
| afxres.CBRS_TOP | |
| afxres.CBRS_TOOLTIPS | |
| afxres.CBRS_FLYBY | |
) | |
self.EnableDocking(afxres.CBRS_ALIGN_ANY) | |
tb = win32ui.CreateToolBar(self, style | win32con.WS_VISIBLE) | |
tb.ModifyStyle(0, commctrl.TBSTYLE_FLAT) | |
tb.LoadToolBar(win32ui.IDR_MAINFRAME) | |
tb.EnableDocking(afxres.CBRS_ALIGN_ANY) | |
tb.SetWindowText("Standard") | |
self.DockControlBar(tb) | |
# Any other packages which use toolbars | |
from pywin.debugger.debugger import PrepareControlBars | |
PrepareControlBars(self) | |
# Note "interact" also uses dockable windows, but they already happen | |
# And a "Tools" menu on the main frame. | |
menu = self.GetMenu() | |
from . import toolmenu | |
toolmenu.SetToolsMenu(menu, 2) | |
# And fix the "Help" menu on the main frame | |
from pywin.framework import help | |
help.SetHelpMenuOtherHelp(menu) | |
def OnClose(self): | |
try: | |
import pywin.debugger | |
if ( | |
pywin.debugger.currentDebugger is not None | |
and pywin.debugger.currentDebugger.pumping | |
): | |
try: | |
pywin.debugger.currentDebugger.close(1) | |
except: | |
traceback.print_exc() | |
return | |
except win32ui.error: | |
pass | |
self.closing = 1 | |
self.SaveBarState("ToolbarDefault") | |
self.SetActiveView(None) # Otherwise MFC's OnClose may _not_ prompt for save. | |
from pywin.framework import help | |
help.FinalizeHelp() | |
self.DestroyControlBar(afxres.AFX_IDW_TOOLBAR) | |
self.DestroyControlBar(win32ui.ID_VIEW_TOOLBAR_DBG) | |
return self._obj_.OnClose() | |
def DestroyControlBar(self, id): | |
try: | |
bar = self.GetControlBar(id) | |
except win32ui.error: | |
return | |
bar.DestroyWindow() | |
def OnCommand(self, wparam, lparam): | |
# By default, the current MDI child frame will process WM_COMMAND | |
# messages before any docked control bars - even if the control bar | |
# has focus. This is a problem for the interactive window when docked. | |
# Therefore, we detect the situation of a view having the main frame | |
# as its parent, and assume it must be a docked view (which it will in an MDI app) | |
try: | |
v = ( | |
self.GetActiveView() | |
) # Raise an exception if none - good - then we want default handling | |
# Main frame _does_ have a current view (ie, a docking view) - see if it wants it. | |
if v.OnCommand(wparam, lparam): | |
return 1 | |
except (win32ui.error, AttributeError): | |
pass | |
return self._obj_.OnCommand(wparam, lparam) | |
class InteractivePythonApp(app.CApp): | |
# This works if necessary - just we dont need to override the Run method. | |
# def Run(self): | |
# return self._obj_.Run() | |
def HookCommands(self): | |
app.CApp.HookCommands(self) | |
dbgcommands.DebuggerCommandHandler().HookCommands() | |
self.HookCommand(self.OnViewBrowse, win32ui.ID_VIEW_BROWSE) | |
self.HookCommand(self.OnFileImport, win32ui.ID_FILE_IMPORT) | |
self.HookCommand(self.OnFileCheck, win32ui.ID_FILE_CHECK) | |
self.HookCommandUpdate(self.OnUpdateFileCheck, win32ui.ID_FILE_CHECK) | |
self.HookCommand(self.OnFileRun, win32ui.ID_FILE_RUN) | |
self.HookCommand(self.OnFileLocate, win32ui.ID_FILE_LOCATE) | |
self.HookCommand(self.OnInteractiveWindow, win32ui.ID_VIEW_INTERACTIVE) | |
self.HookCommandUpdate( | |
self.OnUpdateInteractiveWindow, win32ui.ID_VIEW_INTERACTIVE | |
) | |
self.HookCommand(self.OnViewOptions, win32ui.ID_VIEW_OPTIONS) | |
self.HookCommand(self.OnHelpIndex, afxres.ID_HELP_INDEX) | |
self.HookCommand(self.OnFileSaveAll, win32ui.ID_FILE_SAVE_ALL) | |
self.HookCommand(self.OnViewToolbarDbg, win32ui.ID_VIEW_TOOLBAR_DBG) | |
self.HookCommandUpdate(self.OnUpdateViewToolbarDbg, win32ui.ID_VIEW_TOOLBAR_DBG) | |
def CreateMainFrame(self): | |
return MainFrame() | |
def MakeExistingDDEConnection(self): | |
# Use DDE to connect to an existing instance | |
# Return None if no existing instance | |
try: | |
from . import intpydde | |
except ImportError: | |
# No dde support! | |
return None | |
conv = intpydde.CreateConversation(self.ddeServer) | |
try: | |
conv.ConnectTo("Pythonwin", "System") | |
return conv | |
except intpydde.error: | |
return None | |
def InitDDE(self): | |
# Do all the magic DDE handling. | |
# Returns TRUE if we have pumped the arguments to our | |
# remote DDE app, and we should terminate. | |
try: | |
from . import intpydde | |
except ImportError: | |
self.ddeServer = None | |
intpydde = None | |
if intpydde is not None: | |
self.ddeServer = intpydde.DDEServer(self) | |
self.ddeServer.Create("Pythonwin", intpydde.CBF_FAIL_SELFCONNECTIONS) | |
try: | |
# If there is an existing instance, pump the arguments to it. | |
connection = self.MakeExistingDDEConnection() | |
if connection is not None: | |
connection.Exec("self.Activate()") | |
if self.ProcessArgs(sys.argv, connection) is None: | |
return 1 | |
except: | |
# It is too early to 'print' an exception - we | |
# don't have stdout setup yet! | |
win32ui.DisplayTraceback( | |
sys.exc_info(), " - error in DDE conversation with Pythonwin" | |
) | |
return 1 | |
def InitInstance(self): | |
# Allow "/nodde" and "/new" to optimize this! | |
if ( | |
"/nodde" not in sys.argv | |
and "/new" not in sys.argv | |
and "-nodde" not in sys.argv | |
and "-new" not in sys.argv | |
): | |
if self.InitDDE(): | |
return 1 # A remote DDE client is doing it for us! | |
else: | |
self.ddeServer = None | |
win32ui.SetRegistryKey( | |
"Python %s" % (sys.winver,) | |
) # MFC automatically puts the main frame caption on! | |
app.CApp.InitInstance(self) | |
# Create the taskbar icon | |
win32ui.CreateDebuggerThread() | |
# Allow Pythonwin to host OCX controls. | |
win32ui.EnableControlContainer() | |
# Display the interactive window if the user wants it. | |
from . import interact | |
interact.CreateInteractiveWindowUserPreference() | |
# Load the modules we use internally. | |
self.LoadSystemModules() | |
# Load additional module the user may want. | |
self.LoadUserModules() | |
# Load the ToolBar state near the end of the init process, as | |
# there may be Toolbar IDs created by the user or other modules. | |
# By now all these modules should be loaded, so all the toolbar IDs loaded. | |
try: | |
self.frame.LoadBarState("ToolbarDefault") | |
except win32ui.error: | |
# MFC sucks. It does essentially "GetDlgItem(x)->Something", so if the | |
# toolbar with ID x does not exist, MFC crashes! Pythonwin has a trap for this | |
# but I need to investigate more how to prevent it (AFAIK, ensuring all the | |
# toolbars are created by now _should_ stop it!) | |
pass | |
# Finally process the command line arguments. | |
try: | |
self.ProcessArgs(sys.argv) | |
except: | |
# too early for printing anything. | |
win32ui.DisplayTraceback( | |
sys.exc_info(), " - error processing command line args" | |
) | |
def ExitInstance(self): | |
win32ui.DestroyDebuggerThread() | |
try: | |
from . import interact | |
interact.DestroyInteractiveWindow() | |
except: | |
pass | |
if self.ddeServer is not None: | |
self.ddeServer.Shutdown() | |
self.ddeServer = None | |
return app.CApp.ExitInstance(self) | |
def Activate(self): | |
# Bring to the foreground. Mainly used when another app starts up, it asks | |
# this one to activate itself, then it terminates. | |
frame = win32ui.GetMainFrame() | |
frame.SetForegroundWindow() | |
if frame.GetWindowPlacement()[1] == win32con.SW_SHOWMINIMIZED: | |
frame.ShowWindow(win32con.SW_RESTORE) | |
def ProcessArgs(self, args, dde=None): | |
# If we are going to talk to a remote app via DDE, then | |
# activate it! | |
if ( | |
len(args) < 1 or not args[0] | |
): # argv[0]=='' when started without args, just like Python.exe! | |
return | |
i = 0 | |
while i < len(args): | |
argType = args[i] | |
i += 1 | |
if argType.startswith("-"): | |
# Support dash options. Slash options are misinterpreted by python init | |
# as path and not finding usually 'C:\\' ends up in sys.path[0] | |
argType = "/" + argType[1:] | |
if not argType.startswith("/"): | |
argType = win32ui.GetProfileVal( | |
"Python", "Default Arg Type", "/edit" | |
).lower() | |
i -= 1 # arg is /edit's parameter | |
par = i < len(args) and args[i] or "MISSING" | |
if argType in ("/nodde", "/new", "-nodde", "-new"): | |
# Already handled | |
pass | |
elif argType.startswith("/goto:"): | |
gotoline = int(argType[len("/goto:") :]) | |
if dde: | |
dde.Exec( | |
"from pywin.framework import scriptutils\n" | |
"ed = scriptutils.GetActiveEditControl()\n" | |
"if ed: ed.SetSel(ed.LineIndex(%s - 1))" % gotoline | |
) | |
else: | |
from . import scriptutils | |
ed = scriptutils.GetActiveEditControl() | |
if ed: | |
ed.SetSel(ed.LineIndex(gotoline - 1)) | |
elif argType == "/edit": | |
# Load up the default application. | |
i += 1 | |
fname = win32api.GetFullPathName(par) | |
if not os.path.isfile(fname): | |
# if we don't catch this, OpenDocumentFile() (actually | |
# PyCDocument.SetPathName() in | |
# pywin.scintilla.document.CScintillaDocument.OnOpenDocument) | |
# segfaults Pythonwin on recent PY3 builds (b228) | |
win32ui.MessageBox( | |
"No such file: %s\n\nCommand Line: %s" | |
% (fname, win32api.GetCommandLine()), | |
"Open file for edit", | |
win32con.MB_ICONERROR, | |
) | |
continue | |
if dde: | |
dde.Exec("win32ui.GetApp().OpenDocumentFile(%s)" % (repr(fname))) | |
else: | |
win32ui.GetApp().OpenDocumentFile(par) | |
elif argType == "/rundlg": | |
if dde: | |
dde.Exec( | |
"from pywin.framework import scriptutils;scriptutils.RunScript(%r, %r, 1)" | |
% (par, " ".join(args[i + 1 :])) | |
) | |
else: | |
from . import scriptutils | |
scriptutils.RunScript(par, " ".join(args[i + 1 :])) | |
return | |
elif argType == "/run": | |
if dde: | |
dde.Exec( | |
"from pywin.framework import scriptutils;scriptutils.RunScript(%r, %r, 0)" | |
% (par, " ".join(args[i + 1 :])) | |
) | |
else: | |
from . import scriptutils | |
scriptutils.RunScript(par, " ".join(args[i + 1 :]), 0) | |
return | |
elif argType == "/app": | |
raise RuntimeError( | |
"/app only supported for new instances of Pythonwin.exe" | |
) | |
elif argType == "/dde": # Send arbitary command | |
if dde is not None: | |
dde.Exec(par) | |
else: | |
win32ui.MessageBox( | |
"The /dde command can only be used\r\nwhen Pythonwin is already running" | |
) | |
i += 1 | |
else: | |
raise ValueError("Command line argument not recognised: %s" % argType) | |
def LoadSystemModules(self): | |
self.DoLoadModules("pywin.framework.editor,pywin.framework.stdin") | |
def LoadUserModules(self, moduleNames=None): | |
# Load the users modules. | |
if moduleNames is None: | |
default = "pywin.framework.sgrepmdi,pywin.framework.mdi_pychecker" | |
moduleNames = win32ui.GetProfileVal("Python", "Startup Modules", default) | |
self.DoLoadModules(moduleNames) | |
def DoLoadModules(self, moduleNames): # ", sep string of module names. | |
if not moduleNames: | |
return | |
modules = moduleNames.split(",") | |
for module in modules: | |
try: | |
__import__(module) | |
except: # Catch em all, else the app itself dies! 'ImportError: | |
traceback.print_exc() | |
msg = 'Startup import of user module "%s" failed' % module | |
print(msg) | |
win32ui.MessageBox(msg) | |
# | |
# DDE Callback | |
# | |
def OnDDECommand(self, command): | |
try: | |
exec(command + "\n") | |
except: | |
print("ERROR executing DDE command: ", command) | |
traceback.print_exc() | |
raise | |
# | |
# General handlers | |
# | |
def OnViewBrowse(self, id, code): | |
"Called when ViewBrowse message is received" | |
from pywin.tools import browser | |
obName = dialog.GetSimpleInput("Object", "__builtins__", "Browse Python Object") | |
if obName is None: | |
return | |
try: | |
browser.Browse(eval(obName, __main__.__dict__, __main__.__dict__)) | |
except NameError: | |
win32ui.MessageBox("This is no object with this name") | |
except AttributeError: | |
win32ui.MessageBox("The object has no attribute of that name") | |
except: | |
traceback.print_exc() | |
win32ui.MessageBox("This object can not be browsed") | |
def OnFileImport(self, id, code): | |
"Called when a FileImport message is received. Import the current or specified file" | |
from . import scriptutils | |
scriptutils.ImportFile() | |
def OnFileCheck(self, id, code): | |
"Called when a FileCheck message is received. Check the current file." | |
from . import scriptutils | |
scriptutils.CheckFile() | |
def OnUpdateFileCheck(self, cmdui): | |
from . import scriptutils | |
cmdui.Enable(scriptutils.GetActiveFileName(0) is not None) | |
def OnFileRun(self, id, code): | |
"Called when a FileRun message is received." | |
from . import scriptutils | |
showDlg = win32api.GetKeyState(win32con.VK_SHIFT) >= 0 | |
scriptutils.RunScript(None, None, showDlg) | |
def OnFileLocate(self, id, code): | |
from . import scriptutils | |
global lastLocateFileName # save the new version away for next time... | |
name = dialog.GetSimpleInput( | |
"File name", lastLocateFileName, "Locate Python File" | |
) | |
if name is None: # Cancelled. | |
return | |
lastLocateFileName = name | |
# if ".py" supplied, rip it off! | |
# should also check for .pys and .pyw | |
if lastLocateFileName[-3:].lower() == ".py": | |
lastLocateFileName = lastLocateFileName[:-3] | |
lastLocateFileName = lastLocateFileName.replace(".", "\\") | |
newName = scriptutils.LocatePythonFile(lastLocateFileName) | |
if newName is None: | |
win32ui.MessageBox("The file '%s' can not be located" % lastLocateFileName) | |
else: | |
win32ui.GetApp().OpenDocumentFile(newName) | |
# Display all the "options" proprety pages we can find | |
def OnViewOptions(self, id, code): | |
win32ui.InitRichEdit() | |
sheet = dialog.PropertySheet("Pythonwin Options") | |
# Add property pages we know about that need manual work. | |
from pywin.dialogs import ideoptions | |
sheet.AddPage(ideoptions.OptionsPropPage()) | |
from . import toolmenu | |
sheet.AddPage(toolmenu.ToolMenuPropPage()) | |
# Get other dynamic pages from templates. | |
pages = [] | |
for template in self.GetDocTemplateList(): | |
try: | |
# Dont actually call the function with the exception handler. | |
getter = template.GetPythonPropertyPages | |
except AttributeError: | |
# Template does not provide property pages! | |
continue | |
pages = pages + getter() | |
# Debugger template goes at the end | |
try: | |
from pywin.debugger import configui | |
except ImportError: | |
configui = None | |
if configui is not None: | |
pages.append(configui.DebuggerOptionsPropPage()) | |
# Now simply add the pages, and display the dialog. | |
for page in pages: | |
sheet.AddPage(page) | |
if sheet.DoModal() == win32con.IDOK: | |
win32ui.SetStatusText("Applying configuration changes...", 1) | |
win32ui.DoWaitCursor(1) | |
# Tell every Window in our app that win.ini has changed! | |
win32ui.GetMainFrame().SendMessageToDescendants( | |
win32con.WM_WININICHANGE, 0, 0 | |
) | |
win32ui.DoWaitCursor(0) | |
def OnInteractiveWindow(self, id, code): | |
# toggle the existing state. | |
from . import interact | |
interact.ToggleInteractiveWindow() | |
def OnUpdateInteractiveWindow(self, cmdui): | |
try: | |
interact = sys.modules["pywin.framework.interact"] | |
state = interact.IsInteractiveWindowVisible() | |
except KeyError: # Interactive module hasnt ever been imported. | |
state = 0 | |
cmdui.Enable() | |
cmdui.SetCheck(state) | |
def OnFileSaveAll(self, id, code): | |
# Only attempt to save editor documents. | |
from pywin.framework.editor import editorTemplate | |
num = 0 | |
for doc in editorTemplate.GetDocumentList(): | |
if doc.IsModified() and doc.GetPathName(): | |
num = num = 1 | |
doc.OnSaveDocument(doc.GetPathName()) | |
win32ui.SetStatusText("%d documents saved" % num, 1) | |
def OnViewToolbarDbg(self, id, code): | |
if code == 0: | |
return not win32ui.GetMainFrame().OnBarCheck(id) | |
def OnUpdateViewToolbarDbg(self, cmdui): | |
win32ui.GetMainFrame().OnUpdateControlBarMenu(cmdui) | |
cmdui.Enable(1) | |
def OnHelpIndex(self, id, code): | |
from . import help | |
help.SelectAndRunHelpFile() | |
# As per the comments in app.py, this use is depreciated. | |
# app.AppBuilder = InteractivePythonApp | |
# Now all we do is create the application | |
thisApp = InteractivePythonApp() | |