intpyapp.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. # intpyapp.py - Interactive Python application class
  2. #
  3. import win32con
  4. import win32api
  5. import win32ui
  6. import __main__
  7. import sys
  8. import os
  9. from . import app
  10. import traceback
  11. from pywin.mfc import afxres, dialog
  12. import commctrl
  13. from . import dbgcommands
  14. lastLocateFileName = ".py" # used in the "File/Locate" dialog...
  15. # todo - _SetupSharedMenu should be moved to a framework class.
  16. def _SetupSharedMenu_(self):
  17. sharedMenu = self.GetSharedMenu()
  18. from pywin.framework import toolmenu
  19. toolmenu.SetToolsMenu(sharedMenu)
  20. from pywin.framework import help
  21. help.SetHelpMenuOtherHelp(sharedMenu)
  22. from pywin.mfc import docview
  23. docview.DocTemplate._SetupSharedMenu_=_SetupSharedMenu_
  24. class MainFrame(app.MainFrame):
  25. def OnCreate(self, createStruct):
  26. self.closing = 0
  27. if app.MainFrame.OnCreate(self, createStruct)==-1:
  28. return -1
  29. style = win32con.WS_CHILD | afxres.CBRS_SIZE_DYNAMIC | afxres.CBRS_TOP | afxres.CBRS_TOOLTIPS | afxres.CBRS_FLYBY
  30. self.EnableDocking(afxres.CBRS_ALIGN_ANY)
  31. tb = win32ui.CreateToolBar (self, style | win32con.WS_VISIBLE)
  32. tb.ModifyStyle(0, commctrl.TBSTYLE_FLAT)
  33. tb.LoadToolBar(win32ui.IDR_MAINFRAME)
  34. tb.EnableDocking(afxres.CBRS_ALIGN_ANY)
  35. tb.SetWindowText("Standard")
  36. self.DockControlBar(tb)
  37. # Any other packages which use toolbars
  38. from pywin.debugger.debugger import PrepareControlBars
  39. PrepareControlBars(self)
  40. # Note "interact" also uses dockable windows, but they already happen
  41. # And a "Tools" menu on the main frame.
  42. menu = self.GetMenu()
  43. from . import toolmenu
  44. toolmenu.SetToolsMenu(menu, 2)
  45. # And fix the "Help" menu on the main frame
  46. from pywin.framework import help
  47. help.SetHelpMenuOtherHelp(menu)
  48. def OnClose(self):
  49. try:
  50. import pywin.debugger
  51. if pywin.debugger.currentDebugger is not None and pywin.debugger.currentDebugger.pumping:
  52. try:
  53. pywin.debugger.currentDebugger.close(1)
  54. except:
  55. traceback.print_exc()
  56. return
  57. except win32ui.error:
  58. pass
  59. self.closing = 1
  60. self.SaveBarState("ToolbarDefault")
  61. self.SetActiveView(None) # Otherwise MFC's OnClose may _not_ prompt for save.
  62. from pywin.framework import help
  63. help.FinalizeHelp()
  64. self.DestroyControlBar(afxres.AFX_IDW_TOOLBAR)
  65. self.DestroyControlBar(win32ui.ID_VIEW_TOOLBAR_DBG)
  66. return self._obj_.OnClose()
  67. def DestroyControlBar(self, id):
  68. try:
  69. bar = self.GetControlBar(id)
  70. except win32ui.error:
  71. return
  72. bar.DestroyWindow()
  73. def OnCommand(self, wparam, lparam):
  74. # By default, the current MDI child frame will process WM_COMMAND
  75. # messages before any docked control bars - even if the control bar
  76. # has focus. This is a problem for the interactive window when docked.
  77. # Therefore, we detect the situation of a view having the main frame
  78. # as its parent, and assume it must be a docked view (which it will in an MDI app)
  79. try:
  80. v = self.GetActiveView() # Raise an exception if none - good - then we want default handling
  81. # Main frame _does_ have a current view (ie, a docking view) - see if it wants it.
  82. if v.OnCommand(wparam, lparam):
  83. return 1
  84. except (win32ui.error, AttributeError):
  85. pass
  86. return self._obj_.OnCommand(wparam, lparam)
  87. class InteractivePythonApp(app.CApp):
  88. # This works if necessary - just we dont need to override the Run method.
  89. # def Run(self):
  90. # return self._obj_.Run()
  91. def HookCommands(self):
  92. app.CApp.HookCommands(self)
  93. dbgcommands.DebuggerCommandHandler().HookCommands()
  94. self.HookCommand(self.OnViewBrowse,win32ui.ID_VIEW_BROWSE)
  95. self.HookCommand(self.OnFileImport,win32ui.ID_FILE_IMPORT)
  96. self.HookCommand(self.OnFileCheck,win32ui.ID_FILE_CHECK)
  97. self.HookCommandUpdate(self.OnUpdateFileCheck, win32ui.ID_FILE_CHECK)
  98. self.HookCommand(self.OnFileRun,win32ui.ID_FILE_RUN)
  99. self.HookCommand(self.OnFileLocate,win32ui.ID_FILE_LOCATE)
  100. self.HookCommand(self.OnInteractiveWindow, win32ui.ID_VIEW_INTERACTIVE)
  101. self.HookCommandUpdate(self.OnUpdateInteractiveWindow, win32ui.ID_VIEW_INTERACTIVE)
  102. self.HookCommand(self.OnViewOptions, win32ui.ID_VIEW_OPTIONS)
  103. self.HookCommand(self.OnHelpIndex, afxres.ID_HELP_INDEX)
  104. self.HookCommand(self.OnFileSaveAll, win32ui.ID_FILE_SAVE_ALL)
  105. self.HookCommand(self.OnViewToolbarDbg, win32ui.ID_VIEW_TOOLBAR_DBG)
  106. self.HookCommandUpdate(self.OnUpdateViewToolbarDbg, win32ui.ID_VIEW_TOOLBAR_DBG)
  107. def CreateMainFrame(self):
  108. return MainFrame()
  109. def MakeExistingDDEConnection(self):
  110. # Use DDE to connect to an existing instance
  111. # Return None if no existing instance
  112. try:
  113. from . import intpydde
  114. except ImportError:
  115. # No dde support!
  116. return None
  117. conv = intpydde.CreateConversation(self.ddeServer)
  118. try:
  119. conv.ConnectTo("Pythonwin", "System")
  120. return conv
  121. except intpydde.error:
  122. return None
  123. def InitDDE(self):
  124. # Do all the magic DDE handling.
  125. # Returns TRUE if we have pumped the arguments to our
  126. # remote DDE app, and we should terminate.
  127. try:
  128. from . import intpydde
  129. except ImportError:
  130. self.ddeServer = None
  131. intpydde = None
  132. if intpydde is not None:
  133. self.ddeServer = intpydde.DDEServer(self)
  134. self.ddeServer.Create("Pythonwin", intpydde.CBF_FAIL_SELFCONNECTIONS )
  135. try:
  136. # If there is an existing instance, pump the arguments to it.
  137. connection = self.MakeExistingDDEConnection()
  138. if connection is not None:
  139. connection.Exec("self.Activate()")
  140. if self.ProcessArgs(sys.argv, connection) is None:
  141. return 1
  142. except:
  143. # It is too early to 'print' an exception - we
  144. # don't have stdout setup yet!
  145. win32ui.DisplayTraceback(sys.exc_info(), " - error in DDE conversation with Pythonwin")
  146. return 1
  147. def InitInstance(self):
  148. # Allow "/nodde" and "/new" to optimize this!
  149. if ("/nodde" not in sys.argv and "/new" not in sys.argv
  150. and "-nodde" not in sys.argv and "-new" not in sys.argv):
  151. if self.InitDDE():
  152. return 1 # A remote DDE client is doing it for us!
  153. else:
  154. self.ddeServer = None
  155. win32ui.SetRegistryKey("Python %s" % (sys.winver,)) # MFC automatically puts the main frame caption on!
  156. app.CApp.InitInstance(self)
  157. # Create the taskbar icon
  158. win32ui.CreateDebuggerThread()
  159. # Allow Pythonwin to host OCX controls.
  160. win32ui.EnableControlContainer()
  161. # Display the interactive window if the user wants it.
  162. from . import interact
  163. interact.CreateInteractiveWindowUserPreference()
  164. # Load the modules we use internally.
  165. self.LoadSystemModules()
  166. # Load additional module the user may want.
  167. self.LoadUserModules()
  168. # Load the ToolBar state near the end of the init process, as
  169. # there may be Toolbar IDs created by the user or other modules.
  170. # By now all these modules should be loaded, so all the toolbar IDs loaded.
  171. try:
  172. self.frame.LoadBarState("ToolbarDefault")
  173. except win32ui.error:
  174. # MFC sucks. It does essentially "GetDlgItem(x)->Something", so if the
  175. # toolbar with ID x does not exist, MFC crashes! Pythonwin has a trap for this
  176. # but I need to investigate more how to prevent it (AFAIK, ensuring all the
  177. # toolbars are created by now _should_ stop it!)
  178. pass
  179. # Finally process the command line arguments.
  180. try:
  181. self.ProcessArgs(sys.argv)
  182. except:
  183. # too early for printing anything.
  184. win32ui.DisplayTraceback(sys.exc_info(), " - error processing command line args")
  185. def ExitInstance(self):
  186. win32ui.DestroyDebuggerThread()
  187. try:
  188. from . import interact
  189. interact.DestroyInteractiveWindow()
  190. except:
  191. pass
  192. if self.ddeServer is not None:
  193. self.ddeServer.Shutdown()
  194. self.ddeServer = None
  195. return app.CApp.ExitInstance(self)
  196. def Activate(self):
  197. # Bring to the foreground. Mainly used when another app starts up, it asks
  198. # this one to activate itself, then it terminates.
  199. frame = win32ui.GetMainFrame()
  200. frame.SetForegroundWindow()
  201. if frame.GetWindowPlacement()[1]==win32con.SW_SHOWMINIMIZED:
  202. frame.ShowWindow(win32con.SW_RESTORE)
  203. def ProcessArgs(self, args, dde = None):
  204. # If we are going to talk to a remote app via DDE, then
  205. # activate it!
  206. if len(args)<1 or not args[0]: # argv[0]=='' when started without args, just like Python.exe!
  207. return
  208. i = 0
  209. while i < len(args):
  210. argType = args[i]
  211. i += 1
  212. if argType.startswith('-'):
  213. # Support dash options. Slash options are misinterpreted by python init
  214. # as path and not finding usually 'C:\\' ends up in sys.path[0]
  215. argType = '/' + argType[1:]
  216. if not argType.startswith('/'):
  217. argType = win32ui.GetProfileVal("Python","Default Arg Type","/edit").lower()
  218. i -= 1 # arg is /edit's parameter
  219. par = i < len(args) and args[i] or 'MISSING'
  220. if argType in ['/nodde', '/new', '-nodde', '-new']:
  221. # Already handled
  222. pass
  223. elif argType.startswith('/goto:'):
  224. gotoline = int(argType[len('/goto:'):])
  225. if dde:
  226. dde.Exec("from pywin.framework import scriptutils\n"
  227. "ed = scriptutils.GetActiveEditControl()\n"
  228. "if ed: ed.SetSel(ed.LineIndex(%s - 1))" % gotoline)
  229. else:
  230. from . import scriptutils
  231. ed = scriptutils.GetActiveEditControl()
  232. if ed: ed.SetSel(ed.LineIndex(gotoline - 1))
  233. elif argType == "/edit":
  234. # Load up the default application.
  235. i += 1
  236. fname = win32api.GetFullPathName(par)
  237. if not os.path.isfile(fname):
  238. # if we don't catch this, OpenDocumentFile() (actually
  239. # PyCDocument.SetPathName() in
  240. # pywin.scintilla.document.CScintillaDocument.OnOpenDocument)
  241. # segfaults Pythonwin on recent PY3 builds (b228)
  242. win32ui.MessageBox(
  243. "No such file: %s\n\nCommand Line: %s" % (
  244. fname, win32api.GetCommandLine()),
  245. "Open file for edit", win32con.MB_ICONERROR)
  246. continue
  247. if dde:
  248. dde.Exec("win32ui.GetApp().OpenDocumentFile(%s)" % (repr(fname)))
  249. else:
  250. win32ui.GetApp().OpenDocumentFile(par)
  251. elif argType=="/rundlg":
  252. if dde:
  253. dde.Exec("from pywin.framework import scriptutils;scriptutils.RunScript(%r, %r, 1)" % (par, ' '.join(args[i + 1:])))
  254. else:
  255. from . import scriptutils
  256. scriptutils.RunScript(par, ' '.join(args[i + 1:]))
  257. return
  258. elif argType=="/run":
  259. if dde:
  260. dde.Exec("from pywin.framework import scriptutils;scriptutils.RunScript(%r, %r, 0)" % (par, ' '.join(args[i + 1:])))
  261. else:
  262. from . import scriptutils
  263. scriptutils.RunScript(par, ' '.join(args[i + 1:]), 0)
  264. return
  265. elif argType=="/app":
  266. raise RuntimeError("/app only supported for new instances of Pythonwin.exe")
  267. elif argType=='/dde': # Send arbitary command
  268. if dde is not None:
  269. dde.Exec(par)
  270. else:
  271. win32ui.MessageBox("The /dde command can only be used\r\nwhen Pythonwin is already running")
  272. i += 1
  273. else:
  274. raise ValueError("Command line argument not recognised: %s" % argType)
  275. def LoadSystemModules(self):
  276. self.DoLoadModules("pywin.framework.editor,pywin.framework.stdin")
  277. def LoadUserModules(self, moduleNames = None):
  278. # Load the users modules.
  279. if moduleNames is None:
  280. default = "pywin.framework.sgrepmdi,pywin.framework.mdi_pychecker"
  281. moduleNames=win32ui.GetProfileVal('Python','Startup Modules',default)
  282. self.DoLoadModules(moduleNames)
  283. def DoLoadModules(self, moduleNames): # ", sep string of module names.
  284. if not moduleNames: return
  285. modules = moduleNames.split(",")
  286. for module in modules:
  287. try:
  288. __import__(module)
  289. except: # Catch em all, else the app itself dies! 'ImportError:
  290. traceback.print_exc()
  291. msg = 'Startup import of user module "%s" failed' % module
  292. print(msg)
  293. win32ui.MessageBox(msg)
  294. #
  295. # DDE Callback
  296. #
  297. def OnDDECommand(self, command):
  298. try:
  299. exec(command + "\n")
  300. except:
  301. print("ERROR executing DDE command: ", command)
  302. traceback.print_exc()
  303. raise
  304. #
  305. # General handlers
  306. #
  307. def OnViewBrowse( self, id, code ):
  308. " Called when ViewBrowse message is received "
  309. from pywin.tools import browser
  310. obName = dialog.GetSimpleInput('Object', '__builtins__', 'Browse Python Object')
  311. if obName is None:
  312. return
  313. try:
  314. browser.Browse(eval(obName, __main__.__dict__, __main__.__dict__))
  315. except NameError:
  316. win32ui.MessageBox('This is no object with this name')
  317. except AttributeError:
  318. win32ui.MessageBox('The object has no attribute of that name')
  319. except:
  320. traceback.print_exc()
  321. win32ui.MessageBox('This object can not be browsed')
  322. def OnFileImport( self, id, code ):
  323. " Called when a FileImport message is received. Import the current or specified file"
  324. from . import scriptutils
  325. scriptutils.ImportFile()
  326. def OnFileCheck( self, id, code ):
  327. " Called when a FileCheck message is received. Check the current file."
  328. from . import scriptutils
  329. scriptutils.CheckFile()
  330. def OnUpdateFileCheck(self, cmdui):
  331. from . import scriptutils
  332. cmdui.Enable( scriptutils.GetActiveFileName(0) is not None )
  333. def OnFileRun( self, id, code ):
  334. " Called when a FileRun message is received. "
  335. from . import scriptutils
  336. showDlg = win32api.GetKeyState(win32con.VK_SHIFT) >= 0
  337. scriptutils.RunScript(None, None, showDlg)
  338. def OnFileLocate( self, id, code ):
  339. from . import scriptutils
  340. global lastLocateFileName # save the new version away for next time...
  341. name = dialog.GetSimpleInput('File name', lastLocateFileName, 'Locate Python File')
  342. if name is None: # Cancelled.
  343. return
  344. lastLocateFileName = name
  345. # if ".py" supplied, rip it off!
  346. # should also check for .pys and .pyw
  347. if lastLocateFileName[-3:].lower()=='.py':
  348. lastLocateFileName = lastLocateFileName[:-3]
  349. lastLocateFileName = lastLocateFileName.replace(".","\\")
  350. newName = scriptutils.LocatePythonFile(lastLocateFileName)
  351. if newName is None:
  352. win32ui.MessageBox("The file '%s' can not be located" % lastLocateFileName)
  353. else:
  354. win32ui.GetApp().OpenDocumentFile(newName)
  355. # Display all the "options" proprety pages we can find
  356. def OnViewOptions(self, id, code):
  357. win32ui.InitRichEdit()
  358. sheet = dialog.PropertySheet("Pythonwin Options")
  359. # Add property pages we know about that need manual work.
  360. from pywin.dialogs import ideoptions
  361. sheet.AddPage( ideoptions.OptionsPropPage() )
  362. from . import toolmenu
  363. sheet.AddPage( toolmenu.ToolMenuPropPage() )
  364. # Get other dynamic pages from templates.
  365. pages = []
  366. for template in self.GetDocTemplateList():
  367. try:
  368. # Dont actually call the function with the exception handler.
  369. getter = template.GetPythonPropertyPages
  370. except AttributeError:
  371. # Template does not provide property pages!
  372. continue
  373. pages = pages + getter()
  374. # Debugger template goes at the end
  375. try:
  376. from pywin.debugger import configui
  377. except ImportError:
  378. configui = None
  379. if configui is not None: pages.append(configui.DebuggerOptionsPropPage())
  380. # Now simply add the pages, and display the dialog.
  381. for page in pages:
  382. sheet.AddPage(page)
  383. if sheet.DoModal()==win32con.IDOK:
  384. win32ui.SetStatusText("Applying configuration changes...", 1)
  385. win32ui.DoWaitCursor(1)
  386. # Tell every Window in our app that win.ini has changed!
  387. win32ui.GetMainFrame().SendMessageToDescendants(win32con.WM_WININICHANGE, 0, 0)
  388. win32ui.DoWaitCursor(0)
  389. def OnInteractiveWindow(self, id, code):
  390. # toggle the existing state.
  391. from . import interact
  392. interact.ToggleInteractiveWindow()
  393. def OnUpdateInteractiveWindow(self, cmdui):
  394. try:
  395. interact=sys.modules['pywin.framework.interact']
  396. state = interact.IsInteractiveWindowVisible()
  397. except KeyError: # Interactive module hasnt ever been imported.
  398. state = 0
  399. cmdui.Enable()
  400. cmdui.SetCheck(state)
  401. def OnFileSaveAll(self, id, code):
  402. # Only attempt to save editor documents.
  403. from pywin.framework.editor import editorTemplate
  404. num = 0
  405. for doc in editorTemplate.GetDocumentList():
  406. if doc.IsModified() and doc.GetPathName():
  407. num = num = 1
  408. doc.OnSaveDocument(doc.GetPathName())
  409. win32ui.SetStatusText("%d documents saved" % num, 1)
  410. def OnViewToolbarDbg(self, id, code):
  411. if code==0:
  412. return not win32ui.GetMainFrame().OnBarCheck(id)
  413. def OnUpdateViewToolbarDbg(self, cmdui):
  414. win32ui.GetMainFrame().OnUpdateControlBarMenu(cmdui)
  415. cmdui.Enable(1)
  416. def OnHelpIndex( self, id, code ):
  417. from . import help
  418. help.SelectAndRunHelpFile()
  419. # As per the comments in app.py, this use is depreciated.
  420. # app.AppBuilder = InteractivePythonApp
  421. # Now all we do is create the application
  422. thisApp = InteractivePythonApp()