458 lines
16 KiB
Python
458 lines
16 KiB
Python
# App.py
|
|
# Application stuff.
|
|
# The application is responsible for managing the main frame window.
|
|
#
|
|
# We also grab the FileOpen command, to invoke our Python editor
|
|
" The PythonWin application code. Manages most aspects of MDI, etc "
|
|
import os
|
|
import sys
|
|
import traceback
|
|
|
|
import regutil
|
|
import win32api
|
|
import win32con
|
|
import win32ui
|
|
from pywin.mfc import afxres, dialog, window
|
|
from pywin.mfc.thread import WinApp
|
|
|
|
from . import scriptutils
|
|
|
|
## NOTE: App and AppBuild should NOT be used - instead, you should contruct your
|
|
## APP class manually whenever you like (just ensure you leave these 2 params None!)
|
|
## Whoever wants the generic "Application" should get it via win32iu.GetApp()
|
|
|
|
# These are "legacy"
|
|
AppBuilder = None
|
|
App = None # default - if used, must end up a CApp derived class.
|
|
|
|
|
|
# Helpers that should one day be removed!
|
|
def AddIdleHandler(handler):
|
|
print(
|
|
"app.AddIdleHandler is deprecated - please use win32ui.GetApp().AddIdleHandler() instead."
|
|
)
|
|
return win32ui.GetApp().AddIdleHandler(handler)
|
|
|
|
|
|
def DeleteIdleHandler(handler):
|
|
print(
|
|
"app.DeleteIdleHandler is deprecated - please use win32ui.GetApp().DeleteIdleHandler() instead."
|
|
)
|
|
return win32ui.GetApp().DeleteIdleHandler(handler)
|
|
|
|
|
|
# Helper for writing a Window position by name, and later loading it.
|
|
def SaveWindowSize(section, rect, state=""):
|
|
"""Writes a rectangle to an INI file
|
|
Args: section = section name in the applications INI file
|
|
rect = a rectangle in a (cy, cx, y, x) tuple
|
|
(same format as CREATESTRUCT position tuples)."""
|
|
left, top, right, bottom = rect
|
|
if state:
|
|
state = state + " "
|
|
win32ui.WriteProfileVal(section, state + "left", left)
|
|
win32ui.WriteProfileVal(section, state + "top", top)
|
|
win32ui.WriteProfileVal(section, state + "right", right)
|
|
win32ui.WriteProfileVal(section, state + "bottom", bottom)
|
|
|
|
|
|
def LoadWindowSize(section, state=""):
|
|
"""Loads a section from an INI file, and returns a rect in a tuple (see SaveWindowSize)"""
|
|
if state:
|
|
state = state + " "
|
|
left = win32ui.GetProfileVal(section, state + "left", 0)
|
|
top = win32ui.GetProfileVal(section, state + "top", 0)
|
|
right = win32ui.GetProfileVal(section, state + "right", 0)
|
|
bottom = win32ui.GetProfileVal(section, state + "bottom", 0)
|
|
return (left, top, right, bottom)
|
|
|
|
|
|
def RectToCreateStructRect(rect):
|
|
return (rect[3] - rect[1], rect[2] - rect[0], rect[1], rect[0])
|
|
|
|
|
|
# Define FrameWindow and Application objects
|
|
#
|
|
# The Main Frame of the application.
|
|
class MainFrame(window.MDIFrameWnd):
|
|
sectionPos = "Main Window"
|
|
statusBarIndicators = (
|
|
afxres.ID_SEPARATOR, # // status line indicator
|
|
afxres.ID_INDICATOR_CAPS,
|
|
afxres.ID_INDICATOR_NUM,
|
|
afxres.ID_INDICATOR_SCRL,
|
|
win32ui.ID_INDICATOR_LINENUM,
|
|
win32ui.ID_INDICATOR_COLNUM,
|
|
)
|
|
|
|
def OnCreate(self, cs):
|
|
self._CreateStatusBar()
|
|
return 0
|
|
|
|
def _CreateStatusBar(self):
|
|
self.statusBar = win32ui.CreateStatusBar(self)
|
|
self.statusBar.SetIndicators(self.statusBarIndicators)
|
|
self.HookCommandUpdate(self.OnUpdatePosIndicator, win32ui.ID_INDICATOR_LINENUM)
|
|
self.HookCommandUpdate(self.OnUpdatePosIndicator, win32ui.ID_INDICATOR_COLNUM)
|
|
|
|
def OnUpdatePosIndicator(self, cmdui):
|
|
editControl = scriptutils.GetActiveEditControl()
|
|
value = " " * 5
|
|
if editControl is not None:
|
|
try:
|
|
startChar, endChar = editControl.GetSel()
|
|
lineNo = editControl.LineFromChar(startChar)
|
|
colNo = endChar - editControl.LineIndex(lineNo)
|
|
|
|
if cmdui.m_nID == win32ui.ID_INDICATOR_LINENUM:
|
|
value = "%0*d" % (5, lineNo + 1)
|
|
else:
|
|
value = "%0*d" % (3, colNo + 1)
|
|
except win32ui.error:
|
|
pass
|
|
cmdui.SetText(value)
|
|
cmdui.Enable()
|
|
|
|
def PreCreateWindow(self, cc):
|
|
cc = self._obj_.PreCreateWindow(cc)
|
|
pos = LoadWindowSize(self.sectionPos)
|
|
self.startRect = pos
|
|
if pos[2] - pos[0]:
|
|
rect = RectToCreateStructRect(pos)
|
|
cc = cc[0], cc[1], cc[2], cc[3], rect, cc[5], cc[6], cc[7], cc[8]
|
|
return cc
|
|
|
|
def OnDestroy(self, msg):
|
|
# use GetWindowPlacement(), as it works even when min'd or max'd
|
|
rectNow = self.GetWindowPlacement()[4]
|
|
if rectNow != self.startRect:
|
|
SaveWindowSize(self.sectionPos, rectNow)
|
|
return 0
|
|
|
|
|
|
class CApp(WinApp):
|
|
"A class for the application"
|
|
|
|
def __init__(self):
|
|
self.oldCallbackCaller = None
|
|
WinApp.__init__(self, win32ui.GetApp())
|
|
self.idleHandlers = []
|
|
|
|
def InitInstance(self):
|
|
"Called to crank up the app"
|
|
HookInput()
|
|
numMRU = win32ui.GetProfileVal("Settings", "Recent File List Size", 10)
|
|
win32ui.LoadStdProfileSettings(numMRU)
|
|
# self._obj_.InitMDIInstance()
|
|
if win32api.GetVersionEx()[0] < 4:
|
|
win32ui.SetDialogBkColor()
|
|
win32ui.Enable3dControls()
|
|
|
|
# install a "callback caller" - a manager for the callbacks
|
|
# self.oldCallbackCaller = win32ui.InstallCallbackCaller(self.CallbackManager)
|
|
self.LoadMainFrame()
|
|
self.SetApplicationPaths()
|
|
|
|
def ExitInstance(self):
|
|
"Called as the app dies - too late to prevent it here!"
|
|
win32ui.OutputDebug("Application shutdown\n")
|
|
# Restore the callback manager, if any.
|
|
try:
|
|
win32ui.InstallCallbackCaller(self.oldCallbackCaller)
|
|
except AttributeError:
|
|
pass
|
|
if self.oldCallbackCaller:
|
|
del self.oldCallbackCaller
|
|
self.frame = None # clean Python references to the now destroyed window object.
|
|
self.idleHandlers = []
|
|
# Attempt cleanup if not already done!
|
|
if self._obj_:
|
|
self._obj_.AttachObject(None)
|
|
self._obj_ = None
|
|
global App
|
|
global AppBuilder
|
|
App = None
|
|
AppBuilder = None
|
|
return 0
|
|
|
|
def HaveIdleHandler(self, handler):
|
|
return handler in self.idleHandlers
|
|
|
|
def AddIdleHandler(self, handler):
|
|
self.idleHandlers.append(handler)
|
|
|
|
def DeleteIdleHandler(self, handler):
|
|
self.idleHandlers.remove(handler)
|
|
|
|
def OnIdle(self, count):
|
|
try:
|
|
ret = 0
|
|
handlers = self.idleHandlers[:] # copy list, as may be modified during loop
|
|
for handler in handlers:
|
|
try:
|
|
thisRet = handler(handler, count)
|
|
except:
|
|
print("Idle handler %s failed" % (repr(handler)))
|
|
traceback.print_exc()
|
|
print("Idle handler removed from list")
|
|
try:
|
|
self.DeleteIdleHandler(handler)
|
|
except ValueError: # Item not in list.
|
|
pass
|
|
thisRet = 0
|
|
ret = ret or thisRet
|
|
return ret
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
def CreateMainFrame(self):
|
|
return MainFrame()
|
|
|
|
def LoadMainFrame(self):
|
|
"Create the main applications frame"
|
|
self.frame = self.CreateMainFrame()
|
|
self.SetMainFrame(self.frame)
|
|
self.frame.LoadFrame(win32ui.IDR_MAINFRAME, win32con.WS_OVERLAPPEDWINDOW)
|
|
self.frame.DragAcceptFiles() # we can accept these.
|
|
self.frame.ShowWindow(win32ui.GetInitialStateRequest())
|
|
self.frame.UpdateWindow()
|
|
self.HookCommands()
|
|
|
|
def OnHelp(self, id, code):
|
|
try:
|
|
if id == win32ui.ID_HELP_GUI_REF:
|
|
helpFile = regutil.GetRegisteredHelpFile("Pythonwin Reference")
|
|
helpCmd = win32con.HELP_CONTENTS
|
|
else:
|
|
helpFile = regutil.GetRegisteredHelpFile("Main Python Documentation")
|
|
helpCmd = win32con.HELP_FINDER
|
|
if helpFile is None:
|
|
win32ui.MessageBox("The help file is not registered!")
|
|
else:
|
|
from . import help
|
|
|
|
help.OpenHelpFile(helpFile, helpCmd)
|
|
except:
|
|
t, v, tb = sys.exc_info()
|
|
win32ui.MessageBox(
|
|
"Internal error in help file processing\r\n%s: %s" % (t, v)
|
|
)
|
|
tb = None # Prevent a cycle
|
|
|
|
def DoLoadModules(self, modules):
|
|
# XXX - this should go, but the debugger uses it :-(
|
|
# dont do much checking!
|
|
for module in modules:
|
|
__import__(module)
|
|
|
|
def HookCommands(self):
|
|
self.frame.HookMessage(self.OnDropFiles, win32con.WM_DROPFILES)
|
|
self.HookCommand(self.HandleOnFileOpen, win32ui.ID_FILE_OPEN)
|
|
self.HookCommand(self.HandleOnFileNew, win32ui.ID_FILE_NEW)
|
|
self.HookCommand(self.OnFileMRU, win32ui.ID_FILE_MRU_FILE1)
|
|
self.HookCommand(self.OnHelpAbout, win32ui.ID_APP_ABOUT)
|
|
self.HookCommand(self.OnHelp, win32ui.ID_HELP_PYTHON)
|
|
self.HookCommand(self.OnHelp, win32ui.ID_HELP_GUI_REF)
|
|
# Hook for the right-click menu.
|
|
self.frame.GetWindow(win32con.GW_CHILD).HookMessage(
|
|
self.OnRClick, win32con.WM_RBUTTONDOWN
|
|
)
|
|
|
|
def SetApplicationPaths(self):
|
|
# Load the users/application paths
|
|
new_path = []
|
|
apppath = win32ui.GetProfileVal("Python", "Application Path", "").split(";")
|
|
for path in apppath:
|
|
if len(path) > 0:
|
|
new_path.append(win32ui.FullPath(path))
|
|
for extra_num in range(1, 11):
|
|
apppath = win32ui.GetProfileVal(
|
|
"Python", "Application Path %d" % extra_num, ""
|
|
).split(";")
|
|
if len(apppath) == 0:
|
|
break
|
|
for path in apppath:
|
|
if len(path) > 0:
|
|
new_path.append(win32ui.FullPath(path))
|
|
sys.path = new_path + sys.path
|
|
|
|
def OnRClick(self, params):
|
|
"Handle right click message"
|
|
# put up the entire FILE menu!
|
|
menu = win32ui.LoadMenu(win32ui.IDR_TEXTTYPE).GetSubMenu(0)
|
|
menu.TrackPopupMenu(params[5]) # track at mouse position.
|
|
return 0
|
|
|
|
def OnDropFiles(self, msg):
|
|
"Handle a file being dropped from file manager"
|
|
hDropInfo = msg[2]
|
|
self.frame.SetActiveWindow() # active us
|
|
nFiles = win32api.DragQueryFile(hDropInfo)
|
|
try:
|
|
for iFile in range(0, nFiles):
|
|
fileName = win32api.DragQueryFile(hDropInfo, iFile)
|
|
win32ui.GetApp().OpenDocumentFile(fileName)
|
|
finally:
|
|
win32api.DragFinish(hDropInfo)
|
|
|
|
return 0
|
|
|
|
# No longer used by Pythonwin, as the C++ code has this same basic functionality
|
|
# but handles errors slightly better.
|
|
# It all still works, tho, so if you need similar functionality, you can use it.
|
|
# Therefore I havent deleted this code completely!
|
|
# def CallbackManager( self, ob, args = () ):
|
|
# """Manage win32 callbacks. Trap exceptions, report on them, then return 'All OK'
|
|
# to the frame-work. """
|
|
# import traceback
|
|
# try:
|
|
# ret = apply(ob, args)
|
|
# return ret
|
|
# except:
|
|
# # take copies of the exception values, else other (handled) exceptions may get
|
|
# # copied over by the other fns called.
|
|
# win32ui.SetStatusText('An exception occured in a windows command handler.')
|
|
# t, v, tb = sys.exc_info()
|
|
# traceback.print_exception(t, v, tb.tb_next)
|
|
# try:
|
|
# sys.stdout.flush()
|
|
# except (NameError, AttributeError):
|
|
# pass
|
|
|
|
# Command handlers.
|
|
def OnFileMRU(self, id, code):
|
|
"Called when a File 1-n message is recieved"
|
|
fileName = win32ui.GetRecentFileList()[id - win32ui.ID_FILE_MRU_FILE1]
|
|
win32ui.GetApp().OpenDocumentFile(fileName)
|
|
|
|
def HandleOnFileOpen(self, id, code):
|
|
"Called when FileOpen message is received"
|
|
win32ui.GetApp().OnFileOpen()
|
|
|
|
def HandleOnFileNew(self, id, code):
|
|
"Called when FileNew message is received"
|
|
win32ui.GetApp().OnFileNew()
|
|
|
|
def OnHelpAbout(self, id, code):
|
|
"Called when HelpAbout message is received. Displays the About dialog."
|
|
win32ui.InitRichEdit()
|
|
dlg = AboutBox()
|
|
dlg.DoModal()
|
|
|
|
|
|
def _GetRegistryValue(key, val, default=None):
|
|
# val is registry value - None for default val.
|
|
try:
|
|
hkey = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, key)
|
|
return win32api.RegQueryValueEx(hkey, val)[0]
|
|
except win32api.error:
|
|
try:
|
|
hkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, key)
|
|
return win32api.RegQueryValueEx(hkey, val)[0]
|
|
except win32api.error:
|
|
return default
|
|
|
|
|
|
scintilla = "Scintilla is Copyright 1998-2008 Neil Hodgson (http://www.scintilla.org)"
|
|
idle = "This program uses IDLE extensions by Guido van Rossum, Tim Peters and others."
|
|
contributors = "Thanks to the following people for making significant contributions: Roger Upole, Sidnei da Silva, Sam Rushing, Curt Hagenlocher, Dave Brennan, Roger Burnham, Gordon McMillan, Neil Hodgson, Laramie Leavitt. (let me know if I have forgotten you!)"
|
|
|
|
|
|
# The About Box
|
|
class AboutBox(dialog.Dialog):
|
|
def __init__(self, idd=win32ui.IDD_ABOUTBOX):
|
|
dialog.Dialog.__init__(self, idd)
|
|
|
|
def OnInitDialog(self):
|
|
text = (
|
|
"Pythonwin - Python IDE and GUI Framework for Windows.\n\n%s\n\nPython is %s\n\n%s\n\n%s\n\n%s"
|
|
% (win32ui.copyright, sys.copyright, scintilla, idle, contributors)
|
|
)
|
|
self.SetDlgItemText(win32ui.IDC_EDIT1, text)
|
|
# Get the build number - written by installers.
|
|
# For distutils build, read pywin32.version.txt
|
|
import sysconfig
|
|
|
|
site_packages = sysconfig.get_paths()["platlib"]
|
|
try:
|
|
build_no = (
|
|
open(os.path.join(site_packages, "pywin32.version.txt")).read().strip()
|
|
)
|
|
ver = "pywin32 build %s" % build_no
|
|
except EnvironmentError:
|
|
ver = None
|
|
if ver is None:
|
|
# See if we are Part of Active Python
|
|
ver = _GetRegistryValue(
|
|
"SOFTWARE\\ActiveState\\ActivePython", "CurrentVersion"
|
|
)
|
|
if ver is not None:
|
|
ver = "ActivePython build %s" % (ver,)
|
|
if ver is None:
|
|
ver = ""
|
|
self.SetDlgItemText(win32ui.IDC_ABOUT_VERSION, ver)
|
|
self.HookCommand(self.OnButHomePage, win32ui.IDC_BUTTON1)
|
|
|
|
def OnButHomePage(self, id, code):
|
|
if code == win32con.BN_CLICKED:
|
|
win32api.ShellExecute(
|
|
0, "open", "https://github.com/mhammond/pywin32", None, "", 1
|
|
)
|
|
|
|
|
|
def Win32RawInput(prompt=None):
|
|
"Provide raw_input() for gui apps"
|
|
# flush stderr/out first.
|
|
try:
|
|
sys.stdout.flush()
|
|
sys.stderr.flush()
|
|
except:
|
|
pass
|
|
if prompt is None:
|
|
prompt = ""
|
|
ret = dialog.GetSimpleInput(prompt)
|
|
if ret == None:
|
|
raise KeyboardInterrupt("operation cancelled")
|
|
return ret
|
|
|
|
|
|
def Win32Input(prompt=None):
|
|
"Provide input() for gui apps"
|
|
return eval(input(prompt))
|
|
|
|
|
|
def HookInput():
|
|
try:
|
|
raw_input
|
|
# must be py2x...
|
|
sys.modules["__builtin__"].raw_input = Win32RawInput
|
|
sys.modules["__builtin__"].input = Win32Input
|
|
except NameError:
|
|
# must be py3k
|
|
import code
|
|
|
|
sys.modules["builtins"].input = Win32RawInput
|
|
|
|
|
|
def HaveGoodGUI():
|
|
"""Returns true if we currently have a good gui available."""
|
|
return "pywin.framework.startup" in sys.modules
|
|
|
|
|
|
def CreateDefaultGUI(appClass=None):
|
|
"""Creates a default GUI environment"""
|
|
if appClass is None:
|
|
from . import intpyapp # Bring in the default app - could be param'd later.
|
|
|
|
appClass = intpyapp.InteractivePythonApp
|
|
# Create and init the app.
|
|
appClass().InitInstance()
|
|
|
|
|
|
def CheckCreateDefaultGUI():
|
|
"""Checks and creates if necessary a default GUI environment."""
|
|
rc = HaveGoodGUI()
|
|
if not rc:
|
|
CreateDefaultGUI()
|
|
return rc
|