Todo: 集成多平台 解决因SaiNiu线程抢占资源问题 本地提交测试环境打包 和 正式打包脚本与正式环境打包bat 提交Python32环境包 改进多日志文件生成情况修改打包日志细节
This commit is contained in:
BIN
Utils/PythonNew32/Lib/site-packages/pythonwin/Pythonwin.exe
Normal file
BIN
Utils/PythonNew32/Lib/site-packages/pythonwin/Pythonwin.exe
Normal file
Binary file not shown.
BIN
Utils/PythonNew32/Lib/site-packages/pythonwin/dde.pyd
Normal file
BIN
Utils/PythonNew32/Lib/site-packages/pythonwin/dde.pyd
Normal file
Binary file not shown.
30
Utils/PythonNew32/Lib/site-packages/pythonwin/license.txt
Normal file
30
Utils/PythonNew32/Lib/site-packages/pythonwin/license.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
Unless stated in the specfic source file, this work is
|
||||
Copyright (c) 1994-2008, Mark Hammond
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
Neither name of Mark Hammond nor the name of contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
|
||||
IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
BIN
Utils/PythonNew32/Lib/site-packages/pythonwin/mfc140u.dll
Normal file
BIN
Utils/PythonNew32/Lib/site-packages/pythonwin/mfc140u.dll
Normal file
Binary file not shown.
@@ -0,0 +1,258 @@
|
||||
# basictimerapp - a really simple timer application.
|
||||
# This should be run using the command line:
|
||||
# pythonwin /app demos\basictimerapp.py
|
||||
import sys
|
||||
import time
|
||||
|
||||
import timer
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework import dlgappcore
|
||||
|
||||
|
||||
class TimerAppDialog(dlgappcore.AppDialog):
|
||||
softspace = 1
|
||||
|
||||
def __init__(self, appName=""):
|
||||
dlgappcore.AppDialog.__init__(self, win32ui.IDD_GENERAL_STATUS)
|
||||
self.timerAppName = appName
|
||||
self.argOff = 0
|
||||
if len(self.timerAppName) == 0:
|
||||
if len(sys.argv) > 1 and sys.argv[1][0] != "/":
|
||||
self.timerAppName = sys.argv[1]
|
||||
self.argOff = 1
|
||||
|
||||
def PreDoModal(self):
|
||||
# sys.stderr = sys.stdout
|
||||
pass
|
||||
|
||||
def ProcessArgs(self, args):
|
||||
for arg in args:
|
||||
if arg == "/now":
|
||||
self.OnOK()
|
||||
|
||||
def OnInitDialog(self):
|
||||
win32ui.SetProfileFileName("pytimer.ini")
|
||||
self.title = win32ui.GetProfileVal(
|
||||
self.timerAppName, "Title", "Remote System Timer"
|
||||
)
|
||||
self.buildTimer = win32ui.GetProfileVal(
|
||||
self.timerAppName, "Timer", "EachMinuteIntervaler()"
|
||||
)
|
||||
self.doWork = win32ui.GetProfileVal(self.timerAppName, "Work", "DoDemoWork()")
|
||||
# replace "\n" with real \n.
|
||||
self.doWork = self.doWork.replace("\\n", "\n")
|
||||
dlgappcore.AppDialog.OnInitDialog(self)
|
||||
|
||||
self.SetWindowText(self.title)
|
||||
self.prompt1 = self.GetDlgItem(win32ui.IDC_PROMPT1)
|
||||
self.prompt2 = self.GetDlgItem(win32ui.IDC_PROMPT2)
|
||||
self.prompt3 = self.GetDlgItem(win32ui.IDC_PROMPT3)
|
||||
self.butOK = self.GetDlgItem(win32con.IDOK)
|
||||
self.butCancel = self.GetDlgItem(win32con.IDCANCEL)
|
||||
self.prompt1.SetWindowText("Python Timer App")
|
||||
self.prompt2.SetWindowText("")
|
||||
self.prompt3.SetWindowText("")
|
||||
self.butOK.SetWindowText("Do it now")
|
||||
self.butCancel.SetWindowText("Close")
|
||||
|
||||
self.timerManager = TimerManager(self)
|
||||
self.ProcessArgs(sys.argv[self.argOff :])
|
||||
self.timerManager.go()
|
||||
return 1
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
dlgappcore.AppDialog.OnDestroy(self, msg)
|
||||
self.timerManager.stop()
|
||||
|
||||
def OnOK(self):
|
||||
# stop the timer, then restart after setting special boolean
|
||||
self.timerManager.stop()
|
||||
self.timerManager.bConnectNow = 1
|
||||
self.timerManager.go()
|
||||
return
|
||||
|
||||
|
||||
# def OnCancel(self): default behaviour - cancel == close.
|
||||
# return
|
||||
|
||||
|
||||
class TimerManager:
|
||||
def __init__(self, dlg):
|
||||
self.dlg = dlg
|
||||
self.timerId = None
|
||||
self.intervaler = eval(self.dlg.buildTimer)
|
||||
self.bConnectNow = 0
|
||||
self.bHaveSetPrompt1 = 0
|
||||
|
||||
def CaptureOutput(self):
|
||||
self.oldOut = sys.stdout
|
||||
self.oldErr = sys.stderr
|
||||
sys.stdout = sys.stderr = self
|
||||
self.bHaveSetPrompt1 = 0
|
||||
|
||||
def ReleaseOutput(self):
|
||||
sys.stdout = self.oldOut
|
||||
sys.stderr = self.oldErr
|
||||
|
||||
def write(self, str):
|
||||
s = str.strip()
|
||||
if len(s):
|
||||
if self.bHaveSetPrompt1:
|
||||
dest = self.dlg.prompt3
|
||||
else:
|
||||
dest = self.dlg.prompt1
|
||||
self.bHaveSetPrompt1 = 1
|
||||
dest.SetWindowText(s)
|
||||
|
||||
def go(self):
|
||||
self.OnTimer(None, None)
|
||||
|
||||
def stop(self):
|
||||
if self.timerId:
|
||||
timer.kill_timer(self.timerId)
|
||||
self.timerId = None
|
||||
|
||||
def OnTimer(self, id, timeVal):
|
||||
if id:
|
||||
timer.kill_timer(id)
|
||||
if self.intervaler.IsTime() or self.bConnectNow:
|
||||
# do the work.
|
||||
try:
|
||||
self.dlg.SetWindowText(self.dlg.title + " - Working...")
|
||||
self.dlg.butOK.EnableWindow(0)
|
||||
self.dlg.butCancel.EnableWindow(0)
|
||||
self.CaptureOutput()
|
||||
try:
|
||||
exec(self.dlg.doWork)
|
||||
print("The last operation completed successfully.")
|
||||
except:
|
||||
t, v, tb = sys.exc_info()
|
||||
str = f"Failed: {t}: {v!r}"
|
||||
print(str)
|
||||
self.oldErr.write(str)
|
||||
tb = None # Prevent cycle
|
||||
finally:
|
||||
self.ReleaseOutput()
|
||||
self.dlg.butOK.EnableWindow()
|
||||
self.dlg.butCancel.EnableWindow()
|
||||
self.dlg.SetWindowText(self.dlg.title)
|
||||
else:
|
||||
now = time.time()
|
||||
nextTime = self.intervaler.GetNextTime()
|
||||
if nextTime:
|
||||
timeDiffSeconds = nextTime - now
|
||||
timeDiffMinutes = int(timeDiffSeconds / 60)
|
||||
timeDiffSeconds %= 60
|
||||
timeDiffHours = int(timeDiffMinutes / 60)
|
||||
timeDiffMinutes %= 60
|
||||
self.dlg.prompt1.SetWindowText(
|
||||
"Next connection due in %02d:%02d:%02d"
|
||||
% (timeDiffHours, timeDiffMinutes, timeDiffSeconds)
|
||||
)
|
||||
self.timerId = timer.set_timer(
|
||||
self.intervaler.GetWakeupInterval(), self.OnTimer
|
||||
)
|
||||
self.bConnectNow = 0
|
||||
|
||||
|
||||
class TimerIntervaler:
|
||||
def __init__(self):
|
||||
self.nextTime = None
|
||||
self.wakeUpInterval = 2000
|
||||
|
||||
def GetWakeupInterval(self):
|
||||
return self.wakeUpInterval
|
||||
|
||||
def GetNextTime(self):
|
||||
return self.nextTime
|
||||
|
||||
def IsTime(self):
|
||||
now = time.time()
|
||||
if self.nextTime is None:
|
||||
self.nextTime = self.SetFirstTime(now)
|
||||
ret = 0
|
||||
if now >= self.nextTime:
|
||||
ret = 1
|
||||
self.nextTime = self.SetNextTime(self.nextTime, now)
|
||||
# do the work.
|
||||
return ret
|
||||
|
||||
|
||||
class EachAnyIntervaler(TimerIntervaler):
|
||||
def __init__(self, timeAt, timePos, timeAdd, wakeUpInterval=None):
|
||||
TimerIntervaler.__init__(self)
|
||||
self.timeAt = timeAt
|
||||
self.timePos = timePos
|
||||
self.timeAdd = timeAdd
|
||||
if wakeUpInterval:
|
||||
self.wakeUpInterval = wakeUpInterval
|
||||
|
||||
def SetFirstTime(self, now):
|
||||
timeTup = time.localtime(now)
|
||||
lst = []
|
||||
for item in timeTup:
|
||||
lst.append(item)
|
||||
bAdd = timeTup[self.timePos] > self.timeAt
|
||||
lst[self.timePos] = self.timeAt
|
||||
for pos in range(self.timePos + 1, 6):
|
||||
lst[pos] = 0
|
||||
ret = time.mktime(tuple(lst))
|
||||
if bAdd:
|
||||
ret += self.timeAdd
|
||||
return ret
|
||||
|
||||
def SetNextTime(self, lastTime, now):
|
||||
return lastTime + self.timeAdd
|
||||
|
||||
|
||||
class EachMinuteIntervaler(EachAnyIntervaler):
|
||||
def __init__(self, at=0):
|
||||
EachAnyIntervaler.__init__(self, at, 5, 60, 2000)
|
||||
|
||||
|
||||
class EachHourIntervaler(EachAnyIntervaler):
|
||||
def __init__(self, at=0):
|
||||
EachAnyIntervaler.__init__(self, at, 4, 3600, 10000)
|
||||
|
||||
|
||||
class EachDayIntervaler(EachAnyIntervaler):
|
||||
def __init__(self, at=0):
|
||||
EachAnyIntervaler.__init__(self, at, 3, 86400, 10000)
|
||||
|
||||
|
||||
class TimerDialogApp(dlgappcore.DialogApp):
|
||||
def CreateDialog(self):
|
||||
return TimerAppDialog()
|
||||
|
||||
|
||||
def DoDemoWork():
|
||||
print("Doing the work...")
|
||||
print("About to connect")
|
||||
win32api.MessageBeep(win32con.MB_ICONASTERISK)
|
||||
win32api.Sleep(2000)
|
||||
print("Doing something else...")
|
||||
win32api.MessageBeep(win32con.MB_ICONEXCLAMATION)
|
||||
win32api.Sleep(2000)
|
||||
print("More work.")
|
||||
win32api.MessageBeep(win32con.MB_ICONHAND)
|
||||
win32api.Sleep(2000)
|
||||
print("The last bit.")
|
||||
win32api.MessageBeep(win32con.MB_OK)
|
||||
win32api.Sleep(2000)
|
||||
|
||||
|
||||
app = TimerDialogApp()
|
||||
|
||||
|
||||
def t():
|
||||
t = TimerAppDialog("Test Dialog")
|
||||
t.DoModal()
|
||||
return t
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
demoutils.NeedApp()
|
||||
@@ -0,0 +1,183 @@
|
||||
# A demo of an Application object that has some custom print functionality.
|
||||
|
||||
# If you desire, you can also run this from inside Pythonwin, in which
|
||||
# case it will do the demo inside the Pythonwin environment.
|
||||
|
||||
# This sample was contributed by Roger Burnham.
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework import app
|
||||
from pywin.mfc import afxres, dialog, docview
|
||||
|
||||
PRINTDLGORD = 1538
|
||||
IDC_PRINT_MAG_EDIT = 1010
|
||||
|
||||
|
||||
class PrintDemoTemplate(docview.DocTemplate):
|
||||
def _SetupSharedMenu_(self):
|
||||
pass
|
||||
|
||||
|
||||
class PrintDemoView(docview.ScrollView):
|
||||
def OnInitialUpdate(self):
|
||||
ret = self._obj_.OnInitialUpdate()
|
||||
self.colors = {
|
||||
"Black": (0x00 << 0) + (0x00 << 8) + (0x00 << 16),
|
||||
"Red": (0xFF << 0) + (0x00 << 8) + (0x00 << 16),
|
||||
"Green": (0x00 << 0) + (0xFF << 8) + (0x00 << 16),
|
||||
"Blue": (0x00 << 0) + (0x00 << 8) + (0xFF << 16),
|
||||
"Cyan": (0x00 << 0) + (0xFF << 8) + (0xFF << 16),
|
||||
"Magenta": (0xFF << 0) + (0x00 << 8) + (0xFF << 16),
|
||||
"Yellow": (0xFF << 0) + (0xFF << 8) + (0x00 << 16),
|
||||
}
|
||||
self.pens = {}
|
||||
for name, color in self.colors.items():
|
||||
self.pens[name] = win32ui.CreatePen(win32con.PS_SOLID, 5, color)
|
||||
self.pen = None
|
||||
self.size = (128, 128)
|
||||
self.SetScaleToFitSize(self.size)
|
||||
self.HookCommand(self.OnFilePrint, afxres.ID_FILE_PRINT)
|
||||
self.HookCommand(self.OnFilePrintPreview, win32ui.ID_FILE_PRINT_PREVIEW)
|
||||
return ret
|
||||
|
||||
def OnDraw(self, dc):
|
||||
oldPen = None
|
||||
x, y = self.size
|
||||
delta = 2
|
||||
colors = sorted(self.colors) * 2
|
||||
for color in colors:
|
||||
if oldPen is None:
|
||||
oldPen = dc.SelectObject(self.pens[color])
|
||||
else:
|
||||
dc.SelectObject(self.pens[color])
|
||||
dc.MoveTo((delta, delta))
|
||||
dc.LineTo((x - delta, delta))
|
||||
dc.LineTo((x - delta, y - delta))
|
||||
dc.LineTo((delta, y - delta))
|
||||
dc.LineTo((delta, delta))
|
||||
delta += 4
|
||||
if x - delta <= 0 or y - delta <= 0:
|
||||
break
|
||||
dc.SelectObject(oldPen)
|
||||
|
||||
def OnPrepareDC(self, dc, pInfo):
|
||||
if dc.IsPrinting():
|
||||
mag = self.prtDlg["mag"]
|
||||
dc.SetMapMode(win32con.MM_ANISOTROPIC)
|
||||
dc.SetWindowOrg((0, 0))
|
||||
dc.SetWindowExt((1, 1))
|
||||
dc.SetViewportOrg((0, 0))
|
||||
dc.SetViewportExt((mag, mag))
|
||||
|
||||
def OnPreparePrinting(self, pInfo):
|
||||
flags = (
|
||||
win32ui.PD_USEDEVMODECOPIES
|
||||
| win32ui.PD_PAGENUMS
|
||||
| win32ui.PD_NOPAGENUMS
|
||||
| win32ui.PD_NOSELECTION
|
||||
)
|
||||
self.prtDlg = ImagePrintDialog(pInfo, PRINTDLGORD, flags)
|
||||
pInfo.SetPrintDialog(self.prtDlg)
|
||||
pInfo.SetMinPage(1)
|
||||
pInfo.SetMaxPage(1)
|
||||
pInfo.SetFromPage(1)
|
||||
pInfo.SetToPage(1)
|
||||
ret = self.DoPreparePrinting(pInfo)
|
||||
return ret
|
||||
|
||||
def OnBeginPrinting(self, dc, pInfo):
|
||||
return self._obj_.OnBeginPrinting(dc, pInfo)
|
||||
|
||||
def OnEndPrinting(self, dc, pInfo):
|
||||
del self.prtDlg
|
||||
return self._obj_.OnEndPrinting(dc, pInfo)
|
||||
|
||||
def OnFilePrintPreview(self, *arg):
|
||||
self._obj_.OnFilePrintPreview()
|
||||
|
||||
def OnFilePrint(self, *arg):
|
||||
self._obj_.OnFilePrint()
|
||||
|
||||
def OnPrint(self, dc, pInfo):
|
||||
doc = self.GetDocument()
|
||||
metrics = dc.GetTextMetrics()
|
||||
cxChar = metrics["tmAveCharWidth"]
|
||||
cyChar = metrics["tmHeight"]
|
||||
left, top, right, bottom = pInfo.GetDraw()
|
||||
dc.TextOut(0, 2 * cyChar, doc.GetTitle())
|
||||
top += 7 * cyChar / 2
|
||||
dc.MoveTo(left, top)
|
||||
dc.LineTo(right, top)
|
||||
top += cyChar
|
||||
# this seems to have not effect...
|
||||
# get what I want with the dc.SetWindowOrg calls
|
||||
pInfo.SetDraw((left, top, right, bottom))
|
||||
dc.SetWindowOrg((0, -top))
|
||||
|
||||
self.OnDraw(dc)
|
||||
dc.SetTextAlign(win32con.TA_LEFT | win32con.TA_BOTTOM)
|
||||
|
||||
rect = self.GetWindowRect()
|
||||
rect = self.ScreenToClient(rect)
|
||||
height = rect[3] - rect[1]
|
||||
dc.SetWindowOrg((0, -(top + height + cyChar)))
|
||||
dc.MoveTo(left, 0)
|
||||
dc.LineTo(right, 0)
|
||||
|
||||
x = 0
|
||||
y = (3 * cyChar) / 2
|
||||
|
||||
dc.TextOut(x, y, doc.GetTitle())
|
||||
y += cyChar
|
||||
|
||||
|
||||
class PrintDemoApp(app.CApp):
|
||||
def __init__(self):
|
||||
app.CApp.__init__(self)
|
||||
|
||||
def InitInstance(self):
|
||||
template = PrintDemoTemplate(None, None, None, PrintDemoView)
|
||||
self.AddDocTemplate(template)
|
||||
self._obj_.InitMDIInstance()
|
||||
self.LoadMainFrame()
|
||||
doc = template.OpenDocumentFile(None)
|
||||
doc.SetTitle("Custom Print Document")
|
||||
|
||||
|
||||
class ImagePrintDialog(dialog.PrintDialog):
|
||||
sectionPos = "Image Print Demo"
|
||||
|
||||
def __init__(self, pInfo, dlgID, flags=win32ui.PD_USEDEVMODECOPIES):
|
||||
dialog.PrintDialog.__init__(self, pInfo, dlgID, flags=flags)
|
||||
mag = win32ui.GetProfileVal(self.sectionPos, "Document Magnification", 0)
|
||||
if mag <= 0:
|
||||
mag = 2
|
||||
win32ui.WriteProfileVal(self.sectionPos, "Document Magnification", mag)
|
||||
|
||||
self["mag"] = mag
|
||||
|
||||
def OnInitDialog(self):
|
||||
self.magCtl = self.GetDlgItem(IDC_PRINT_MAG_EDIT)
|
||||
self.magCtl.SetWindowText(repr(self["mag"]))
|
||||
return dialog.PrintDialog.OnInitDialog(self)
|
||||
|
||||
def OnOK(self):
|
||||
dialog.PrintDialog.OnOK(self)
|
||||
strMag = self.magCtl.GetWindowText()
|
||||
try:
|
||||
self["mag"] = int(strMag)
|
||||
except:
|
||||
pass
|
||||
win32ui.WriteProfileVal(self.sectionPos, "Document Magnification", self["mag"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Running under Pythonwin
|
||||
def test():
|
||||
template = PrintDemoTemplate(None, None, None, PrintDemoView)
|
||||
template.OpenDocumentFile(None)
|
||||
|
||||
test()
|
||||
else:
|
||||
app = PrintDemoApp()
|
||||
@@ -0,0 +1,63 @@
|
||||
# Utilities for the demos
|
||||
|
||||
import sys
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
NotScriptMsg = """\
|
||||
This demo program is not designed to be run as a Script, but is
|
||||
probably used by some other test program. Please try another demo.
|
||||
"""
|
||||
|
||||
NeedGUIMsg = """\
|
||||
This demo program can only be run from inside of Pythonwin
|
||||
|
||||
You must start Pythonwin, and select 'Run' from the toolbar or File menu
|
||||
"""
|
||||
|
||||
|
||||
NeedAppMsg = """\
|
||||
This demo program is a 'Pythonwin Application'.
|
||||
|
||||
It is more demo code than an example of Pythonwin's capabilities.
|
||||
|
||||
To run it, you must execute the command:
|
||||
pythonwin.exe /app "%s"
|
||||
|
||||
Would you like to execute it now?
|
||||
"""
|
||||
|
||||
|
||||
def NotAScript():
|
||||
import win32ui
|
||||
|
||||
win32ui.MessageBox(NotScriptMsg, "Demos")
|
||||
|
||||
|
||||
def NeedGoodGUI():
|
||||
from pywin.framework.app import HaveGoodGUI
|
||||
|
||||
rc = HaveGoodGUI()
|
||||
if not rc:
|
||||
win32ui.MessageBox(NeedGUIMsg, "Demos")
|
||||
return rc
|
||||
|
||||
|
||||
def NeedApp():
|
||||
import win32ui
|
||||
|
||||
rc = win32ui.MessageBox(NeedAppMsg % sys.argv[0], "Demos", win32con.MB_YESNO)
|
||||
if rc == win32con.IDYES:
|
||||
try:
|
||||
parent = win32ui.GetMainFrame().GetSafeHwnd()
|
||||
win32api.ShellExecute(
|
||||
parent, None, "pythonwin.exe", '/app "%s"' % sys.argv[0], None, 1
|
||||
)
|
||||
except win32api.error as details:
|
||||
win32ui.MessageBox("Error executing command - %s" % (details), "Demos")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
NotAScript()
|
||||
@@ -0,0 +1,51 @@
|
||||
# dlgappdemo - a demo of a dialog application.
|
||||
# This is a demonstration of both a custom "application" module,
|
||||
# and a Python program in a dialog box.
|
||||
#
|
||||
# NOTE: You CAN NOT import this module from either PythonWin or Python.
|
||||
# This module must be specified on the commandline to PythonWin only.
|
||||
# eg, PythonWin /app dlgappdemo.py
|
||||
|
||||
import sys
|
||||
|
||||
import win32ui
|
||||
from pywin.framework import dlgappcore
|
||||
|
||||
|
||||
class TestDialogApp(dlgappcore.DialogApp):
|
||||
def CreateDialog(self):
|
||||
return TestAppDialog()
|
||||
|
||||
|
||||
class TestAppDialog(dlgappcore.AppDialog):
|
||||
def __init__(self):
|
||||
self.edit = None
|
||||
dlgappcore.AppDialog.__init__(self, win32ui.IDD_LARGE_EDIT)
|
||||
|
||||
def OnInitDialog(self):
|
||||
self.SetWindowText("Test dialog application")
|
||||
self.edit = self.GetDlgItem(win32ui.IDC_EDIT1)
|
||||
print("Hello from Python")
|
||||
print("args are:", end=" ")
|
||||
for arg in sys.argv:
|
||||
print(arg)
|
||||
return 1
|
||||
|
||||
def PreDoModal(self):
|
||||
sys.stdout = sys.stderr = self
|
||||
|
||||
def write(self, str):
|
||||
if self.edit:
|
||||
self.edit.SetSel(-2)
|
||||
# translate \n to \n\r
|
||||
self.edit.ReplaceSel(str.replace("\n", "\r\n"))
|
||||
else:
|
||||
win32ui.OutputDebug("dlgapp - no edit control! >>\n%s\n<<\n" % str)
|
||||
|
||||
|
||||
app = TestDialogApp()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
demoutils.NeedApp()
|
||||
@@ -0,0 +1,71 @@
|
||||
# dojobapp - do a job, show the result in a dialog, and exit.
|
||||
#
|
||||
# Very simple - faily minimal dialog based app.
|
||||
#
|
||||
# This should be run using the command line:
|
||||
# pythonwin /app demos\dojobapp.py
|
||||
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework import dlgappcore
|
||||
|
||||
|
||||
class DoJobAppDialog(dlgappcore.AppDialog):
|
||||
softspace = 1
|
||||
|
||||
def __init__(self, appName=""):
|
||||
self.appName = appName
|
||||
dlgappcore.AppDialog.__init__(self, win32ui.IDD_GENERAL_STATUS)
|
||||
|
||||
def PreDoModal(self):
|
||||
pass
|
||||
|
||||
def ProcessArgs(self, args):
|
||||
pass
|
||||
|
||||
def OnInitDialog(self):
|
||||
self.SetWindowText(self.appName)
|
||||
butCancel = self.GetDlgItem(win32con.IDCANCEL)
|
||||
butCancel.ShowWindow(win32con.SW_HIDE)
|
||||
p1 = self.GetDlgItem(win32ui.IDC_PROMPT1)
|
||||
p2 = self.GetDlgItem(win32ui.IDC_PROMPT2)
|
||||
|
||||
# Do something here!
|
||||
|
||||
p1.SetWindowText("Hello there")
|
||||
p2.SetWindowText("from the demo")
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
pass
|
||||
|
||||
|
||||
# def OnOK(self):
|
||||
# pass
|
||||
# def OnCancel(self): default behaviour - cancel == close.
|
||||
# return
|
||||
|
||||
|
||||
class DoJobDialogApp(dlgappcore.DialogApp):
|
||||
def CreateDialog(self):
|
||||
return DoJobAppDialog("Do Something")
|
||||
|
||||
|
||||
class CopyToDialogApp(DoJobDialogApp):
|
||||
def __init__(self):
|
||||
DoJobDialogApp.__init__(self)
|
||||
|
||||
|
||||
app = DoJobDialogApp()
|
||||
|
||||
|
||||
def t():
|
||||
t = DoJobAppDialog("Copy To")
|
||||
t.DoModal()
|
||||
return t
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
demoutils.NeedApp()
|
||||
@@ -0,0 +1,53 @@
|
||||
##
|
||||
## helloapp.py
|
||||
##
|
||||
##
|
||||
## A nice, small 'hello world' Pythonwin application.
|
||||
## NOT an MDI application - just a single, normal, top-level window.
|
||||
##
|
||||
## MUST be run with the command line "pythonwin.exe /app helloapp.py"
|
||||
## (or if you are really keen, rename "pythonwin.exe" to something else, then
|
||||
## using MSVC or similar, edit the string section in the .EXE to name this file)
|
||||
##
|
||||
## Originally by Willy Heineman <wheineman@uconect.net>
|
||||
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import window
|
||||
from pywin.mfc.thread import WinApp
|
||||
|
||||
|
||||
# The main frame.
|
||||
# Does almost nothing at all - doesn't even create a child window!
|
||||
class HelloWindow(window.Wnd):
|
||||
def __init__(self):
|
||||
# The window.Wnd ctor creates a Window object, and places it in
|
||||
# self._obj_. Note the window object exists, but the window itself
|
||||
# does not!
|
||||
window.Wnd.__init__(self, win32ui.CreateWnd())
|
||||
|
||||
# Now we ask the window object to create the window itself.
|
||||
self._obj_.CreateWindowEx(
|
||||
win32con.WS_EX_CLIENTEDGE,
|
||||
win32ui.RegisterWndClass(0, 0, win32con.COLOR_WINDOW + 1),
|
||||
"Hello World!",
|
||||
win32con.WS_OVERLAPPEDWINDOW,
|
||||
(100, 100, 400, 300),
|
||||
None,
|
||||
0,
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
# The application object itself.
|
||||
class HelloApp(WinApp):
|
||||
def InitInstance(self):
|
||||
self.frame = HelloWindow()
|
||||
self.frame.ShowWindow(win32con.SW_SHOWNORMAL)
|
||||
# We need to tell MFC what our main frame is.
|
||||
self.SetMainFrame(self.frame)
|
||||
|
||||
|
||||
# Now create the application object itself!
|
||||
app = HelloApp()
|
||||
@@ -0,0 +1,113 @@
|
||||
# cmdserver.py
|
||||
|
||||
# Demo code that is not Pythonwin related, but too good to throw away...
|
||||
|
||||
import _thread
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import win32api
|
||||
from pywin.framework import winout
|
||||
|
||||
|
||||
class ThreadWriter:
|
||||
"Assign an instance to sys.stdout for per-thread printing objects - Courtesy Guido!"
|
||||
|
||||
def __init__(self):
|
||||
"Constructor -- initialize the table of writers"
|
||||
self.writers = {}
|
||||
self.origStdOut = None
|
||||
|
||||
def register(self, writer):
|
||||
"Register the writer for the current thread"
|
||||
self.writers[_thread.get_ident()] = writer
|
||||
if self.origStdOut is None:
|
||||
self.origStdOut = sys.stdout
|
||||
sys.stdout = self
|
||||
|
||||
def unregister(self):
|
||||
"Remove the writer for the current thread, if any"
|
||||
try:
|
||||
del self.writers[_thread.get_ident()]
|
||||
except KeyError:
|
||||
pass
|
||||
if len(self.writers) == 0:
|
||||
sys.stdout = self.origStdOut
|
||||
self.origStdOut = None
|
||||
|
||||
def getwriter(self):
|
||||
"Return the current thread's writer, default sys.stdout"
|
||||
self.writers.get(_thread.get_ident(), self.origStdOut)
|
||||
|
||||
def write(self, str):
|
||||
"Write to the current thread's writer, default sys.stdout"
|
||||
self.getwriter().write(str)
|
||||
|
||||
|
||||
def Test():
|
||||
num = 1
|
||||
while num < 1000:
|
||||
print("Hello there no", num)
|
||||
win32api.Sleep(50)
|
||||
num += 1
|
||||
|
||||
|
||||
class flags:
|
||||
SERVER_BEST = 0
|
||||
SERVER_IMMEDIATE = 1
|
||||
SERVER_THREAD = 2
|
||||
SERVER_PROCESS = 3
|
||||
|
||||
|
||||
def StartServer(cmd, title=None, bCloseOnEnd=0, serverFlags=flags.SERVER_BEST):
|
||||
out = winout.WindowOutput(title, None, winout.flags.WQ_IDLE)
|
||||
if not title:
|
||||
title = cmd
|
||||
out.Create(title)
|
||||
# ServerThread((out, cmd, title, bCloseOnEnd))
|
||||
# out = sys.stdout
|
||||
_thread.start_new_thread(ServerThread, (out, cmd, title, bCloseOnEnd))
|
||||
|
||||
|
||||
def ServerThread(myout, cmd, title, bCloseOnEnd):
|
||||
try:
|
||||
writer.register(myout)
|
||||
print('Executing "%s"\n' % cmd)
|
||||
bOK = 1
|
||||
try:
|
||||
import __main__
|
||||
|
||||
exec(cmd + "\n", __main__.__dict__)
|
||||
except:
|
||||
bOK = 0
|
||||
if bOK:
|
||||
print("Command terminated without errors.")
|
||||
else:
|
||||
t, v, tb = sys.exc_info()
|
||||
print(t, ": ", v)
|
||||
traceback.print_tb(tb)
|
||||
tb = None # prevent a cycle
|
||||
print("Command terminated with an unhandled exception")
|
||||
writer.unregister()
|
||||
if bOK and bCloseOnEnd:
|
||||
myout.frame.DestroyWindow()
|
||||
|
||||
# Unhandled exception of any kind in a thread kills the gui!
|
||||
except:
|
||||
t, v, tb = sys.exc_info()
|
||||
print(t, ": ", v)
|
||||
traceback.print_tb(tb)
|
||||
tb = None
|
||||
print("Thread failed")
|
||||
|
||||
|
||||
# assist for reloading (when debugging) - use only 1 tracer object,
|
||||
# else a large chain of tracer objects will exist.
|
||||
try:
|
||||
writer
|
||||
except NameError:
|
||||
writer = ThreadWriter()
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
demoutils.NotAScript()
|
||||
@@ -0,0 +1,114 @@
|
||||
#
|
||||
# Window creation example
|
||||
#
|
||||
# This example creates a minimal "control" that just fills in its
|
||||
# window with red. To make your own control, subclass Control and
|
||||
# write your own OnPaint() method. See PyCWnd.HookMessage for what
|
||||
# the parameters to OnPaint are.
|
||||
#
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog, window
|
||||
|
||||
|
||||
class Control(window.Wnd):
|
||||
"""Generic control class"""
|
||||
|
||||
def __init__(self):
|
||||
window.Wnd.__init__(self, win32ui.CreateWnd())
|
||||
|
||||
def OnPaint(self):
|
||||
dc, paintStruct = self.BeginPaint()
|
||||
self.DoPaint(dc)
|
||||
self.EndPaint(paintStruct)
|
||||
|
||||
def DoPaint(self, dc): # Override this!
|
||||
pass
|
||||
|
||||
|
||||
class RedBox(Control):
|
||||
def DoPaint(self, dc):
|
||||
dc.FillSolidRect(self.GetClientRect(), win32api.RGB(255, 0, 0))
|
||||
|
||||
|
||||
class RedBoxWithPie(RedBox):
|
||||
def DoPaint(self, dc):
|
||||
RedBox.DoPaint(self, dc)
|
||||
r = self.GetClientRect()
|
||||
dc.Pie(r[0], r[1], r[2], r[3], 0, 0, r[2], r[3] // 2)
|
||||
|
||||
|
||||
def MakeDlgTemplate():
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
cs = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
|
||||
w = 64
|
||||
h = 64
|
||||
|
||||
dlg = [
|
||||
["Red box", (0, 0, w, h), style, None, (8, "MS Sans Serif")],
|
||||
]
|
||||
|
||||
s = win32con.WS_TABSTOP | cs
|
||||
|
||||
dlg.append(
|
||||
[
|
||||
128,
|
||||
"Cancel",
|
||||
win32con.IDCANCEL,
|
||||
(7, h - 18, 50, 14),
|
||||
s | win32con.BS_PUSHBUTTON,
|
||||
]
|
||||
)
|
||||
|
||||
return dlg
|
||||
|
||||
|
||||
class TestDialog(dialog.Dialog):
|
||||
def OnInitDialog(self):
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
self.redbox = RedBox()
|
||||
self.redbox.CreateWindow(
|
||||
None,
|
||||
"RedBox",
|
||||
win32con.WS_CHILD | win32con.WS_VISIBLE,
|
||||
(5, 5, 90, 68),
|
||||
self,
|
||||
1003,
|
||||
)
|
||||
return rc
|
||||
|
||||
|
||||
class TestPieDialog(dialog.Dialog):
|
||||
def OnInitDialog(self):
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
self.control = RedBoxWithPie()
|
||||
self.control.CreateWindow(
|
||||
None,
|
||||
"RedBox with Pie",
|
||||
win32con.WS_CHILD | win32con.WS_VISIBLE,
|
||||
(5, 5, 90, 68),
|
||||
self,
|
||||
1003,
|
||||
)
|
||||
|
||||
|
||||
def demo(modal=0):
|
||||
d = TestPieDialog(MakeDlgTemplate())
|
||||
if modal:
|
||||
d.DoModal()
|
||||
else:
|
||||
d.CreateWindow()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo(1)
|
||||
@@ -0,0 +1,65 @@
|
||||
# Utilities for the demos
|
||||
|
||||
import sys
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
NotScriptMsg = """\
|
||||
This demo program is not designed to be run as a Script, but is
|
||||
probably used by some other test program. Please try another demo.
|
||||
"""
|
||||
|
||||
NeedGUIMsg = """\
|
||||
This demo program can only be run from inside of Pythonwin
|
||||
|
||||
You must start Pythonwin, and select 'Run' from the toolbar or File menu
|
||||
"""
|
||||
|
||||
|
||||
NeedAppMsg = """\
|
||||
This demo program is a 'Pythonwin Application'.
|
||||
|
||||
It is more demo code than an example of Pythonwin's capabilities.
|
||||
|
||||
To run it, you must execute the command:
|
||||
pythonwin.exe /app "%s"
|
||||
|
||||
Would you like to execute it now?
|
||||
"""
|
||||
|
||||
|
||||
def NotAScript():
|
||||
import win32ui
|
||||
|
||||
win32ui.MessageBox(NotScriptMsg, "Demos")
|
||||
|
||||
|
||||
def NeedGoodGUI():
|
||||
from pywin.framework.app import HaveGoodGUI
|
||||
|
||||
rc = HaveGoodGUI()
|
||||
if not rc:
|
||||
win32ui.MessageBox(NeedGUIMsg, "Demos")
|
||||
return rc
|
||||
|
||||
|
||||
def NeedApp():
|
||||
import win32ui
|
||||
|
||||
rc = win32ui.MessageBox(NeedAppMsg % sys.argv[0], "Demos", win32con.MB_YESNO)
|
||||
if rc == win32con.IDYES:
|
||||
try:
|
||||
parent = win32ui.GetMainFrame().GetSafeHwnd()
|
||||
win32api.ShellExecute(
|
||||
parent, None, "pythonwin.exe", '/app "%s"' % sys.argv[0], None, 1
|
||||
)
|
||||
except win32api.error as details:
|
||||
win32ui.MessageBox("Error executing command - %s" % (details), "Demos")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
demoutils.NotAScript()
|
||||
@@ -0,0 +1,73 @@
|
||||
# A demo which creates a view and a frame which displays a PPM format bitmap
|
||||
#
|
||||
# This hasnnt been run in a while, as I don't have many of that format around!
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
|
||||
class DIBView:
|
||||
def __init__(self, doc, dib):
|
||||
self.dib = dib
|
||||
self.view = win32ui.CreateView(doc)
|
||||
self.width = self.height = 0
|
||||
# set up message handlers
|
||||
# self.view.OnPrepareDC = self.OnPrepareDC
|
||||
self.view.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
|
||||
def OnSize(self, params):
|
||||
lParam = params[3]
|
||||
self.width = win32api.LOWORD(lParam)
|
||||
self.height = win32api.HIWORD(lParam)
|
||||
|
||||
def OnDraw(self, ob, dc):
|
||||
# set sizes used for "non strecth" mode.
|
||||
self.view.SetScrollSizes(win32con.MM_TEXT, self.dib.GetSize())
|
||||
dibSize = self.dib.GetSize()
|
||||
dibRect = (0, 0, dibSize[0], dibSize[1])
|
||||
# stretch BMP.
|
||||
# self.dib.Paint(dc, (0,0,self.width, self.height),dibRect)
|
||||
# non stretch.
|
||||
self.dib.Paint(dc)
|
||||
|
||||
|
||||
class DIBDemo:
|
||||
def __init__(self, filename, *bPBM):
|
||||
# init data members
|
||||
f = open(filename, "rb")
|
||||
dib = win32ui.CreateDIBitmap()
|
||||
if len(bPBM) > 0:
|
||||
magic = f.readline()
|
||||
if magic != "P6\n":
|
||||
print("The file is not a PBM format file")
|
||||
raise ValueError("Failed - The file is not a PBM format file")
|
||||
# check magic?
|
||||
rowcollist = f.readline().split()
|
||||
cols = int(rowcollist[0])
|
||||
rows = int(rowcollist[1])
|
||||
f.readline() # what's this one?
|
||||
dib.LoadPBMData(f, (cols, rows))
|
||||
else:
|
||||
dib.LoadWindowsFormatFile(f)
|
||||
f.close()
|
||||
# create doc/view
|
||||
self.doc = win32ui.CreateDoc()
|
||||
self.dibView = DIBView(self.doc, dib)
|
||||
self.frame = win32ui.CreateMDIFrame()
|
||||
self.frame.LoadFrame() # this will force OnCreateClient
|
||||
self.doc.SetTitle("DIB Demo")
|
||||
self.frame.ShowWindow()
|
||||
|
||||
# display the sucka
|
||||
self.frame.ActivateFrame()
|
||||
|
||||
def OnCreateClient(self, createparams, context):
|
||||
self.dibView.view.CreateWindow(self.frame)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
demoutils.NotAScript()
|
||||
@@ -0,0 +1,145 @@
|
||||
# A Demo of Pythonwin's Dialog and Property Page support.
|
||||
|
||||
###################
|
||||
#
|
||||
# First demo - use the built-in to Pythonwin "Tab Stop" dialog, but
|
||||
# customise it heavily.
|
||||
#
|
||||
# ID's for the tabstop dialog - out test.
|
||||
#
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
from win32con import IDCANCEL
|
||||
from win32ui import IDC_EDIT_TABS, IDC_PROMPT_TABS, IDD_SET_TABSTOPS
|
||||
|
||||
|
||||
class TestDialog(dialog.Dialog):
|
||||
def __init__(self, modal=1):
|
||||
dialog.Dialog.__init__(self, IDD_SET_TABSTOPS)
|
||||
self.counter = 0
|
||||
if modal:
|
||||
self.DoModal()
|
||||
else:
|
||||
self.CreateWindow()
|
||||
|
||||
def OnInitDialog(self):
|
||||
# Set the caption of the dialog itself.
|
||||
self.SetWindowText("Used to be Tab Stops!")
|
||||
# Get a child control, remember it, and change its text.
|
||||
self.edit = self.GetDlgItem(IDC_EDIT_TABS) # the text box.
|
||||
self.edit.SetWindowText("Test")
|
||||
# Hook a Windows message for the dialog.
|
||||
self.edit.HookMessage(self.KillFocus, win32con.WM_KILLFOCUS)
|
||||
# Get the prompt control, and change its next.
|
||||
prompt = self.GetDlgItem(IDC_PROMPT_TABS) # the prompt box.
|
||||
prompt.SetWindowText("Prompt")
|
||||
# And the same for the button..
|
||||
cancel = self.GetDlgItem(IDCANCEL) # the cancel button
|
||||
cancel.SetWindowText("&Kill me")
|
||||
|
||||
# And just for demonstration purposes, we hook the notify message for the dialog.
|
||||
# This allows us to be notified when the Edit Control text changes.
|
||||
self.HookCommand(self.OnNotify, IDC_EDIT_TABS)
|
||||
|
||||
def OnNotify(self, controlid, code):
|
||||
if code == win32con.EN_CHANGE:
|
||||
print("Edit text changed!")
|
||||
return 1 # I handled this, so no need to call defaults!
|
||||
|
||||
# kill focus for the edit box.
|
||||
# Simply increment the value in the text box.
|
||||
def KillFocus(self, msg):
|
||||
self.counter += 1
|
||||
if self.edit is not None:
|
||||
self.edit.SetWindowText(str(self.counter))
|
||||
|
||||
# Called when the dialog box is terminating...
|
||||
def OnDestroy(self, msg):
|
||||
del self.edit
|
||||
del self.counter
|
||||
|
||||
|
||||
# A very simply Property Sheet.
|
||||
# We only make a new class for demonstration purposes.
|
||||
class TestSheet(dialog.PropertySheet):
|
||||
def __init__(self, title):
|
||||
dialog.PropertySheet.__init__(self, title)
|
||||
self.HookMessage(self.OnActivate, win32con.WM_ACTIVATE)
|
||||
|
||||
def OnActivate(self, msg):
|
||||
pass
|
||||
|
||||
|
||||
# A very simply Property Page, which will be "owned" by the above
|
||||
# Property Sheet.
|
||||
# We create a new class, just so we can hook a control notification.
|
||||
class TestPage(dialog.PropertyPage):
|
||||
def OnInitDialog(self):
|
||||
# We use the HookNotify function to allow Python to respond to
|
||||
# Windows WM_NOTIFY messages.
|
||||
# In this case, we are interested in BN_CLICKED messages.
|
||||
self.HookNotify(self.OnNotify, win32con.BN_CLICKED)
|
||||
|
||||
def OnNotify(self, std, extra):
|
||||
print("OnNotify", std, extra)
|
||||
|
||||
|
||||
# Some code that actually uses these objects.
|
||||
def demo(modal=0):
|
||||
TestDialog(modal)
|
||||
|
||||
# property sheet/page demo
|
||||
ps = win32ui.CreatePropertySheet("Property Sheet/Page Demo")
|
||||
# Create a completely standard PropertyPage.
|
||||
page1 = win32ui.CreatePropertyPage(win32ui.IDD_PROPDEMO1)
|
||||
# Create our custom property page.
|
||||
page2 = TestPage(win32ui.IDD_PROPDEMO2)
|
||||
ps.AddPage(page1)
|
||||
ps.AddPage(page2)
|
||||
if modal:
|
||||
ps.DoModal()
|
||||
else:
|
||||
style = (
|
||||
win32con.WS_SYSMENU
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.DS_MODALFRAME
|
||||
| win32con.WS_VISIBLE
|
||||
)
|
||||
styleex = win32con.WS_EX_DLGMODALFRAME | win32con.WS_EX_PALETTEWINDOW
|
||||
ps.CreateWindow(win32ui.GetMainFrame(), style, styleex)
|
||||
|
||||
|
||||
def test(modal=1):
|
||||
# dlg=dialog.Dialog(1010)
|
||||
# dlg.CreateWindow()
|
||||
# dlg.EndDialog(0)
|
||||
# del dlg
|
||||
# return
|
||||
# property sheet/page demo
|
||||
ps = TestSheet("Property Sheet/Page Demo")
|
||||
page1 = win32ui.CreatePropertyPage(win32ui.IDD_PROPDEMO1)
|
||||
page2 = win32ui.CreatePropertyPage(win32ui.IDD_PROPDEMO2)
|
||||
ps.AddPage(page1)
|
||||
ps.AddPage(page2)
|
||||
del page1
|
||||
del page2
|
||||
if modal:
|
||||
ps.DoModal()
|
||||
else:
|
||||
ps.CreateWindow(win32ui.GetMainFrame())
|
||||
return ps
|
||||
|
||||
|
||||
def d():
|
||||
dlg = win32ui.CreateDialog(win32ui.IDD_DEBUGGER)
|
||||
dlg.datalist.append((win32ui.IDC_DBG_RADIOSTACK, "radio"))
|
||||
print("data list is ", dlg.datalist)
|
||||
dlg.data["radio"] = 1
|
||||
dlg.DoModal()
|
||||
print(dlg.data["radio"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo(1)
|
||||
@@ -0,0 +1,104 @@
|
||||
# dyndlg.py
|
||||
# contributed by Curt Hagenlocher <chi@earthlink.net>
|
||||
|
||||
# Dialog Template params:
|
||||
# Parameter 0 - Window caption
|
||||
# Parameter 1 - Bounds (rect tuple)
|
||||
# Parameter 2 - Window style
|
||||
# Parameter 3 - Extended style
|
||||
# Parameter 4 - Font tuple
|
||||
# Parameter 5 - Menu name
|
||||
# Parameter 6 - Window class
|
||||
# Dialog item params:
|
||||
# Parameter 0 - Window class
|
||||
# Parameter 1 - Text
|
||||
# Parameter 2 - ID
|
||||
# Parameter 3 - Bounds
|
||||
# Parameter 4 - Style
|
||||
# Parameter 5 - Extended style
|
||||
# Parameter 6 - Extra data
|
||||
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
|
||||
|
||||
def MakeDlgTemplate():
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
cs = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
dlg = [
|
||||
["Select Warehouse", (0, 0, 177, 93), style, None, (8, "MS Sans Serif")],
|
||||
]
|
||||
dlg.append([130, "Current Warehouse:", -1, (7, 7, 69, 9), cs | win32con.SS_LEFT])
|
||||
dlg.append([130, "ASTORIA", 128, (16, 17, 99, 7), cs | win32con.SS_LEFT])
|
||||
dlg.append([130, "New &Warehouse:", -1, (7, 29, 69, 9), cs | win32con.SS_LEFT])
|
||||
s = win32con.WS_TABSTOP | cs
|
||||
# dlg.append([131, None, 130, (5, 40, 110, 48),
|
||||
# s | win32con.LBS_NOTIFY | win32con.LBS_SORT | win32con.LBS_NOINTEGRALHEIGHT | win32con.WS_VSCROLL | win32con.WS_BORDER])
|
||||
dlg.append(
|
||||
[
|
||||
"{8E27C92B-1264-101C-8A2F-040224009C02}",
|
||||
None,
|
||||
131,
|
||||
(5, 40, 110, 48),
|
||||
win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
|
||||
dlg.append(
|
||||
[128, "OK", win32con.IDOK, (124, 5, 50, 14), s | win32con.BS_DEFPUSHBUTTON]
|
||||
)
|
||||
s = win32con.BS_PUSHBUTTON | s
|
||||
dlg.append([128, "Cancel", win32con.IDCANCEL, (124, 22, 50, 14), s])
|
||||
dlg.append([128, "&Help", 100, (124, 74, 50, 14), s])
|
||||
|
||||
return dlg
|
||||
|
||||
|
||||
def test1():
|
||||
win32ui.CreateDialogIndirect(MakeDlgTemplate()).DoModal()
|
||||
|
||||
|
||||
def test2():
|
||||
dialog.Dialog(MakeDlgTemplate()).DoModal()
|
||||
|
||||
|
||||
def test3():
|
||||
dlg = win32ui.LoadDialogResource(win32ui.IDD_SET_TABSTOPS)
|
||||
dlg[0][0] = "New Dialog Title"
|
||||
dlg[0][1] = (80, 20, 161, 60)
|
||||
dlg[1][1] = "&Confusion:"
|
||||
cs = (
|
||||
win32con.WS_CHILD
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_TABSTOP
|
||||
| win32con.BS_PUSHBUTTON
|
||||
)
|
||||
dlg.append([128, "&Help", 100, (111, 41, 40, 14), cs])
|
||||
dialog.Dialog(dlg).DoModal()
|
||||
|
||||
|
||||
def test4():
|
||||
page1 = dialog.PropertyPage(win32ui.LoadDialogResource(win32ui.IDD_PROPDEMO1))
|
||||
page2 = dialog.PropertyPage(win32ui.LoadDialogResource(win32ui.IDD_PROPDEMO2))
|
||||
ps = dialog.PropertySheet("Property Sheet/Page Demo", None, [page1, page2])
|
||||
ps.DoModal()
|
||||
|
||||
|
||||
def testall():
|
||||
test1()
|
||||
test2()
|
||||
test3()
|
||||
test4()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
testall()
|
||||
@@ -0,0 +1,85 @@
|
||||
# Demo of Generic document windows, DC, and Font usage
|
||||
# by Dave Brennan (brennan@hal.com)
|
||||
|
||||
# usage examples:
|
||||
|
||||
# >>> from fontdemo import *
|
||||
# >>> d = FontDemo('Hello, Python')
|
||||
# >>> f1 = { 'name':'Arial', 'height':36, 'weight':win32con.FW_BOLD}
|
||||
# >>> d.SetFont(f1)
|
||||
# >>> f2 = {'name':'Courier New', 'height':24, 'italic':1}
|
||||
# >>> d.SetFont (f2)
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import docview
|
||||
|
||||
# font is a dictionary in which the following elements matter:
|
||||
# (the best matching font to supplied parameters is returned)
|
||||
# name string name of the font as known by Windows
|
||||
# size point size of font in logical units
|
||||
# weight weight of font (win32con.FW_NORMAL, win32con.FW_BOLD)
|
||||
# italic boolean; true if set to anything but None
|
||||
# underline boolean; true if set to anything but None
|
||||
|
||||
|
||||
class FontView(docview.ScrollView):
|
||||
def __init__(
|
||||
self, doc, text="Python Rules!", font_spec={"name": "Arial", "height": 42}
|
||||
):
|
||||
docview.ScrollView.__init__(self, doc)
|
||||
self.font = win32ui.CreateFont(font_spec)
|
||||
self.text = text
|
||||
self.width = self.height = 0
|
||||
# set up message handlers
|
||||
self.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
|
||||
def OnAttachedObjectDeath(self):
|
||||
docview.ScrollView.OnAttachedObjectDeath(self)
|
||||
del self.font
|
||||
|
||||
def SetFont(self, new_font):
|
||||
# Change font on the fly
|
||||
self.font = win32ui.CreateFont(new_font)
|
||||
# redraw the entire client window
|
||||
self.InvalidateRect(None)
|
||||
|
||||
def OnSize(self, params):
|
||||
lParam = params[3]
|
||||
self.width = win32api.LOWORD(lParam)
|
||||
self.height = win32api.HIWORD(lParam)
|
||||
|
||||
def OnPrepareDC(self, dc, printinfo):
|
||||
# Set up the DC for forthcoming OnDraw call
|
||||
self.SetScrollSizes(win32con.MM_TEXT, (100, 100))
|
||||
dc.SetTextColor(win32api.RGB(0, 0, 255))
|
||||
dc.SetBkColor(win32api.GetSysColor(win32con.COLOR_WINDOW))
|
||||
dc.SelectObject(self.font)
|
||||
dc.SetTextAlign(win32con.TA_CENTER | win32con.TA_BASELINE)
|
||||
|
||||
def OnDraw(self, dc):
|
||||
if self.width == 0 and self.height == 0:
|
||||
left, top, right, bottom = self.GetClientRect()
|
||||
self.width = right - left
|
||||
self.height = bottom - top
|
||||
x, y = self.width // 2, self.height // 2
|
||||
dc.TextOut(x, y, self.text)
|
||||
|
||||
|
||||
def FontDemo():
|
||||
# create doc/view
|
||||
template = docview.DocTemplate(win32ui.IDR_PYTHONTYPE, None, None, FontView)
|
||||
doc = template.OpenDocumentFile(None)
|
||||
doc.SetTitle("Font Demo")
|
||||
# print("template is ", template, "obj is", template._obj_)
|
||||
template.close()
|
||||
# print("closed")
|
||||
# del template
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
if demoutils.NeedGoodGUI():
|
||||
FontDemo()
|
||||
@@ -0,0 +1,53 @@
|
||||
# GUI Demo - just a worker script to invoke all the other demo/test scripts.
|
||||
import sys
|
||||
|
||||
import __main__
|
||||
import pywin.dialogs.list
|
||||
|
||||
demos = [
|
||||
("Font", "import fontdemo;fontdemo.FontDemo()"),
|
||||
("Open GL Demo", "import openGLDemo;openGLDemo.test()"),
|
||||
("Threaded GUI", "import threadedgui;threadedgui.ThreadedDemo()"),
|
||||
("Tree View Demo", "import hiertest;hiertest.demoboth()"),
|
||||
("3-Way Splitter Window", "import splittst;splittst.demo()"),
|
||||
("Custom Toolbars and Tooltips", "import toolbar;toolbar.test()"),
|
||||
("Progress Bar", "import progressbar;progressbar.demo()"),
|
||||
("Slider Control", "import sliderdemo;sliderdemo.demo()"),
|
||||
("Dynamic window creation", "import createwin;createwin.demo()"),
|
||||
("Various Dialog demos", "import dlgtest;dlgtest.demo()"),
|
||||
("OCX Control Demo", "from ocx import ocxtest;ocxtest.demo()"),
|
||||
("OCX Serial Port Demo", "from ocx import ocxserialtest; ocxserialtest.test()"),
|
||||
(
|
||||
"Internet Explorer Control Demo",
|
||||
'from ocx import webbrowser; webbrowser.Demo("https://www.python.org")',
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _exec_demo(cmd):
|
||||
try:
|
||||
exec(cmd)
|
||||
except Exception as error:
|
||||
print(f"Demo of {cmd} failed - {type(error)}:{error}")
|
||||
|
||||
|
||||
def demo():
|
||||
if "/go" in sys.argv:
|
||||
for name, cmd in demos:
|
||||
_exec_demo(cmd)
|
||||
return
|
||||
|
||||
# Otherwise allow the user to select the demo to run
|
||||
while True:
|
||||
rc = pywin.dialogs.list.SelectFromLists("Select a Demo", demos, ["Demo Title"])
|
||||
if rc is None:
|
||||
break
|
||||
title, cmd = demos[rc]
|
||||
_exec_demo(cmd)
|
||||
|
||||
|
||||
if __name__ == __main__.__name__:
|
||||
import demoutils
|
||||
|
||||
if demoutils.NeedGoodGUI():
|
||||
demo()
|
||||
@@ -0,0 +1,138 @@
|
||||
import os
|
||||
|
||||
import commctrl
|
||||
import win32ui
|
||||
from pywin.mfc import docview, window
|
||||
from pywin.tools import hierlist
|
||||
|
||||
|
||||
# directory listbox
|
||||
# This has obvious limitations - doesn't track subdirs, etc. Demonstrates
|
||||
# simple use of Python code for querying the tree as needed.
|
||||
# Only use strings, and lists of strings (from curdir())
|
||||
class DirHierList(hierlist.HierList):
|
||||
def __init__(self, root, listBoxID=win32ui.IDC_LIST1):
|
||||
hierlist.HierList.__init__(self, root, win32ui.IDB_HIERFOLDERS, listBoxID)
|
||||
|
||||
def GetText(self, item):
|
||||
return os.path.basename(item)
|
||||
|
||||
def GetSubList(self, item):
|
||||
if os.path.isdir(item):
|
||||
ret = [os.path.join(item, fname) for fname in os.listdir(item)]
|
||||
else:
|
||||
ret = None
|
||||
return ret
|
||||
|
||||
# if the item is a dir, it is expandable.
|
||||
def IsExpandable(self, item):
|
||||
return os.path.isdir(item)
|
||||
|
||||
def GetSelectedBitmapColumn(self, item):
|
||||
return self.GetBitmapColumn(item) + 6 # Use different color for selection
|
||||
|
||||
|
||||
class TestDocument(docview.Document):
|
||||
def __init__(self, template):
|
||||
docview.Document.__init__(self, template)
|
||||
self.hierlist = hierlist.HierListWithItems(
|
||||
HLIFileDir("\\"), win32ui.IDB_HIERFOLDERS, win32ui.AFX_IDW_PANE_FIRST
|
||||
)
|
||||
|
||||
|
||||
class HierListView(docview.TreeView):
|
||||
def OnInitialUpdate(self):
|
||||
rc = self._obj_.OnInitialUpdate()
|
||||
self.hierList = self.GetDocument().hierlist
|
||||
self.hierList.HierInit(self.GetParent())
|
||||
self.hierList.SetStyle(
|
||||
commctrl.TVS_HASLINES | commctrl.TVS_LINESATROOT | commctrl.TVS_HASBUTTONS
|
||||
)
|
||||
return rc
|
||||
|
||||
|
||||
class HierListFrame(window.MDIChildWnd):
|
||||
pass
|
||||
|
||||
|
||||
def GetTestRoot():
|
||||
tree1 = ("Tree 1", [("Item 1", "Item 1 data"), "Item 2", 3])
|
||||
tree2 = ("Tree 2", [("Item 2.1", "Item 2 data"), "Item 2.2", 2.3])
|
||||
return ("Root", [tree1, tree2, "Item 3"])
|
||||
|
||||
|
||||
def demoboth():
|
||||
template = docview.DocTemplate(
|
||||
win32ui.IDR_PYTHONTYPE, TestDocument, HierListFrame, HierListView
|
||||
)
|
||||
template.OpenDocumentFile(None).SetTitle("Hierlist demo")
|
||||
|
||||
demomodeless()
|
||||
|
||||
|
||||
def demomodeless():
|
||||
testList2 = DirHierList("\\")
|
||||
dlg = hierlist.HierDialog("hier list test", testList2)
|
||||
dlg.CreateWindow()
|
||||
|
||||
|
||||
def demodlg():
|
||||
testList2 = DirHierList("\\")
|
||||
dlg = hierlist.HierDialog("hier list test", testList2)
|
||||
dlg.DoModal()
|
||||
|
||||
|
||||
def demo():
|
||||
template = docview.DocTemplate(
|
||||
win32ui.IDR_PYTHONTYPE, TestDocument, HierListFrame, HierListView
|
||||
)
|
||||
template.OpenDocumentFile(None).SetTitle("Hierlist demo")
|
||||
|
||||
|
||||
#
|
||||
# Demo/Test for HierList items.
|
||||
#
|
||||
# Easy to make a better directory program.
|
||||
#
|
||||
class HLIFileDir(hierlist.HierListItem):
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
hierlist.HierListItem.__init__(self)
|
||||
|
||||
def GetText(self):
|
||||
try:
|
||||
return "%-20s %d bytes" % (
|
||||
os.path.basename(self.filename),
|
||||
os.stat(self.filename)[6],
|
||||
)
|
||||
except OSError as details:
|
||||
return "%-20s - %s" % (self.filename, details[1])
|
||||
|
||||
def IsExpandable(self):
|
||||
return os.path.isdir(self.filename)
|
||||
|
||||
def GetSubList(self):
|
||||
ret = []
|
||||
for newname in os.listdir(self.filename):
|
||||
if newname not in (".", ".."):
|
||||
ret.append(HLIFileDir(os.path.join(self.filename, newname)))
|
||||
return ret
|
||||
|
||||
|
||||
def demohli():
|
||||
template = docview.DocTemplate(
|
||||
win32ui.IDR_PYTHONTYPE,
|
||||
TestDocument,
|
||||
hierlist.HierListFrame,
|
||||
hierlist.HierListView,
|
||||
)
|
||||
template.OpenDocumentFile(None).SetTitle("Hierlist demo")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
if demoutils.HaveGoodGUI():
|
||||
demoboth()
|
||||
else:
|
||||
demodlg()
|
||||
@@ -0,0 +1,13 @@
|
||||
# Run this as a python script, to gray "close" off the edit window system menu.
|
||||
import win32con
|
||||
from pywin.framework import interact
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
if demoutils.NeedGoodGUI():
|
||||
win = interact.edit.currentView.GetParent()
|
||||
menu = win.GetSystemMenu()
|
||||
id = menu.GetMenuItemID(6)
|
||||
menu.EnableMenuItem(id, win32con.MF_BYCOMMAND | win32con.MF_GRAYED)
|
||||
print("The interactive window's 'Close' menu item is now disabled.")
|
||||
@@ -0,0 +1,57 @@
|
||||
# This is a sample file, and shows the basic framework for using an "Object" based
|
||||
# document, rather than a "filename" based document.
|
||||
# This is referenced by the Pythonwin .html documentation.
|
||||
|
||||
# In the example below, the OpenObject() method is used instead of OpenDocumentFile,
|
||||
# and all the core MFC document open functionality is retained.
|
||||
|
||||
import win32ui
|
||||
from pywin.mfc import docview
|
||||
|
||||
|
||||
class object_template(docview.DocTemplate):
|
||||
def __init__(self):
|
||||
docview.DocTemplate.__init__(self, None, None, None, object_view)
|
||||
|
||||
def OpenObject(self, object): # Use this instead of OpenDocumentFile.
|
||||
# Look for existing open document
|
||||
for doc in self.GetDocumentList():
|
||||
print("document is ", doc)
|
||||
if doc.object is object:
|
||||
doc.GetFirstView().ActivateFrame()
|
||||
return doc
|
||||
# not found - new one.
|
||||
doc = object_document(self, object)
|
||||
frame = self.CreateNewFrame(doc)
|
||||
doc.OnNewDocument()
|
||||
doc.SetTitle(str(object))
|
||||
self.InitialUpdateFrame(frame, doc)
|
||||
return doc
|
||||
|
||||
|
||||
class object_document(docview.Document):
|
||||
def __init__(self, template, object):
|
||||
docview.Document.__init__(self, template)
|
||||
self.object = object
|
||||
|
||||
def OnOpenDocument(self, name):
|
||||
raise RuntimeError("Should not be called if template strings set up correctly")
|
||||
return 0
|
||||
|
||||
|
||||
class object_view(docview.EditView):
|
||||
def OnInitialUpdate(self):
|
||||
self.ReplaceSel(f"Object is {self.GetDocument().object!r}")
|
||||
|
||||
|
||||
def demo():
|
||||
t = object_template()
|
||||
d = t.OpenObject(win32ui)
|
||||
return (t, d)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
if demoutils.NeedGoodGUI():
|
||||
demo()
|
||||
@@ -0,0 +1,63 @@
|
||||
# Utilities for the demos
|
||||
|
||||
import sys
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
NotScriptMsg = """\
|
||||
This demo program is not designed to be run as a Script, but is
|
||||
probably used by some other test program. Please try another demo.
|
||||
"""
|
||||
|
||||
NeedGUIMsg = """\
|
||||
This demo program can only be run from inside of Pythonwin
|
||||
|
||||
You must start Pythonwin, and select 'Run' from the toolbar or File menu
|
||||
"""
|
||||
|
||||
|
||||
NeedAppMsg = """\
|
||||
This demo program is a 'Pythonwin Application'.
|
||||
|
||||
It is more demo code than an example of Pythonwin's capabilities.
|
||||
|
||||
To run it, you must execute the command:
|
||||
pythonwin.exe /app "%s"
|
||||
|
||||
Would you like to execute it now?
|
||||
"""
|
||||
|
||||
|
||||
def NotAScript():
|
||||
import win32ui
|
||||
|
||||
win32ui.MessageBox(NotScriptMsg, "Demos")
|
||||
|
||||
|
||||
def NeedGoodGUI():
|
||||
from pywin.framework.app import HaveGoodGUI
|
||||
|
||||
rc = HaveGoodGUI()
|
||||
if not rc:
|
||||
win32ui.MessageBox(NeedGUIMsg, "Demos")
|
||||
return rc
|
||||
|
||||
|
||||
def NeedApp():
|
||||
import win32ui
|
||||
|
||||
rc = win32ui.MessageBox(NeedAppMsg % sys.argv[0], "Demos", win32con.MB_YESNO)
|
||||
if rc == win32con.IDYES:
|
||||
try:
|
||||
parent = win32ui.GetMainFrame().GetSafeHwnd()
|
||||
win32api.ShellExecute(
|
||||
parent, None, "pythonwin.exe", '/app "%s"' % sys.argv[0], None, 1
|
||||
)
|
||||
except win32api.error as details:
|
||||
win32ui.MessageBox("Error executing command - %s" % (details), "Demos")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
NotAScript()
|
||||
@@ -0,0 +1,95 @@
|
||||
# By Bradley Schatz
|
||||
# simple flash/python application demonstrating bidirectional
|
||||
# communicaion between flash and python. Click the sphere to see
|
||||
# behavior. Uses Bounce.swf from FlashBounce.zip, available from
|
||||
# https://cspages.ucalgary.ca/~saul/vb_examples/tutorial12/
|
||||
|
||||
# Update to the path of the .swf file (note it could be a true URL)
|
||||
flash_url = "c:\\bounce.swf"
|
||||
|
||||
import sys
|
||||
|
||||
import regutil
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import activex, window
|
||||
from win32com.client import gencache
|
||||
|
||||
FlashModule = gencache.EnsureModule("{D27CDB6B-AE6D-11CF-96B8-444553540000}", 0, 1, 0)
|
||||
|
||||
if FlashModule is None:
|
||||
raise ImportError("Flash does not appear to be installed.")
|
||||
|
||||
|
||||
class MyFlashComponent(activex.Control, FlashModule.ShockwaveFlash):
|
||||
def __init__(self):
|
||||
activex.Control.__init__(self)
|
||||
FlashModule.ShockwaveFlash.__init__(self)
|
||||
self.x = 50
|
||||
self.y = 50
|
||||
self.angle = 30
|
||||
self.started = 0
|
||||
|
||||
def OnFSCommand(self, command, args):
|
||||
print("FSCommend", command, args)
|
||||
self.x += 20
|
||||
self.y += 20
|
||||
self.angle += 20
|
||||
if self.x > 200 or self.y > 200:
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
if self.angle > 360:
|
||||
self.angle = 0
|
||||
self.SetVariable("xVal", self.x)
|
||||
self.SetVariable("yVal", self.y)
|
||||
self.SetVariable("angle", self.angle)
|
||||
self.TPlay("_root.mikeBall")
|
||||
|
||||
def OnProgress(self, percentDone):
|
||||
print("PercentDone", percentDone)
|
||||
|
||||
def OnReadyStateChange(self, newState):
|
||||
# 0=Loading, 1=Uninitialized, 2=Loaded, 3=Interactive, 4=Complete
|
||||
print("State", newState)
|
||||
|
||||
|
||||
class BrowserFrame(window.MDIChildWnd):
|
||||
def __init__(self, url=None):
|
||||
if url is None:
|
||||
self.url = regutil.GetRegisteredHelpFile("Main Python Documentation")
|
||||
else:
|
||||
self.url = url
|
||||
pass # Don't call base class doc/view version...
|
||||
|
||||
def Create(self, title, rect=None, parent=None):
|
||||
style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.WS_OVERLAPPEDWINDOW
|
||||
self._obj_ = win32ui.CreateMDIChild()
|
||||
self._obj_.AttachObject(self)
|
||||
self._obj_.CreateWindow(None, title, style, rect, parent)
|
||||
rect = self.GetClientRect()
|
||||
rect = (0, 0, rect[2] - rect[0], rect[3] - rect[1])
|
||||
self.ocx = MyFlashComponent()
|
||||
self.ocx.CreateControl(
|
||||
"Flash Player", win32con.WS_VISIBLE | win32con.WS_CHILD, rect, self, 1000
|
||||
)
|
||||
self.ocx.LoadMovie(0, flash_url)
|
||||
self.ocx.Play()
|
||||
self.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
|
||||
def OnSize(self, params):
|
||||
rect = self.GetClientRect()
|
||||
rect = (0, 0, rect[2] - rect[0], rect[3] - rect[1])
|
||||
self.ocx.SetWindowPos(0, rect, 0)
|
||||
|
||||
|
||||
def Demo():
|
||||
url = None
|
||||
if len(sys.argv) > 1:
|
||||
url = win32api.GetFullPathName(sys.argv[1])
|
||||
f = BrowserFrame(url)
|
||||
f.Create("Flash Player")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Demo()
|
||||
@@ -0,0 +1,159 @@
|
||||
# This demo uses some of the Microsoft Office components.
|
||||
#
|
||||
# It was taken from an MSDN article showing how to embed excel.
|
||||
# It is not comlpete yet, but it _does_ show an Excel spreadsheet in a frame!
|
||||
#
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
import win32uiole
|
||||
from pywin.mfc import activex, docview, object, window
|
||||
from win32com.client import gencache
|
||||
|
||||
|
||||
class OleClientItem(object.CmdTarget):
|
||||
def __init__(self, doc):
|
||||
object.CmdTarget.__init__(self, win32uiole.CreateOleClientItem(doc))
|
||||
|
||||
def OnGetItemPosition(self):
|
||||
# For now return a hard-coded rect.
|
||||
return (10, 10, 210, 210)
|
||||
|
||||
def OnActivate(self):
|
||||
# Allow only one inplace activate item per frame
|
||||
view = self.GetActiveView()
|
||||
item = self.GetDocument().GetInPlaceActiveItem(view)
|
||||
if item is not None and item._obj_ != self._obj_:
|
||||
item.Close()
|
||||
self._obj_.OnActivate()
|
||||
|
||||
def OnChange(self, oleNotification, dwParam):
|
||||
self._obj_.OnChange(oleNotification, dwParam)
|
||||
self.GetDocument().UpdateAllViews(None)
|
||||
|
||||
def OnChangeItemPosition(self, rect):
|
||||
# During in-place activation CEmbed_ExcelCntrItem::OnChangeItemPosition
|
||||
# is called by the server to change the position of the in-place
|
||||
# window. Usually, this is a result of the data in the server
|
||||
# document changing such that the extent has changed or as a result
|
||||
# of in-place resizing.
|
||||
#
|
||||
# The default here is to call the base class, which will call
|
||||
# COleClientItem::SetItemRects to move the item
|
||||
# to the new position.
|
||||
if not self._obj_.OnChangeItemPosition(self, rect):
|
||||
return 0
|
||||
|
||||
# TODO: update any cache you may have of the item's rectangle/extent
|
||||
return 1
|
||||
|
||||
|
||||
class OleDocument(object.CmdTarget):
|
||||
def __init__(self, template):
|
||||
object.CmdTarget.__init__(self, win32uiole.CreateOleDocument(template))
|
||||
self.EnableCompoundFile()
|
||||
|
||||
|
||||
class ExcelView(docview.ScrollView):
|
||||
def OnInitialUpdate(self):
|
||||
self.HookMessage(self.OnSetFocus, win32con.WM_SETFOCUS)
|
||||
self.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
|
||||
self.SetScrollSizes(win32con.MM_TEXT, (100, 100))
|
||||
rc = self._obj_.OnInitialUpdate()
|
||||
self.EmbedExcel()
|
||||
return rc
|
||||
|
||||
def EmbedExcel(self):
|
||||
doc = self.GetDocument()
|
||||
self.clientItem = OleClientItem(doc)
|
||||
self.clientItem.CreateNewItem("Excel.Sheet")
|
||||
self.clientItem.DoVerb(-1, self)
|
||||
doc.UpdateAllViews(None)
|
||||
|
||||
def OnDraw(self, dc):
|
||||
doc = self.GetDocument()
|
||||
pos = doc.GetStartPosition()
|
||||
clientItem, pos = doc.GetNextItem(pos)
|
||||
clientItem.Draw(dc, (10, 10, 210, 210))
|
||||
|
||||
# Special handling of OnSetFocus and OnSize are required for a container
|
||||
# when an object is being edited in-place.
|
||||
def OnSetFocus(self, msg):
|
||||
item = self.GetDocument().GetInPlaceActiveItem(self)
|
||||
if (
|
||||
item is not None
|
||||
and item.GetItemState() == win32uiole.COleClientItem_activeUIState
|
||||
):
|
||||
wnd = item.GetInPlaceWindow()
|
||||
if wnd is not None:
|
||||
wnd.SetFocus()
|
||||
return 0 # Don't get the base version called.
|
||||
return 1 # Call the base version.
|
||||
|
||||
def OnSize(self, params):
|
||||
item = self.GetDocument().GetInPlaceActiveItem(self)
|
||||
if item is not None:
|
||||
item.SetItemRects()
|
||||
return 1 # do call the base!
|
||||
|
||||
|
||||
class OleTemplate(docview.DocTemplate):
|
||||
def __init__(
|
||||
self, resourceId=None, MakeDocument=None, MakeFrame=None, MakeView=None
|
||||
):
|
||||
if MakeDocument is None:
|
||||
MakeDocument = OleDocument
|
||||
if MakeView is None:
|
||||
MakeView = ExcelView
|
||||
docview.DocTemplate.__init__(
|
||||
self, resourceId, MakeDocument, MakeFrame, MakeView
|
||||
)
|
||||
|
||||
|
||||
class WordFrame(window.MDIChildWnd):
|
||||
def __init__(self, doc=None):
|
||||
self._obj_ = win32ui.CreateMDIChild()
|
||||
self._obj_.AttachObject(self)
|
||||
# Don't call base class doc/view version...
|
||||
|
||||
def Create(self, title, rect=None, parent=None):
|
||||
WordModule = gencache.EnsureModule(
|
||||
"{00020905-0000-0000-C000-000000000046}", 1033, 8, 0
|
||||
)
|
||||
if WordModule is None:
|
||||
raise ImportError(
|
||||
"Microsoft Word version 8 does not appear to be installed."
|
||||
)
|
||||
|
||||
# WordModule.Word doesn't exist in WordModule, WordModule.Words does, but CreateControl still fails
|
||||
class MyWordControl(activex.Control, WordModule.Word): ...
|
||||
|
||||
style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.WS_OVERLAPPEDWINDOW
|
||||
self._obj_.CreateWindow(None, title, style, rect, parent)
|
||||
|
||||
rect = self.GetClientRect()
|
||||
rect = (0, 0, rect[2] - rect[0], rect[3] - rect[1])
|
||||
self.ocx = MyWordControl()
|
||||
self.ocx.CreateControl(
|
||||
"Microsoft Word", win32con.WS_VISIBLE | win32con.WS_CHILD, rect, self, 20000
|
||||
)
|
||||
|
||||
|
||||
def Demo():
|
||||
import sys
|
||||
|
||||
import win32api
|
||||
|
||||
docName = None
|
||||
if len(sys.argv) > 1:
|
||||
docName = win32api.GetFullPathName(sys.argv[1])
|
||||
OleTemplate().OpenDocumentFile(docName)
|
||||
|
||||
# ActiveX not currently working
|
||||
# f = WordFrame(docName)
|
||||
# f.Create("Microsoft Office")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Demo()
|
||||
@@ -0,0 +1,132 @@
|
||||
# ocxserialtest.py
|
||||
#
|
||||
# Sample that uses the mscomm OCX to talk to a serial
|
||||
# device.
|
||||
|
||||
# Very simple - queries a modem for ATI responses
|
||||
|
||||
import pythoncom
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import activex, dialog
|
||||
from win32com.client import gencache
|
||||
|
||||
SERIAL_SETTINGS = "19200,n,8,1"
|
||||
SERIAL_PORT = 2
|
||||
|
||||
win32ui.DoWaitCursor(1)
|
||||
serialModule = gencache.EnsureModule("{648A5603-2C6E-101B-82B6-000000000014}", 0, 1, 1)
|
||||
win32ui.DoWaitCursor(0)
|
||||
if serialModule is None:
|
||||
raise ImportError("MS COMM Control does not appear to be installed on the PC")
|
||||
|
||||
|
||||
def MakeDlgTemplate():
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
cs = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
dlg = [
|
||||
["Very Basic Terminal", (0, 0, 350, 180), style, None, (8, "MS Sans Serif")],
|
||||
]
|
||||
s = win32con.WS_TABSTOP | cs
|
||||
dlg.append(
|
||||
[
|
||||
"RICHEDIT",
|
||||
None,
|
||||
132,
|
||||
(5, 5, 340, 170),
|
||||
s
|
||||
| win32con.ES_WANTRETURN
|
||||
| win32con.ES_MULTILINE
|
||||
| win32con.ES_AUTOVSCROLL
|
||||
| win32con.WS_VSCROLL,
|
||||
]
|
||||
)
|
||||
return dlg
|
||||
|
||||
|
||||
####################################
|
||||
#
|
||||
# Serial Control
|
||||
#
|
||||
class MySerialControl(activex.Control, serialModule.MSComm):
|
||||
def __init__(self, parent):
|
||||
activex.Control.__init__(self)
|
||||
serialModule.MSComm.__init__(self)
|
||||
self.parent = parent
|
||||
|
||||
def OnComm(self):
|
||||
self.parent.OnComm()
|
||||
|
||||
|
||||
class TestSerDialog(dialog.Dialog):
|
||||
def __init__(self, *args):
|
||||
dialog.Dialog.__init__(*(self,) + args)
|
||||
self.olectl = None
|
||||
|
||||
def OnComm(self):
|
||||
event = self.olectl.CommEvent
|
||||
if event == serialModule.OnCommConstants.comEvReceive:
|
||||
self.editwindow.ReplaceSel(self.olectl.Input)
|
||||
|
||||
def OnKey(self, key):
|
||||
if self.olectl:
|
||||
self.olectl.Output = chr(key)
|
||||
|
||||
def OnInitDialog(self):
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
self.editwindow = self.GetDlgItem(132)
|
||||
self.editwindow.HookAllKeyStrokes(self.OnKey)
|
||||
|
||||
self.olectl = MySerialControl(self)
|
||||
try:
|
||||
self.olectl.CreateControl(
|
||||
"OCX",
|
||||
win32con.WS_TABSTOP | win32con.WS_VISIBLE,
|
||||
(7, 43, 500, 300),
|
||||
self._obj_,
|
||||
131,
|
||||
)
|
||||
except win32ui.error:
|
||||
self.MessageBox("The Serial Control could not be created")
|
||||
self.olectl = None
|
||||
self.EndDialog(win32con.IDCANCEL)
|
||||
if self.olectl:
|
||||
self.olectl.Settings = SERIAL_SETTINGS
|
||||
self.olectl.CommPort = SERIAL_PORT
|
||||
self.olectl.RThreshold = 1
|
||||
try:
|
||||
self.olectl.PortOpen = 1
|
||||
except pythoncom.com_error as details:
|
||||
print(
|
||||
"Could not open the specified serial port - %s"
|
||||
% (details.excepinfo[2])
|
||||
)
|
||||
self.EndDialog(win32con.IDCANCEL)
|
||||
return rc
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
if self.olectl:
|
||||
try:
|
||||
self.olectl.PortOpen = 0
|
||||
except pythoncom.com_error as details:
|
||||
print("Error closing port - %s" % (details.excepinfo[2]))
|
||||
return dialog.Dialog.OnDestroy(self, msg)
|
||||
|
||||
|
||||
def test():
|
||||
d = TestSerDialog(MakeDlgTemplate())
|
||||
d.DoModal()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
if demoutils.NeedGoodGUI():
|
||||
test()
|
||||
@@ -0,0 +1,243 @@
|
||||
# OCX Tester for Pythonwin
|
||||
#
|
||||
# This file _is_ ready to run. All that is required is that the OCXs being tested
|
||||
# are installed on your machine.
|
||||
#
|
||||
# The .py files behind the OCXs will be automatically generated and imported.
|
||||
|
||||
import glob
|
||||
import os
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import activex, dialog, window
|
||||
from win32com.client import gencache
|
||||
|
||||
|
||||
def MakeDlgTemplate():
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
cs = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
dlg = [
|
||||
["OCX Demos", (0, 0, 350, 350), style, None, (8, "MS Sans Serif")],
|
||||
]
|
||||
s = win32con.WS_TABSTOP | cs
|
||||
# dlg.append([131, None, 130, (5, 40, 110, 48),
|
||||
# s | win32con.LBS_NOTIFY | win32con.LBS_SORT | win32con.LBS_NOINTEGRALHEIGHT | win32con.WS_VSCROLL | win32con.WS_BORDER])
|
||||
# dlg.append(["{8E27C92B-1264-101C-8A2F-040224009C02}", None, 131, (5, 40, 110, 48),win32con.WS_TABSTOP])
|
||||
|
||||
dlg.append(
|
||||
[128, "About", win32con.IDOK, (124, 5, 50, 14), s | win32con.BS_DEFPUSHBUTTON]
|
||||
)
|
||||
s = win32con.BS_PUSHBUTTON | s
|
||||
dlg.append([128, "Close", win32con.IDCANCEL, (124, 22, 50, 14), s])
|
||||
|
||||
return dlg
|
||||
|
||||
|
||||
####################################
|
||||
#
|
||||
# Calendar test code
|
||||
#
|
||||
|
||||
|
||||
def GetTestCalendarClass():
|
||||
global calendarParentModule
|
||||
win32ui.DoWaitCursor(1)
|
||||
calendarParentModule = gencache.EnsureModule(
|
||||
"{8E27C92E-1264-101C-8A2F-040224009C02}", 0, 7, 0
|
||||
)
|
||||
win32ui.DoWaitCursor(0)
|
||||
if calendarParentModule is None:
|
||||
return None
|
||||
|
||||
class TestCalDialog(dialog.Dialog):
|
||||
def OnInitDialog(self):
|
||||
class MyCal(activex.Control, calendarParentModule.Calendar):
|
||||
def OnAfterUpdate(self):
|
||||
print("OnAfterUpdate")
|
||||
|
||||
def OnClick(self):
|
||||
print("OnClick")
|
||||
|
||||
def OnDblClick(self):
|
||||
print("OnDblClick")
|
||||
|
||||
def OnKeyDown(self, KeyCode, Shift):
|
||||
print("OnKeyDown", KeyCode, Shift)
|
||||
|
||||
def OnKeyPress(self, KeyAscii):
|
||||
print("OnKeyPress", KeyAscii)
|
||||
|
||||
def OnKeyUp(self, KeyCode, Shift):
|
||||
print("OnKeyUp", KeyCode, Shift)
|
||||
|
||||
def OnBeforeUpdate(self, Cancel):
|
||||
print("OnBeforeUpdate", Cancel)
|
||||
|
||||
def OnNewMonth(self):
|
||||
print("OnNewMonth")
|
||||
|
||||
def OnNewYear(self):
|
||||
print("OnNewYear")
|
||||
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
self.olectl = MyCal()
|
||||
try:
|
||||
self.olectl.CreateControl(
|
||||
"OCX",
|
||||
win32con.WS_TABSTOP | win32con.WS_VISIBLE,
|
||||
(7, 43, 500, 300),
|
||||
self._obj_,
|
||||
131,
|
||||
)
|
||||
except win32ui.error:
|
||||
self.MessageBox("The Calendar Control could not be created")
|
||||
self.olectl = None
|
||||
self.EndDialog(win32con.IDCANCEL)
|
||||
|
||||
return rc
|
||||
|
||||
def OnOK(self):
|
||||
self.olectl.AboutBox()
|
||||
|
||||
return TestCalDialog
|
||||
|
||||
|
||||
####################################
|
||||
#
|
||||
# Video Control
|
||||
#
|
||||
def GetTestVideoModule():
|
||||
global videoControlModule, videoControlFileName
|
||||
win32ui.DoWaitCursor(1)
|
||||
videoControlModule = gencache.EnsureModule(
|
||||
"{05589FA0-C356-11CE-BF01-00AA0055595A}", 0, 2, 0
|
||||
)
|
||||
win32ui.DoWaitCursor(0)
|
||||
if videoControlModule is None:
|
||||
return None
|
||||
fnames = glob.glob(os.path.join(win32api.GetWindowsDirectory(), "*.avi"))
|
||||
if not fnames:
|
||||
print("No AVI files available in system directory")
|
||||
return None
|
||||
videoControlFileName = fnames[0]
|
||||
return videoControlModule
|
||||
|
||||
|
||||
def GetTestVideoDialogClass():
|
||||
if GetTestVideoModule() is None:
|
||||
return None
|
||||
|
||||
class TestVideoDialog(dialog.Dialog):
|
||||
def OnInitDialog(self):
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
try:
|
||||
self.olectl = activex.MakeControlInstance(
|
||||
videoControlModule.ActiveMovie
|
||||
)
|
||||
self.olectl.CreateControl(
|
||||
"",
|
||||
win32con.WS_TABSTOP | win32con.WS_VISIBLE,
|
||||
(7, 43, 500, 300),
|
||||
self._obj_,
|
||||
131,
|
||||
)
|
||||
except win32ui.error:
|
||||
self.MessageBox("The Video Control could not be created")
|
||||
self.olectl = None
|
||||
self.EndDialog(win32con.IDCANCEL)
|
||||
return
|
||||
|
||||
self.olectl.FileName = videoControlFileName
|
||||
# self.olectl.Run()
|
||||
return rc
|
||||
|
||||
def OnOK(self):
|
||||
self.olectl.AboutBox()
|
||||
|
||||
return TestVideoDialog
|
||||
|
||||
|
||||
###############
|
||||
#
|
||||
# An OCX in an MDI Frame
|
||||
#
|
||||
class OCXFrame(window.MDIChildWnd):
|
||||
def __init__(self):
|
||||
pass # Don't call base class doc/view version...
|
||||
|
||||
def Create(self, controlClass, title, rect=None, parent=None):
|
||||
style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.WS_OVERLAPPEDWINDOW
|
||||
self._obj_ = win32ui.CreateMDIChild()
|
||||
self._obj_.AttachObject(self)
|
||||
self._obj_.CreateWindow(None, title, style, rect, parent)
|
||||
|
||||
rect = self.GetClientRect()
|
||||
rect = (0, 0, rect[2] - rect[0], rect[3] - rect[1])
|
||||
self.ocx = controlClass()
|
||||
self.ocx.CreateControl(
|
||||
"", win32con.WS_VISIBLE | win32con.WS_CHILD, rect, self, 1000
|
||||
)
|
||||
|
||||
|
||||
def MDITest():
|
||||
calendarParentModule = gencache.EnsureModule(
|
||||
"{8E27C92E-1264-101C-8A2F-040224009C02}", 0, 7, 0
|
||||
)
|
||||
|
||||
class MyCal(activex.Control, calendarParentModule.Calendar):
|
||||
def OnAfterUpdate(self):
|
||||
print("OnAfterUpdate")
|
||||
|
||||
def OnClick(self):
|
||||
print("OnClick")
|
||||
|
||||
f = OCXFrame()
|
||||
f.Create(MyCal, "Calendar Test")
|
||||
|
||||
|
||||
def test1():
|
||||
klass = GetTestCalendarClass()
|
||||
if klass is None:
|
||||
print(
|
||||
"Can not test the MSAccess Calendar control - it does not appear to be installed"
|
||||
)
|
||||
return
|
||||
|
||||
d = klass(MakeDlgTemplate())
|
||||
d.DoModal()
|
||||
|
||||
|
||||
def test2():
|
||||
klass = GetTestVideoDialogClass()
|
||||
if klass is None:
|
||||
print("Can not test the Video OCX - it does not appear to be installed,")
|
||||
print("or no AVI files can be found.")
|
||||
return
|
||||
d = klass(MakeDlgTemplate())
|
||||
d.DoModal()
|
||||
d = None
|
||||
|
||||
|
||||
def testall():
|
||||
test1()
|
||||
test2()
|
||||
|
||||
|
||||
def demo():
|
||||
testall()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
if demoutils.NeedGoodGUI():
|
||||
testall()
|
||||
@@ -0,0 +1,72 @@
|
||||
# This demo uses the Internet Explorer Web Browser control.
|
||||
|
||||
# It catches an "OnNavigate" event, and updates the frame title.
|
||||
# (event stuff by Neil Hodgson)
|
||||
|
||||
import sys
|
||||
|
||||
import regutil
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import activex, window
|
||||
from win32com.client import gencache
|
||||
|
||||
WebBrowserModule = gencache.EnsureModule(
|
||||
"{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}", 0, 1, 1
|
||||
)
|
||||
if WebBrowserModule is None:
|
||||
raise ImportError("Internet Explorer does not appear to be installed.")
|
||||
|
||||
|
||||
class MyWebBrowser(activex.Control, WebBrowserModule.WebBrowser):
|
||||
def OnBeforeNavigate2(
|
||||
self, pDisp, URL, Flags, TargetFrameName, PostData, Headers, Cancel
|
||||
):
|
||||
self.GetParent().OnNavigate(URL)
|
||||
# print("BeforeNavigate2", pDisp, URL, Flags, TargetFrameName, PostData, Headers, Cancel)
|
||||
|
||||
|
||||
class BrowserFrame(window.MDIChildWnd):
|
||||
def __init__(self, url=None):
|
||||
if url is None:
|
||||
self.url = regutil.GetRegisteredHelpFile("Main Python Documentation")
|
||||
if self.url is None:
|
||||
self.url = "https://www.python.org"
|
||||
else:
|
||||
self.url = url
|
||||
pass # Don't call base class doc/view version...
|
||||
|
||||
def Create(self, title, rect=None, parent=None):
|
||||
style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.WS_OVERLAPPEDWINDOW
|
||||
self._obj_ = win32ui.CreateMDIChild()
|
||||
self._obj_.AttachObject(self)
|
||||
self._obj_.CreateWindow(None, title, style, rect, parent)
|
||||
rect = self.GetClientRect()
|
||||
rect = (0, 0, rect[2] - rect[0], rect[3] - rect[1])
|
||||
self.ocx = MyWebBrowser()
|
||||
self.ocx.CreateControl(
|
||||
"Web Browser", win32con.WS_VISIBLE | win32con.WS_CHILD, rect, self, 1000
|
||||
)
|
||||
self.ocx.Navigate(self.url)
|
||||
self.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
|
||||
def OnSize(self, params):
|
||||
rect = self.GetClientRect()
|
||||
rect = (0, 0, rect[2] - rect[0], rect[3] - rect[1])
|
||||
self.ocx.SetWindowPos(0, rect, 0)
|
||||
|
||||
def OnNavigate(self, url):
|
||||
title = f"Web Browser - {url}"
|
||||
self.SetWindowText(title)
|
||||
|
||||
|
||||
def Demo(url=None):
|
||||
if url is None and len(sys.argv) > 1:
|
||||
url = win32api.GetFullPathName(sys.argv[1])
|
||||
f = BrowserFrame(url)
|
||||
f.Create("Web Browser")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Demo()
|
||||
@@ -0,0 +1,415 @@
|
||||
# Ported from the win32 and MFC OpenGL Samples.
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from OpenGL.GL import (
|
||||
GL_COLOR_BUFFER_BIT,
|
||||
GL_DEPTH_BUFFER_BIT,
|
||||
GL_DEPTH_TEST,
|
||||
GL_MODELVIEW,
|
||||
GL_PROJECTION,
|
||||
GL_QUAD_STRIP,
|
||||
GL_QUADS,
|
||||
GL_TRIANGLE_FAN,
|
||||
glBegin,
|
||||
glClear,
|
||||
glClearColor,
|
||||
glClearDepth,
|
||||
glColor3f,
|
||||
glEnable,
|
||||
glEnd,
|
||||
glFinish,
|
||||
glLoadIdentity,
|
||||
glMatrixMode,
|
||||
glPopMatrix,
|
||||
glPushMatrix,
|
||||
glRotatef,
|
||||
glTranslatef,
|
||||
glVertex3f,
|
||||
glViewport,
|
||||
)
|
||||
from OpenGL.GLU import (
|
||||
GLU_FILL,
|
||||
GLU_SMOOTH,
|
||||
gluCylinder,
|
||||
gluNewQuadric,
|
||||
gluPerspective,
|
||||
gluQuadricDrawStyle,
|
||||
gluQuadricNormals,
|
||||
)
|
||||
from OpenGL.WGL import (
|
||||
PIXELFORMATDESCRIPTOR,
|
||||
ChoosePixelFormat,
|
||||
DescribePixelFormat,
|
||||
GetPixelFormat,
|
||||
SetPixelFormat,
|
||||
SwapBuffers,
|
||||
wglCreateContext,
|
||||
wglDeleteContext,
|
||||
wglGetCurrentContext,
|
||||
wglGetCurrentDC,
|
||||
wglMakeCurrent,
|
||||
)
|
||||
except ImportError:
|
||||
print("The OpenGL extensions do not appear to be installed.")
|
||||
print("This Pythonwin demo can not run")
|
||||
sys.exit(1)
|
||||
|
||||
import timer
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import docview
|
||||
|
||||
PFD_TYPE_RGBA = 0
|
||||
PFD_TYPE_COLORINDEX = 1
|
||||
PFD_MAIN_PLANE = 0
|
||||
PFD_OVERLAY_PLANE = 1
|
||||
PFD_UNDERLAY_PLANE = -1
|
||||
PFD_DOUBLEBUFFER = 0x00000001
|
||||
PFD_STEREO = 0x00000002
|
||||
PFD_DRAW_TO_WINDOW = 0x00000004
|
||||
PFD_DRAW_TO_BITMAP = 0x00000008
|
||||
PFD_SUPPORT_GDI = 0x00000010
|
||||
PFD_SUPPORT_OPENGL = 0x00000020
|
||||
PFD_GENERIC_FORMAT = 0x00000040
|
||||
PFD_NEED_PALETTE = 0x00000080
|
||||
PFD_NEED_SYSTEM_PALETTE = 0x00000100
|
||||
PFD_SWAP_EXCHANGE = 0x00000200
|
||||
PFD_SWAP_COPY = 0x00000400
|
||||
PFD_SWAP_LAYER_BUFFERS = 0x00000800
|
||||
PFD_GENERIC_ACCELERATED = 0x00001000
|
||||
PFD_DEPTH_DONTCARE = 0x20000000
|
||||
PFD_DOUBLEBUFFER_DONTCARE = 0x40000000
|
||||
PFD_STEREO_DONTCARE = 0x80000000
|
||||
|
||||
|
||||
# threeto8 = [0, 0o111>>1, 0o222>>1, 0o333>>1, 0o444>>1, 0o555>>1, 0o666>>1, 0o377]
|
||||
threeto8 = [0, 73 >> 1, 146 >> 1, 219 >> 1, 292 >> 1, 365 >> 1, 438 >> 1, 255]
|
||||
twoto8 = [0, 0x55, 0xAA, 0xFF]
|
||||
oneto8 = [0, 255]
|
||||
|
||||
|
||||
def ComponentFromIndex(i, nbits, shift):
|
||||
# val = (unsigned char) (i >> shift);
|
||||
val = (i >> shift) & 0xF
|
||||
if nbits == 1:
|
||||
val &= 0x1
|
||||
return oneto8[val]
|
||||
elif nbits == 2:
|
||||
val &= 0x3
|
||||
return twoto8[val]
|
||||
elif nbits == 3:
|
||||
val &= 0x7
|
||||
return threeto8[val]
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
OpenGLViewParent = docview.ScrollView
|
||||
|
||||
|
||||
class OpenGLView(OpenGLViewParent):
|
||||
def PreCreateWindow(self, cc):
|
||||
self.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
# An OpenGL window must be created with the following flags and must not
|
||||
# include CS_PARENTDC for the class style. Refer to SetPixelFormat
|
||||
# documentation in the "Comments" section for further information.
|
||||
style = cc[5]
|
||||
style |= win32con.WS_CLIPSIBLINGS | win32con.WS_CLIPCHILDREN
|
||||
cc = cc[0], cc[1], cc[2], cc[3], cc[4], style, cc[6], cc[7], cc[8]
|
||||
cc = self._obj_.PreCreateWindow(cc)
|
||||
return cc
|
||||
|
||||
def OnSize(self, params):
|
||||
lParam = params[3]
|
||||
cx = win32api.LOWORD(lParam)
|
||||
cy = win32api.HIWORD(lParam)
|
||||
glViewport(0, 0, cx, cy)
|
||||
|
||||
if self.oldrect[2] > cx or self.oldrect[3] > cy:
|
||||
self.RedrawWindow()
|
||||
|
||||
self.OnSizeChange(cx, cy)
|
||||
|
||||
self.oldrect = self.oldrect[0], self.oldrect[1], cx, cy
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
self.SetScaleToFitSize(
|
||||
(100, 100)
|
||||
) # or SetScrollSizes() - A Pythonwin requirement
|
||||
return self._obj_.OnInitialUpdate()
|
||||
|
||||
# return rc
|
||||
|
||||
def OnCreate(self, cs):
|
||||
self.oldrect = self.GetClientRect()
|
||||
self._InitContexts()
|
||||
self.Init()
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
self.Term()
|
||||
self._DestroyContexts()
|
||||
return OpenGLViewParent.OnDestroy(self, msg)
|
||||
|
||||
def OnDraw(self, dc):
|
||||
self.DrawScene()
|
||||
|
||||
def OnEraseBkgnd(self, dc):
|
||||
return 1
|
||||
|
||||
# The OpenGL helpers
|
||||
def _SetupPixelFormat(self):
|
||||
dc = self.dc.GetSafeHdc()
|
||||
pfd = PIXELFORMATDESCRIPTOR()
|
||||
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER
|
||||
pfd.iPixelType = PFD_TYPE_RGBA
|
||||
pfd.cColorBits = 24
|
||||
pfd.cDepthBits = 32
|
||||
pfd.iLayerType = PFD_MAIN_PLANE
|
||||
pixelformat = ChoosePixelFormat(dc, pfd)
|
||||
SetPixelFormat(dc, pixelformat, pfd)
|
||||
self._CreateRGBPalette(pfd)
|
||||
|
||||
def _CreateRGBPalette(self, pfd):
|
||||
hdc = self.dc.GetSafeHdc()
|
||||
iPixelFormat = GetPixelFormat(hdc)
|
||||
DescribePixelFormat(hdc, iPixelFormat, pfd.nSize, pfd)
|
||||
if pfd.dwFlags & PFD_NEED_PALETTE:
|
||||
iPixelFormat = 1 << pfd.cColorBits
|
||||
pal = []
|
||||
for i in range(iPixelFormat):
|
||||
this = (
|
||||
ComponentFromIndex(i, pfd.cRedBits, pfd.cRedShift),
|
||||
ComponentFromIndex(i, pfd.cGreenBits, pfd.cGreenShift),
|
||||
ComponentFromIndex(i, pfd.cBlueBits, pfd.cBlueShift),
|
||||
0,
|
||||
)
|
||||
pal.append(this)
|
||||
hpal = win32ui.CreatePalette(pal)
|
||||
self.dc.SelectPalette(hpal, 0)
|
||||
self.dc.RealizePalette()
|
||||
|
||||
def _InitContexts(self):
|
||||
self.dc = self.GetDC()
|
||||
self._SetupPixelFormat()
|
||||
hrc = wglCreateContext(self.dc.GetSafeHdc())
|
||||
wglMakeCurrent(self.dc.GetSafeHdc(), hrc)
|
||||
|
||||
def _DestroyContexts(self):
|
||||
hrc = wglGetCurrentContext()
|
||||
wglMakeCurrent(0, 0)
|
||||
if hrc:
|
||||
wglDeleteContext(hrc)
|
||||
|
||||
# The methods to support OpenGL
|
||||
def DrawScene(self):
|
||||
raise NotImplementedError("You must override this method")
|
||||
|
||||
def Init(self):
|
||||
raise NotImplementedError("You must override this method")
|
||||
|
||||
def OnSizeChange(self, cx, cy):
|
||||
pass
|
||||
|
||||
def Term(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestView(OpenGLView):
|
||||
def OnSizeChange(self, right, bottom):
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0)
|
||||
glClearDepth(1.0)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
if bottom:
|
||||
aspect = right / bottom
|
||||
else:
|
||||
aspect = 0 # When window created!
|
||||
glLoadIdentity()
|
||||
gluPerspective(45.0, aspect, 3.0, 7.0)
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
|
||||
near_plane = 3.0
|
||||
far_plane = 7.0
|
||||
maxObjectSize = 3.0
|
||||
self.radius = near_plane + maxObjectSize / 2.0
|
||||
|
||||
def Init(self):
|
||||
pass
|
||||
|
||||
def DrawScene(self):
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0)
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
glPushMatrix()
|
||||
glTranslatef(0.0, 0.0, -self.radius)
|
||||
|
||||
self._DrawCone()
|
||||
|
||||
self._DrawPyramid()
|
||||
|
||||
glPopMatrix()
|
||||
glFinish()
|
||||
|
||||
SwapBuffers(wglGetCurrentDC())
|
||||
|
||||
def _DrawCone(self):
|
||||
glColor3f(0.0, 1.0, 0.0)
|
||||
|
||||
glPushMatrix()
|
||||
glTranslatef(-1.0, 0.0, 0.0)
|
||||
quadObj = gluNewQuadric()
|
||||
gluQuadricDrawStyle(quadObj, GLU_FILL)
|
||||
gluQuadricNormals(quadObj, GLU_SMOOTH)
|
||||
gluCylinder(quadObj, 1.0, 0.0, 1.0, 20, 10)
|
||||
# gluDeleteQuadric(quadObj);
|
||||
glPopMatrix()
|
||||
|
||||
def _DrawPyramid(self):
|
||||
glPushMatrix()
|
||||
glTranslatef(1.0, 0.0, 0.0)
|
||||
glBegin(GL_TRIANGLE_FAN)
|
||||
glColor3f(1.0, 0.0, 0.0)
|
||||
glVertex3f(0.0, 1.0, 0.0)
|
||||
glColor3f(0.0, 1.0, 0.0)
|
||||
glVertex3f(-1.0, 0.0, 0.0)
|
||||
glColor3f(0.0, 0.0, 1.0)
|
||||
glVertex3f(0.0, 0.0, 1.0)
|
||||
glColor3f(0.0, 1.0, 0.0)
|
||||
glVertex3f(1.0, 0.0, 0.0)
|
||||
glEnd()
|
||||
glPopMatrix()
|
||||
|
||||
|
||||
class CubeView(OpenGLView):
|
||||
def OnSizeChange(self, right, bottom):
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0)
|
||||
glClearDepth(1.0)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
if bottom:
|
||||
aspect = right / bottom
|
||||
else:
|
||||
aspect = 0 # When window created!
|
||||
glLoadIdentity()
|
||||
gluPerspective(45.0, aspect, 3.0, 7.0)
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
|
||||
near_plane = 3.0
|
||||
far_plane = 7.0
|
||||
maxObjectSize = 3.0
|
||||
self.radius = near_plane + maxObjectSize / 2.0
|
||||
|
||||
def Init(self):
|
||||
self.busy = 0
|
||||
self.wAngleY = 10.0
|
||||
self.wAngleX = 1.0
|
||||
self.wAngleZ = 5.0
|
||||
self.timerid = timer.set_timer(150, self.OnTimer)
|
||||
|
||||
def OnTimer(self, id, timeVal):
|
||||
self.DrawScene()
|
||||
|
||||
def Term(self):
|
||||
timer.kill_timer(self.timerid)
|
||||
|
||||
def DrawScene(self):
|
||||
if self.busy:
|
||||
return
|
||||
self.busy = 1
|
||||
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0)
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
glPushMatrix()
|
||||
|
||||
glTranslatef(0.0, 0.0, -self.radius)
|
||||
glRotatef(self.wAngleX, 1.0, 0.0, 0.0)
|
||||
glRotatef(self.wAngleY, 0.0, 1.0, 0.0)
|
||||
glRotatef(self.wAngleZ, 0.0, 0.0, 1.0)
|
||||
|
||||
self.wAngleX += 1.0
|
||||
self.wAngleY += 10.0
|
||||
self.wAngleZ += 5.0
|
||||
|
||||
glBegin(GL_QUAD_STRIP)
|
||||
glColor3f(1.0, 0.0, 1.0)
|
||||
glVertex3f(-0.5, 0.5, 0.5)
|
||||
|
||||
glColor3f(1.0, 0.0, 0.0)
|
||||
glVertex3f(-0.5, -0.5, 0.5)
|
||||
|
||||
glColor3f(1.0, 1.0, 1.0)
|
||||
glVertex3f(0.5, 0.5, 0.5)
|
||||
|
||||
glColor3f(1.0, 1.0, 0.0)
|
||||
glVertex3f(0.5, -0.5, 0.5)
|
||||
|
||||
glColor3f(0.0, 1.0, 1.0)
|
||||
glVertex3f(0.5, 0.5, -0.5)
|
||||
|
||||
glColor3f(0.0, 1.0, 0.0)
|
||||
glVertex3f(0.5, -0.5, -0.5)
|
||||
|
||||
glColor3f(0.0, 0.0, 1.0)
|
||||
glVertex3f(-0.5, 0.5, -0.5)
|
||||
|
||||
glColor3f(0.0, 0.0, 0.0)
|
||||
glVertex3f(-0.5, -0.5, -0.5)
|
||||
|
||||
glColor3f(1.0, 0.0, 1.0)
|
||||
glVertex3f(-0.5, 0.5, 0.5)
|
||||
|
||||
glColor3f(1.0, 0.0, 0.0)
|
||||
glVertex3f(-0.5, -0.5, 0.5)
|
||||
|
||||
glEnd()
|
||||
|
||||
glBegin(GL_QUADS)
|
||||
glColor3f(1.0, 0.0, 1.0)
|
||||
glVertex3f(-0.5, 0.5, 0.5)
|
||||
|
||||
glColor3f(1.0, 1.0, 1.0)
|
||||
glVertex3f(0.5, 0.5, 0.5)
|
||||
|
||||
glColor3f(0.0, 1.0, 1.0)
|
||||
glVertex3f(0.5, 0.5, -0.5)
|
||||
|
||||
glColor3f(0.0, 0.0, 1.0)
|
||||
glVertex3f(-0.5, 0.5, -0.5)
|
||||
glEnd()
|
||||
|
||||
glBegin(GL_QUADS)
|
||||
glColor3f(1.0, 0.0, 0.0)
|
||||
glVertex3f(-0.5, -0.5, 0.5)
|
||||
|
||||
glColor3f(1.0, 1.0, 0.0)
|
||||
glVertex3f(0.5, -0.5, 0.5)
|
||||
|
||||
glColor3f(0.0, 1.0, 0.0)
|
||||
glVertex3f(0.5, -0.5, -0.5)
|
||||
|
||||
glColor3f(0.0, 0.0, 0.0)
|
||||
glVertex3f(-0.5, -0.5, -0.5)
|
||||
glEnd()
|
||||
|
||||
glPopMatrix()
|
||||
|
||||
glFinish()
|
||||
SwapBuffers(wglGetCurrentDC())
|
||||
|
||||
self.busy = 0
|
||||
|
||||
|
||||
def test():
|
||||
template = docview.DocTemplate(None, None, None, CubeView)
|
||||
# template = docview.DocTemplate(None, None, None, TestView )
|
||||
template.OpenDocumentFile(None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
@@ -0,0 +1,105 @@
|
||||
#
|
||||
# Progress bar control example
|
||||
#
|
||||
# PyCProgressCtrl encapsulates the MFC CProgressCtrl class. To use it,
|
||||
# you:
|
||||
#
|
||||
# - Create the control with win32ui.CreateProgressCtrl()
|
||||
# - Create the control window with PyCProgressCtrl.CreateWindow()
|
||||
# - Initialize the range if you want it to be other than (0, 100) using
|
||||
# PyCProgressCtrl.SetRange()
|
||||
# - Either:
|
||||
# - Set the step size with PyCProgressCtrl.SetStep(), and
|
||||
# - Increment using PyCProgressCtrl.StepIt()
|
||||
# or:
|
||||
# - Set the amount completed using PyCProgressCtrl.SetPos()
|
||||
#
|
||||
# Example and progress bar code courtesy of KDL Technologies, Ltd., Hong Kong SAR, China.
|
||||
#
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
|
||||
|
||||
def MakeDlgTemplate():
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
cs = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
|
||||
w = 215
|
||||
h = 36
|
||||
|
||||
dlg = [
|
||||
[
|
||||
"Progress bar control example",
|
||||
(0, 0, w, h),
|
||||
style,
|
||||
None,
|
||||
(8, "MS Sans Serif"),
|
||||
],
|
||||
]
|
||||
|
||||
s = win32con.WS_TABSTOP | cs
|
||||
|
||||
dlg.append(
|
||||
[
|
||||
128,
|
||||
"Tick",
|
||||
win32con.IDOK,
|
||||
(10, h - 18, 50, 14),
|
||||
s | win32con.BS_DEFPUSHBUTTON,
|
||||
]
|
||||
)
|
||||
|
||||
dlg.append(
|
||||
[
|
||||
128,
|
||||
"Cancel",
|
||||
win32con.IDCANCEL,
|
||||
(w - 60, h - 18, 50, 14),
|
||||
s | win32con.BS_PUSHBUTTON,
|
||||
]
|
||||
)
|
||||
|
||||
return dlg
|
||||
|
||||
|
||||
class TestDialog(dialog.Dialog):
|
||||
def OnInitDialog(self):
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
self.pbar = win32ui.CreateProgressCtrl()
|
||||
self.pbar.CreateWindow(
|
||||
win32con.WS_CHILD | win32con.WS_VISIBLE, (10, 10, 310, 24), self, 1001
|
||||
)
|
||||
# self.pbar.SetStep (5)
|
||||
self.progress = 0
|
||||
self.pincr = 5
|
||||
return rc
|
||||
|
||||
def OnOK(self):
|
||||
# NB: StepIt wraps at the end if you increment past the upper limit!
|
||||
# self.pbar.StepIt()
|
||||
self.progress += self.pincr
|
||||
if self.progress > 100:
|
||||
self.progress = 100
|
||||
if self.progress <= 100:
|
||||
self.pbar.SetPos(self.progress)
|
||||
|
||||
|
||||
def demo(modal=0):
|
||||
d = TestDialog(MakeDlgTemplate())
|
||||
if modal:
|
||||
d.DoModal()
|
||||
else:
|
||||
d.CreateWindow()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo(1)
|
||||
@@ -0,0 +1,76 @@
|
||||
# sliderdemo.py
|
||||
# Demo of the slider control courtesy of Mike Fletcher.
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
|
||||
|
||||
class MyDialog(dialog.Dialog):
|
||||
"""
|
||||
Example using simple controls
|
||||
"""
|
||||
|
||||
_dialogstyle = (
|
||||
win32con.WS_MINIMIZEBOX
|
||||
| win32con.WS_DLGFRAME
|
||||
| win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
_buttonstyle = (
|
||||
win32con.BS_PUSHBUTTON
|
||||
| win32con.WS_TABSTOP
|
||||
| win32con.WS_CHILD
|
||||
| win32con.WS_VISIBLE
|
||||
)
|
||||
### The static template, contains all "normal" dialog items
|
||||
DIALOGTEMPLATE = [
|
||||
# the dialog itself is the first element in the template
|
||||
["Example slider", (0, 0, 50, 43), _dialogstyle, None, (8, "MS SansSerif")],
|
||||
# rest of elements are the controls within the dialog
|
||||
# standard "Close" button
|
||||
[128, "Close", win32con.IDCANCEL, (0, 30, 50, 13), _buttonstyle],
|
||||
]
|
||||
### ID of the control to be created during dialog initialisation
|
||||
IDC_SLIDER = 9500
|
||||
|
||||
def __init__(self):
|
||||
dialog.Dialog.__init__(self, self.DIALOGTEMPLATE)
|
||||
|
||||
def OnInitDialog(self):
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
# now initialise your controls that you want to create
|
||||
# programmatically, including those which are OLE controls
|
||||
# those created directly by win32ui.Create*
|
||||
# and your "custom controls" which are subclasses/whatever
|
||||
win32ui.EnableControlContainer()
|
||||
self.slider = win32ui.CreateSliderCtrl()
|
||||
self.slider.CreateWindow(
|
||||
win32con.WS_TABSTOP | win32con.WS_VISIBLE,
|
||||
(0, 0, 100, 30),
|
||||
self._obj_,
|
||||
self.IDC_SLIDER,
|
||||
)
|
||||
self.HookMessage(self.OnSliderMove, win32con.WM_HSCROLL)
|
||||
return rc
|
||||
|
||||
def OnSliderMove(self, params):
|
||||
print("Slider moved")
|
||||
|
||||
def OnCancel(self):
|
||||
print("The slider control is at position", self.slider.GetPos())
|
||||
self._obj_.OnCancel()
|
||||
|
||||
|
||||
###
|
||||
def demo():
|
||||
dia = MyDialog()
|
||||
dia.DoModal()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo()
|
||||
@@ -0,0 +1,79 @@
|
||||
import commctrl
|
||||
import fontdemo
|
||||
import win32ui
|
||||
from pywin.mfc import docview, window
|
||||
|
||||
# derive from CMDIChild. This does much work for us.
|
||||
|
||||
|
||||
class SplitterFrame(window.MDIChildWnd):
|
||||
def __init__(self):
|
||||
# call base CreateFrame
|
||||
self.images = None
|
||||
window.MDIChildWnd.__init__(self)
|
||||
|
||||
def OnCreateClient(self, cp, context):
|
||||
splitter = win32ui.CreateSplitter()
|
||||
doc = context.doc
|
||||
frame_rect = self.GetWindowRect()
|
||||
size = ((frame_rect[2] - frame_rect[0]), (frame_rect[3] - frame_rect[1]) // 2)
|
||||
sub_size = (size[0] // 2, size[1])
|
||||
splitter.CreateStatic(self, 2, 1)
|
||||
self.v1 = win32ui.CreateEditView(doc)
|
||||
self.v2 = fontdemo.FontView(doc)
|
||||
# CListControl view
|
||||
self.v3 = win32ui.CreateListView(doc)
|
||||
sub_splitter = win32ui.CreateSplitter()
|
||||
# pass "splitter" so each view knows how to get to the others
|
||||
sub_splitter.CreateStatic(splitter, 1, 2)
|
||||
sub_splitter.CreateView(self.v1, 0, 0, (sub_size))
|
||||
sub_splitter.CreateView(self.v2, 0, 1, (0, 0)) # size ignored.
|
||||
splitter.SetRowInfo(0, size[1], 0)
|
||||
splitter.CreateView(self.v3, 1, 0, (0, 0)) # size ignored.
|
||||
# Setup items in the imagelist
|
||||
self.images = win32ui.CreateImageList(32, 32, 1, 5, 5)
|
||||
self.images.Add(win32ui.GetApp().LoadIcon(win32ui.IDR_MAINFRAME))
|
||||
self.images.Add(win32ui.GetApp().LoadIcon(win32ui.IDR_PYTHONCONTYPE))
|
||||
self.images.Add(win32ui.GetApp().LoadIcon(win32ui.IDR_TEXTTYPE))
|
||||
self.v3.SetImageList(self.images, commctrl.LVSIL_NORMAL)
|
||||
self.v3.InsertItem(0, "Icon 1", 0)
|
||||
self.v3.InsertItem(0, "Icon 2", 1)
|
||||
self.v3.InsertItem(0, "Icon 3", 2)
|
||||
# self.v3.Arrange(commctrl.LVA_DEFAULT) Hmmm - win95 aligns left always???
|
||||
return 1
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
window.MDIChildWnd.OnDestroy(self, msg)
|
||||
if self.images:
|
||||
self.images.DeleteImageList()
|
||||
self.images = None
|
||||
|
||||
def InitialUpdateFrame(self, doc, makeVisible):
|
||||
self.v1.ReplaceSel("Hello from Edit Window 1")
|
||||
self.v1.SetModifiedFlag(0)
|
||||
|
||||
|
||||
class SampleTemplate(docview.DocTemplate):
|
||||
def __init__(self):
|
||||
docview.DocTemplate.__init__(
|
||||
self, win32ui.IDR_PYTHONTYPE, None, SplitterFrame, None
|
||||
)
|
||||
|
||||
def InitialUpdateFrame(self, frame, doc, makeVisible):
|
||||
# print("frame is ", frame, frame._obj_)
|
||||
# print("doc is ", doc, doc._obj_)
|
||||
self._obj_.InitialUpdateFrame(frame, doc, makeVisible) # call default handler.
|
||||
frame.InitialUpdateFrame(doc, makeVisible)
|
||||
|
||||
|
||||
def demo():
|
||||
template = SampleTemplate()
|
||||
doc = template.OpenDocumentFile(None)
|
||||
doc.SetTitle("Splitter Demo")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
if demoutils.NeedGoodGUI():
|
||||
demo()
|
||||
@@ -0,0 +1,189 @@
|
||||
# Demo of using just windows, without documents and views.
|
||||
|
||||
# Also demo of a GUI thread, pretty much direct from the MFC C++ sample MTMDI.
|
||||
|
||||
import timer
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import window
|
||||
from pywin.mfc.thread import WinThread
|
||||
|
||||
WM_USER_PREPARE_TO_CLOSE = win32con.WM_USER + 32
|
||||
|
||||
# font is a dictionary in which the following elements matter:
|
||||
# (the best matching font to supplied parameters is returned)
|
||||
# name string name of the font as known by Windows
|
||||
# size point size of font in logical units
|
||||
# weight weight of font (win32con.FW_NORMAL, win32con.FW_BOLD)
|
||||
# italic boolean; true if set to anything but None
|
||||
# underline boolean; true if set to anything but None
|
||||
|
||||
|
||||
# This window is a child window of a frame. It is not the frame window itself.
|
||||
class FontWindow(window.Wnd):
|
||||
def __init__(self, text="Python Rules!"):
|
||||
window.Wnd.__init__(self)
|
||||
self.text = text
|
||||
self.index = 0
|
||||
self.incr = 1
|
||||
self.width = self.height = 0
|
||||
self.ChangeAttributes()
|
||||
# set up message handlers
|
||||
|
||||
def Create(self, title, style, rect, parent):
|
||||
classStyle = win32con.CS_HREDRAW | win32con.CS_VREDRAW
|
||||
className = win32ui.RegisterWndClass(
|
||||
classStyle, 0, win32con.COLOR_WINDOW + 1, 0
|
||||
)
|
||||
self._obj_ = win32ui.CreateWnd()
|
||||
self._obj_.AttachObject(self)
|
||||
self._obj_.CreateWindow(
|
||||
className, title, style, rect, parent, win32ui.AFX_IDW_PANE_FIRST
|
||||
)
|
||||
self.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
self.HookMessage(self.OnPrepareToClose, WM_USER_PREPARE_TO_CLOSE)
|
||||
self.HookMessage(self.OnDestroy, win32con.WM_DESTROY)
|
||||
self.timerid = timer.set_timer(100, self.OnTimer)
|
||||
self.InvalidateRect()
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
timer.kill_timer(self.timerid)
|
||||
|
||||
def OnTimer(self, id, timeVal):
|
||||
self.index += self.incr
|
||||
if self.index > len(self.text):
|
||||
self.incr = -1
|
||||
self.index = len(self.text)
|
||||
elif self.index < 0:
|
||||
self.incr = 1
|
||||
self.index = 0
|
||||
self.InvalidateRect()
|
||||
|
||||
def OnPaint(self):
|
||||
# print("Paint message from thread", win32api.GetCurrentThreadId())
|
||||
dc, paintStruct = self.BeginPaint()
|
||||
self.OnPrepareDC(dc, None)
|
||||
|
||||
if self.width == 0 and self.height == 0:
|
||||
left, top, right, bottom = self.GetClientRect()
|
||||
self.width = right - left
|
||||
self.height = bottom - top
|
||||
x, y = self.width // 2, self.height // 2
|
||||
dc.TextOut(x, y, self.text[: self.index])
|
||||
self.EndPaint(paintStruct)
|
||||
|
||||
def ChangeAttributes(self):
|
||||
font_spec = {"name": "Arial", "height": 42}
|
||||
self.font = win32ui.CreateFont(font_spec)
|
||||
|
||||
def OnPrepareToClose(self, params):
|
||||
self.DestroyWindow()
|
||||
|
||||
def OnSize(self, params):
|
||||
lParam = params[3]
|
||||
self.width = win32api.LOWORD(lParam)
|
||||
self.height = win32api.HIWORD(lParam)
|
||||
|
||||
def OnPrepareDC(self, dc, printinfo):
|
||||
# Set up the DC for forthcoming OnDraw call
|
||||
dc.SetTextColor(win32api.RGB(0, 0, 255))
|
||||
dc.SetBkColor(win32api.GetSysColor(win32con.COLOR_WINDOW))
|
||||
dc.SelectObject(self.font)
|
||||
dc.SetTextAlign(win32con.TA_CENTER | win32con.TA_BASELINE)
|
||||
|
||||
|
||||
class FontFrame(window.MDIChildWnd):
|
||||
def __init__(self):
|
||||
pass # Don't call base class doc/view version...
|
||||
|
||||
def Create(self, title, rect=None, parent=None):
|
||||
style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.WS_OVERLAPPEDWINDOW
|
||||
self._obj_ = win32ui.CreateMDIChild()
|
||||
self._obj_.AttachObject(self)
|
||||
|
||||
self._obj_.CreateWindow(None, title, style, rect, parent)
|
||||
rect = self.GetClientRect()
|
||||
rect = (0, 0, rect[2] - rect[0], rect[3] - rect[1])
|
||||
self.child = FontWindow("Not threaded")
|
||||
self.child.Create(
|
||||
"FontDemo", win32con.WS_CHILD | win32con.WS_VISIBLE, rect, self
|
||||
)
|
||||
|
||||
|
||||
class TestThread(WinThread):
|
||||
def __init__(self, parentWindow):
|
||||
self.parentWindow = parentWindow
|
||||
self.child = None
|
||||
WinThread.__init__(self)
|
||||
|
||||
def InitInstance(self):
|
||||
rect = self.parentWindow.GetClientRect()
|
||||
rect = (0, 0, rect[2] - rect[0], rect[3] - rect[1])
|
||||
|
||||
self.child = FontWindow()
|
||||
self.child.Create(
|
||||
"FontDemo", win32con.WS_CHILD | win32con.WS_VISIBLE, rect, self.parentWindow
|
||||
)
|
||||
self.SetMainFrame(self.child)
|
||||
return WinThread.InitInstance(self)
|
||||
|
||||
def ExitInstance(self):
|
||||
return 0
|
||||
|
||||
|
||||
class ThreadedFontFrame(window.MDIChildWnd):
|
||||
def __init__(self):
|
||||
pass # Don't call base class doc/view version...
|
||||
self.thread = None
|
||||
|
||||
def Create(self, title, rect=None, parent=None):
|
||||
style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.WS_OVERLAPPEDWINDOW
|
||||
self._obj_ = win32ui.CreateMDIChild()
|
||||
self._obj_.CreateWindow(None, title, style, rect, parent)
|
||||
self._obj_.HookMessage(self.OnDestroy, win32con.WM_DESTROY)
|
||||
self._obj_.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
|
||||
self.thread = TestThread(self)
|
||||
self.thread.CreateThread()
|
||||
|
||||
def OnSize(self, msg):
|
||||
pass
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
win32ui.OutputDebugString("OnDestroy\n")
|
||||
if self.thread and self.thread.child:
|
||||
child = self.thread.child
|
||||
child.SendMessage(WM_USER_PREPARE_TO_CLOSE, 0, 0)
|
||||
win32ui.OutputDebugString("Destroyed\n")
|
||||
|
||||
|
||||
def Demo():
|
||||
f = FontFrame()
|
||||
f.Create("Font Demo")
|
||||
|
||||
|
||||
def ThreadedDemo():
|
||||
rect = win32ui.GetMainFrame().GetMDIClient().GetClientRect()
|
||||
rect = rect[0], int(rect[3] * 3 / 4), int(rect[2] / 4), rect[3]
|
||||
incr = rect[2]
|
||||
for i in range(4):
|
||||
if i == 0:
|
||||
f = FontFrame()
|
||||
title = "Not threaded"
|
||||
else:
|
||||
f = ThreadedFontFrame()
|
||||
title = "Threaded GUI Demo"
|
||||
f.Create(title, rect)
|
||||
rect = rect[0] + incr, rect[1], rect[2] + incr, rect[3]
|
||||
# Givem a chance to start
|
||||
win32api.Sleep(100)
|
||||
win32ui.PumpWaitingMessages()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
if demoutils.NeedGoodGUI():
|
||||
ThreadedDemo()
|
||||
# Demo()
|
||||
@@ -0,0 +1,105 @@
|
||||
# Demo of ToolBars
|
||||
|
||||
# Shows the toolbar control.
|
||||
# Demos how to make custom tooltips, etc.
|
||||
|
||||
import commctrl
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import afxres, docview, window
|
||||
|
||||
|
||||
class GenericFrame(window.MDIChildWnd):
|
||||
def OnCreateClient(self, cp, context):
|
||||
# handlers for toolbar buttons
|
||||
self.HookCommand(self.OnPrevious, 401)
|
||||
self.HookCommand(self.OnNext, 402)
|
||||
# It's not necessary for us to hook both of these - the
|
||||
# common controls should fall-back all by themselves.
|
||||
# Indeed, given we hook TTN_NEEDTEXTW, commctrl.TTN_NEEDTEXTA
|
||||
# will not be called.
|
||||
self.HookNotify(self.GetTTText, commctrl.TTN_NEEDTEXT)
|
||||
self.HookNotify(self.GetTTText, commctrl.TTN_NEEDTEXTW)
|
||||
|
||||
# parent = win32ui.GetMainFrame()
|
||||
parent = self
|
||||
style = (
|
||||
win32con.WS_CHILD
|
||||
| win32con.WS_VISIBLE
|
||||
| afxres.CBRS_SIZE_DYNAMIC
|
||||
| afxres.CBRS_TOP
|
||||
| afxres.CBRS_TOOLTIPS
|
||||
| afxres.CBRS_FLYBY
|
||||
)
|
||||
|
||||
buttons = (win32ui.ID_APP_ABOUT, win32ui.ID_VIEW_INTERACTIVE)
|
||||
bitmap = win32ui.IDB_BROWSER_HIER
|
||||
tbid = 0xE840
|
||||
self.toolbar = tb = win32ui.CreateToolBar(parent, style, tbid)
|
||||
tb.LoadBitmap(bitmap)
|
||||
tb.SetButtons(buttons)
|
||||
|
||||
tb.EnableDocking(afxres.CBRS_ALIGN_ANY)
|
||||
tb.SetWindowText("Test")
|
||||
parent.EnableDocking(afxres.CBRS_ALIGN_ANY)
|
||||
parent.DockControlBar(tb)
|
||||
parent.LoadBarState("ToolbarTest")
|
||||
window.MDIChildWnd.OnCreateClient(self, cp, context)
|
||||
return 1
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
self.SaveBarState("ToolbarTest")
|
||||
|
||||
def GetTTText(self, std, extra):
|
||||
(hwndFrom, idFrom, code) = std
|
||||
text, hinst, flags = extra
|
||||
if flags & commctrl.TTF_IDISHWND:
|
||||
return # Not handled
|
||||
if idFrom == win32ui.ID_APP_ABOUT:
|
||||
# our 'extra' return value needs to be the following
|
||||
# entries from a NMTTDISPINFO[W] struct:
|
||||
# (szText, hinst, uFlags). None means 'don't change
|
||||
# the value'
|
||||
return 0, ("It works!", None, None)
|
||||
return None # not handled.
|
||||
|
||||
def GetMessageString(self, id):
|
||||
if id == win32ui.ID_APP_ABOUT:
|
||||
return "Dialog Test\nTest"
|
||||
else:
|
||||
return self._obj_.GetMessageString(id)
|
||||
|
||||
def OnSize(self, params):
|
||||
print("OnSize called with ", params)
|
||||
|
||||
def OnNext(self, id, cmd):
|
||||
print("OnNext called")
|
||||
|
||||
def OnPrevious(self, id, cmd):
|
||||
print("OnPrevious called")
|
||||
|
||||
|
||||
msg = """\
|
||||
This toolbar was dynamically created.\r
|
||||
\r
|
||||
The first item's tooltips is provided by Python code.\r
|
||||
\r
|
||||
(Don't close the window with the toolbar in a floating state - it may not re-appear!)\r
|
||||
"""
|
||||
|
||||
|
||||
def test():
|
||||
template = docview.DocTemplate(
|
||||
win32ui.IDR_PYTHONTYPE, None, GenericFrame, docview.EditView
|
||||
)
|
||||
doc = template.OpenDocumentFile(None)
|
||||
doc.SetTitle("Toolbar Test")
|
||||
view = doc.GetFirstView()
|
||||
view.SetWindowText(msg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import demoutils
|
||||
|
||||
if demoutils.NeedGoodGUI():
|
||||
test()
|
||||
28
Utils/PythonNew32/Lib/site-packages/pythonwin/pywin/IDLE.cfg
Normal file
28
Utils/PythonNew32/Lib/site-packages/pythonwin/pywin/IDLE.cfg
Normal file
@@ -0,0 +1,28 @@
|
||||
[General]
|
||||
# We base this configuration on the default config.
|
||||
# You can list "Based On" as many times as you like
|
||||
Based On = default
|
||||
|
||||
[Keys]
|
||||
# Only list keys different to default.
|
||||
# Note you may wish to rebind some of the default
|
||||
# Pythonwin keys to "Beep" or "DoNothing"
|
||||
|
||||
Alt+L = LocateSelectedFile
|
||||
Ctrl+Q = AppExit
|
||||
|
||||
# Other non-default Pythonwin keys
|
||||
Alt+A = EditSelectAll
|
||||
Alt+M = LocateModule
|
||||
|
||||
# Movement
|
||||
Ctrl+D = GotoEndOfFile
|
||||
|
||||
# Tabs and other indent features
|
||||
Alt+T = <<toggle-tabs>>
|
||||
Ctrl+[ = <<indent-region>>
|
||||
Ctrl+] = <<dedent-region>>
|
||||
|
||||
[Keys:Interactive]
|
||||
Alt+P = <<history-previous>>
|
||||
Alt+N = <<history-next>>
|
||||
@@ -0,0 +1,3 @@
|
||||
# Referenced and used variable - but in practice can't be changed.
|
||||
# Scintilla _only_ supports "utf-8" ATM
|
||||
default_scintilla_encoding = "utf-8"
|
||||
@@ -0,0 +1,133 @@
|
||||
import sys
|
||||
|
||||
isInprocApp = -1
|
||||
|
||||
|
||||
def _CheckNeedGUI():
|
||||
global isInprocApp
|
||||
if isInprocApp == -1:
|
||||
import win32ui
|
||||
|
||||
isInprocApp = win32ui.GetApp().IsInproc()
|
||||
if isInprocApp:
|
||||
# MAY Need it - may already have one
|
||||
need = "pywin.framework.app" not in sys.modules
|
||||
else:
|
||||
need = 0
|
||||
if need:
|
||||
import pywin.framework.app
|
||||
|
||||
from . import dbgpyapp
|
||||
|
||||
pywin.framework.app.CreateDefaultGUI(dbgpyapp.DebuggerPythonApp)
|
||||
|
||||
else:
|
||||
# Check we have the appropriate editor
|
||||
# No longer necessary!
|
||||
pass
|
||||
return need
|
||||
|
||||
|
||||
# Inject some methods in the top level name-space.
|
||||
currentDebugger = None # Wipe out any old one on reload.
|
||||
|
||||
|
||||
def _GetCurrentDebugger():
|
||||
global currentDebugger
|
||||
if currentDebugger is None:
|
||||
_CheckNeedGUI()
|
||||
from . import debugger
|
||||
|
||||
currentDebugger = debugger.Debugger()
|
||||
return currentDebugger
|
||||
|
||||
|
||||
def GetDebugger():
|
||||
# An error here is not nice - as we are probably trying to
|
||||
# break into the debugger on a Python error, any
|
||||
# error raised by this is usually silent, and causes
|
||||
# big problems later!
|
||||
try:
|
||||
rc = _GetCurrentDebugger()
|
||||
rc.GUICheckInit()
|
||||
return rc
|
||||
except:
|
||||
print("Could not create the debugger!")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
|
||||
def close():
|
||||
if currentDebugger is not None:
|
||||
currentDebugger.close()
|
||||
|
||||
|
||||
def run(cmd, globals=None, locals=None, start_stepping=1):
|
||||
_GetCurrentDebugger().run(cmd, globals, locals, start_stepping)
|
||||
|
||||
|
||||
def runeval(expression, globals=None, locals=None):
|
||||
return _GetCurrentDebugger().runeval(expression, globals, locals)
|
||||
|
||||
|
||||
def runcall(*args):
|
||||
return _GetCurrentDebugger().runcall(*args)
|
||||
|
||||
|
||||
def set_trace():
|
||||
import sys
|
||||
|
||||
d = _GetCurrentDebugger()
|
||||
|
||||
if d.frameShutdown:
|
||||
return # App closing
|
||||
|
||||
if d.stopframe != d.botframe:
|
||||
# If I'm not "running"
|
||||
return
|
||||
|
||||
sys.settrace(None) # May be hooked
|
||||
d.reset()
|
||||
d.set_trace()
|
||||
|
||||
|
||||
# "brk" is an alias for "set_trace" ("break" is a reserved word :-(
|
||||
brk = set_trace
|
||||
|
||||
# Post-Mortem interface
|
||||
|
||||
|
||||
def post_mortem(t=None):
|
||||
if t is None:
|
||||
t = sys.exc_info()[2] # Will be valid if we are called from an except handler.
|
||||
if t is None:
|
||||
try:
|
||||
t = sys.last_traceback
|
||||
except AttributeError:
|
||||
print(
|
||||
"No traceback can be found from which to perform post-mortem debugging!"
|
||||
)
|
||||
print("No debugging can continue")
|
||||
return
|
||||
p = _GetCurrentDebugger()
|
||||
if p.frameShutdown:
|
||||
return # App closing
|
||||
# No idea why I need to settrace to None - it should have been reset by now?
|
||||
sys.settrace(None)
|
||||
p.reset()
|
||||
while t.tb_next is not None:
|
||||
t = t.tb_next
|
||||
p.bAtPostMortem = 1
|
||||
p.prep_run(None)
|
||||
try:
|
||||
p.interaction(t.tb_frame, t)
|
||||
finally:
|
||||
t = None
|
||||
p.bAtPostMortem = 0
|
||||
p.done_run()
|
||||
|
||||
|
||||
def pm(t=None):
|
||||
post_mortem(t)
|
||||
@@ -0,0 +1,34 @@
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
|
||||
from . import dbgcon
|
||||
|
||||
|
||||
class DebuggerOptionsPropPage(dialog.PropertyPage):
|
||||
def __init__(self):
|
||||
dialog.PropertyPage.__init__(self, win32ui.IDD_PP_DEBUGGER)
|
||||
|
||||
def OnInitDialog(self):
|
||||
options = self.options = dbgcon.LoadDebuggerOptions()
|
||||
self.AddDDX(win32ui.IDC_CHECK1, dbgcon.OPT_HIDE)
|
||||
self[dbgcon.OPT_STOP_EXCEPTIONS] = options[dbgcon.OPT_STOP_EXCEPTIONS]
|
||||
self.AddDDX(win32ui.IDC_CHECK2, dbgcon.OPT_STOP_EXCEPTIONS)
|
||||
self[dbgcon.OPT_HIDE] = options[dbgcon.OPT_HIDE]
|
||||
return dialog.PropertyPage.OnInitDialog(self)
|
||||
|
||||
def OnOK(self):
|
||||
self.UpdateData()
|
||||
dirty = 0
|
||||
for key, val in self.items():
|
||||
if key in self.options:
|
||||
if self.options[key] != val:
|
||||
self.options[key] = val
|
||||
dirty = 1
|
||||
if dirty:
|
||||
dbgcon.SaveDebuggerOptions(self.options)
|
||||
# If there is a debugger open, then set its options.
|
||||
import pywin.debugger
|
||||
|
||||
if pywin.debugger.currentDebugger is not None:
|
||||
pywin.debugger.currentDebugger.options = self.options
|
||||
return 1
|
||||
@@ -0,0 +1,31 @@
|
||||
# General constants for the debugger
|
||||
|
||||
DBGSTATE_NOT_DEBUGGING = 0
|
||||
DBGSTATE_RUNNING = 1
|
||||
DBGSTATE_BREAK = 2
|
||||
DBGSTATE_QUITTING = 3 # Attempting to back out of the debug session.
|
||||
|
||||
LINESTATE_CURRENT = 0x1 # This line is where we are stopped
|
||||
LINESTATE_BREAKPOINT = 0x2 # This line is a breakpoint
|
||||
LINESTATE_CALLSTACK = 0x4 # This line is in the callstack.
|
||||
|
||||
OPT_HIDE = "hide"
|
||||
OPT_STOP_EXCEPTIONS = "stopatexceptions"
|
||||
|
||||
import win32ui
|
||||
|
||||
|
||||
def DoGetOption(optsDict, optName, default):
|
||||
optsDict[optName] = win32ui.GetProfileVal("Debugger Options", optName, default)
|
||||
|
||||
|
||||
def LoadDebuggerOptions():
|
||||
opts = {}
|
||||
DoGetOption(opts, OPT_HIDE, 0)
|
||||
DoGetOption(opts, OPT_STOP_EXCEPTIONS, 1)
|
||||
return opts
|
||||
|
||||
|
||||
def SaveDebuggerOptions(opts):
|
||||
for key, val in opts.items():
|
||||
win32ui.WriteProfileVal("Debugger Options", key, val)
|
||||
@@ -0,0 +1,48 @@
|
||||
# dbgpyapp.py - Debugger Python application class
|
||||
#
|
||||
import sys
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework import intpyapp
|
||||
|
||||
version = "0.3.0"
|
||||
|
||||
|
||||
class DebuggerPythonApp(intpyapp.InteractivePythonApp):
|
||||
def LoadMainFrame(self):
|
||||
"Create the main applications frame"
|
||||
self.frame = self.CreateMainFrame()
|
||||
self.SetMainFrame(self.frame)
|
||||
self.frame.LoadFrame(win32ui.IDR_DEBUGGER, win32con.WS_OVERLAPPEDWINDOW)
|
||||
self.frame.DragAcceptFiles() # we can accept these.
|
||||
self.frame.ShowWindow(win32con.SW_HIDE)
|
||||
self.frame.UpdateWindow()
|
||||
|
||||
# but we do rehook, hooking the new code objects.
|
||||
self.HookCommands()
|
||||
|
||||
def InitInstance(self):
|
||||
# Use a registry path of "Python\Pythonwin Debugger
|
||||
win32ui.SetAppName(win32ui.LoadString(win32ui.IDR_DEBUGGER))
|
||||
win32ui.SetRegistryKey(f"Python {sys.winver}")
|
||||
# We _need_ the Scintilla color editor.
|
||||
# (and we _always_ get it now :-)
|
||||
|
||||
numMRU = win32ui.GetProfileVal("Settings", "Recent File List Size", 10)
|
||||
win32ui.LoadStdProfileSettings(numMRU)
|
||||
|
||||
self.LoadMainFrame()
|
||||
|
||||
# Display the interactive window if the user wants it.
|
||||
from pywin.framework import interact
|
||||
|
||||
interact.CreateInteractiveWindowUserPreference()
|
||||
|
||||
# Load the modules we use internally.
|
||||
self.LoadSystemModules()
|
||||
# Load additional module the user may want.
|
||||
self.LoadUserModules()
|
||||
|
||||
# win32ui.CreateDebuggerThread()
|
||||
win32ui.EnableControlContainer()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
# NOTE NOTE - This module is designed to fail!
|
||||
#
|
||||
# The ONLY purpose for this script is testing/demoing the
|
||||
# Pythonwin debugger package.
|
||||
|
||||
# It does nothing useful, and it even doesn't do that!
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
import pywin.debugger
|
||||
|
||||
|
||||
def a():
|
||||
a = 1
|
||||
try:
|
||||
b()
|
||||
except:
|
||||
# Break into the debugger with the exception information.
|
||||
pywin.debugger.post_mortem(sys.exc_info()[2])
|
||||
a = 1
|
||||
a = 2
|
||||
a = 3
|
||||
a = 4
|
||||
|
||||
|
||||
def b():
|
||||
b = 1
|
||||
pywin.debugger.set_trace()
|
||||
# After importing or running this module, you are likely to be
|
||||
# sitting at the next line. This is because we explicitly
|
||||
# broke into the debugger using the "set_trace() function
|
||||
# "pywin.debugger.brk()" is a shorter alias for this.
|
||||
c()
|
||||
|
||||
|
||||
def c():
|
||||
c = 1
|
||||
d()
|
||||
|
||||
|
||||
def d():
|
||||
d = 1
|
||||
e(d)
|
||||
raise ValueError("Hi")
|
||||
|
||||
|
||||
def e(arg):
|
||||
e = 1
|
||||
time.sleep(1)
|
||||
return e
|
||||
|
||||
|
||||
a()
|
||||
214
Utils/PythonNew32/Lib/site-packages/pythonwin/pywin/default.cfg
Normal file
214
Utils/PythonNew32/Lib/site-packages/pythonwin/pywin/default.cfg
Normal file
@@ -0,0 +1,214 @@
|
||||
# The default keyboard etc configuration file for Pythonwin.
|
||||
#
|
||||
# The format of this file is very similar to a Windows INI file.
|
||||
# Sections are identified with [Section] lines, but comments
|
||||
# use the standatd Python # character. Depending on the section,
|
||||
# lines may not be in the standard "key=value" format.
|
||||
|
||||
# NOTE: You should not need to modify this file.
|
||||
# Simply create a new .CFG file, and add an entry:
|
||||
# [General]
|
||||
# BasedOn = Default
|
||||
#
|
||||
# and add your customisations. Then select your new configuration
|
||||
# from the Pythonwin View/Options/Editor dialog.
|
||||
# This way you get to add your own customisations,
|
||||
# but still take advantage of changes to the default
|
||||
# configuration in new releases.
|
||||
|
||||
# See IDLE.cfg for an example extension configuration.
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
[IDLE Extensions]
|
||||
|
||||
# The list of IDLE extensions to load. The extensions
|
||||
# AutoIndent, AutoFormat and possibly others are
|
||||
# "built-in", so do not need specifying.
|
||||
|
||||
FormatParagraph
|
||||
CallTips
|
||||
|
||||
|
||||
[Keys]
|
||||
|
||||
# The list of _default_ key definitions.
|
||||
# See [Keys:Interactive] and [Keys:Editor] below for further defs.
|
||||
|
||||
#Events of the format <<event-name>>
|
||||
# are events defined in IDLE extensions.
|
||||
|
||||
Alt+Q = <<format-paragraph>>
|
||||
|
||||
Ctrl+W = ViewWhitespace
|
||||
Ctrl+Shift+8 = ViewWhitespace # The MSVC default key def.
|
||||
|
||||
Ctrl+Shift+F = ViewFixedFont
|
||||
|
||||
# Auto-complete, call-tips, etc.
|
||||
Alt+/ = <<expand-word>>
|
||||
Ctrl+Space = <<expand-word>>
|
||||
( = <<paren-open>>
|
||||
) = <<paren-close>>
|
||||
Up = <<check-calltip-cancel>>
|
||||
Down = <<check-calltip-cancel>>
|
||||
Left = <<check-calltip-cancel>>
|
||||
Right = <<check-calltip-cancel>>
|
||||
. = KeyDot
|
||||
|
||||
# Debugger - These are the MSVC default keys, for want of a better choice.
|
||||
F9 = DbgBreakpointToggle
|
||||
F5 = DbgGo
|
||||
Shift+F5 = DbgClose
|
||||
F11 = DbgStep
|
||||
F10 = DbgStepOver
|
||||
Shift+F11 = DbgStepOut
|
||||
|
||||
Ctrl+F3 = AutoFindNext
|
||||
|
||||
|
||||
[Keys:Editor]
|
||||
# Key bindings specific to the editor
|
||||
F2 = GotoNextBookmark
|
||||
Ctrl+F2 = ToggleBookmark
|
||||
Ctrl+G = GotoLine
|
||||
|
||||
Alt+I = ShowInteractiveWindow
|
||||
Alt-B = AddBanner # A sample Event defined in this file.
|
||||
|
||||
# Block operations
|
||||
Alt+3 = <<comment-region>>
|
||||
Shift+Alt+3 = <<uncomment-region>>
|
||||
Alt+4 = <<uncomment-region>> # IDLE default.
|
||||
Alt+5 = <<tabify-region>>
|
||||
Alt+6 = <<untabify-region>>
|
||||
|
||||
# Tabs and other indent features
|
||||
Back = <<smart-backspace>>
|
||||
Ctrl+T = <<toggle-tabs>>
|
||||
Alt+U = <<change-indentwidth>>
|
||||
Enter = EnterKey
|
||||
Tab = TabKey
|
||||
Shift-Tab = <<dedent-region>>
|
||||
|
||||
# Folding
|
||||
Add = FoldExpand
|
||||
Alt+Add = FoldExpandAll
|
||||
Shift+Add = FoldExpandSecondLevel
|
||||
Subtract = FoldCollapse
|
||||
Alt+Subtract = FoldCollapseAll
|
||||
Shift+Subtract = FoldCollapseSecondLevel
|
||||
Multiply = FoldTopLevel
|
||||
|
||||
[Keys:Interactive]
|
||||
# Key bindings specific to the interactive window.
|
||||
# History for the interactive window
|
||||
Ctrl+Up = <<history-previous>>
|
||||
Ctrl+Down = <<history-next>>
|
||||
Enter = ProcessEnter
|
||||
Ctrl+Enter = ProcessEnter
|
||||
Shift+Enter = ProcessEnter
|
||||
Esc = ProcessEsc
|
||||
Alt+I = WindowBack # Toggle back to previous window.
|
||||
Home = InteractiveHome # A sample Event defined in this file.
|
||||
Shift+Home = InteractiveHomeExtend # A sample Event defined in this file.
|
||||
|
||||
# When docked, the Ctrl+Tab and Shift+Ctrl+Tab keys don't work as expected.
|
||||
Ctrl+Tab = MDINext
|
||||
Ctrl+Shift+Tab = MDIPrev
|
||||
|
||||
[Extensions]
|
||||
# Python event handlers specific to this config file.
|
||||
# All functions not starting with an "_" are assumed
|
||||
# to be events, and take 2 params:
|
||||
# * editor_window is the same object passed to IDLE
|
||||
# extensions. editor_window.text is a text widget
|
||||
# that conforms to the Tk text widget interface.
|
||||
# * event is the event being fired. Will always be None
|
||||
# in the current implementation.
|
||||
|
||||
# Simply by defining these functions, they are available as
|
||||
# events.
|
||||
# Note that we bind keystrokes to these events in the various
|
||||
# [Keys] sections.
|
||||
|
||||
# Add a simple file/class/function simple banner
|
||||
def AddBanner(editor_window, event):
|
||||
|
||||
text = editor_window.text
|
||||
big_line = "#" * 70
|
||||
banner = "%s\n## \n## \n## \n%s\n" % (big_line, big_line)
|
||||
|
||||
# Insert at the start of the current line.
|
||||
pos = text.index("insert linestart")
|
||||
|
||||
text.undo_block_start() # Allow action to be undone as a single unit.
|
||||
text.insert(pos, banner)
|
||||
text.undo_block_stop()
|
||||
|
||||
# Now set the insert point to the middle of the banner.
|
||||
line, col = [int(s) for s in pos.split(".")]
|
||||
text.mark_set("insert", "%d.1 lineend" % (line+2, ) )
|
||||
|
||||
|
||||
# Here is a sample event bound to the "Home" key in the
|
||||
# interactive window
|
||||
def InteractiveHome(editor_window, event):
|
||||
return _DoInteractiveHome(editor_window.text, 0)
|
||||
|
||||
def InteractiveHomeExtend(editor_window, event):
|
||||
return _DoInteractiveHome(editor_window.text, 1)
|
||||
|
||||
def _DoInteractiveHome(text, extend):
|
||||
import sys
|
||||
# If Scintilla has an autocomplete window open, then let Scintilla handle it.
|
||||
if text.edit.SCIAutoCActive():
|
||||
return 1
|
||||
of_interest = "insert linestart + %d c" % len(sys.ps1)
|
||||
if not text.compare("insert", "==", of_interest) and \
|
||||
text.get("insert linestart", of_interest) in [sys.ps1, sys.ps2]: # Not sys.ps? line
|
||||
end = of_interest
|
||||
else:
|
||||
end = "insert linestart"
|
||||
|
||||
if extend: start = "insert"
|
||||
else: start = end
|
||||
text.tag_add("sel", start, end)
|
||||
|
||||
# From Niki Spahie
|
||||
def AutoFindNext(editor_window, event):
|
||||
"find selected text or word under cursor"
|
||||
|
||||
from pywin.scintilla import find
|
||||
from pywin.scintilla import scintillacon
|
||||
|
||||
try:
|
||||
sci = editor_window.edit
|
||||
word = sci.GetSelText()
|
||||
if word:
|
||||
find.lastSearch.findText = word
|
||||
find.lastSearch.sel = sci.GetSel()
|
||||
else:
|
||||
pos = sci.SendScintilla( scintillacon.SCI_GETCURRENTPOS )
|
||||
start = sci.SendScintilla( scintillacon.SCI_WORDSTARTPOSITION, pos, 1 )
|
||||
end = sci.SendScintilla( scintillacon.SCI_WORDENDPOSITION, pos, 1 )
|
||||
word = sci.GetTextRange( start, end )
|
||||
if word:
|
||||
find.lastSearch.findText = word
|
||||
find.lastSearch.sel = (start,end)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
find.FindNext()
|
||||
|
||||
|
||||
# A couple of generic events.
|
||||
def Beep(editor_window, event):
|
||||
editor_window.text.beep()
|
||||
|
||||
def DoNothing(editor_window, event):
|
||||
pass
|
||||
|
||||
def ContinueEvent(editor_window, event):
|
||||
# Almost an "unbind" - allows Pythonwin/MFC to handle the keystroke
|
||||
return 1
|
||||
@@ -0,0 +1,136 @@
|
||||
# The property page to define generic IDE options for Pythonwin
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework import interact
|
||||
from pywin.mfc import dialog
|
||||
|
||||
buttonControlMap = {
|
||||
win32ui.IDC_BUTTON1: win32ui.IDC_EDIT1,
|
||||
win32ui.IDC_BUTTON2: win32ui.IDC_EDIT2,
|
||||
win32ui.IDC_BUTTON3: win32ui.IDC_EDIT3,
|
||||
}
|
||||
|
||||
|
||||
class OptionsPropPage(dialog.PropertyPage):
|
||||
def __init__(self):
|
||||
dialog.PropertyPage.__init__(self, win32ui.IDD_PP_IDE)
|
||||
self.AddDDX(win32ui.IDC_CHECK1, "bShowAtStartup")
|
||||
self.AddDDX(win32ui.IDC_CHECK2, "bDocking")
|
||||
self.AddDDX(win32ui.IDC_EDIT4, "MRUSize", "i")
|
||||
|
||||
def OnInitDialog(self):
|
||||
edit = self.GetDlgItem(win32ui.IDC_EDIT1)
|
||||
format = eval(
|
||||
win32ui.GetProfileVal(
|
||||
interact.sectionProfile,
|
||||
interact.STYLE_INTERACTIVE_PROMPT,
|
||||
str(interact.formatInput),
|
||||
)
|
||||
)
|
||||
edit.SetDefaultCharFormat(format)
|
||||
edit.SetWindowText("Input Text")
|
||||
|
||||
edit = self.GetDlgItem(win32ui.IDC_EDIT2)
|
||||
format = eval(
|
||||
win32ui.GetProfileVal(
|
||||
interact.sectionProfile,
|
||||
interact.STYLE_INTERACTIVE_OUTPUT,
|
||||
str(interact.formatOutput),
|
||||
)
|
||||
)
|
||||
edit.SetDefaultCharFormat(format)
|
||||
edit.SetWindowText("Output Text")
|
||||
|
||||
edit = self.GetDlgItem(win32ui.IDC_EDIT3)
|
||||
format = eval(
|
||||
win32ui.GetProfileVal(
|
||||
interact.sectionProfile,
|
||||
interact.STYLE_INTERACTIVE_ERROR,
|
||||
str(interact.formatOutputError),
|
||||
)
|
||||
)
|
||||
edit.SetDefaultCharFormat(format)
|
||||
edit.SetWindowText("Error Text")
|
||||
|
||||
self["bShowAtStartup"] = interact.LoadPreference("Show at startup", 1)
|
||||
self["bDocking"] = interact.LoadPreference("Docking", 0)
|
||||
self["MRUSize"] = win32ui.GetProfileVal("Settings", "Recent File List Size", 10)
|
||||
|
||||
# Hook the button clicks.
|
||||
self.HookCommand(self.HandleCharFormatChange, win32ui.IDC_BUTTON1)
|
||||
self.HookCommand(self.HandleCharFormatChange, win32ui.IDC_BUTTON2)
|
||||
self.HookCommand(self.HandleCharFormatChange, win32ui.IDC_BUTTON3)
|
||||
|
||||
# Ensure the spin control remains in range.
|
||||
spinner = self.GetDlgItem(win32ui.IDC_SPIN1)
|
||||
spinner.SetRange(1, 16)
|
||||
|
||||
return dialog.PropertyPage.OnInitDialog(self)
|
||||
|
||||
# Called to save away the new format tuple for the specified item.
|
||||
def HandleCharFormatChange(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
editId = buttonControlMap.get(id)
|
||||
assert editId is not None, "Format button has no associated edit control"
|
||||
editControl = self.GetDlgItem(editId)
|
||||
existingFormat = editControl.GetDefaultCharFormat()
|
||||
flags = win32con.CF_SCREENFONTS
|
||||
d = win32ui.CreateFontDialog(existingFormat, flags, None, self)
|
||||
if d.DoModal() == win32con.IDOK:
|
||||
cf = d.GetCharFormat()
|
||||
editControl.SetDefaultCharFormat(cf)
|
||||
self.SetModified(1)
|
||||
return 0 # We handled this fully!
|
||||
|
||||
def OnOK(self):
|
||||
# Handle the edit controls - get all the fonts, put them back into interact, then
|
||||
# get interact to save its stuff!
|
||||
controlAttrs = [
|
||||
(win32ui.IDC_EDIT1, interact.STYLE_INTERACTIVE_PROMPT),
|
||||
(win32ui.IDC_EDIT2, interact.STYLE_INTERACTIVE_OUTPUT),
|
||||
(win32ui.IDC_EDIT3, interact.STYLE_INTERACTIVE_ERROR),
|
||||
]
|
||||
for id, key in controlAttrs:
|
||||
control = self.GetDlgItem(id)
|
||||
fmt = control.GetDefaultCharFormat()
|
||||
win32ui.WriteProfileVal(interact.sectionProfile, key, str(fmt))
|
||||
|
||||
# Save the other interactive window options.
|
||||
interact.SavePreference("Show at startup", self["bShowAtStartup"])
|
||||
interact.SavePreference("Docking", self["bDocking"])
|
||||
|
||||
# And the other options.
|
||||
win32ui.WriteProfileVal("Settings", "Recent File List Size", self["MRUSize"])
|
||||
|
||||
return 1
|
||||
|
||||
def ChangeFormat(self, fmtAttribute, fmt):
|
||||
dlg = win32ui.CreateFontDialog(fmt)
|
||||
if dlg.DoModal() != win32con.IDOK:
|
||||
return None
|
||||
return dlg.GetCharFormat()
|
||||
|
||||
def OnFormatTitle(self, command, code):
|
||||
fmt = self.GetFormat(interact.formatTitle)
|
||||
if fmt:
|
||||
interact.formatTitle = fmt
|
||||
interact.SaveFontPreferences()
|
||||
|
||||
def OnFormatInput(self, command, code):
|
||||
fmt = self.GetFormat(interact.formatInput)
|
||||
if fmt:
|
||||
interact.formatInput = fmt
|
||||
interact.SaveFontPreferences()
|
||||
|
||||
def OnFormatOutput(self, command, code):
|
||||
fmt = self.GetFormat(interact.formatOutput)
|
||||
if fmt:
|
||||
interact.formatOutput = fmt
|
||||
interact.SaveFontPreferences()
|
||||
|
||||
def OnFormatError(self, command, code):
|
||||
fmt = self.GetFormat(interact.formatOutputError)
|
||||
if fmt:
|
||||
interact.formatOutputError = fmt
|
||||
interact.SaveFontPreferences()
|
||||
@@ -0,0 +1,146 @@
|
||||
import commctrl
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
|
||||
|
||||
class ListDialog(dialog.Dialog):
|
||||
def __init__(self, title, list):
|
||||
dialog.Dialog.__init__(self, self._maketemplate(title))
|
||||
self.HookMessage(self.on_size, win32con.WM_SIZE)
|
||||
self.HookNotify(self.OnListItemChange, commctrl.LVN_ITEMCHANGED)
|
||||
self.HookCommand(self.OnListClick, win32ui.IDC_LIST1)
|
||||
self.items = list
|
||||
|
||||
def _maketemplate(self, title):
|
||||
style = win32con.WS_DLGFRAME | win32con.WS_SYSMENU | win32con.WS_VISIBLE
|
||||
ls = (
|
||||
win32con.WS_CHILD
|
||||
| win32con.WS_VISIBLE
|
||||
| commctrl.LVS_ALIGNLEFT
|
||||
| commctrl.LVS_REPORT
|
||||
)
|
||||
bs = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
return [
|
||||
[title, (0, 0, 200, 200), style, None, (8, "MS Sans Serif")],
|
||||
["SysListView32", None, win32ui.IDC_LIST1, (0, 0, 200, 200), ls],
|
||||
[128, "OK", win32con.IDOK, (10, 0, 50, 14), bs | win32con.BS_DEFPUSHBUTTON],
|
||||
[128, "Cancel", win32con.IDCANCEL, (0, 0, 50, 14), bs],
|
||||
]
|
||||
|
||||
def FillList(self):
|
||||
size = self.GetWindowRect()
|
||||
width = size[2] - size[0] - (10)
|
||||
itemDetails = (commctrl.LVCFMT_LEFT, width, "Item", 0)
|
||||
self.itemsControl.InsertColumn(0, itemDetails)
|
||||
index = 0
|
||||
for item in self.items:
|
||||
index = self.itemsControl.InsertItem(index + 1, str(item), 0)
|
||||
|
||||
def OnListClick(self, id, code):
|
||||
if code == commctrl.NM_DBLCLK:
|
||||
self.EndDialog(win32con.IDOK)
|
||||
return 1
|
||||
|
||||
def OnListItemChange(self, std, extra):
|
||||
(
|
||||
(hwndFrom, idFrom, code),
|
||||
(
|
||||
itemNotify,
|
||||
sub,
|
||||
newState,
|
||||
oldState,
|
||||
change,
|
||||
point,
|
||||
lparam,
|
||||
),
|
||||
) = (std, extra)
|
||||
oldSel = (oldState & commctrl.LVIS_SELECTED) != 0
|
||||
newSel = (newState & commctrl.LVIS_SELECTED) != 0
|
||||
if oldSel != newSel:
|
||||
try:
|
||||
self.selecteditem = itemNotify
|
||||
self.butOK.EnableWindow(1)
|
||||
except win32ui.error:
|
||||
self.selecteditem = None
|
||||
|
||||
def OnInitDialog(self):
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
self.itemsControl = self.GetDlgItem(win32ui.IDC_LIST1)
|
||||
self.butOK = self.GetDlgItem(win32con.IDOK)
|
||||
self.butCancel = self.GetDlgItem(win32con.IDCANCEL)
|
||||
|
||||
self.FillList()
|
||||
|
||||
size = self.GetWindowRect()
|
||||
self.LayoutControls(size[2] - size[0], size[3] - size[1])
|
||||
self.butOK.EnableWindow(0) # wait for first selection
|
||||
return rc
|
||||
|
||||
def LayoutControls(self, w, h):
|
||||
self.itemsControl.MoveWindow((0, 0, w, h - 30))
|
||||
self.butCancel.MoveWindow((10, h - 24, 60, h - 4))
|
||||
self.butOK.MoveWindow((w - 60, h - 24, w - 10, h - 4))
|
||||
|
||||
def on_size(self, params):
|
||||
lparam = params[3]
|
||||
w = win32api.LOWORD(lparam)
|
||||
h = win32api.HIWORD(lparam)
|
||||
self.LayoutControls(w, h)
|
||||
|
||||
|
||||
class ListsDialog(ListDialog):
|
||||
def __init__(self, title, list, colHeadings=["Item"]):
|
||||
ListDialog.__init__(self, title, list)
|
||||
self.colHeadings = colHeadings
|
||||
|
||||
def FillList(self):
|
||||
index = 0
|
||||
size = self.GetWindowRect()
|
||||
width = (
|
||||
size[2] - size[0] - (10) - win32api.GetSystemMetrics(win32con.SM_CXVSCROLL)
|
||||
)
|
||||
numCols = len(self.colHeadings)
|
||||
|
||||
for col in self.colHeadings:
|
||||
itemDetails = (commctrl.LVCFMT_LEFT, int(width / numCols), col, 0)
|
||||
self.itemsControl.InsertColumn(index, itemDetails)
|
||||
index += 1
|
||||
index = 0
|
||||
for items in self.items:
|
||||
index = self.itemsControl.InsertItem(index + 1, str(items[0]), 0)
|
||||
for itemno in range(1, numCols):
|
||||
item = items[itemno]
|
||||
self.itemsControl.SetItemText(index, itemno, str(item))
|
||||
|
||||
|
||||
def SelectFromList(title, lst):
|
||||
dlg = ListDialog(title, lst)
|
||||
if dlg.DoModal() == win32con.IDOK:
|
||||
return dlg.selecteditem
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def SelectFromLists(title, lists, headings):
|
||||
dlg = ListsDialog(title, lists, headings)
|
||||
if dlg.DoModal() == win32con.IDOK:
|
||||
return dlg.selecteditem
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def test():
|
||||
# print SelectFromList('Single list', [1,2,3])
|
||||
print(
|
||||
SelectFromLists(
|
||||
"Multi-List",
|
||||
[("1", 1, "a"), ("2", 2, "b"), ("3", 3, "c")],
|
||||
["Col 1", "Col 2"],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
@@ -0,0 +1,155 @@
|
||||
"""login -- PythonWin user ID and password dialog box
|
||||
|
||||
(Adapted from originally distributed with Mark Hammond's PythonWin -
|
||||
this now replaces it!)
|
||||
|
||||
login.GetLogin() displays a modal "OK/Cancel" dialog box with input
|
||||
fields for a user ID and password. The password field input is masked
|
||||
with *'s. GetLogin takes two optional parameters, a window title, and a
|
||||
default user ID. If these parameters are omitted, the title defaults to
|
||||
"Login", and the user ID is left blank. GetLogin returns a (userid, password)
|
||||
tuple. GetLogin can be called from scripts running on the console - i.e. you
|
||||
don't need to write a full-blown GUI app to use it.
|
||||
|
||||
login.GetPassword() is similar, except there is no username field.
|
||||
|
||||
Example:
|
||||
import pywin.dialogs.login
|
||||
title = "FTP Login"
|
||||
def_user = "fred"
|
||||
userid, password = pywin.dialogs.login.GetLogin(title, def_user)
|
||||
|
||||
Jim Eggleston, 28 August 1996
|
||||
Merged with dlgpass and moved to pywin.dialogs by Mark Hammond Jan 1998.
|
||||
"""
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
|
||||
|
||||
def MakeLoginDlgTemplate(title):
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
cs = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
|
||||
# Window frame and title
|
||||
dlg = [
|
||||
[title, (0, 0, 184, 40), style, None, (8, "MS Sans Serif")],
|
||||
]
|
||||
|
||||
# ID label and text box
|
||||
dlg.append([130, "User ID:", -1, (7, 9, 69, 9), cs | win32con.SS_LEFT])
|
||||
s = cs | win32con.WS_TABSTOP | win32con.WS_BORDER
|
||||
dlg.append(["EDIT", None, win32ui.IDC_EDIT1, (50, 7, 60, 12), s])
|
||||
|
||||
# Password label and text box
|
||||
dlg.append([130, "Password:", -1, (7, 22, 69, 9), cs | win32con.SS_LEFT])
|
||||
s = cs | win32con.WS_TABSTOP | win32con.WS_BORDER
|
||||
dlg.append(
|
||||
["EDIT", None, win32ui.IDC_EDIT2, (50, 20, 60, 12), s | win32con.ES_PASSWORD]
|
||||
)
|
||||
|
||||
# OK/Cancel Buttons
|
||||
s = cs | win32con.WS_TABSTOP
|
||||
dlg.append(
|
||||
[128, "OK", win32con.IDOK, (124, 5, 50, 14), s | win32con.BS_DEFPUSHBUTTON]
|
||||
)
|
||||
s = win32con.BS_PUSHBUTTON | s
|
||||
dlg.append([128, "Cancel", win32con.IDCANCEL, (124, 20, 50, 14), s])
|
||||
return dlg
|
||||
|
||||
|
||||
def MakePasswordDlgTemplate(title):
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
cs = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
# Window frame and title
|
||||
dlg = [
|
||||
[title, (0, 0, 177, 45), style, None, (8, "MS Sans Serif")],
|
||||
]
|
||||
|
||||
# Password label and text box
|
||||
dlg.append([130, "Password:", -1, (7, 7, 69, 9), cs | win32con.SS_LEFT])
|
||||
s = cs | win32con.WS_TABSTOP | win32con.WS_BORDER
|
||||
dlg.append(
|
||||
["EDIT", None, win32ui.IDC_EDIT1, (50, 7, 60, 12), s | win32con.ES_PASSWORD]
|
||||
)
|
||||
|
||||
# OK/Cancel Buttons
|
||||
s = cs | win32con.WS_TABSTOP | win32con.BS_PUSHBUTTON
|
||||
dlg.append(
|
||||
[128, "OK", win32con.IDOK, (124, 5, 50, 14), s | win32con.BS_DEFPUSHBUTTON]
|
||||
)
|
||||
dlg.append([128, "Cancel", win32con.IDCANCEL, (124, 22, 50, 14), s])
|
||||
return dlg
|
||||
|
||||
|
||||
class LoginDlg(dialog.Dialog):
|
||||
Cancel = 0
|
||||
|
||||
def __init__(self, title):
|
||||
dialog.Dialog.__init__(self, MakeLoginDlgTemplate(title))
|
||||
self.AddDDX(win32ui.IDC_EDIT1, "userid")
|
||||
self.AddDDX(win32ui.IDC_EDIT2, "password")
|
||||
|
||||
|
||||
def GetLogin(title="Login", userid="", password=""):
|
||||
d = LoginDlg(title)
|
||||
d["userid"] = userid
|
||||
d["password"] = password
|
||||
if d.DoModal() != win32con.IDOK:
|
||||
return (None, None)
|
||||
else:
|
||||
return (d["userid"], d["password"])
|
||||
|
||||
|
||||
class PasswordDlg(dialog.Dialog):
|
||||
def __init__(self, title):
|
||||
dialog.Dialog.__init__(self, MakePasswordDlgTemplate(title))
|
||||
self.AddDDX(win32ui.IDC_EDIT1, "password")
|
||||
|
||||
|
||||
def GetPassword(title="Password", password=""):
|
||||
d = PasswordDlg(title)
|
||||
d["password"] = password
|
||||
if d.DoModal() != win32con.IDOK:
|
||||
return None
|
||||
return d["password"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
title = "Login"
|
||||
def_user = ""
|
||||
if len(sys.argv) > 1:
|
||||
title = sys.argv[1]
|
||||
if len(sys.argv) > 2:
|
||||
def_userid = sys.argv[2]
|
||||
userid, password = GetLogin(title, def_user)
|
||||
if userid == password is None:
|
||||
print("User pressed Cancel")
|
||||
else:
|
||||
print("User ID: ", userid)
|
||||
print("Password:", password)
|
||||
newpassword = GetPassword("Reenter just for fun", password)
|
||||
if newpassword is None:
|
||||
print("User cancelled")
|
||||
else:
|
||||
what = ""
|
||||
if newpassword != password:
|
||||
what = "not "
|
||||
print("The passwords did %smatch" % (what))
|
||||
@@ -0,0 +1,242 @@
|
||||
# No cancel button.
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
from pywin.mfc.thread import WinThread
|
||||
|
||||
|
||||
def MakeProgressDlgTemplate(caption, staticText=""):
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
cs = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
|
||||
w = 215
|
||||
h = 36 # With button
|
||||
h = 40
|
||||
|
||||
dlg = [
|
||||
[caption, (0, 0, w, h), style, None, (8, "MS Sans Serif")],
|
||||
]
|
||||
|
||||
s = win32con.WS_TABSTOP | cs
|
||||
|
||||
dlg.append([130, staticText, 1000, (7, 7, w - 7, h - 32), cs | win32con.SS_LEFT])
|
||||
|
||||
# dlg.append([128,
|
||||
# "Cancel",
|
||||
# win32con.IDCANCEL,
|
||||
# (w - 60, h - 18, 50, 14), s | win32con.BS_PUSHBUTTON])
|
||||
|
||||
return dlg
|
||||
|
||||
|
||||
class CStatusProgressDialog(dialog.Dialog):
|
||||
def __init__(self, title, msg="", maxticks=100, tickincr=1):
|
||||
self.initMsg = msg
|
||||
templ = MakeProgressDlgTemplate(title, msg)
|
||||
dialog.Dialog.__init__(self, templ)
|
||||
self.maxticks = maxticks
|
||||
self.tickincr = tickincr
|
||||
self.pbar = None
|
||||
|
||||
def OnInitDialog(self):
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
self.static = self.GetDlgItem(1000)
|
||||
self.pbar = win32ui.CreateProgressCtrl()
|
||||
self.pbar.CreateWindow(
|
||||
win32con.WS_CHILD | win32con.WS_VISIBLE, (10, 30, 310, 44), self, 1001
|
||||
)
|
||||
self.pbar.SetRange(0, self.maxticks)
|
||||
self.pbar.SetStep(self.tickincr)
|
||||
self.progress = 0
|
||||
self.pincr = 5
|
||||
return rc
|
||||
|
||||
def Close(self):
|
||||
self.EndDialog(0)
|
||||
|
||||
def SetMaxTicks(self, maxticks):
|
||||
if self.pbar is not None:
|
||||
self.pbar.SetRange(0, maxticks)
|
||||
|
||||
def Tick(self):
|
||||
if self.pbar is not None:
|
||||
self.pbar.StepIt()
|
||||
|
||||
def SetTitle(self, text):
|
||||
self.SetWindowText(text)
|
||||
|
||||
def SetText(self, text):
|
||||
self.SetDlgItemText(1000, text)
|
||||
|
||||
def Set(self, pos, max=None):
|
||||
if self.pbar is not None:
|
||||
self.pbar.SetPos(pos)
|
||||
if max is not None:
|
||||
self.pbar.SetRange(0, max)
|
||||
|
||||
|
||||
# a progress dialog created in a new thread - especially suitable for
|
||||
# console apps with no message loop.
|
||||
MYWM_SETTITLE = win32con.WM_USER + 10
|
||||
MYWM_SETMSG = win32con.WM_USER + 11
|
||||
MYWM_TICK = win32con.WM_USER + 12
|
||||
MYWM_SETMAXTICKS = win32con.WM_USER + 13
|
||||
MYWM_SET = win32con.WM_USER + 14
|
||||
|
||||
|
||||
class CThreadedStatusProcessDialog(CStatusProgressDialog):
|
||||
def __init__(self, title, msg="", maxticks=100, tickincr=1):
|
||||
self.title = title
|
||||
self.msg = msg
|
||||
self.threadid = win32api.GetCurrentThreadId()
|
||||
CStatusProgressDialog.__init__(self, title, msg, maxticks, tickincr)
|
||||
|
||||
def OnInitDialog(self):
|
||||
rc = CStatusProgressDialog.OnInitDialog(self)
|
||||
self.HookMessage(self.OnTitle, MYWM_SETTITLE)
|
||||
self.HookMessage(self.OnMsg, MYWM_SETMSG)
|
||||
self.HookMessage(self.OnTick, MYWM_TICK)
|
||||
self.HookMessage(self.OnMaxTicks, MYWM_SETMAXTICKS)
|
||||
self.HookMessage(self.OnSet, MYWM_SET)
|
||||
return rc
|
||||
|
||||
def _Send(self, msg):
|
||||
try:
|
||||
self.PostMessage(msg)
|
||||
except win32ui.error:
|
||||
# the user closed the window - but this does not cancel the
|
||||
# process - so just ignore it.
|
||||
pass
|
||||
|
||||
def OnTitle(self, msg):
|
||||
CStatusProgressDialog.SetTitle(self, self.title)
|
||||
|
||||
def OnMsg(self, msg):
|
||||
CStatusProgressDialog.SetText(self, self.msg)
|
||||
|
||||
def OnTick(self, msg):
|
||||
CStatusProgressDialog.Tick(self)
|
||||
|
||||
def OnMaxTicks(self, msg):
|
||||
CStatusProgressDialog.SetMaxTicks(self, self.maxticks)
|
||||
|
||||
def OnSet(self, msg):
|
||||
CStatusProgressDialog.Set(self, self.pos, self.max)
|
||||
|
||||
def Close(self):
|
||||
assert self.threadid, "No thread!"
|
||||
win32api.PostThreadMessage(self.threadid, win32con.WM_QUIT, 0, 0)
|
||||
|
||||
def SetMaxTicks(self, maxticks):
|
||||
self.maxticks = maxticks
|
||||
self._Send(MYWM_SETMAXTICKS)
|
||||
|
||||
def SetTitle(self, title):
|
||||
self.title = title
|
||||
self._Send(MYWM_SETTITLE)
|
||||
|
||||
def SetText(self, text):
|
||||
self.msg = text
|
||||
self._Send(MYWM_SETMSG)
|
||||
|
||||
def Tick(self):
|
||||
self._Send(MYWM_TICK)
|
||||
|
||||
def Set(self, pos, max=None):
|
||||
self.pos = pos
|
||||
self.max = max
|
||||
self._Send(MYWM_SET)
|
||||
|
||||
|
||||
class ProgressThread(WinThread):
|
||||
def __init__(self, title, msg="", maxticks=100, tickincr=1):
|
||||
self.title = title
|
||||
self.msg = msg
|
||||
self.maxticks = maxticks
|
||||
self.tickincr = tickincr
|
||||
self.dialog = None
|
||||
WinThread.__init__(self)
|
||||
self.createdEvent = threading.Event()
|
||||
|
||||
def InitInstance(self):
|
||||
self.dialog = CThreadedStatusProcessDialog(
|
||||
self.title, self.msg, self.maxticks, self.tickincr
|
||||
)
|
||||
self.dialog.CreateWindow()
|
||||
try:
|
||||
self.dialog.SetForegroundWindow()
|
||||
except win32ui.error:
|
||||
pass
|
||||
self.createdEvent.set()
|
||||
return WinThread.InitInstance(self)
|
||||
|
||||
def ExitInstance(self):
|
||||
return 0
|
||||
|
||||
|
||||
def StatusProgressDialog(title, msg="", maxticks=100, parent=None):
|
||||
d = CStatusProgressDialog(title, msg, maxticks)
|
||||
d.CreateWindow(parent)
|
||||
return d
|
||||
|
||||
|
||||
def ThreadedStatusProgressDialog(title, msg="", maxticks=100):
|
||||
t = ProgressThread(title, msg, maxticks)
|
||||
t.CreateThread()
|
||||
# Need to run a basic "PumpWaitingMessages" loop just incase we are
|
||||
# running inside Pythonwin.
|
||||
# Basic timeout incase things go terribly wrong. Ideally we should use
|
||||
# win32event.MsgWaitForMultipleObjects(), but we use a threading module
|
||||
# event - so use a dumb strategy
|
||||
end_time = time.time() + 10
|
||||
while time.time() < end_time:
|
||||
if t.createdEvent.isSet():
|
||||
break
|
||||
win32ui.PumpWaitingMessages()
|
||||
time.sleep(0.1)
|
||||
return t.dialog
|
||||
|
||||
|
||||
def demo():
|
||||
d = StatusProgressDialog("A Demo", "Doing something...")
|
||||
import win32api
|
||||
|
||||
for i in range(100):
|
||||
if i == 50:
|
||||
d.SetText("Getting there...")
|
||||
if i == 90:
|
||||
d.SetText("Nearly done...")
|
||||
win32api.Sleep(20)
|
||||
d.Tick()
|
||||
d.Close()
|
||||
|
||||
|
||||
def thread_demo():
|
||||
d = ThreadedStatusProgressDialog("A threaded demo", "Doing something")
|
||||
import win32api
|
||||
|
||||
for i in range(100):
|
||||
if i == 50:
|
||||
d.SetText("Getting there...")
|
||||
if i == 90:
|
||||
d.SetText("Nearly done...")
|
||||
win32api.Sleep(20)
|
||||
d.Tick()
|
||||
d.Close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
thread_demo()
|
||||
# demo()
|
||||
@@ -0,0 +1,676 @@
|
||||
# DockingBar.py
|
||||
|
||||
# Ported directly (comments and all) from the samples at www.codeguru.com
|
||||
|
||||
# WARNING: Use at your own risk, as this interface is highly likely to change.
|
||||
# Currently we support only one child per DockingBar. Later we need to add
|
||||
# support for multiple children.
|
||||
|
||||
import struct
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import afxres, window
|
||||
|
||||
clrBtnHilight = win32api.GetSysColor(win32con.COLOR_BTNHILIGHT)
|
||||
clrBtnShadow = win32api.GetSysColor(win32con.COLOR_BTNSHADOW)
|
||||
|
||||
|
||||
def CenterPoint(rect):
|
||||
width = rect[2] - rect[0]
|
||||
height = rect[3] - rect[1]
|
||||
return rect[0] + width // 2, rect[1] + height // 2
|
||||
|
||||
|
||||
def OffsetRect(rect, point):
|
||||
(x, y) = point
|
||||
return rect[0] + x, rect[1] + y, rect[2] + x, rect[3] + y
|
||||
|
||||
|
||||
def DeflateRect(rect, point):
|
||||
(x, y) = point
|
||||
return rect[0] + x, rect[1] + y, rect[2] - x, rect[3] - y
|
||||
|
||||
|
||||
def PtInRect(rect, pt):
|
||||
return rect[0] <= pt[0] < rect[2] and rect[1] <= pt[1] < rect[3]
|
||||
|
||||
|
||||
class DockingBar(window.Wnd):
|
||||
def __init__(self, obj=None):
|
||||
if obj is None:
|
||||
obj = win32ui.CreateControlBar()
|
||||
window.Wnd.__init__(self, obj)
|
||||
self.dialog = None
|
||||
self.nDockBarID = 0
|
||||
self.sizeMin = 32, 32
|
||||
self.sizeHorz = 200, 200
|
||||
self.sizeVert = 200, 200
|
||||
self.sizeFloat = 200, 200
|
||||
self.bTracking = 0
|
||||
self.bInRecalcNC = 0
|
||||
self.cxEdge = 6
|
||||
self.cxBorder = 3
|
||||
self.cxGripper = 20
|
||||
self.brushBkgd = win32ui.CreateBrush()
|
||||
self.brushBkgd.CreateSolidBrush(win32api.GetSysColor(win32con.COLOR_BTNFACE))
|
||||
|
||||
# Support for diagonal resizing
|
||||
self.cyBorder = 3
|
||||
self.cCaptionSize = win32api.GetSystemMetrics(win32con.SM_CYSMCAPTION)
|
||||
self.cMinWidth = win32api.GetSystemMetrics(win32con.SM_CXMIN)
|
||||
self.cMinHeight = win32api.GetSystemMetrics(win32con.SM_CYMIN)
|
||||
self.rectUndock = (0, 0, 0, 0)
|
||||
|
||||
def OnUpdateCmdUI(self, target, bDisableIfNoHndler):
|
||||
return self.UpdateDialogControls(target, bDisableIfNoHndler)
|
||||
|
||||
def CreateWindow(
|
||||
self,
|
||||
parent,
|
||||
childCreator,
|
||||
title,
|
||||
id,
|
||||
style=win32con.WS_CHILD | win32con.WS_VISIBLE | afxres.CBRS_LEFT,
|
||||
childCreatorArgs=(),
|
||||
):
|
||||
assert not (
|
||||
(style & afxres.CBRS_SIZE_FIXED) and (style & afxres.CBRS_SIZE_DYNAMIC)
|
||||
), "Invalid style"
|
||||
self.rectClose = self.rectBorder = self.rectGripper = self.rectTracker = (
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
|
||||
# save the style
|
||||
self._obj_.dwStyle = style & afxres.CBRS_ALL
|
||||
|
||||
cursor = win32api.LoadCursor(0, win32con.IDC_ARROW)
|
||||
wndClass = win32ui.RegisterWndClass(
|
||||
win32con.CS_DBLCLKS, cursor, self.brushBkgd.GetSafeHandle(), 0
|
||||
)
|
||||
|
||||
self._obj_.CreateWindow(wndClass, title, style, (0, 0, 0, 0), parent, id)
|
||||
|
||||
# Create the child dialog
|
||||
self.dialog = childCreator(*(self,) + childCreatorArgs)
|
||||
|
||||
# use the dialog dimensions as default base dimensions
|
||||
assert self.dialog.IsWindow(), (
|
||||
"The childCreator function %s did not create a window!" % childCreator
|
||||
)
|
||||
rect = self.dialog.GetWindowRect()
|
||||
self.sizeHorz = self.sizeVert = self.sizeFloat = (
|
||||
rect[2] - rect[0],
|
||||
rect[3] - rect[1],
|
||||
)
|
||||
|
||||
self.sizeHorz = self.sizeHorz[0], self.sizeHorz[1] + self.cxEdge + self.cxBorder
|
||||
self.sizeVert = self.sizeVert[0] + self.cxEdge + self.cxBorder, self.sizeVert[1]
|
||||
self.HookMessages()
|
||||
|
||||
def CalcFixedLayout(self, bStretch, bHorz):
|
||||
rectTop = self.dockSite.GetControlBar(
|
||||
afxres.AFX_IDW_DOCKBAR_TOP
|
||||
).GetWindowRect()
|
||||
rectLeft = self.dockSite.GetControlBar(
|
||||
afxres.AFX_IDW_DOCKBAR_LEFT
|
||||
).GetWindowRect()
|
||||
if bStretch:
|
||||
nHorzDockBarWidth = 32767
|
||||
nVertDockBarHeight = 32767
|
||||
else:
|
||||
nHorzDockBarWidth = rectTop[2] - rectTop[0] + 4
|
||||
nVertDockBarHeight = rectLeft[3] - rectLeft[1] + 4
|
||||
|
||||
if self.IsFloating():
|
||||
return self.sizeFloat
|
||||
if bHorz:
|
||||
return nHorzDockBarWidth, self.sizeHorz[1]
|
||||
return self.sizeVert[0], nVertDockBarHeight
|
||||
|
||||
def CalcDynamicLayout(self, length, mode):
|
||||
# Support for diagonal sizing.
|
||||
if self.IsFloating():
|
||||
self.GetParent().GetParent().ModifyStyle(win32ui.MFS_4THICKFRAME, 0)
|
||||
if mode & (win32ui.LM_HORZDOCK | win32ui.LM_VERTDOCK):
|
||||
flags = (
|
||||
win32con.SWP_NOSIZE
|
||||
| win32con.SWP_NOMOVE
|
||||
| win32con.SWP_NOZORDER
|
||||
| win32con.SWP_NOACTIVATE
|
||||
| win32con.SWP_FRAMECHANGED
|
||||
)
|
||||
self.SetWindowPos(
|
||||
0,
|
||||
(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
flags,
|
||||
)
|
||||
self.dockSite.RecalcLayout()
|
||||
return self._obj_.CalcDynamicLayout(length, mode)
|
||||
|
||||
if mode & win32ui.LM_MRUWIDTH:
|
||||
return self.sizeFloat
|
||||
if mode & win32ui.LM_COMMIT:
|
||||
self.sizeFloat = length, self.sizeFloat[1]
|
||||
return self.sizeFloat
|
||||
# More diagonal sizing.
|
||||
if self.IsFloating():
|
||||
dc = self.dockContext
|
||||
pt = win32api.GetCursorPos()
|
||||
windowRect = self.GetParent().GetParent().GetWindowRect()
|
||||
|
||||
hittest = dc.nHitTest
|
||||
if hittest == win32con.HTTOPLEFT:
|
||||
cx = max(windowRect[2] - pt[0], self.cMinWidth) - self.cxBorder
|
||||
cy = max(windowRect[3] - self.cCaptionSize - pt[1], self.cMinHeight) - 1
|
||||
self.sizeFloat = cx, cy
|
||||
|
||||
top = (
|
||||
min(pt[1], windowRect[3] - self.cCaptionSize - self.cMinHeight)
|
||||
- self.cyBorder
|
||||
)
|
||||
left = min(pt[0], windowRect[2] - self.cMinWidth) - 1
|
||||
dc.rectFrameDragHorz = (
|
||||
left,
|
||||
top,
|
||||
dc.rectFrameDragHorz[2],
|
||||
dc.rectFrameDragHorz[3],
|
||||
)
|
||||
return self.sizeFloat
|
||||
if hittest == win32con.HTTOPRIGHT:
|
||||
cx = max(pt[0] - windowRect[0], self.cMinWidth)
|
||||
cy = max(windowRect[3] - self.cCaptionSize - pt[1], self.cMinHeight) - 1
|
||||
self.sizeFloat = cx, cy
|
||||
|
||||
top = (
|
||||
min(pt[1], windowRect[3] - self.cCaptionSize - self.cMinHeight)
|
||||
- self.cyBorder
|
||||
)
|
||||
dc.rectFrameDragHorz = (
|
||||
dc.rectFrameDragHorz[0],
|
||||
top,
|
||||
dc.rectFrameDragHorz[2],
|
||||
dc.rectFrameDragHorz[3],
|
||||
)
|
||||
return self.sizeFloat
|
||||
|
||||
if hittest == win32con.HTBOTTOMLEFT:
|
||||
cx = max(windowRect[2] - pt[0], self.cMinWidth) - self.cxBorder
|
||||
cy = max(pt[1] - windowRect[1] - self.cCaptionSize, self.cMinHeight)
|
||||
self.sizeFloat = cx, cy
|
||||
|
||||
left = min(pt[0], windowRect[2] - self.cMinWidth) - 1
|
||||
dc.rectFrameDragHorz = (
|
||||
left,
|
||||
dc.rectFrameDragHorz[1],
|
||||
dc.rectFrameDragHorz[2],
|
||||
dc.rectFrameDragHorz[3],
|
||||
)
|
||||
return self.sizeFloat
|
||||
|
||||
if hittest == win32con.HTBOTTOMRIGHT:
|
||||
cx = max(pt[0] - windowRect[0], self.cMinWidth)
|
||||
cy = max(pt[1] - windowRect[1] - self.cCaptionSize, self.cMinHeight)
|
||||
self.sizeFloat = cx, cy
|
||||
return self.sizeFloat
|
||||
|
||||
if mode & win32ui.LM_LENGTHY:
|
||||
self.sizeFloat = self.sizeFloat[0], max(self.sizeMin[1], length)
|
||||
return self.sizeFloat
|
||||
else:
|
||||
return max(self.sizeMin[0], length), self.sizeFloat[1]
|
||||
|
||||
def OnWindowPosChanged(self, msg):
|
||||
if self.GetSafeHwnd() == 0 or self.dialog is None:
|
||||
return 0
|
||||
lparam = msg[3]
|
||||
""" LPARAM used with WM_WINDOWPOSCHANGED:
|
||||
typedef struct {
|
||||
HWND hwnd;
|
||||
HWND hwndInsertAfter;
|
||||
int x;
|
||||
int y;
|
||||
int cx;
|
||||
int cy;
|
||||
UINT flags;} WINDOWPOS;
|
||||
"""
|
||||
format = "PPiiiii"
|
||||
bytes = win32ui.GetBytes(lparam, struct.calcsize(format))
|
||||
hwnd, hwndAfter, x, y, cx, cy, flags = struct.unpack(format, bytes)
|
||||
|
||||
if self.bInRecalcNC:
|
||||
rc = self.GetClientRect()
|
||||
self.dialog.MoveWindow(rc)
|
||||
return 0
|
||||
# Find on which side are we docked
|
||||
nDockBarID = self.GetParent().GetDlgCtrlID()
|
||||
# Return if dropped at same location
|
||||
# no docking side change and no size change
|
||||
if (
|
||||
(nDockBarID == self.nDockBarID)
|
||||
and (flags & win32con.SWP_NOSIZE)
|
||||
and (
|
||||
(self._obj_.dwStyle & afxres.CBRS_BORDER_ANY) != afxres.CBRS_BORDER_ANY
|
||||
)
|
||||
):
|
||||
return
|
||||
self.nDockBarID = nDockBarID
|
||||
|
||||
# Force recalc the non-client area
|
||||
self.bInRecalcNC = 1
|
||||
try:
|
||||
swpflags = (
|
||||
win32con.SWP_NOSIZE
|
||||
| win32con.SWP_NOMOVE
|
||||
| win32con.SWP_NOZORDER
|
||||
| win32con.SWP_FRAMECHANGED
|
||||
)
|
||||
self.SetWindowPos(0, (0, 0, 0, 0), swpflags)
|
||||
finally:
|
||||
self.bInRecalcNC = 0
|
||||
return 0
|
||||
|
||||
# This is a virtual and not a message hook.
|
||||
def OnSetCursor(self, window, nHitTest, wMouseMsg):
|
||||
if nHitTest != win32con.HTSIZE or self.bTracking:
|
||||
return self._obj_.OnSetCursor(window, nHitTest, wMouseMsg)
|
||||
|
||||
if self.IsHorz():
|
||||
win32api.SetCursor(win32api.LoadCursor(0, win32con.IDC_SIZENS))
|
||||
else:
|
||||
win32api.SetCursor(win32api.LoadCursor(0, win32con.IDC_SIZEWE))
|
||||
return 1
|
||||
|
||||
# Mouse Handling
|
||||
def OnLButtonUp(self, msg):
|
||||
if not self.bTracking:
|
||||
return 1 # pass it on.
|
||||
self.StopTracking(1)
|
||||
return 0 # Don't pass on
|
||||
|
||||
def OnLButtonDown(self, msg):
|
||||
# UINT nFlags, CPoint point)
|
||||
# only start dragging if clicked in "void" space
|
||||
if self.dockBar is not None:
|
||||
# start the drag
|
||||
pt = msg[5]
|
||||
pt = self.ClientToScreen(pt)
|
||||
self.dockContext.StartDrag(pt)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def OnNcLButtonDown(self, msg):
|
||||
if self.bTracking:
|
||||
return 0
|
||||
nHitTest = wparam = msg[2]
|
||||
pt = msg[5]
|
||||
|
||||
if nHitTest == win32con.HTSYSMENU and not self.IsFloating():
|
||||
self.GetDockingFrame().ShowControlBar(self, 0, 0)
|
||||
elif nHitTest == win32con.HTMINBUTTON and not self.IsFloating():
|
||||
self.dockContext.ToggleDocking()
|
||||
elif (
|
||||
nHitTest == win32con.HTCAPTION
|
||||
and not self.IsFloating()
|
||||
and self.dockBar is not None
|
||||
):
|
||||
self.dockContext.StartDrag(pt)
|
||||
elif nHitTest == win32con.HTSIZE and not self.IsFloating():
|
||||
self.StartTracking()
|
||||
else:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def OnLButtonDblClk(self, msg):
|
||||
# only toggle docking if clicked in "void" space
|
||||
if self.dockBar is not None:
|
||||
# toggle docking
|
||||
self.dockContext.ToggleDocking()
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def OnNcLButtonDblClk(self, msg):
|
||||
nHitTest = wparam = msg[2]
|
||||
# UINT nHitTest, CPoint point)
|
||||
if self.dockBar is not None and nHitTest == win32con.HTCAPTION:
|
||||
# toggle docking
|
||||
self.dockContext.ToggleDocking()
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def OnMouseMove(self, msg):
|
||||
flags = wparam = msg[2]
|
||||
lparam = msg[3]
|
||||
if self.IsFloating() or not self.bTracking:
|
||||
return 1
|
||||
|
||||
# Convert unsigned 16 bit to signed 32 bit.
|
||||
x = win32api.LOWORD(lparam)
|
||||
if x & 32768:
|
||||
x |= -65536
|
||||
y = win32api.HIWORD(lparam)
|
||||
if y & 32768:
|
||||
y |= -65536
|
||||
pt = x, y
|
||||
cpt = CenterPoint(self.rectTracker)
|
||||
pt = self.ClientToWnd(pt)
|
||||
if self.IsHorz():
|
||||
if cpt[1] != pt[1]:
|
||||
self.OnInvertTracker(self.rectTracker)
|
||||
self.rectTracker = OffsetRect(self.rectTracker, (0, pt[1] - cpt[1]))
|
||||
self.OnInvertTracker(self.rectTracker)
|
||||
else:
|
||||
if cpt[0] != pt[0]:
|
||||
self.OnInvertTracker(self.rectTracker)
|
||||
self.rectTracker = OffsetRect(self.rectTracker, (pt[0] - cpt[0], 0))
|
||||
self.OnInvertTracker(self.rectTracker)
|
||||
|
||||
return 0 # Don't pass it on.
|
||||
|
||||
# def OnBarStyleChange(self, old, new):
|
||||
|
||||
def OnNcCalcSize(self, bCalcValid, size_info):
|
||||
(rc0, rc1, rc2, pos) = size_info
|
||||
self.rectBorder = self.GetWindowRect()
|
||||
self.rectBorder = OffsetRect(
|
||||
self.rectBorder, (-self.rectBorder[0], -self.rectBorder[1])
|
||||
)
|
||||
|
||||
dwBorderStyle = self._obj_.dwStyle | afxres.CBRS_BORDER_ANY
|
||||
|
||||
if self.nDockBarID == afxres.AFX_IDW_DOCKBAR_TOP:
|
||||
dwBorderStyle &= ~afxres.CBRS_BORDER_BOTTOM
|
||||
rc0.left += self.cxGripper
|
||||
rc0.bottom -= self.cxEdge
|
||||
rc0.top += self.cxBorder
|
||||
rc0.right -= self.cxBorder
|
||||
self.rectBorder = (
|
||||
self.rectBorder[0],
|
||||
self.rectBorder[3] - self.cxEdge,
|
||||
self.rectBorder[2],
|
||||
self.rectBorder[3],
|
||||
)
|
||||
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_BOTTOM:
|
||||
dwBorderStyle &= ~afxres.CBRS_BORDER_TOP
|
||||
rc0.left += self.cxGripper
|
||||
rc0.top += self.cxEdge
|
||||
rc0.bottom -= self.cxBorder
|
||||
rc0.right -= self.cxBorder
|
||||
self.rectBorder = (
|
||||
self.rectBorder[0],
|
||||
self.rectBorder[1],
|
||||
self.rectBorder[2],
|
||||
self.rectBorder[1] + self.cxEdge,
|
||||
)
|
||||
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_LEFT:
|
||||
dwBorderStyle &= ~afxres.CBRS_BORDER_RIGHT
|
||||
rc0.right -= self.cxEdge
|
||||
rc0.left += self.cxBorder
|
||||
rc0.bottom -= self.cxBorder
|
||||
rc0.top += self.cxGripper
|
||||
self.rectBorder = (
|
||||
self.rectBorder[2] - self.cxEdge,
|
||||
self.rectBorder[1],
|
||||
self.rectBorder[2],
|
||||
self.rectBorder[3],
|
||||
)
|
||||
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_RIGHT:
|
||||
dwBorderStyle &= ~afxres.CBRS_BORDER_LEFT
|
||||
rc0.left += self.cxEdge
|
||||
rc0.right -= self.cxBorder
|
||||
rc0.bottom -= self.cxBorder
|
||||
rc0.top += self.cxGripper
|
||||
self.rectBorder = (
|
||||
self.rectBorder[0],
|
||||
self.rectBorder[1],
|
||||
self.rectBorder[0] + self.cxEdge,
|
||||
self.rectBorder[3],
|
||||
)
|
||||
else:
|
||||
self.rectBorder = 0, 0, 0, 0
|
||||
|
||||
self.SetBarStyle(dwBorderStyle)
|
||||
return 0
|
||||
|
||||
def OnNcPaint(self, msg):
|
||||
self.EraseNonClient()
|
||||
dc = self.GetWindowDC()
|
||||
ctl = win32api.GetSysColor(win32con.COLOR_BTNHIGHLIGHT)
|
||||
cbr = win32api.GetSysColor(win32con.COLOR_BTNSHADOW)
|
||||
dc.Draw3dRect(self.rectBorder, ctl, cbr)
|
||||
|
||||
self.DrawGripper(dc)
|
||||
|
||||
rect = self.GetClientRect()
|
||||
self.InvalidateRect(rect, 1)
|
||||
return 0
|
||||
|
||||
def OnNcHitTest(self, pt): # A virtual, not a hooked message.
|
||||
if self.IsFloating():
|
||||
return 1
|
||||
|
||||
ptOrig = pt
|
||||
rect = self.GetWindowRect()
|
||||
pt = pt[0] - rect[0], pt[1] - rect[1]
|
||||
|
||||
if PtInRect(self.rectClose, pt):
|
||||
return win32con.HTSYSMENU
|
||||
elif PtInRect(self.rectUndock, pt):
|
||||
return win32con.HTMINBUTTON
|
||||
elif PtInRect(self.rectGripper, pt):
|
||||
return win32con.HTCAPTION
|
||||
elif PtInRect(self.rectBorder, pt):
|
||||
return win32con.HTSIZE
|
||||
else:
|
||||
return self._obj_.OnNcHitTest(ptOrig)
|
||||
|
||||
def StartTracking(self):
|
||||
self.SetCapture()
|
||||
|
||||
# make sure no updates are pending
|
||||
self.RedrawWindow(None, None, win32con.RDW_ALLCHILDREN | win32con.RDW_UPDATENOW)
|
||||
self.dockSite.LockWindowUpdate()
|
||||
|
||||
self.ptOld = CenterPoint(self.rectBorder)
|
||||
self.bTracking = 1
|
||||
|
||||
self.rectTracker = self.rectBorder
|
||||
if not self.IsHorz():
|
||||
l, t, r, b = self.rectTracker
|
||||
b -= 4
|
||||
self.rectTracker = l, t, r, b
|
||||
|
||||
self.OnInvertTracker(self.rectTracker)
|
||||
|
||||
def OnCaptureChanged(self, msg):
|
||||
hwnd = lparam = msg[3]
|
||||
if self.bTracking and hwnd != self.GetSafeHwnd():
|
||||
self.StopTracking(0) # cancel tracking
|
||||
return 1
|
||||
|
||||
def StopTracking(self, bAccept):
|
||||
self.OnInvertTracker(self.rectTracker)
|
||||
self.dockSite.UnlockWindowUpdate()
|
||||
self.bTracking = 0
|
||||
self.ReleaseCapture()
|
||||
if not bAccept:
|
||||
return
|
||||
|
||||
rcc = self.dockSite.GetWindowRect()
|
||||
if self.IsHorz():
|
||||
newsize = self.sizeHorz[1]
|
||||
maxsize = newsize + (rcc[3] - rcc[1])
|
||||
minsize = self.sizeMin[1]
|
||||
else:
|
||||
newsize = self.sizeVert[0]
|
||||
maxsize = newsize + (rcc[2] - rcc[0])
|
||||
minsize = self.sizeMin[0]
|
||||
|
||||
pt = CenterPoint(self.rectTracker)
|
||||
if self.nDockBarID == afxres.AFX_IDW_DOCKBAR_TOP:
|
||||
newsize += pt[1] - self.ptOld[1]
|
||||
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_BOTTOM:
|
||||
newsize += -pt[1] + self.ptOld[1]
|
||||
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_LEFT:
|
||||
newsize += pt[0] - self.ptOld[0]
|
||||
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_RIGHT:
|
||||
newsize += -pt[0] + self.ptOld[0]
|
||||
newsize = max(minsize, min(maxsize, newsize))
|
||||
if self.IsHorz():
|
||||
self.sizeHorz = self.sizeHorz[0], newsize
|
||||
else:
|
||||
self.sizeVert = newsize, self.sizeVert[1]
|
||||
self.dockSite.RecalcLayout()
|
||||
return 0
|
||||
|
||||
def OnInvertTracker(self, rect):
|
||||
assert rect[2] - rect[0] > 0 and rect[3] - rect[1] > 0, "rect is empty"
|
||||
assert self.bTracking
|
||||
rcc = self.GetWindowRect()
|
||||
rcf = self.dockSite.GetWindowRect()
|
||||
|
||||
rect = OffsetRect(rect, (rcc[0] - rcf[0], rcc[1] - rcf[1]))
|
||||
rect = DeflateRect(rect, (1, 1))
|
||||
|
||||
flags = win32con.DCX_WINDOW | win32con.DCX_CACHE | win32con.DCX_LOCKWINDOWUPDATE
|
||||
dc = self.dockSite.GetDCEx(None, flags)
|
||||
try:
|
||||
brush = win32ui.GetHalftoneBrush()
|
||||
oldBrush = dc.SelectObject(brush)
|
||||
|
||||
dc.PatBlt(
|
||||
(rect[0], rect[1]),
|
||||
(rect[2] - rect[0], rect[3] - rect[1]),
|
||||
win32con.PATINVERT,
|
||||
)
|
||||
dc.SelectObject(oldBrush)
|
||||
finally:
|
||||
self.dockSite.ReleaseDC(dc)
|
||||
|
||||
def IsHorz(self):
|
||||
return (
|
||||
self.nDockBarID == afxres.AFX_IDW_DOCKBAR_TOP
|
||||
or self.nDockBarID == afxres.AFX_IDW_DOCKBAR_BOTTOM
|
||||
)
|
||||
|
||||
def ClientToWnd(self, pt):
|
||||
x, y = pt
|
||||
if self.nDockBarID == afxres.AFX_IDW_DOCKBAR_BOTTOM:
|
||||
y += self.cxEdge
|
||||
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_RIGHT:
|
||||
x += self.cxEdge
|
||||
return x, y
|
||||
|
||||
def DrawGripper(self, dc):
|
||||
# no gripper if floating
|
||||
if self._obj_.dwStyle & afxres.CBRS_FLOATING:
|
||||
return
|
||||
|
||||
# -==HACK==-
|
||||
# in order to calculate the client area properly after docking,
|
||||
# the client area must be recalculated twice (I have no idea why)
|
||||
self.dockSite.RecalcLayout()
|
||||
# -==END HACK==-
|
||||
|
||||
gripper = self.GetWindowRect()
|
||||
gripper = self.ScreenToClient(gripper)
|
||||
gripper = OffsetRect(gripper, (-gripper[0], -gripper[1]))
|
||||
gl, gt, gr, gb = gripper
|
||||
|
||||
if self._obj_.dwStyle & afxres.CBRS_ORIENT_HORZ:
|
||||
# gripper at left
|
||||
self.rectGripper = gl, gt + 40, gl + 20, gb
|
||||
# draw close box
|
||||
self.rectClose = gl + 7, gt + 10, gl + 19, gt + 22
|
||||
dc.DrawFrameControl(
|
||||
self.rectClose, win32con.DFC_CAPTION, win32con.DFCS_CAPTIONCLOSE
|
||||
)
|
||||
# draw docking toggle box
|
||||
self.rectUndock = OffsetRect(self.rectClose, (0, 13))
|
||||
dc.DrawFrameControl(
|
||||
self.rectUndock, win32con.DFC_CAPTION, win32con.DFCS_CAPTIONMAX
|
||||
)
|
||||
|
||||
gt += 38
|
||||
gb -= 10
|
||||
gl += 10
|
||||
gr = gl + 3
|
||||
gripper = gl, gt, gr, gb
|
||||
dc.Draw3dRect(gripper, clrBtnHilight, clrBtnShadow)
|
||||
dc.Draw3dRect(OffsetRect(gripper, (4, 0)), clrBtnHilight, clrBtnShadow)
|
||||
else:
|
||||
# gripper at top
|
||||
self.rectGripper = gl, gt, gr - 40, gt + 20
|
||||
# draw close box
|
||||
self.rectClose = gr - 21, gt + 7, gr - 10, gt + 18
|
||||
dc.DrawFrameControl(
|
||||
self.rectClose, win32con.DFC_CAPTION, win32con.DFCS_CAPTIONCLOSE
|
||||
)
|
||||
# draw docking toggle box
|
||||
self.rectUndock = OffsetRect(self.rectClose, (-13, 0))
|
||||
dc.DrawFrameControl(
|
||||
self.rectUndock, win32con.DFC_CAPTION, win32con.DFCS_CAPTIONMAX
|
||||
)
|
||||
gr -= 38
|
||||
gl += 10
|
||||
gt += 10
|
||||
gb = gt + 3
|
||||
|
||||
gripper = gl, gt, gr, gb
|
||||
dc.Draw3dRect(gripper, clrBtnHilight, clrBtnShadow)
|
||||
dc.Draw3dRect(OffsetRect(gripper, (0, 4)), clrBtnHilight, clrBtnShadow)
|
||||
|
||||
def HookMessages(self):
|
||||
self.HookMessage(self.OnLButtonUp, win32con.WM_LBUTTONUP)
|
||||
self.HookMessage(self.OnLButtonDown, win32con.WM_LBUTTONDOWN)
|
||||
self.HookMessage(self.OnLButtonDblClk, win32con.WM_LBUTTONDBLCLK)
|
||||
self.HookMessage(self.OnNcLButtonDown, win32con.WM_NCLBUTTONDOWN)
|
||||
self.HookMessage(self.OnNcLButtonDblClk, win32con.WM_NCLBUTTONDBLCLK)
|
||||
self.HookMessage(self.OnMouseMove, win32con.WM_MOUSEMOVE)
|
||||
self.HookMessage(self.OnNcPaint, win32con.WM_NCPAINT)
|
||||
self.HookMessage(self.OnCaptureChanged, win32con.WM_CAPTURECHANGED)
|
||||
self.HookMessage(self.OnWindowPosChanged, win32con.WM_WINDOWPOSCHANGED)
|
||||
|
||||
|
||||
# self.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
|
||||
|
||||
def EditCreator(parent):
|
||||
d = win32ui.CreateEdit()
|
||||
es = (
|
||||
win32con.WS_CHILD
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_BORDER
|
||||
| win32con.ES_MULTILINE
|
||||
| win32con.ES_WANTRETURN
|
||||
)
|
||||
d.CreateWindow(es, (0, 0, 150, 150), parent, 1000)
|
||||
return d
|
||||
|
||||
|
||||
def test():
|
||||
bar = DockingBar()
|
||||
creator = EditCreator
|
||||
bar.CreateWindow(win32ui.GetMainFrame(), creator, "Coolbar Demo", 0xFFFFF)
|
||||
# win32ui.GetMainFrame().ShowControlBar(bar, 1, 0)
|
||||
bar.SetBarStyle(
|
||||
bar.GetBarStyle()
|
||||
| afxres.CBRS_TOOLTIPS
|
||||
| afxres.CBRS_FLYBY
|
||||
| afxres.CBRS_SIZE_DYNAMIC
|
||||
)
|
||||
bar.EnableDocking(afxres.CBRS_ALIGN_ANY)
|
||||
win32ui.GetMainFrame().DockControlBar(bar, afxres.AFX_IDW_DOCKBAR_BOTTOM)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
@@ -0,0 +1,411 @@
|
||||
# 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"
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import builtins
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import regutil
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import afxres, dialog, window
|
||||
from pywin.mfc.thread import WinApp
|
||||
|
||||
from . import scriptutils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Literal
|
||||
|
||||
|
||||
# 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 += " "
|
||||
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 += " "
|
||||
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) -> Literal[-1, 0, 1]:
|
||||
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
|
||||
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(f"Idle handler {handler!r} failed")
|
||||
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(f"Internal error in help file processing\r\n{t}: {v}")
|
||||
tb = None # Prevent a cycle
|
||||
|
||||
def DoLoadModules(self, modules):
|
||||
# XXX - this should go, but the debugger uses it :-(
|
||||
# don't 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 haven't 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-2020 Neil Hodgson (https://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{}\n\nPython is {}\n\n{}\n\n{}\n\n{}".format(
|
||||
win32ui.copyright, sys.copyright, scintilla, idle, contributors
|
||||
)
|
||||
self.SetDlgItemText(win32ui.IDC_EDIT1, text)
|
||||
import sysconfig
|
||||
|
||||
site_packages = sysconfig.get_paths()["platlib"]
|
||||
version_path = os.path.join(site_packages, "pywin32.version.txt")
|
||||
try:
|
||||
with open(version_path) as f:
|
||||
ver = "pywin32 build %s" % f.read().strip()
|
||||
except OSError:
|
||||
ver = None
|
||||
if not ver:
|
||||
warnings.warn(f"Could not read pywin32's version from '{version_path}'")
|
||||
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 Win32Input(prompt=None):
|
||||
"Provide 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 is None:
|
||||
raise KeyboardInterrupt("operation cancelled")
|
||||
return ret
|
||||
|
||||
|
||||
def HookInput():
|
||||
builtins.input = Win32Input
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,168 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import docview, window
|
||||
|
||||
bStretch = 1
|
||||
|
||||
|
||||
class BitmapDocument(docview.Document):
|
||||
"A bitmap document. Holds the bitmap data itself."
|
||||
|
||||
def __init__(self, template):
|
||||
docview.Document.__init__(self, template)
|
||||
self.bitmap = None
|
||||
|
||||
def OnNewDocument(self):
|
||||
# I can not create new bitmaps.
|
||||
win32ui.MessageBox("Bitmaps can not be created.")
|
||||
|
||||
def OnOpenDocument(self, filename):
|
||||
self.bitmap = win32ui.CreateBitmap()
|
||||
# init data members
|
||||
f = open(filename, "rb")
|
||||
try:
|
||||
try:
|
||||
self.bitmap.LoadBitmapFile(f)
|
||||
except OSError:
|
||||
win32ui.MessageBox("Could not load the bitmap from %s" % filename)
|
||||
return 0
|
||||
finally:
|
||||
f.close()
|
||||
self.size = self.bitmap.GetSize()
|
||||
return 1
|
||||
|
||||
def DeleteContents(self):
|
||||
self.bitmap = None
|
||||
|
||||
|
||||
class BitmapView(docview.ScrollView):
|
||||
"A view of a bitmap. Obtains data from document."
|
||||
|
||||
def __init__(self, doc):
|
||||
docview.ScrollView.__init__(self, doc)
|
||||
self.width = self.height = 0
|
||||
# set up message handlers
|
||||
self.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
doc = self.GetDocument()
|
||||
if doc.bitmap:
|
||||
bitmapSize = doc.bitmap.GetSize()
|
||||
self.SetScrollSizes(win32con.MM_TEXT, bitmapSize)
|
||||
|
||||
def OnSize(self, params):
|
||||
lParam = params[3]
|
||||
self.width = win32api.LOWORD(lParam)
|
||||
self.height = win32api.HIWORD(lParam)
|
||||
|
||||
def OnDraw(self, dc):
|
||||
# set sizes used for "non stretch" mode.
|
||||
doc = self.GetDocument()
|
||||
if doc.bitmap is None:
|
||||
return
|
||||
bitmapSize = doc.bitmap.GetSize()
|
||||
if bStretch:
|
||||
# stretch BMP.
|
||||
viewRect = (0, 0, self.width, self.height)
|
||||
bitmapRect = (0, 0, bitmapSize[0], bitmapSize[1])
|
||||
doc.bitmap.Paint(dc, viewRect, bitmapRect)
|
||||
else:
|
||||
# non stretch.
|
||||
doc.bitmap.Paint(dc)
|
||||
|
||||
|
||||
class BitmapFrame(window.MDIChildWnd):
|
||||
def OnCreateClient(self, createparams, context):
|
||||
borderX = win32api.GetSystemMetrics(win32con.SM_CXFRAME)
|
||||
borderY = win32api.GetSystemMetrics(win32con.SM_CYFRAME)
|
||||
titleY = win32api.GetSystemMetrics(win32con.SM_CYCAPTION) # includes border
|
||||
# try and maintain default window pos, else adjust if can't fit
|
||||
# get the main client window dimensions.
|
||||
mdiClient = win32ui.GetMainFrame().GetWindow(win32con.GW_CHILD)
|
||||
clientWindowRect = mdiClient.ScreenToClient(mdiClient.GetWindowRect())
|
||||
clientWindowSize = (
|
||||
clientWindowRect[2] - clientWindowRect[0],
|
||||
clientWindowRect[3] - clientWindowRect[1],
|
||||
)
|
||||
left, top, right, bottom = mdiClient.ScreenToClient(self.GetWindowRect())
|
||||
# width, height=context.doc.size[0], context.doc.size[1]
|
||||
# width = width+borderX*2
|
||||
# height= height+titleY+borderY*2-1
|
||||
# if (left+width)>clientWindowSize[0]:
|
||||
# left = clientWindowSize[0] - width
|
||||
# if left<0:
|
||||
# left = 0
|
||||
# width = clientWindowSize[0]
|
||||
# if (top+height)>clientWindowSize[1]:
|
||||
# top = clientWindowSize[1] - height
|
||||
# if top<0:
|
||||
# top = 0
|
||||
# height = clientWindowSize[1]
|
||||
# self.frame.MoveWindow((left, top, left+width, top+height),0)
|
||||
window.MDIChildWnd.OnCreateClient(self, createparams, context)
|
||||
return 1
|
||||
|
||||
|
||||
class BitmapTemplate(docview.DocTemplate):
|
||||
def __init__(self):
|
||||
docview.DocTemplate.__init__(
|
||||
self, win32ui.IDR_PYTHONTYPE, BitmapDocument, BitmapFrame, BitmapView
|
||||
)
|
||||
|
||||
def MatchDocType(self, fileName, fileType):
|
||||
doc = self.FindOpenDocument(fileName)
|
||||
if doc:
|
||||
return doc
|
||||
ext = os.path.splitext(fileName)[1].lower()
|
||||
if ext == ".bmp": # removed due to PIL! or ext=='.ppm':
|
||||
return win32ui.CDocTemplate_Confidence_yesAttemptNative
|
||||
return win32ui.CDocTemplate_Confidence_maybeAttemptForeign
|
||||
|
||||
|
||||
# return win32ui.CDocTemplate_Confidence_noAttempt
|
||||
|
||||
# For debugging purposes, when this module may be reloaded many times.
|
||||
try:
|
||||
win32ui.GetApp().RemoveDocTemplate(bitmapTemplate) # type: ignore[has-type, used-before-def]
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
bitmapTemplate = BitmapTemplate()
|
||||
bitmapTemplate.SetDocStrings(
|
||||
"\nBitmap\nBitmap\nBitmap (*.bmp)\n.bmp\nPythonBitmapFileType\nPython Bitmap File"
|
||||
)
|
||||
win32ui.GetApp().AddDocTemplate(bitmapTemplate)
|
||||
|
||||
# This works, but just didn't make it through the code reorg.
|
||||
# class PPMBitmap(Bitmap):
|
||||
# def LoadBitmapFile(self, file ):
|
||||
# magic=file.readline()
|
||||
# if magic <> "P6\n":
|
||||
# raise TypeError, "The file is not a PPM format file"
|
||||
# rowcollist=file.readline().split()
|
||||
# cols=int(rowcollist[0])
|
||||
# rows=int(rowcollist[1])
|
||||
# file.readline() # what's this one?
|
||||
# self.bitmap.LoadPPMFile(file,(cols,rows))
|
||||
|
||||
|
||||
def t():
|
||||
bitmapTemplate.OpenDocumentFile("d:\\winnt\\arcade.bmp")
|
||||
# OpenBMPFile( 'd:\\winnt\\arcade.bmp')
|
||||
|
||||
|
||||
def demo():
|
||||
import glob
|
||||
|
||||
winDir = win32api.GetWindowsDirectory()
|
||||
if sys.version_info >= (3, 10):
|
||||
fileNames = glob.glob("*.bmp", root_dir=winDir)[:2]
|
||||
else:
|
||||
fileNames = glob.glob1(winDir, "*.bmp")[:2]
|
||||
|
||||
for fileName in fileNames:
|
||||
bitmapTemplate.OpenDocumentFile(os.path.join(winDir, fileName))
|
||||
@@ -0,0 +1,54 @@
|
||||
# cmdline - command line utilities.
|
||||
import string
|
||||
import sys
|
||||
|
||||
import win32ui
|
||||
|
||||
|
||||
def ParseArgs(str):
|
||||
ret = []
|
||||
pos = 0
|
||||
length = len(str)
|
||||
while pos < length:
|
||||
try:
|
||||
while str[pos] in string.whitespace:
|
||||
pos += 1
|
||||
except IndexError:
|
||||
break
|
||||
if pos >= length:
|
||||
break
|
||||
if str[pos] == '"':
|
||||
pos += 1
|
||||
try:
|
||||
endPos = str.index('"', pos) - 1
|
||||
nextPos = endPos + 2
|
||||
except ValueError:
|
||||
endPos = length
|
||||
nextPos = endPos + 1
|
||||
else:
|
||||
endPos = pos
|
||||
while endPos < length and not str[endPos] in string.whitespace:
|
||||
endPos += 1
|
||||
nextPos = endPos + 1
|
||||
ret.append(str[pos : endPos + 1].strip())
|
||||
pos = nextPos
|
||||
return ret
|
||||
|
||||
|
||||
def FixArgFileName(fileName):
|
||||
"""Convert a filename on the commandline to something useful.
|
||||
Given an automatic filename on the commandline, turn it a python module name,
|
||||
with the path added to sys.path."""
|
||||
import os
|
||||
|
||||
path, fname = os.path.split(fileName)
|
||||
if len(path) == 0:
|
||||
path = os.curdir
|
||||
path = os.path.abspath(path)
|
||||
# must check that the command line arg's path is in sys.path
|
||||
for syspath in sys.path:
|
||||
if os.path.abspath(syspath) == path:
|
||||
break
|
||||
else:
|
||||
sys.path.append(path)
|
||||
return os.path.splitext(fname)[0]
|
||||
@@ -0,0 +1,188 @@
|
||||
# Command Handlers for the debugger.
|
||||
|
||||
# Not in the debugger package, as I always want these interfaces to be
|
||||
# available, even if the debugger has not yet been (or can not be)
|
||||
# imported
|
||||
import warnings
|
||||
|
||||
import win32ui
|
||||
from pywin.scintilla.control import CScintillaEditInterface
|
||||
|
||||
from . import scriptutils
|
||||
|
||||
IdToBarNames = {
|
||||
win32ui.IDC_DBG_STACK: ("Stack", 0),
|
||||
win32ui.IDC_DBG_BREAKPOINTS: ("Breakpoints", 0),
|
||||
win32ui.IDC_DBG_WATCH: ("Watch", 1),
|
||||
}
|
||||
|
||||
|
||||
class DebuggerCommandHandler:
|
||||
def HookCommands(self):
|
||||
commands = (
|
||||
(self.OnStep, None, win32ui.IDC_DBG_STEP),
|
||||
(self.OnStepOut, self.OnUpdateOnlyBreak, win32ui.IDC_DBG_STEPOUT),
|
||||
(self.OnStepOver, None, win32ui.IDC_DBG_STEPOVER),
|
||||
(self.OnGo, None, win32ui.IDC_DBG_GO),
|
||||
(self.OnClose, self.OnUpdateClose, win32ui.IDC_DBG_CLOSE),
|
||||
(self.OnAdd, self.OnUpdateAddBreakpoints, win32ui.IDC_DBG_ADD),
|
||||
(self.OnClearAll, self.OnUpdateClearAllBreakpoints, win32ui.IDC_DBG_CLEAR),
|
||||
# (self.OnDebuggerToolbar, self.OnUpdateDebuggerToolbar, win32ui.ID_DEBUGGER_TOOLBAR),
|
||||
)
|
||||
|
||||
frame = win32ui.GetMainFrame()
|
||||
|
||||
for methHandler, methUpdate, id in commands:
|
||||
frame.HookCommand(methHandler, id)
|
||||
if not methUpdate is None:
|
||||
frame.HookCommandUpdate(methUpdate, id)
|
||||
|
||||
for id in IdToBarNames:
|
||||
frame.HookCommand(self.OnDebuggerBar, id)
|
||||
frame.HookCommandUpdate(self.OnUpdateDebuggerBar, id)
|
||||
|
||||
def OnDebuggerToolbar(self, id, code):
|
||||
if code == 0:
|
||||
return not win32ui.GetMainFrame().OnBarCheck(id)
|
||||
|
||||
def OnUpdateDebuggerToolbar(self, cmdui):
|
||||
win32ui.GetMainFrame().OnUpdateControlBarMenu(cmdui)
|
||||
cmdui.Enable(1)
|
||||
|
||||
def _GetDebugger(self):
|
||||
try:
|
||||
import pywin.debugger
|
||||
|
||||
return pywin.debugger.currentDebugger
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
def _DoOrStart(self, doMethod, startFlag):
|
||||
d = self._GetDebugger()
|
||||
if d is not None and d.IsDebugging():
|
||||
method = getattr(d, doMethod)
|
||||
method()
|
||||
else:
|
||||
scriptutils.RunScript(
|
||||
defName=None, defArgs=None, bShowDialog=0, debuggingType=startFlag
|
||||
)
|
||||
|
||||
def OnStep(self, msg, code):
|
||||
self._DoOrStart("do_set_step", scriptutils.RS_DEBUGGER_STEP)
|
||||
|
||||
def OnStepOver(self, msg, code):
|
||||
self._DoOrStart("do_set_next", scriptutils.RS_DEBUGGER_STEP)
|
||||
|
||||
def OnStepOut(self, msg, code):
|
||||
d = self._GetDebugger()
|
||||
if d is not None and d.IsDebugging():
|
||||
d.do_set_return()
|
||||
|
||||
def OnGo(self, msg, code):
|
||||
self._DoOrStart("do_set_continue", scriptutils.RS_DEBUGGER_GO)
|
||||
|
||||
def OnClose(self, msg, code):
|
||||
d = self._GetDebugger()
|
||||
if d is not None:
|
||||
if d.IsDebugging():
|
||||
d.set_quit()
|
||||
else:
|
||||
d.close()
|
||||
|
||||
def OnUpdateClose(self, cmdui):
|
||||
d = self._GetDebugger()
|
||||
if d is not None and d.inited:
|
||||
cmdui.Enable(1)
|
||||
else:
|
||||
cmdui.Enable(0)
|
||||
|
||||
def OnAdd(self, msg, code):
|
||||
doc, view = scriptutils.GetActiveEditorDocument()
|
||||
if doc is None:
|
||||
## Don't do a messagebox, as this could be triggered from the app's
|
||||
## idle loop whenever the debug toolbar is visible, giving a never-ending
|
||||
## series of dialogs. This can happen when the OnUpdate handler
|
||||
## for the toolbar button IDC_DBG_ADD fails, since MFC falls back to
|
||||
## sending a normal command if the UI update command fails.
|
||||
## win32ui.MessageBox('There is no active window - no breakpoint can be added')
|
||||
warnings.warn("There is no active window - no breakpoint can be added")
|
||||
return None
|
||||
pathName = doc.GetPathName()
|
||||
lineNo = view.LineFromChar(view.GetSel()[0]) + 1
|
||||
# If I have a debugger, then tell it, otherwise just add a marker
|
||||
d = self._GetDebugger()
|
||||
if d is None:
|
||||
import pywin.framework.editor.color.coloreditor
|
||||
|
||||
doc.MarkerToggle(
|
||||
lineNo, pywin.framework.editor.color.coloreditor.MARKER_BREAKPOINT
|
||||
)
|
||||
else:
|
||||
if d.get_break(pathName, lineNo):
|
||||
win32ui.SetStatusText("Clearing breakpoint", 1)
|
||||
rc = d.clear_break(pathName, lineNo)
|
||||
else:
|
||||
win32ui.SetStatusText("Setting breakpoint", 1)
|
||||
rc = d.set_break(pathName, lineNo)
|
||||
if rc:
|
||||
win32ui.MessageBox(rc)
|
||||
d.GUIRespondDebuggerData()
|
||||
|
||||
def OnClearAll(self, msg, code):
|
||||
win32ui.SetStatusText("Clearing all breakpoints")
|
||||
d = self._GetDebugger()
|
||||
if d is None:
|
||||
import pywin.framework.editor
|
||||
import pywin.framework.editor.color.coloreditor
|
||||
|
||||
for doc in pywin.framework.editor.editorTemplate.GetDocumentList():
|
||||
doc.MarkerDeleteAll(
|
||||
pywin.framework.editor.color.coloreditor.MARKER_BREAKPOINT
|
||||
)
|
||||
else:
|
||||
d.clear_all_breaks()
|
||||
d.UpdateAllLineStates()
|
||||
d.GUIRespondDebuggerData()
|
||||
|
||||
def OnUpdateOnlyBreak(self, cmdui):
|
||||
d = self._GetDebugger()
|
||||
ok = d is not None and d.IsBreak()
|
||||
cmdui.Enable(ok)
|
||||
|
||||
def OnUpdateAddBreakpoints(self, cmdui):
|
||||
doc, view = scriptutils.GetActiveEditorDocument()
|
||||
if doc is None or not isinstance(view, CScintillaEditInterface):
|
||||
enabled = 0
|
||||
else:
|
||||
enabled = 1
|
||||
lineNo = view.LineFromChar(view.GetSel()[0]) + 1
|
||||
import pywin.framework.editor.color.coloreditor
|
||||
|
||||
cmdui.SetCheck(
|
||||
doc.MarkerAtLine(
|
||||
lineNo, pywin.framework.editor.color.coloreditor.MARKER_BREAKPOINT
|
||||
)
|
||||
!= 0
|
||||
)
|
||||
cmdui.Enable(enabled)
|
||||
|
||||
def OnUpdateClearAllBreakpoints(self, cmdui):
|
||||
d = self._GetDebugger()
|
||||
cmdui.Enable(d is None or len(d.breaks) != 0)
|
||||
|
||||
def OnUpdateDebuggerBar(self, cmdui):
|
||||
name, enabled = IdToBarNames.get(cmdui.m_nID, (None, 0))
|
||||
d = self._GetDebugger()
|
||||
if d is not None and d.IsDebugging() and name is not None:
|
||||
enabled = 1
|
||||
bar = d.GetDebuggerBar(name)
|
||||
cmdui.SetCheck(bar.IsWindowVisible())
|
||||
cmdui.Enable(enabled)
|
||||
|
||||
def OnDebuggerBar(self, id, code):
|
||||
name = IdToBarNames.get(id)[0]
|
||||
d = self._GetDebugger()
|
||||
if d is not None and name is not None:
|
||||
bar = d.GetDebuggerBar(name)
|
||||
newState = not bar.IsWindowVisible()
|
||||
win32ui.GetMainFrame().ShowControlBar(bar, newState, 1)
|
||||
@@ -0,0 +1,76 @@
|
||||
# dlgappcore.
|
||||
#
|
||||
# base classes for dialog based apps.
|
||||
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
|
||||
from . import app
|
||||
|
||||
|
||||
class AppDialog(dialog.Dialog):
|
||||
"The dialog box for the application"
|
||||
|
||||
def __init__(self, id, dll=None):
|
||||
self.iconId = win32ui.IDR_MAINFRAME
|
||||
dialog.Dialog.__init__(self, id, dll)
|
||||
|
||||
def OnInitDialog(self):
|
||||
return dialog.Dialog.OnInitDialog(self)
|
||||
|
||||
# Provide support for a dlg app using an icon
|
||||
def OnPaint(self):
|
||||
if not self.IsIconic():
|
||||
return self._obj_.OnPaint()
|
||||
dc, paintStruct = self.BeginPaint()
|
||||
self.DefWindowProc(win32con.WM_ICONERASEBKGND, dc.GetHandleOutput(), 0)
|
||||
left, top, right, bottom = self.GetClientRect()
|
||||
left = (right - win32api.GetSystemMetrics(win32con.SM_CXICON)) >> 1
|
||||
top = (bottom - win32api.GetSystemMetrics(win32con.SM_CYICON)) >> 1
|
||||
hIcon = win32ui.GetApp().LoadIcon(self.iconId)
|
||||
dc.DrawIcon((left, top), hIcon)
|
||||
self.EndPaint(paintStruct)
|
||||
|
||||
# Only needed to provide a minimized icon (and this seems
|
||||
# less important under win95/NT4
|
||||
def OnEraseBkgnd(self, dc):
|
||||
if self.IsIconic():
|
||||
return 1
|
||||
else:
|
||||
return self._obj_.OnEraseBkgnd(dc)
|
||||
|
||||
def OnQueryDragIcon(self):
|
||||
return win32ui.GetApp().LoadIcon(self.iconId)
|
||||
|
||||
def PreDoModal(self):
|
||||
pass
|
||||
|
||||
|
||||
class DialogApp(app.CApp):
|
||||
"An application class, for an app with main dialog box"
|
||||
|
||||
def InitInstance(self):
|
||||
# win32ui.SetProfileFileName('dlgapp.ini')
|
||||
win32ui.LoadStdProfileSettings()
|
||||
win32ui.EnableControlContainer()
|
||||
win32ui.Enable3dControls()
|
||||
self.dlg = self.frame = self.CreateDialog()
|
||||
|
||||
if self.frame is None:
|
||||
raise NotImplementedError(
|
||||
"No dialog was created by CreateDialog(). Subclasses need to implement CreateDialog."
|
||||
)
|
||||
|
||||
self._obj_.InitDlgInstance(self.dlg)
|
||||
self.PreDoModal()
|
||||
self.dlg.PreDoModal()
|
||||
self.dlg.DoModal()
|
||||
|
||||
def CreateDialog(self):
|
||||
pass
|
||||
|
||||
def PreDoModal(self):
|
||||
pass
|
||||
@@ -0,0 +1,235 @@
|
||||
# ModuleBrowser.py - A view that provides a module browser for an editor document.
|
||||
import pyclbr
|
||||
|
||||
import commctrl
|
||||
import pywin.framework.scriptutils
|
||||
import pywin.mfc.docview
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import afxres
|
||||
from pywin.tools import hierlist
|
||||
|
||||
|
||||
class HierListCLBRModule(hierlist.HierListItem):
|
||||
def __init__(self, modName, clbrdata):
|
||||
self.modName = modName
|
||||
self.clbrdata = clbrdata
|
||||
|
||||
def GetText(self):
|
||||
return self.modName
|
||||
|
||||
def GetSubList(self):
|
||||
ret = []
|
||||
for item in self.clbrdata.values():
|
||||
if (
|
||||
item.__class__ != pyclbr.Class
|
||||
): # ie, it is a pyclbr Function instance (only introduced post 1.5.2)
|
||||
ret.append(HierListCLBRFunction(item))
|
||||
else:
|
||||
ret.append(HierListCLBRClass(item))
|
||||
ret.sort()
|
||||
return ret
|
||||
|
||||
def IsExpandable(self):
|
||||
return 1
|
||||
|
||||
|
||||
class HierListCLBRItem(hierlist.HierListItem):
|
||||
def __init__(self, name, file, lineno, suffix=""):
|
||||
self.name = str(name)
|
||||
self.file = file
|
||||
self.lineno = lineno
|
||||
self.suffix = suffix
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.name < other.name
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name
|
||||
|
||||
def GetText(self):
|
||||
return self.name + self.suffix
|
||||
|
||||
def TakeDefaultAction(self):
|
||||
if self.file:
|
||||
pywin.framework.scriptutils.JumpToDocument(
|
||||
self.file, self.lineno, bScrollToTop=1
|
||||
)
|
||||
else:
|
||||
win32ui.SetStatusText("Can not locate the source code for this object.")
|
||||
|
||||
def PerformItemSelected(self):
|
||||
if self.file is None:
|
||||
msg = f"{self.name} - source can not be located."
|
||||
else:
|
||||
msg = "%s defined at line %d of %s" % (self.name, self.lineno, self.file)
|
||||
win32ui.SetStatusText(msg)
|
||||
|
||||
|
||||
class HierListCLBRClass(HierListCLBRItem):
|
||||
def __init__(self, clbrclass, suffix=""):
|
||||
try:
|
||||
name = clbrclass.name
|
||||
file = clbrclass.file
|
||||
lineno = clbrclass.lineno
|
||||
self.super = clbrclass.super
|
||||
self.methods = clbrclass.methods
|
||||
except AttributeError:
|
||||
name = clbrclass
|
||||
file = lineno = None
|
||||
self.super = []
|
||||
self.methods = {}
|
||||
HierListCLBRItem.__init__(self, name, file, lineno, suffix)
|
||||
|
||||
def GetSubList(self):
|
||||
r1 = []
|
||||
for c in self.super:
|
||||
r1.append(HierListCLBRClass(c, " (Parent class)"))
|
||||
r1.sort()
|
||||
r2 = []
|
||||
for meth, lineno in self.methods.items():
|
||||
r2.append(HierListCLBRMethod(meth, self.file, lineno))
|
||||
r2.sort()
|
||||
return r1 + r2
|
||||
|
||||
def IsExpandable(self):
|
||||
return len(self.methods) + len(self.super)
|
||||
|
||||
def GetBitmapColumn(self):
|
||||
return 21
|
||||
|
||||
|
||||
class HierListCLBRFunction(HierListCLBRItem):
|
||||
def __init__(self, clbrfunc, suffix=""):
|
||||
name = clbrfunc.name
|
||||
file = clbrfunc.file
|
||||
lineno = clbrfunc.lineno
|
||||
HierListCLBRItem.__init__(self, name, file, lineno, suffix)
|
||||
|
||||
def GetBitmapColumn(self):
|
||||
return 22
|
||||
|
||||
|
||||
class HierListCLBRMethod(HierListCLBRItem):
|
||||
def GetBitmapColumn(self):
|
||||
return 22
|
||||
|
||||
|
||||
class HierListCLBRErrorItem(hierlist.HierListItem):
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def GetText(self):
|
||||
return self.text
|
||||
|
||||
def GetSubList(self):
|
||||
return [HierListCLBRErrorItem(self.text)]
|
||||
|
||||
def IsExpandable(self):
|
||||
return 0
|
||||
|
||||
|
||||
class HierListCLBRErrorRoot(HierListCLBRErrorItem):
|
||||
def IsExpandable(self):
|
||||
return 1
|
||||
|
||||
|
||||
class BrowserView(pywin.mfc.docview.TreeView):
|
||||
def OnInitialUpdate(self):
|
||||
self.list = None
|
||||
rc = self._obj_.OnInitialUpdate()
|
||||
self.HookMessage(self.OnSize, win32con.WM_SIZE)
|
||||
self.bDirty = 0
|
||||
self.destroying = 0
|
||||
return rc
|
||||
|
||||
def DestroyBrowser(self):
|
||||
self.DestroyList()
|
||||
|
||||
def OnActivateView(self, activate, av, dv):
|
||||
# print("AV", self.bDirty, activate)
|
||||
if activate:
|
||||
self.CheckRefreshList()
|
||||
return self._obj_.OnActivateView(activate, av, dv)
|
||||
|
||||
def _MakeRoot(self):
|
||||
path = self.GetDocument().GetPathName()
|
||||
if not path:
|
||||
return HierListCLBRErrorRoot(
|
||||
"Error: Can not browse a file until it is saved"
|
||||
)
|
||||
else:
|
||||
mod, path = pywin.framework.scriptutils.GetPackageModuleName(path)
|
||||
if self.bDirty:
|
||||
what = "Refreshing"
|
||||
# Hack for pyclbr being too smart
|
||||
try:
|
||||
del pyclbr._modules[mod]
|
||||
except (KeyError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
what = "Building"
|
||||
win32ui.SetStatusText(f"{what} class list - please wait...", 1)
|
||||
win32ui.DoWaitCursor(1)
|
||||
try:
|
||||
reader = pyclbr.readmodule_ex # new version post 1.5.2
|
||||
except AttributeError:
|
||||
reader = pyclbr.readmodule
|
||||
try:
|
||||
data = reader(mod, [path])
|
||||
if data:
|
||||
return HierListCLBRModule(mod, data)
|
||||
else:
|
||||
return HierListCLBRErrorRoot("No Python classes in module.")
|
||||
|
||||
finally:
|
||||
win32ui.DoWaitCursor(0)
|
||||
win32ui.SetStatusText(win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE))
|
||||
|
||||
def DestroyList(self):
|
||||
self.destroying = 1
|
||||
list = getattr(
|
||||
self, "list", None
|
||||
) # If the document was not successfully opened, we may not have a list.
|
||||
self.list = None
|
||||
if list is not None:
|
||||
list.HierTerm()
|
||||
self.destroying = 0
|
||||
|
||||
def CheckMadeList(self):
|
||||
if self.list is not None or self.destroying:
|
||||
return
|
||||
self.rootitem = root = self._MakeRoot()
|
||||
self.list = list = hierlist.HierListWithItems(root, win32ui.IDB_BROWSER_HIER)
|
||||
list.HierInit(self.GetParentFrame(), self)
|
||||
list.SetStyle(
|
||||
commctrl.TVS_HASLINES | commctrl.TVS_LINESATROOT | commctrl.TVS_HASBUTTONS
|
||||
)
|
||||
|
||||
def CheckRefreshList(self):
|
||||
if self.bDirty:
|
||||
if self.list is None:
|
||||
self.CheckMadeList()
|
||||
else:
|
||||
new_root = self._MakeRoot()
|
||||
if self.rootitem.__class__ == new_root.__class__ == HierListCLBRModule:
|
||||
self.rootitem.modName = new_root.modName
|
||||
self.rootitem.clbrdata = new_root.clbrdata
|
||||
self.list.Refresh()
|
||||
else:
|
||||
self.list.AcceptRoot(self._MakeRoot())
|
||||
self.bDirty = 0
|
||||
|
||||
def OnSize(self, params):
|
||||
lparam = params[3]
|
||||
w = win32api.LOWORD(lparam)
|
||||
h = win32api.HIWORD(lparam)
|
||||
if w != 0:
|
||||
self.CheckMadeList()
|
||||
elif w == 0:
|
||||
self.DestroyList()
|
||||
return 1
|
||||
|
||||
def _UpdateUIForState(self):
|
||||
self.bDirty = 1
|
||||
@@ -0,0 +1,105 @@
|
||||
# __init__ for the Pythonwin editor package.
|
||||
#
|
||||
# We used to support optional editors - eg, color or non-color.
|
||||
#
|
||||
# This really isn't necessary with Scintilla, and scintilla
|
||||
# is getting so deeply embedded that it was too much work.
|
||||
|
||||
import win32ui
|
||||
|
||||
defaultCharacterFormat = (-402653169, 0, 200, 0, 0, 0, 49, "Courier New")
|
||||
|
||||
##def GetDefaultEditorModuleName():
|
||||
## import pywin
|
||||
## # If someone has set pywin.editormodulename, then this is what we use
|
||||
## try:
|
||||
## prefModule = pywin.editormodulename
|
||||
## except AttributeError:
|
||||
## prefModule = win32ui.GetProfileVal("Editor","Module", "")
|
||||
## return prefModule
|
||||
##
|
||||
##def WriteDefaultEditorModule(module):
|
||||
## try:
|
||||
## module = module.__name__
|
||||
## except:
|
||||
## pass
|
||||
## win32ui.WriteProfileVal("Editor", "Module", module)
|
||||
|
||||
|
||||
def LoadDefaultEditor():
|
||||
pass
|
||||
|
||||
|
||||
## prefModule = GetDefaultEditorModuleName()
|
||||
## restorePrefModule = None
|
||||
## mod = None
|
||||
## if prefModule:
|
||||
## try:
|
||||
## mod = __import__(prefModule)
|
||||
## except 'xx':
|
||||
## msg = "Importing your preferred editor ('%s') failed.\n\nError %s: %s\n\nAn attempt will be made to load the default editor.\n\nWould you like this editor disabled in the future?" % (prefModule, sys.exc_info()[0], sys.exc_info()[1])
|
||||
## rc = win32ui.MessageBox(msg, "Error importing editor", win32con.MB_YESNO)
|
||||
## if rc == win32con.IDNO:
|
||||
## restorePrefModule = prefModule
|
||||
## WriteDefaultEditorModule("")
|
||||
## del rc
|
||||
##
|
||||
## try:
|
||||
## # Try and load the default one - don't catch errors here.
|
||||
## if mod is None:
|
||||
## prefModule = "pywin.framework.editor.color.coloreditor"
|
||||
## mod = __import__(prefModule)
|
||||
##
|
||||
## # Get at the real module.
|
||||
## mod = sys.modules[prefModule]
|
||||
##
|
||||
## # Do a "from mod import *"
|
||||
## globals().update(mod.__dict__)
|
||||
##
|
||||
## finally:
|
||||
## # Restore the users default editor if it failed and they requested not to disable it.
|
||||
## if restorePrefModule:
|
||||
## WriteDefaultEditorModule(restorePrefModule)
|
||||
|
||||
|
||||
def GetEditorOption(option, defaultValue, min=None, max=None):
|
||||
rc = win32ui.GetProfileVal("Editor", option, defaultValue)
|
||||
if min is not None and rc < min:
|
||||
rc = defaultValue
|
||||
if max is not None and rc > max:
|
||||
rc = defaultValue
|
||||
return rc
|
||||
|
||||
|
||||
def SetEditorOption(option, newValue):
|
||||
win32ui.WriteProfileVal("Editor", option, newValue)
|
||||
|
||||
|
||||
def DeleteEditorOption(option):
|
||||
try:
|
||||
win32ui.WriteProfileVal("Editor", option, None)
|
||||
except win32ui.error:
|
||||
pass
|
||||
|
||||
|
||||
# Load and save font tuples
|
||||
def GetEditorFontOption(option, default=None):
|
||||
if default is None:
|
||||
default = defaultCharacterFormat
|
||||
fmt = GetEditorOption(option, "")
|
||||
if fmt == "":
|
||||
return default
|
||||
try:
|
||||
return eval(fmt)
|
||||
except:
|
||||
print("WARNING: Invalid font setting in registry - setting ignored")
|
||||
return default
|
||||
|
||||
|
||||
def SetEditorFontOption(option, newValue):
|
||||
SetEditorOption(option, str(newValue))
|
||||
|
||||
|
||||
from pywin.framework.editor.color.coloreditor import ( # nopycln: import
|
||||
editorTemplate as editorTemplate, # Adds doc template & Re-export
|
||||
)
|
||||
@@ -0,0 +1,646 @@
|
||||
# Color Editor originally by Neil Hodgson, but restructured by mh to integrate
|
||||
# even tighter into Pythonwin.
|
||||
|
||||
import pywin.scintilla.keycodes
|
||||
import pywin.scintilla.view
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.debugger import dbgcon
|
||||
from pywin.framework.editor import GetEditorOption
|
||||
from pywin.framework.editor.document import EditorDocumentBase
|
||||
from pywin.framework.editor.frame import EditorFrame
|
||||
from pywin.framework.editor.template import EditorTemplateBase
|
||||
from pywin.scintilla import bindings, scintillacon
|
||||
from pywin.scintilla.view import CScintillaView as SyntEditViewParent
|
||||
|
||||
# WARNING: Duplicated in document.py and editor.py
|
||||
MSG_CHECK_EXTERNAL_FILE = win32con.WM_USER + 1999
|
||||
|
||||
# Define a few common markers
|
||||
MARKER_BOOKMARK = 0
|
||||
MARKER_BREAKPOINT = 1
|
||||
MARKER_CURRENT = 2
|
||||
|
||||
|
||||
class SyntEditDocument(EditorDocumentBase):
|
||||
"A SyntEdit document."
|
||||
|
||||
def OnDebuggerStateChange(self, state):
|
||||
self._ApplyOptionalToViews("OnDebuggerStateChange", state)
|
||||
|
||||
def HookViewNotifications(self, view):
|
||||
EditorDocumentBase.HookViewNotifications(self, view)
|
||||
view.SCISetUndoCollection(1)
|
||||
|
||||
def FinalizeViewCreation(self, view):
|
||||
EditorDocumentBase.FinalizeViewCreation(self, view)
|
||||
if view == self.GetFirstView():
|
||||
self.GetDocTemplate().CheckIDLEMenus(view.idle)
|
||||
|
||||
|
||||
class SyntEditView(SyntEditViewParent):
|
||||
"A view of a SyntEdit. Obtains data from document."
|
||||
|
||||
def __init__(self, doc):
|
||||
SyntEditViewParent.__init__(self, doc)
|
||||
self.bCheckingFile = 0
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
SyntEditViewParent.OnInitialUpdate(self)
|
||||
|
||||
self.HookMessage(self.OnRClick, win32con.WM_RBUTTONDOWN)
|
||||
|
||||
for id in (
|
||||
win32ui.ID_VIEW_FOLD_COLLAPSE,
|
||||
win32ui.ID_VIEW_FOLD_COLLAPSE_ALL,
|
||||
win32ui.ID_VIEW_FOLD_EXPAND,
|
||||
win32ui.ID_VIEW_FOLD_EXPAND_ALL,
|
||||
):
|
||||
self.HookCommand(self.OnCmdViewFold, id)
|
||||
self.HookCommandUpdate(self.OnUpdateViewFold, id)
|
||||
self.HookCommand(self.OnCmdViewFoldTopLevel, win32ui.ID_VIEW_FOLD_TOPLEVEL)
|
||||
|
||||
# Define the markers
|
||||
# self.SCIMarkerDeleteAll()
|
||||
self.SCIMarkerDefineAll(
|
||||
MARKER_BOOKMARK,
|
||||
scintillacon.SC_MARK_ROUNDRECT,
|
||||
win32api.RGB(0x0, 0x0, 0x0),
|
||||
win32api.RGB(0, 0xFF, 0xFF),
|
||||
)
|
||||
|
||||
self.SCIMarkerDefine(MARKER_CURRENT, scintillacon.SC_MARK_ARROW)
|
||||
self.SCIMarkerSetBack(MARKER_CURRENT, win32api.RGB(0xFF, 0xFF, 0x00))
|
||||
|
||||
# Define the folding markers
|
||||
if 1: # traditional markers
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDEROPEN,
|
||||
scintillacon.SC_MARK_MINUS,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDER,
|
||||
scintillacon.SC_MARK_PLUS,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDERSUB,
|
||||
scintillacon.SC_MARK_EMPTY,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDERTAIL,
|
||||
scintillacon.SC_MARK_EMPTY,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDEREND,
|
||||
scintillacon.SC_MARK_EMPTY,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDEROPENMID,
|
||||
scintillacon.SC_MARK_EMPTY,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDERMIDTAIL,
|
||||
scintillacon.SC_MARK_EMPTY,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
else: # curved markers
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDEROPEN,
|
||||
scintillacon.SC_MARK_CIRCLEMINUS,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDER,
|
||||
scintillacon.SC_MARK_CIRCLEPLUS,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDERSUB,
|
||||
scintillacon.SC_MARK_VLINE,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDERTAIL,
|
||||
scintillacon.SC_MARK_LCORNERCURVE,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDEREND,
|
||||
scintillacon.SC_MARK_CIRCLEPLUSCONNECTED,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDEROPENMID,
|
||||
scintillacon.SC_MARK_CIRCLEMINUSCONNECTED,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
self.SCIMarkerDefineAll(
|
||||
scintillacon.SC_MARKNUM_FOLDERMIDTAIL,
|
||||
scintillacon.SC_MARK_TCORNERCURVE,
|
||||
win32api.RGB(0xFF, 0xFF, 0xFF),
|
||||
win32api.RGB(0, 0, 0),
|
||||
)
|
||||
|
||||
self.SCIMarkerDefine(MARKER_BREAKPOINT, scintillacon.SC_MARK_CIRCLE)
|
||||
# Marker background depends on debugger state
|
||||
self.SCIMarkerSetFore(MARKER_BREAKPOINT, win32api.RGB(0x0, 0, 0))
|
||||
# Get the current debugger state.
|
||||
try:
|
||||
import pywin.debugger
|
||||
|
||||
if pywin.debugger.currentDebugger is None:
|
||||
state = dbgcon.DBGSTATE_NOT_DEBUGGING
|
||||
else:
|
||||
state = pywin.debugger.currentDebugger.debuggerState
|
||||
except ImportError:
|
||||
state = dbgcon.DBGSTATE_NOT_DEBUGGING
|
||||
self.OnDebuggerStateChange(state)
|
||||
|
||||
def _GetSubConfigNames(self):
|
||||
return ["editor"] # Allow [Keys:Editor] sections to be specific to us
|
||||
|
||||
def DoConfigChange(self):
|
||||
SyntEditViewParent.DoConfigChange(self)
|
||||
tabSize = GetEditorOption("Tab Size", 4, 2)
|
||||
indentSize = GetEditorOption("Indent Size", 4, 2)
|
||||
bUseTabs = GetEditorOption("Use Tabs", 0)
|
||||
bSmartTabs = GetEditorOption("Smart Tabs", 1)
|
||||
ext = self.idle.IDLEExtension("AutoIndent") # Required extension.
|
||||
|
||||
self.SCISetViewWS(GetEditorOption("View Whitespace", 0))
|
||||
self.SCISetViewEOL(GetEditorOption("View EOL", 0))
|
||||
self.SCISetIndentationGuides(GetEditorOption("View Indentation Guides", 0))
|
||||
|
||||
if GetEditorOption("Right Edge Enabled", 0):
|
||||
mode = scintillacon.EDGE_BACKGROUND
|
||||
else:
|
||||
mode = scintillacon.EDGE_NONE
|
||||
self.SCISetEdgeMode(mode)
|
||||
self.SCISetEdgeColumn(GetEditorOption("Right Edge Column", 75))
|
||||
self.SCISetEdgeColor(
|
||||
GetEditorOption("Right Edge Color", win32api.RGB(0xEF, 0xEF, 0xEF))
|
||||
)
|
||||
|
||||
width = GetEditorOption("Marker Margin Width", 16)
|
||||
self.SCISetMarginWidthN(1, width)
|
||||
width = GetEditorOption("Fold Margin Width", 12)
|
||||
self.SCISetMarginWidthN(2, width)
|
||||
width = GetEditorOption("Line Number Margin Width", 0)
|
||||
self.SCISetMarginWidthN(0, width)
|
||||
self.bFolding = GetEditorOption("Enable Folding", 1)
|
||||
fold_flags = 0
|
||||
self.SendScintilla(
|
||||
scintillacon.SCI_SETMODEVENTMASK, scintillacon.SC_MOD_CHANGEFOLD
|
||||
)
|
||||
if self.bFolding:
|
||||
if GetEditorOption("Fold Lines", 1):
|
||||
fold_flags = 16
|
||||
|
||||
self.SCISetProperty("fold", self.bFolding)
|
||||
self.SCISetFoldFlags(fold_flags)
|
||||
|
||||
tt_color = GetEditorOption("Tab Timmy Color", win32api.RGB(0xFF, 0, 0))
|
||||
self.SendScintilla(scintillacon.SCI_INDICSETFORE, 1, tt_color)
|
||||
|
||||
tt_use = GetEditorOption("Use Tab Timmy", 1)
|
||||
if tt_use:
|
||||
self.SCISetProperty("tab.timmy.whinge.level", "1")
|
||||
|
||||
# Auto-indent has very complicated behaviour. In a nutshell, the only
|
||||
# way to get sensible behaviour from it is to ensure tabwidth != indentsize.
|
||||
# Further, usetabs will only ever go from 1->0, never 0->1.
|
||||
# This is _not_ the behaviour Pythonwin wants:
|
||||
# * Tab width is arbitary, so should have no impact on smarts.
|
||||
# * bUseTabs setting should reflect how new files are created, and
|
||||
# if Smart Tabs disabled, existing files are edited
|
||||
# * If "Smart Tabs" is enabled, bUseTabs should have no bearing
|
||||
# for existing files (unless of course no context can be determined)
|
||||
#
|
||||
# So for smart tabs we configure the widget with completely dummy
|
||||
# values (ensuring tabwidth != indentwidth), ask it to guess, then
|
||||
# look at the values it has guessed, and re-configure
|
||||
if bSmartTabs:
|
||||
ext.config(usetabs=1, tabwidth=5, indentwidth=4)
|
||||
ext.set_indentation_params(1)
|
||||
if ext.indentwidth == 5:
|
||||
# Either 5 literal spaces, or a single tab character. Assume a tab
|
||||
usetabs = 1
|
||||
indentwidth = tabSize
|
||||
else:
|
||||
# Either Indented with spaces, and indent size has been guessed or
|
||||
# an empty file (or no context found - tough!)
|
||||
if self.GetTextLength() == 0: # emtpy
|
||||
usetabs = bUseTabs
|
||||
indentwidth = indentSize
|
||||
else: # guessed.
|
||||
indentwidth = ext.indentwidth
|
||||
usetabs = 0
|
||||
# Tab size can never be guessed - set at user preference.
|
||||
ext.config(usetabs=usetabs, indentwidth=indentwidth, tabwidth=tabSize)
|
||||
else:
|
||||
# Don't want smart-tabs - just set the options!
|
||||
ext.config(usetabs=bUseTabs, tabwidth=tabSize, indentwidth=indentSize)
|
||||
self.SCISetIndent(indentSize)
|
||||
self.SCISetTabWidth(tabSize)
|
||||
|
||||
def OnDebuggerStateChange(self, state):
|
||||
if state == dbgcon.DBGSTATE_NOT_DEBUGGING:
|
||||
# Indicate breakpoints aren't really usable.
|
||||
# Not quite white - useful when no marker margin, so set as background color.
|
||||
self.SCIMarkerSetBack(MARKER_BREAKPOINT, win32api.RGB(0xEF, 0xEF, 0xEF))
|
||||
else:
|
||||
# A light-red, so still readable when no marker margin.
|
||||
self.SCIMarkerSetBack(MARKER_BREAKPOINT, win32api.RGB(0xFF, 0x80, 0x80))
|
||||
|
||||
def HookDocumentHandlers(self):
|
||||
SyntEditViewParent.HookDocumentHandlers(self)
|
||||
self.HookMessage(self.OnCheckExternalDocumentUpdated, MSG_CHECK_EXTERNAL_FILE)
|
||||
|
||||
def HookHandlers(self):
|
||||
SyntEditViewParent.HookHandlers(self)
|
||||
self.HookMessage(self.OnSetFocus, win32con.WM_SETFOCUS)
|
||||
|
||||
def _PrepareUserStateChange(self):
|
||||
return self.GetSel(), self.GetFirstVisibleLine()
|
||||
|
||||
def _EndUserStateChange(self, info):
|
||||
scrollOff = info[1] - self.GetFirstVisibleLine()
|
||||
if scrollOff:
|
||||
self.LineScroll(scrollOff)
|
||||
# Make sure we don't reset the cursor beyond the buffer.
|
||||
max = self.GetTextLength()
|
||||
newPos = min(info[0][0], max), min(info[0][1], max)
|
||||
self.SetSel(newPos)
|
||||
|
||||
#######################################
|
||||
# The Windows Message or Notify handlers.
|
||||
#######################################
|
||||
def OnMarginClick(self, std, extra):
|
||||
notify = self.SCIUnpackNotifyMessage(extra)
|
||||
if notify.margin == 2: # Our fold margin
|
||||
line_click = self.LineFromChar(notify.position)
|
||||
# max_line = self.GetLineCount()
|
||||
if self.SCIGetFoldLevel(line_click) & scintillacon.SC_FOLDLEVELHEADERFLAG:
|
||||
# If a fold point.
|
||||
self.SCIToggleFold(line_click)
|
||||
return 1
|
||||
|
||||
def OnSetFocus(self, msg):
|
||||
# Even though we use file change notifications, we should be very sure about it here.
|
||||
self.OnCheckExternalDocumentUpdated(msg)
|
||||
return 1
|
||||
|
||||
def OnCheckExternalDocumentUpdated(self, msg):
|
||||
if self.bCheckingFile:
|
||||
return
|
||||
self.bCheckingFile = 1
|
||||
self.GetDocument().CheckExternalDocumentUpdated()
|
||||
self.bCheckingFile = 0
|
||||
|
||||
def OnRClick(self, params):
|
||||
menu = win32ui.CreatePopupMenu()
|
||||
self.AppendMenu(menu, "&Locate module", "LocateModule")
|
||||
self.AppendMenu(menu, flags=win32con.MF_SEPARATOR)
|
||||
self.AppendMenu(menu, "&Undo", "EditUndo")
|
||||
self.AppendMenu(menu, "&Redo", "EditRedo")
|
||||
self.AppendMenu(menu, flags=win32con.MF_SEPARATOR)
|
||||
self.AppendMenu(menu, "Cu&t", "EditCut")
|
||||
self.AppendMenu(menu, "&Copy", "EditCopy")
|
||||
self.AppendMenu(menu, "&Paste", "EditPaste")
|
||||
self.AppendMenu(menu, flags=win32con.MF_SEPARATOR)
|
||||
self.AppendMenu(menu, "&Select all", "EditSelectAll")
|
||||
self.AppendMenu(
|
||||
menu, "View &Whitespace", "ViewWhitespace", checked=self.SCIGetViewWS()
|
||||
)
|
||||
self.AppendMenu(
|
||||
menu, "&Fixed Font", "ViewFixedFont", checked=self._GetColorizer().bUseFixed
|
||||
)
|
||||
self.AppendMenu(menu, flags=win32con.MF_SEPARATOR)
|
||||
self.AppendMenu(menu, "&Goto line...", "GotoLine")
|
||||
|
||||
submenu = win32ui.CreatePopupMenu()
|
||||
newitems = self.idle.GetMenuItems("edit")
|
||||
for text, event in newitems:
|
||||
self.AppendMenu(submenu, text, event)
|
||||
|
||||
flags = win32con.MF_STRING | win32con.MF_ENABLED | win32con.MF_POPUP
|
||||
menu.AppendMenu(flags, submenu.GetHandle(), "&Source code")
|
||||
|
||||
flags = (
|
||||
win32con.TPM_LEFTALIGN | win32con.TPM_LEFTBUTTON | win32con.TPM_RIGHTBUTTON
|
||||
)
|
||||
menu.TrackPopupMenu(params[5], flags, self)
|
||||
return 0
|
||||
|
||||
def OnCmdViewFold(self, cid, code): # Handle the menu command
|
||||
if cid == win32ui.ID_VIEW_FOLD_EXPAND_ALL:
|
||||
self.FoldExpandAllEvent(None)
|
||||
elif cid == win32ui.ID_VIEW_FOLD_EXPAND:
|
||||
self.FoldExpandEvent(None)
|
||||
elif cid == win32ui.ID_VIEW_FOLD_COLLAPSE_ALL:
|
||||
self.FoldCollapseAllEvent(None)
|
||||
elif cid == win32ui.ID_VIEW_FOLD_COLLAPSE:
|
||||
self.FoldCollapseEvent(None)
|
||||
else:
|
||||
print("Unknown collapse/expand ID")
|
||||
|
||||
def OnUpdateViewFold(self, cmdui): # Update the tick on the UI.
|
||||
if not self.bFolding:
|
||||
cmdui.Enable(0)
|
||||
return
|
||||
id = cmdui.m_nID
|
||||
if id in (win32ui.ID_VIEW_FOLD_EXPAND_ALL, win32ui.ID_VIEW_FOLD_COLLAPSE_ALL):
|
||||
cmdui.Enable()
|
||||
else:
|
||||
enable = 0
|
||||
lineno = self.LineFromChar(self.GetSel()[0])
|
||||
foldable = (
|
||||
self.SCIGetFoldLevel(lineno) & scintillacon.SC_FOLDLEVELHEADERFLAG
|
||||
)
|
||||
is_expanded = self.SCIGetFoldExpanded(lineno)
|
||||
if id == win32ui.ID_VIEW_FOLD_EXPAND:
|
||||
if foldable and not is_expanded:
|
||||
enable = 1
|
||||
elif id == win32ui.ID_VIEW_FOLD_COLLAPSE:
|
||||
if foldable and is_expanded:
|
||||
enable = 1
|
||||
cmdui.Enable(enable)
|
||||
|
||||
def OnCmdViewFoldTopLevel(self, cid, code): # Handle the menu command
|
||||
self.FoldTopLevelEvent(None)
|
||||
|
||||
#######################################
|
||||
# The Events
|
||||
#######################################
|
||||
def ToggleBookmarkEvent(self, event, pos=-1):
|
||||
"""Toggle a bookmark at the specified or current position"""
|
||||
if pos == -1:
|
||||
pos, end = self.GetSel()
|
||||
startLine = self.LineFromChar(pos)
|
||||
self.GetDocument().MarkerToggle(startLine + 1, MARKER_BOOKMARK)
|
||||
return 0
|
||||
|
||||
def GotoNextBookmarkEvent(self, event, fromPos=-1):
|
||||
"""Move to the next bookmark"""
|
||||
if fromPos == -1:
|
||||
fromPos, end = self.GetSel()
|
||||
startLine = self.LineFromChar(fromPos) + 1 # Zero based line to start
|
||||
nextLine = self.GetDocument().MarkerGetNext(startLine + 1, MARKER_BOOKMARK) - 1
|
||||
if nextLine < 0:
|
||||
nextLine = self.GetDocument().MarkerGetNext(0, MARKER_BOOKMARK) - 1
|
||||
if nextLine < 0 or nextLine == startLine - 1:
|
||||
win32api.MessageBeep()
|
||||
else:
|
||||
self.SCIEnsureVisible(nextLine)
|
||||
self.SCIGotoLine(nextLine)
|
||||
return 0
|
||||
|
||||
def TabKeyEvent(self, event):
|
||||
"""Insert an indent. If no selection, a single indent, otherwise a block indent"""
|
||||
# Handle auto-complete first.
|
||||
if self.SCIAutoCActive():
|
||||
self.SCIAutoCComplete()
|
||||
return 0
|
||||
# Call the IDLE event.
|
||||
return self.bindings.fire("<<smart-indent>>", event)
|
||||
|
||||
def EnterKeyEvent(self, event):
|
||||
"""Handle the enter key with special handling for auto-complete"""
|
||||
# Handle auto-complete first.
|
||||
if self.SCIAutoCActive():
|
||||
self.SCIAutoCComplete()
|
||||
self.SCIAutoCCancel()
|
||||
# Call the IDLE event.
|
||||
return self.bindings.fire("<<newline-and-indent>>", event)
|
||||
|
||||
def ShowInteractiveWindowEvent(self, event):
|
||||
import pywin.framework.interact
|
||||
|
||||
pywin.framework.interact.ShowInteractiveWindow()
|
||||
|
||||
def FoldTopLevelEvent(self, event=None):
|
||||
if not self.bFolding:
|
||||
return 1
|
||||
|
||||
win32ui.DoWaitCursor(1)
|
||||
try:
|
||||
self.Colorize()
|
||||
maxLine = self.GetLineCount()
|
||||
# Find the first line, and check out its state.
|
||||
for lineSeek in range(maxLine):
|
||||
if self.SCIGetFoldLevel(lineSeek) & scintillacon.SC_FOLDLEVELHEADERFLAG:
|
||||
expanding = not self.SCIGetFoldExpanded(lineSeek)
|
||||
break
|
||||
else:
|
||||
# no folds here!
|
||||
return
|
||||
for lineSeek in range(lineSeek, maxLine):
|
||||
level = self.SCIGetFoldLevel(lineSeek)
|
||||
level_no = (
|
||||
level
|
||||
& scintillacon.SC_FOLDLEVELNUMBERMASK
|
||||
- scintillacon.SC_FOLDLEVELBASE
|
||||
)
|
||||
is_header = level & scintillacon.SC_FOLDLEVELHEADERFLAG
|
||||
# print(lineSeek, level_no, is_header)
|
||||
if level_no == 0 and is_header:
|
||||
if (expanding and not self.SCIGetFoldExpanded(lineSeek)) or (
|
||||
not expanding and self.SCIGetFoldExpanded(lineSeek)
|
||||
):
|
||||
self.SCIToggleFold(lineSeek)
|
||||
finally:
|
||||
win32ui.DoWaitCursor(-1)
|
||||
|
||||
def FoldExpandSecondLevelEvent(self, event):
|
||||
if not self.bFolding:
|
||||
return 1
|
||||
win32ui.DoWaitCursor(1)
|
||||
## I think this is needed since Scintilla may not have
|
||||
## already formatted parts of file outside visible window.
|
||||
self.Colorize()
|
||||
levels = [scintillacon.SC_FOLDLEVELBASE]
|
||||
## Scintilla's level number is based on amount of whitespace indentation
|
||||
for lineno in range(self.GetLineCount()):
|
||||
level = self.SCIGetFoldLevel(lineno)
|
||||
if not level & scintillacon.SC_FOLDLEVELHEADERFLAG:
|
||||
continue
|
||||
curr_level = level & scintillacon.SC_FOLDLEVELNUMBERMASK
|
||||
if curr_level > levels[-1]:
|
||||
levels.append(curr_level)
|
||||
try:
|
||||
level_ind = levels.index(curr_level)
|
||||
except ValueError:
|
||||
## probably syntax error in source file, bail
|
||||
break
|
||||
levels = levels[: level_ind + 1]
|
||||
if level_ind == 1 and not self.SCIGetFoldExpanded(lineno):
|
||||
self.SCIToggleFold(lineno)
|
||||
win32ui.DoWaitCursor(-1)
|
||||
|
||||
def FoldCollapseSecondLevelEvent(self, event):
|
||||
if not self.bFolding:
|
||||
return 1
|
||||
win32ui.DoWaitCursor(1)
|
||||
## I think this is needed since Scintilla may not have
|
||||
## already formatted parts of file outside visible window.
|
||||
self.Colorize()
|
||||
levels = [scintillacon.SC_FOLDLEVELBASE]
|
||||
## Scintilla's level number is based on amount of whitespace indentation
|
||||
for lineno in range(self.GetLineCount()):
|
||||
level = self.SCIGetFoldLevel(lineno)
|
||||
if not level & scintillacon.SC_FOLDLEVELHEADERFLAG:
|
||||
continue
|
||||
curr_level = level & scintillacon.SC_FOLDLEVELNUMBERMASK
|
||||
if curr_level > levels[-1]:
|
||||
levels.append(curr_level)
|
||||
try:
|
||||
level_ind = levels.index(curr_level)
|
||||
except ValueError:
|
||||
## probably syntax error in source file, bail
|
||||
break
|
||||
levels = levels[: level_ind + 1]
|
||||
if level_ind == 1 and self.SCIGetFoldExpanded(lineno):
|
||||
self.SCIToggleFold(lineno)
|
||||
win32ui.DoWaitCursor(-1)
|
||||
|
||||
def FoldExpandEvent(self, event):
|
||||
if not self.bFolding:
|
||||
return 1
|
||||
win32ui.DoWaitCursor(1)
|
||||
lineno = self.LineFromChar(self.GetSel()[0])
|
||||
if self.SCIGetFoldLevel(
|
||||
lineno
|
||||
) & scintillacon.SC_FOLDLEVELHEADERFLAG and not self.SCIGetFoldExpanded(lineno):
|
||||
self.SCIToggleFold(lineno)
|
||||
win32ui.DoWaitCursor(-1)
|
||||
|
||||
def FoldExpandAllEvent(self, event):
|
||||
if not self.bFolding:
|
||||
return 1
|
||||
win32ui.DoWaitCursor(1)
|
||||
for lineno in range(0, self.GetLineCount()):
|
||||
if self.SCIGetFoldLevel(
|
||||
lineno
|
||||
) & scintillacon.SC_FOLDLEVELHEADERFLAG and not self.SCIGetFoldExpanded(
|
||||
lineno
|
||||
):
|
||||
self.SCIToggleFold(lineno)
|
||||
win32ui.DoWaitCursor(-1)
|
||||
|
||||
def FoldCollapseEvent(self, event):
|
||||
if not self.bFolding:
|
||||
return 1
|
||||
win32ui.DoWaitCursor(1)
|
||||
lineno = self.LineFromChar(self.GetSel()[0])
|
||||
if self.SCIGetFoldLevel(
|
||||
lineno
|
||||
) & scintillacon.SC_FOLDLEVELHEADERFLAG and self.SCIGetFoldExpanded(lineno):
|
||||
self.SCIToggleFold(lineno)
|
||||
win32ui.DoWaitCursor(-1)
|
||||
|
||||
def FoldCollapseAllEvent(self, event):
|
||||
if not self.bFolding:
|
||||
return 1
|
||||
win32ui.DoWaitCursor(1)
|
||||
self.Colorize()
|
||||
for lineno in range(0, self.GetLineCount()):
|
||||
if self.SCIGetFoldLevel(
|
||||
lineno
|
||||
) & scintillacon.SC_FOLDLEVELHEADERFLAG and self.SCIGetFoldExpanded(lineno):
|
||||
self.SCIToggleFold(lineno)
|
||||
win32ui.DoWaitCursor(-1)
|
||||
|
||||
|
||||
class SplitterFrame(EditorFrame):
|
||||
def OnCreate(self, cs):
|
||||
self.HookCommand(self.OnWindowSplit, win32ui.ID_WINDOW_SPLIT)
|
||||
return 1
|
||||
|
||||
def OnWindowSplit(self, id, code):
|
||||
self.GetDlgItem(win32ui.AFX_IDW_PANE_FIRST).DoKeyboardSplit()
|
||||
return 1
|
||||
|
||||
|
||||
class SyntEditTemplate(EditorTemplateBase):
|
||||
def __init__(
|
||||
self, res=win32ui.IDR_TEXTTYPE, makeDoc=None, makeFrame=None, makeView=None
|
||||
):
|
||||
if makeDoc is None:
|
||||
makeDoc = SyntEditDocument
|
||||
if makeView is None:
|
||||
makeView = SyntEditView
|
||||
if makeFrame is None:
|
||||
makeFrame = SplitterFrame
|
||||
self.bSetMenus = 0
|
||||
EditorTemplateBase.__init__(self, res, makeDoc, makeFrame, makeView)
|
||||
|
||||
def CheckIDLEMenus(self, idle):
|
||||
if self.bSetMenus:
|
||||
return
|
||||
self.bSetMenus = 1
|
||||
|
||||
submenu = win32ui.CreatePopupMenu()
|
||||
newitems = idle.GetMenuItems("edit")
|
||||
flags = win32con.MF_STRING | win32con.MF_ENABLED
|
||||
for text, event in newitems:
|
||||
id = bindings.event_to_commands.get(event)
|
||||
if id is not None:
|
||||
keyname = pywin.scintilla.view.configManager.get_key_binding(
|
||||
event, ["editor"]
|
||||
)
|
||||
if keyname is not None:
|
||||
text += "\t" + keyname
|
||||
submenu.AppendMenu(flags, id, text)
|
||||
|
||||
mainMenu = self.GetSharedMenu()
|
||||
editMenu = mainMenu.GetSubMenu(1)
|
||||
editMenu.AppendMenu(win32con.MF_SEPARATOR, 0, "")
|
||||
editMenu.AppendMenu(
|
||||
win32con.MF_STRING | win32con.MF_POPUP | win32con.MF_ENABLED,
|
||||
submenu.GetHandle(),
|
||||
"&Source Code",
|
||||
)
|
||||
|
||||
def _CreateDocTemplate(self, resourceId):
|
||||
return win32ui.CreateDocTemplate(resourceId)
|
||||
|
||||
def CreateWin32uiDocument(self):
|
||||
return self.DoCreateDoc()
|
||||
|
||||
def GetPythonPropertyPages(self):
|
||||
"""Returns a list of property pages"""
|
||||
from pywin.scintilla import configui
|
||||
|
||||
return EditorTemplateBase.GetPythonPropertyPages(self) + [
|
||||
configui.ScintillaFormatPropertyPage()
|
||||
]
|
||||
|
||||
|
||||
# For debugging purposes, when this module may be reloaded many times.
|
||||
try:
|
||||
win32ui.GetApp().RemoveDocTemplate(editorTemplate) # type: ignore[has-type, used-before-def]
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
editorTemplate = SyntEditTemplate()
|
||||
win32ui.GetApp().AddDocTemplate(editorTemplate)
|
||||
@@ -0,0 +1,299 @@
|
||||
import pywin.scintilla.config
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework.editor import DeleteEditorOption, GetEditorOption, SetEditorOption
|
||||
from pywin.mfc import dialog
|
||||
|
||||
from . import document
|
||||
|
||||
# The standard 16 color VGA palette should always be possible
|
||||
paletteVGA = (
|
||||
("Black", 0, 0, 0),
|
||||
("Navy", 0, 0, 128),
|
||||
("Green", 0, 128, 0),
|
||||
("Cyan", 0, 128, 128),
|
||||
("Maroon", 128, 0, 0),
|
||||
("Purple", 128, 0, 128),
|
||||
("Olive", 128, 128, 0),
|
||||
("Gray", 128, 128, 128),
|
||||
("Silver", 192, 192, 192),
|
||||
("Blue", 0, 0, 255),
|
||||
("Lime", 0, 255, 0),
|
||||
("Aqua", 0, 255, 255),
|
||||
("Red", 255, 0, 0),
|
||||
("Fuchsia", 255, 0, 255),
|
||||
("Yellow", 255, 255, 0),
|
||||
("White", 255, 255, 255),
|
||||
)
|
||||
|
||||
|
||||
######################################################
|
||||
#
|
||||
# Property Page for editor options
|
||||
#
|
||||
class EditorPropertyPage(dialog.PropertyPage):
|
||||
def __init__(self):
|
||||
dialog.PropertyPage.__init__(self, win32ui.IDD_PP_EDITOR)
|
||||
self.autooptions = []
|
||||
self._AddEditorOption(win32ui.IDC_AUTO_RELOAD, "i", "Auto Reload", 1)
|
||||
self._AddEditorOption(
|
||||
win32ui.IDC_COMBO1, "i", "Backup Type", document.BAK_DOT_BAK_BAK_DIR
|
||||
)
|
||||
self._AddEditorOption(
|
||||
win32ui.IDC_AUTOCOMPLETE, "i", "Autocomplete Attributes", 1
|
||||
)
|
||||
self._AddEditorOption(win32ui.IDC_CALLTIPS, "i", "Show Call Tips", 1)
|
||||
self._AddEditorOption(
|
||||
win32ui.IDC_MARGIN_LINENUMBER, "i", "Line Number Margin Width", 0
|
||||
)
|
||||
self._AddEditorOption(win32ui.IDC_RADIO1, "i", "MarkersInMargin", None)
|
||||
self._AddEditorOption(
|
||||
win32ui.IDC_MARGIN_MARKER, "i", "Marker Margin Width", None
|
||||
)
|
||||
self["Marker Margin Width"] = GetEditorOption("Marker Margin Width", 16)
|
||||
|
||||
# Folding
|
||||
self._AddEditorOption(win32ui.IDC_MARGIN_FOLD, "i", "Fold Margin Width", 12)
|
||||
self._AddEditorOption(win32ui.IDC_FOLD_ENABLE, "i", "Enable Folding", 1)
|
||||
self._AddEditorOption(win32ui.IDC_FOLD_ON_OPEN, "i", "Fold On Open", 0)
|
||||
self._AddEditorOption(win32ui.IDC_FOLD_SHOW_LINES, "i", "Fold Lines", 1)
|
||||
|
||||
# Right edge.
|
||||
self._AddEditorOption(
|
||||
win32ui.IDC_RIGHTEDGE_ENABLE, "i", "Right Edge Enabled", 0
|
||||
)
|
||||
self._AddEditorOption(
|
||||
win32ui.IDC_RIGHTEDGE_COLUMN, "i", "Right Edge Column", 75
|
||||
)
|
||||
|
||||
# Source control, etc
|
||||
self.AddDDX(win32ui.IDC_VSS_INTEGRATE, "bVSS")
|
||||
self.AddDDX(win32ui.IDC_KEYBOARD_CONFIG, "Configs", "l")
|
||||
self["Configs"] = pywin.scintilla.config.find_config_files()
|
||||
|
||||
def _AddEditorOption(self, idd, typ, optionName, defaultVal):
|
||||
self.AddDDX(idd, optionName, typ)
|
||||
# some options are "derived" - ie, can be implied from others
|
||||
# (eg, "view markers in background" is implied from "markerMarginWidth==0"
|
||||
# So we don't actually store these values, but they do still get DDX support.
|
||||
if defaultVal is not None:
|
||||
self[optionName] = GetEditorOption(optionName, defaultVal)
|
||||
self.autooptions.append((optionName, defaultVal))
|
||||
|
||||
def OnInitDialog(self):
|
||||
for name, val in self.autooptions:
|
||||
self[name] = GetEditorOption(name, val)
|
||||
|
||||
# Note that these MUST be in the same order as the BAK constants.
|
||||
cbo = self.GetDlgItem(win32ui.IDC_COMBO1)
|
||||
cbo.AddString("None")
|
||||
cbo.AddString(".BAK File")
|
||||
cbo.AddString("TEMP dir")
|
||||
cbo.AddString("Own dir")
|
||||
|
||||
# Source Safe
|
||||
bVSS = (
|
||||
GetEditorOption("Source Control Module", "") == "pywin.framework.editor.vss"
|
||||
)
|
||||
self["bVSS"] = bVSS
|
||||
|
||||
edit = self.GetDlgItem(win32ui.IDC_RIGHTEDGE_SAMPLE)
|
||||
edit.SetWindowText("Sample Color")
|
||||
|
||||
rc = dialog.PropertyPage.OnInitDialog(self)
|
||||
|
||||
try:
|
||||
self.GetDlgItem(win32ui.IDC_KEYBOARD_CONFIG).SelectString(
|
||||
-1, GetEditorOption("Keyboard Config", "default")
|
||||
)
|
||||
except win32ui.error:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
self.HookCommand(self.OnButSimple, win32ui.IDC_FOLD_ENABLE)
|
||||
self.HookCommand(self.OnButSimple, win32ui.IDC_RADIO1)
|
||||
self.HookCommand(self.OnButSimple, win32ui.IDC_RADIO2)
|
||||
self.HookCommand(self.OnButSimple, win32ui.IDC_RIGHTEDGE_ENABLE)
|
||||
self.HookCommand(self.OnButEdgeColor, win32ui.IDC_RIGHTEDGE_DEFINE)
|
||||
|
||||
butMarginEnabled = self["Marker Margin Width"] > 0
|
||||
self.GetDlgItem(win32ui.IDC_RADIO1).SetCheck(butMarginEnabled)
|
||||
self.GetDlgItem(win32ui.IDC_RADIO2).SetCheck(not butMarginEnabled)
|
||||
|
||||
self.edgeColor = self.initialEdgeColor = GetEditorOption(
|
||||
"Right Edge Color", win32api.RGB(0xEF, 0xEF, 0xEF)
|
||||
)
|
||||
for spinner_id in (win32ui.IDC_SPIN1, win32ui.IDC_SPIN2, win32ui.IDC_SPIN3):
|
||||
spinner = self.GetDlgItem(spinner_id)
|
||||
spinner.SetRange(0, 100)
|
||||
self.UpdateUIForState()
|
||||
|
||||
return rc
|
||||
|
||||
def OnButSimple(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
self.UpdateUIForState()
|
||||
|
||||
def OnButEdgeColor(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
d = win32ui.CreateColorDialog(self.edgeColor, 0, self)
|
||||
# Ensure the current color is a custom color (as it may not be in the swatch)
|
||||
# plus some other nice gray scales.
|
||||
ccs = [self.edgeColor]
|
||||
for c in range(0xEF, 0x4F, -0x10):
|
||||
ccs.append(win32api.RGB(c, c, c))
|
||||
d.SetCustomColors(ccs)
|
||||
if d.DoModal() == win32con.IDOK:
|
||||
self.edgeColor = d.GetColor()
|
||||
self.UpdateUIForState()
|
||||
|
||||
def UpdateUIForState(self):
|
||||
folding = self.GetDlgItem(win32ui.IDC_FOLD_ENABLE).GetCheck()
|
||||
self.GetDlgItem(win32ui.IDC_FOLD_ON_OPEN).EnableWindow(folding)
|
||||
self.GetDlgItem(win32ui.IDC_FOLD_SHOW_LINES).EnableWindow(folding)
|
||||
|
||||
widthEnabled = self.GetDlgItem(win32ui.IDC_RADIO1).GetCheck()
|
||||
self.GetDlgItem(win32ui.IDC_MARGIN_MARKER).EnableWindow(widthEnabled)
|
||||
self.UpdateData() # Ensure self[] is up to date with the control data.
|
||||
if widthEnabled and self["Marker Margin Width"] == 0:
|
||||
self["Marker Margin Width"] = 16
|
||||
self.UpdateData(0) # Ensure control up to date with self[]
|
||||
|
||||
# Right edge
|
||||
edgeEnabled = self.GetDlgItem(win32ui.IDC_RIGHTEDGE_ENABLE).GetCheck()
|
||||
self.GetDlgItem(win32ui.IDC_RIGHTEDGE_COLUMN).EnableWindow(edgeEnabled)
|
||||
self.GetDlgItem(win32ui.IDC_RIGHTEDGE_SAMPLE).EnableWindow(edgeEnabled)
|
||||
self.GetDlgItem(win32ui.IDC_RIGHTEDGE_DEFINE).EnableWindow(edgeEnabled)
|
||||
|
||||
edit = self.GetDlgItem(win32ui.IDC_RIGHTEDGE_SAMPLE)
|
||||
edit.SetBackgroundColor(0, self.edgeColor)
|
||||
|
||||
def OnOK(self):
|
||||
for name, defVal in self.autooptions:
|
||||
SetEditorOption(name, self[name])
|
||||
# Margin width gets handled differently.
|
||||
if self["MarkersInMargin"] == 0:
|
||||
SetEditorOption("Marker Margin Width", self["Marker Margin Width"])
|
||||
else:
|
||||
SetEditorOption("Marker Margin Width", 0)
|
||||
if self.edgeColor != self.initialEdgeColor:
|
||||
SetEditorOption("Right Edge Color", self.edgeColor)
|
||||
if self["bVSS"]:
|
||||
SetEditorOption("Source Control Module", "pywin.framework.editor.vss")
|
||||
else:
|
||||
if (
|
||||
GetEditorOption("Source Control Module", "")
|
||||
== "pywin.framework.editor.vss"
|
||||
):
|
||||
SetEditorOption("Source Control Module", "")
|
||||
# Keyboard config
|
||||
configname = self.GetDlgItem(win32ui.IDC_KEYBOARD_CONFIG).GetWindowText()
|
||||
if configname:
|
||||
if configname == "default":
|
||||
DeleteEditorOption("Keyboard Config")
|
||||
else:
|
||||
SetEditorOption("Keyboard Config", configname)
|
||||
|
||||
import pywin.scintilla.view
|
||||
|
||||
pywin.scintilla.view.LoadConfiguration()
|
||||
|
||||
# Now tell all views we have changed.
|
||||
## for doc in editorTemplate.GetDocumentList():
|
||||
## for view in doc.GetAllViews():
|
||||
## try:
|
||||
## fn = view.OnConfigChange
|
||||
## except AttributeError:
|
||||
## continue
|
||||
## fn()
|
||||
return 1
|
||||
|
||||
|
||||
class EditorWhitespacePropertyPage(dialog.PropertyPage):
|
||||
def __init__(self):
|
||||
dialog.PropertyPage.__init__(self, win32ui.IDD_PP_TABS)
|
||||
self.autooptions = []
|
||||
self._AddEditorOption(win32ui.IDC_TAB_SIZE, "i", "Tab Size", 4)
|
||||
self._AddEditorOption(win32ui.IDC_INDENT_SIZE, "i", "Indent Size", 4)
|
||||
self._AddEditorOption(win32ui.IDC_USE_SMART_TABS, "i", "Smart Tabs", 1)
|
||||
self._AddEditorOption(win32ui.IDC_VIEW_WHITESPACE, "i", "View Whitespace", 0)
|
||||
self._AddEditorOption(win32ui.IDC_VIEW_EOL, "i", "View EOL", 0)
|
||||
self._AddEditorOption(
|
||||
win32ui.IDC_VIEW_INDENTATIONGUIDES, "i", "View Indentation Guides", 0
|
||||
)
|
||||
|
||||
def _AddEditorOption(self, idd, typ, optionName, defaultVal):
|
||||
self.AddDDX(idd, optionName, typ)
|
||||
self[optionName] = GetEditorOption(optionName, defaultVal)
|
||||
self.autooptions.append((optionName, defaultVal))
|
||||
|
||||
def OnInitDialog(self):
|
||||
for name, val in self.autooptions:
|
||||
self[name] = GetEditorOption(name, val)
|
||||
|
||||
rc = dialog.PropertyPage.OnInitDialog(self)
|
||||
|
||||
idc = win32ui.IDC_TABTIMMY_NONE
|
||||
if GetEditorOption("Use Tab Timmy", 1):
|
||||
idc = win32ui.IDC_TABTIMMY_IND
|
||||
self.GetDlgItem(idc).SetCheck(1)
|
||||
|
||||
idc = win32ui.IDC_RADIO1
|
||||
if GetEditorOption("Use Tabs", 0):
|
||||
idc = win32ui.IDC_USE_TABS
|
||||
self.GetDlgItem(idc).SetCheck(1)
|
||||
|
||||
tt_color = GetEditorOption("Tab Timmy Color", win32api.RGB(0xFF, 0, 0))
|
||||
self.cbo = self.GetDlgItem(win32ui.IDC_COMBO1)
|
||||
for c in paletteVGA:
|
||||
self.cbo.AddString(c[0])
|
||||
sel = 0
|
||||
for c in paletteVGA:
|
||||
if tt_color == win32api.RGB(c[1], c[2], c[3]):
|
||||
break
|
||||
sel += 1
|
||||
else:
|
||||
sel = -1
|
||||
self.cbo.SetCurSel(sel)
|
||||
self.HookCommand(self.OnButSimple, win32ui.IDC_TABTIMMY_NONE)
|
||||
self.HookCommand(self.OnButSimple, win32ui.IDC_TABTIMMY_IND)
|
||||
self.HookCommand(self.OnButSimple, win32ui.IDC_TABTIMMY_BG)
|
||||
# Set ranges for the spinners.
|
||||
for spinner_id in [win32ui.IDC_SPIN1, win32ui.IDC_SPIN2]:
|
||||
spinner = self.GetDlgItem(spinner_id)
|
||||
spinner.SetRange(1, 16)
|
||||
return rc
|
||||
|
||||
def OnButSimple(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
self.UpdateUIForState()
|
||||
|
||||
def UpdateUIForState(self):
|
||||
timmy = self.GetDlgItem(win32ui.IDC_TABTIMMY_NONE).GetCheck()
|
||||
self.GetDlgItem(win32ui.IDC_COMBO1).EnableWindow(not timmy)
|
||||
|
||||
def OnOK(self):
|
||||
for name, defVal in self.autooptions:
|
||||
SetEditorOption(name, self[name])
|
||||
|
||||
SetEditorOption("Use Tabs", self.GetDlgItem(win32ui.IDC_USE_TABS).GetCheck())
|
||||
|
||||
SetEditorOption(
|
||||
"Use Tab Timmy", self.GetDlgItem(win32ui.IDC_TABTIMMY_IND).GetCheck()
|
||||
)
|
||||
c = paletteVGA[self.cbo.GetCurSel()]
|
||||
SetEditorOption("Tab Timmy Color", win32api.RGB(c[1], c[2], c[3]))
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
def testpp():
|
||||
ps = dialog.PropertySheet("Editor Options")
|
||||
ps.AddPage(EditorWhitespacePropertyPage())
|
||||
ps.DoModal()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
testpp()
|
||||
@@ -0,0 +1,379 @@
|
||||
# We no longer support the old, non-colour editor!
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import traceback
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework.editor import GetEditorOption
|
||||
|
||||
BAK_NONE = 0
|
||||
BAK_DOT_BAK = 1
|
||||
BAK_DOT_BAK_TEMP_DIR = 2
|
||||
BAK_DOT_BAK_BAK_DIR = 3
|
||||
|
||||
MSG_CHECK_EXTERNAL_FILE = (
|
||||
win32con.WM_USER + 1999
|
||||
) ## WARNING: Duplicated in editor.py and coloreditor.py
|
||||
|
||||
import pywin.scintilla.document
|
||||
|
||||
ParentEditorDocument = pywin.scintilla.document.CScintillaDocument
|
||||
|
||||
|
||||
class EditorDocumentBase(ParentEditorDocument):
|
||||
def __init__(self, template):
|
||||
self.bAutoReload = GetEditorOption("Auto Reload", 1)
|
||||
self.bDeclinedReload = 0 # Has the user declined to reload.
|
||||
self.fileStat = None
|
||||
self.bReportedFileNotFound = 0
|
||||
|
||||
# what sort of bak file should I create.
|
||||
# default to write to %temp%/bak/filename.ext
|
||||
self.bakFileType = GetEditorOption("Backup Type", BAK_DOT_BAK_BAK_DIR)
|
||||
|
||||
self.watcherThread = FileWatchingThread(self)
|
||||
self.watcherThread.CreateThread()
|
||||
# Should I try and use VSS integration?
|
||||
self.scModuleName = GetEditorOption("Source Control Module", "")
|
||||
self.scModule = None # Loaded when first used.
|
||||
ParentEditorDocument.__init__(self, template, template.CreateWin32uiDocument())
|
||||
|
||||
def OnCloseDocument(self):
|
||||
self.watcherThread.SignalStop()
|
||||
return self._obj_.OnCloseDocument()
|
||||
|
||||
# def OnOpenDocument(self, name):
|
||||
# rc = ParentEditorDocument.OnOpenDocument(self, name)
|
||||
# self.GetFirstView()._SetLoadedText(self.text)
|
||||
# self._DocumentStateChanged()
|
||||
# return rc
|
||||
|
||||
def OnSaveDocument(self, fileName):
|
||||
win32ui.SetStatusText("Saving file...", 1)
|
||||
# rename to bak if required.
|
||||
dir, basename = os.path.split(fileName)
|
||||
if self.bakFileType == BAK_DOT_BAK:
|
||||
bakFileName = dir + "\\" + os.path.splitext(basename)[0] + ".bak"
|
||||
elif self.bakFileType == BAK_DOT_BAK_TEMP_DIR:
|
||||
bakFileName = (
|
||||
win32api.GetTempPath() + "\\" + os.path.splitext(basename)[0] + ".bak"
|
||||
)
|
||||
elif self.bakFileType == BAK_DOT_BAK_BAK_DIR:
|
||||
tempPath = os.path.join(win32api.GetTempPath(), "bak")
|
||||
try:
|
||||
os.mkdir(tempPath, 0)
|
||||
except OSError:
|
||||
pass
|
||||
bakFileName = os.path.join(tempPath, basename)
|
||||
try:
|
||||
os.unlink(bakFileName) # raise NameError if no bakups wanted.
|
||||
except (OSError, NameError):
|
||||
pass
|
||||
try:
|
||||
# Do a copy as it might be on different volumes,
|
||||
# and the file may be a hard-link, causing the link
|
||||
# to follow the backup.
|
||||
shutil.copy2(fileName, bakFileName)
|
||||
except (OSError, NameError):
|
||||
pass
|
||||
try:
|
||||
self.SaveFile(fileName)
|
||||
except OSError as details:
|
||||
win32ui.MessageBox("Error - could not save file\r\n\r\n%s" % details)
|
||||
return 0
|
||||
except (UnicodeEncodeError, LookupError) as details:
|
||||
rc = win32ui.MessageBox(
|
||||
"Encoding failed: \r\n%s" % details
|
||||
+ "\r\nPlease add desired source encoding as first line of file, eg \r\n"
|
||||
+ "# -*- coding: mbcs -*-\r\n\r\n"
|
||||
+ "If you continue, the file will be saved as binary and will\r\n"
|
||||
+ "not be valid in the declared encoding.\r\n\r\n"
|
||||
+ "Save the file as binary with an invalid encoding?",
|
||||
"File save failed",
|
||||
win32con.MB_YESNO | win32con.MB_DEFBUTTON2,
|
||||
)
|
||||
if rc == win32con.IDYES:
|
||||
try:
|
||||
self.SaveFile(fileName, encoding="latin-1")
|
||||
except OSError as details:
|
||||
win32ui.MessageBox(
|
||||
"Error - could not save file\r\n\r\n%s" % details
|
||||
)
|
||||
return 0
|
||||
else:
|
||||
return 0
|
||||
self.SetModifiedFlag(0) # No longer dirty
|
||||
self.bDeclinedReload = 0 # They probably want to know if it changes again!
|
||||
win32ui.AddToRecentFileList(fileName)
|
||||
self.SetPathName(fileName)
|
||||
win32ui.SetStatusText("Ready")
|
||||
self._DocumentStateChanged()
|
||||
return 1
|
||||
|
||||
def FinalizeViewCreation(self, view):
|
||||
ParentEditorDocument.FinalizeViewCreation(self, view)
|
||||
if view == self.GetFirstView():
|
||||
self._DocumentStateChanged()
|
||||
if view.bFolding and GetEditorOption("Fold On Open", 0):
|
||||
view.FoldTopLevelEvent()
|
||||
|
||||
def HookViewNotifications(self, view):
|
||||
ParentEditorDocument.HookViewNotifications(self, view)
|
||||
|
||||
# Support for reloading the document from disk - presumably after some
|
||||
# external application has modified it (or possibly source control has
|
||||
# checked it out.
|
||||
def ReloadDocument(self):
|
||||
"""Reloads the document from disk. Assumes the file has
|
||||
been saved and user has been asked if necessary - it just does it!
|
||||
"""
|
||||
win32ui.SetStatusText("Reloading document. Please wait...", 1)
|
||||
self.SetModifiedFlag(0)
|
||||
# Loop over all views, saving their state, then reload the document
|
||||
views = self.GetAllViews()
|
||||
states = []
|
||||
for view in views:
|
||||
try:
|
||||
info = view._PrepareUserStateChange()
|
||||
except AttributeError: # Not our editor view?
|
||||
info = None
|
||||
states.append(info)
|
||||
self.OnOpenDocument(self.GetPathName())
|
||||
for view, info in zip(views, states):
|
||||
if info is not None:
|
||||
view._EndUserStateChange(info)
|
||||
self._DocumentStateChanged()
|
||||
win32ui.SetStatusText("Document reloaded.")
|
||||
|
||||
# Reloading the file
|
||||
def CheckExternalDocumentUpdated(self):
|
||||
if self.bDeclinedReload or not self.GetPathName():
|
||||
return
|
||||
try:
|
||||
newstat = os.stat(self.GetPathName())
|
||||
except OSError as exc:
|
||||
if not self.bReportedFileNotFound:
|
||||
print(
|
||||
"The file '{}' is open for editing, but\nchecking it for changes caused the error: {}".format(
|
||||
self.GetPathName(), exc.strerror
|
||||
)
|
||||
)
|
||||
self.bReportedFileNotFound = 1
|
||||
return
|
||||
if self.bReportedFileNotFound:
|
||||
print(
|
||||
"The file '{}' has re-appeared - continuing to watch for changes...".format(
|
||||
self.GetPathName()
|
||||
)
|
||||
)
|
||||
self.bReportedFileNotFound = (
|
||||
0 # Once found again we want to start complaining.
|
||||
)
|
||||
changed = (
|
||||
(self.fileStat is None)
|
||||
or self.fileStat[0] != newstat[0]
|
||||
or self.fileStat[6] != newstat[6]
|
||||
or self.fileStat[8] != newstat[8]
|
||||
or self.fileStat[9] != newstat[9]
|
||||
)
|
||||
if changed:
|
||||
question = None
|
||||
if self.IsModified():
|
||||
question = (
|
||||
"%s\r\n\r\nThis file has been modified outside of the source editor.\r\nDo you want to reload it and LOSE THE CHANGES in the source editor?"
|
||||
% self.GetPathName()
|
||||
)
|
||||
mbStyle = win32con.MB_YESNO | win32con.MB_DEFBUTTON2 # Default to "No"
|
||||
else:
|
||||
if not self.bAutoReload:
|
||||
question = (
|
||||
"%s\r\n\r\nThis file has been modified outside of the source editor.\r\nDo you want to reload it?"
|
||||
% self.GetPathName()
|
||||
)
|
||||
mbStyle = win32con.MB_YESNO # Default to "Yes"
|
||||
if question:
|
||||
rc = win32ui.MessageBox(question, None, mbStyle)
|
||||
if rc != win32con.IDYES:
|
||||
self.bDeclinedReload = 1
|
||||
return
|
||||
self.ReloadDocument()
|
||||
|
||||
def _DocumentStateChanged(self):
|
||||
"""Called whenever the documents state (on disk etc) has been changed
|
||||
by the editor (eg, as the result of a save operation)
|
||||
"""
|
||||
if self.GetPathName():
|
||||
try:
|
||||
self.fileStat = os.stat(self.GetPathName())
|
||||
except OSError:
|
||||
self.fileStat = None
|
||||
else:
|
||||
self.fileStat = None
|
||||
self.watcherThread._DocumentStateChanged()
|
||||
self._UpdateUIForState()
|
||||
self._ApplyOptionalToViews("_UpdateUIForState")
|
||||
self._ApplyOptionalToViews("SetReadOnly", self._IsReadOnly())
|
||||
self._ApplyOptionalToViews("SCISetSavePoint")
|
||||
# Allow the debugger to reset us too.
|
||||
import pywin.debugger
|
||||
|
||||
if pywin.debugger.currentDebugger is not None:
|
||||
pywin.debugger.currentDebugger.UpdateDocumentLineStates(self)
|
||||
|
||||
# Read-only document support - make it obvious to the user
|
||||
# that the file is read-only.
|
||||
def _IsReadOnly(self):
|
||||
return self.fileStat is not None and (self.fileStat[0] & 128) == 0
|
||||
|
||||
def _UpdateUIForState(self):
|
||||
"""Change the title to reflect the state of the document -
|
||||
eg ReadOnly, Dirty, etc
|
||||
"""
|
||||
filename = self.GetPathName()
|
||||
if not filename:
|
||||
return # New file - nothing to do
|
||||
try:
|
||||
# This seems necessary so the internal state of the window becomes
|
||||
# "visible". without it, it is still shown, but certain functions
|
||||
# (such as updating the title) don't immediately work?
|
||||
self.GetFirstView().ShowWindow(win32con.SW_SHOW)
|
||||
title = win32ui.GetFileTitle(filename)
|
||||
except win32ui.error:
|
||||
title = filename
|
||||
if self._IsReadOnly():
|
||||
title += " (read-only)"
|
||||
self.SetTitle(title)
|
||||
|
||||
def MakeDocumentWritable(self):
|
||||
pretend_ss = 0 # Set to 1 to test this without source safe :-)
|
||||
if not self.scModuleName and not pretend_ss: # No Source Control support.
|
||||
win32ui.SetStatusText(
|
||||
"Document is read-only, and no source-control system is configured"
|
||||
)
|
||||
win32api.MessageBeep()
|
||||
return 0
|
||||
|
||||
# We have source control support - check if the user wants to use it.
|
||||
msg = "Would you like to check this file out?"
|
||||
defButton = win32con.MB_YESNO
|
||||
if self.IsModified():
|
||||
msg += "\r\n\r\nALL CHANGES IN THE EDITOR WILL BE LOST"
|
||||
defButton = win32con.MB_YESNO
|
||||
if win32ui.MessageBox(msg, None, defButton) != win32con.IDYES:
|
||||
return 0
|
||||
|
||||
if pretend_ss:
|
||||
print("We are only pretending to check it out!")
|
||||
win32api.SetFileAttributes(
|
||||
self.GetPathName(), win32con.FILE_ATTRIBUTE_NORMAL
|
||||
)
|
||||
self.ReloadDocument()
|
||||
return 1
|
||||
|
||||
# Now call on the module to do it.
|
||||
if self.scModule is None:
|
||||
try:
|
||||
self.scModule = __import__(self.scModuleName)
|
||||
for part in self.scModuleName.split(".")[1:]:
|
||||
self.scModule = getattr(self.scModule, part)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
print("Error loading source control module.")
|
||||
return 0
|
||||
|
||||
if self.scModule.CheckoutFile(self.GetPathName()):
|
||||
self.ReloadDocument()
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def CheckMakeDocumentWritable(self):
|
||||
if self._IsReadOnly():
|
||||
return self.MakeDocumentWritable()
|
||||
return 1
|
||||
|
||||
def SaveModified(self):
|
||||
# Called as the document is closed. If we are about
|
||||
# to prompt for a save, bring the document to the foreground.
|
||||
if self.IsModified():
|
||||
frame = self.GetFirstView().GetParentFrame()
|
||||
try:
|
||||
frame.MDIActivate()
|
||||
frame.AutoRestore()
|
||||
except:
|
||||
print("Could not bring document to foreground")
|
||||
return self._obj_.SaveModified()
|
||||
|
||||
|
||||
# NOTE - I DONT use the standard threading module,
|
||||
# as this waits for all threads to terminate at shutdown.
|
||||
# When using the debugger, it is possible shutdown will
|
||||
# occur without Pythonwin getting a complete shutdown,
|
||||
# so we deadlock at the end - threading is waiting for
|
||||
import pywin.mfc.thread
|
||||
import win32event
|
||||
|
||||
|
||||
class FileWatchingThread(pywin.mfc.thread.WinThread):
|
||||
def __init__(self, doc):
|
||||
self.doc = doc
|
||||
self.adminEvent = win32event.CreateEvent(None, 0, 0, None)
|
||||
self.stopEvent = win32event.CreateEvent(None, 0, 0, None)
|
||||
self.watchEvent = None
|
||||
pywin.mfc.thread.WinThread.__init__(self)
|
||||
|
||||
def _DocumentStateChanged(self):
|
||||
win32event.SetEvent(self.adminEvent)
|
||||
|
||||
def RefreshEvent(self):
|
||||
self.hwnd = self.doc.GetFirstView().GetSafeHwnd()
|
||||
if self.watchEvent is not None:
|
||||
win32api.FindCloseChangeNotification(self.watchEvent)
|
||||
self.watchEvent = None
|
||||
path = self.doc.GetPathName()
|
||||
if path:
|
||||
path = os.path.dirname(path)
|
||||
if path:
|
||||
filter = (
|
||||
win32con.FILE_NOTIFY_CHANGE_FILE_NAME
|
||||
| win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||
| win32con.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||
)
|
||||
try:
|
||||
self.watchEvent = win32api.FindFirstChangeNotification(path, 0, filter)
|
||||
except win32api.error as exc:
|
||||
print("Can not watch file", path, "for changes -", exc.strerror)
|
||||
|
||||
def SignalStop(self):
|
||||
win32event.SetEvent(self.stopEvent)
|
||||
|
||||
def Run(self):
|
||||
while 1:
|
||||
handles = [self.stopEvent, self.adminEvent]
|
||||
if self.watchEvent is not None:
|
||||
handles.append(self.watchEvent)
|
||||
rc = win32event.WaitForMultipleObjects(handles, 0, win32event.INFINITE)
|
||||
if rc == win32event.WAIT_OBJECT_0:
|
||||
break
|
||||
elif rc == win32event.WAIT_OBJECT_0 + 1:
|
||||
self.RefreshEvent()
|
||||
else:
|
||||
win32api.PostMessage(self.hwnd, MSG_CHECK_EXTERNAL_FILE, 0, 0)
|
||||
try:
|
||||
# If the directory has been removed underneath us, we get this error.
|
||||
win32api.FindNextChangeNotification(self.watchEvent)
|
||||
except win32api.error as exc:
|
||||
print(
|
||||
"Can not watch file",
|
||||
self.doc.GetPathName(),
|
||||
"for changes -",
|
||||
exc.strerror,
|
||||
)
|
||||
break
|
||||
|
||||
# close a circular reference
|
||||
self.doc = None
|
||||
if self.watchEvent:
|
||||
win32api.FindCloseChangeNotification(self.watchEvent)
|
||||
@@ -0,0 +1,511 @@
|
||||
#####################################################################
|
||||
#
|
||||
# editor.py
|
||||
#
|
||||
# A general purpose text editor, built on top of the win32ui edit
|
||||
# type, which is built on an MFC CEditView
|
||||
#
|
||||
#
|
||||
# We now support reloading of externally modified documented
|
||||
# (eg, presumably by some other process, such as source control or
|
||||
# another editor.
|
||||
# We also support auto-loading of externally modified files.
|
||||
# - if the current document has not been modified in this
|
||||
# editor, but has been modified on disk, then the file
|
||||
# can be automatically reloaded.
|
||||
#
|
||||
# Note that it will _always_ prompt you if the file in the editor has been modified.
|
||||
|
||||
|
||||
import re
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework.editor import (
|
||||
GetEditorFontOption,
|
||||
GetEditorOption,
|
||||
defaultCharacterFormat,
|
||||
)
|
||||
from pywin.mfc import afxres
|
||||
from pywin.mfc.docview import RichEditView as ParentEditorView
|
||||
|
||||
from .document import EditorDocumentBase as ParentEditorDocument
|
||||
|
||||
# from pywin.mfc.docview import EditView as ParentEditorView
|
||||
# from pywin.mfc.docview import Document as ParentEditorDocument
|
||||
|
||||
patImport = re.compile(r"import (?P<name>.*)")
|
||||
patIndent = re.compile(r"^([ \t]*[~ \t])")
|
||||
|
||||
ID_LOCATE_FILE = 0xE200
|
||||
ID_GOTO_LINE = 0xE2001
|
||||
# WARNING: Duplicated in document.py and coloreditor.py
|
||||
MSG_CHECK_EXTERNAL_FILE = win32con.WM_USER + 1999
|
||||
|
||||
# Key Codes that modify the bufffer when Ctrl or Alt are NOT pressed.
|
||||
MODIFYING_VK_KEYS = [
|
||||
win32con.VK_BACK,
|
||||
win32con.VK_TAB,
|
||||
win32con.VK_RETURN,
|
||||
win32con.VK_SPACE,
|
||||
win32con.VK_DELETE,
|
||||
]
|
||||
for k in range(48, 91):
|
||||
MODIFYING_VK_KEYS.append(k)
|
||||
|
||||
# Key Codes that modify the bufffer when Ctrl is pressed.
|
||||
MODIFYING_VK_KEYS_CTRL = [
|
||||
win32con.VK_BACK,
|
||||
win32con.VK_RETURN,
|
||||
win32con.VK_SPACE,
|
||||
win32con.VK_DELETE,
|
||||
]
|
||||
|
||||
# Key Codes that modify the bufffer when Alt is pressed.
|
||||
MODIFYING_VK_KEYS_ALT = [
|
||||
win32con.VK_BACK,
|
||||
win32con.VK_RETURN,
|
||||
win32con.VK_SPACE,
|
||||
win32con.VK_DELETE,
|
||||
]
|
||||
|
||||
|
||||
# The editor itself starts here.
|
||||
# Using the MFC Document/View model, we have an EditorDocument, which is responsible for
|
||||
# managing the contents of the file, and a view which is responsible for rendering it.
|
||||
#
|
||||
# Due to a limitation in the Windows edit controls, we are limited to one view
|
||||
# per document, although nothing in this code assumes this (I hope!)
|
||||
|
||||
isRichText = 1 # We are using the Rich Text control. This has not been tested with value "0" for quite some time!
|
||||
|
||||
|
||||
class EditorDocument(ParentEditorDocument):
|
||||
#
|
||||
# File loading and saving operations
|
||||
#
|
||||
def OnOpenDocument(self, filename):
|
||||
#
|
||||
# handle Unix and PC text file format.
|
||||
#
|
||||
|
||||
# Get the "long name" of the file name, as it may have been translated
|
||||
# to short names by the shell.
|
||||
self.SetPathName(filename) # Must set this early!
|
||||
# Now do the work!
|
||||
self.BeginWaitCursor()
|
||||
win32ui.SetStatusText("Loading file...", 1)
|
||||
try:
|
||||
f = open(filename, "rb")
|
||||
except OSError:
|
||||
win32ui.MessageBox(
|
||||
filename
|
||||
+ "\nCan not find this file\nPlease verify that the correct path and file name are given"
|
||||
)
|
||||
self.EndWaitCursor()
|
||||
return 0
|
||||
raw = f.read()
|
||||
f.close()
|
||||
contents = self.TranslateLoadedData(raw)
|
||||
rc = 0
|
||||
try:
|
||||
self.GetFirstView().SetWindowText(contents)
|
||||
rc = 1
|
||||
except TypeError: # Null byte in file.
|
||||
win32ui.MessageBox("This file contains NULL bytes, and can not be edited")
|
||||
rc = 0
|
||||
|
||||
self.EndWaitCursor()
|
||||
self.SetModifiedFlag(0) # No longer dirty
|
||||
self._DocumentStateChanged()
|
||||
return rc
|
||||
|
||||
def TranslateLoadedData(self, data):
|
||||
"""Given raw data read from a file, massage it suitable for the edit window"""
|
||||
# if a CR in the first 250 chars, then perform the expensive translate
|
||||
if data[:250].find("\r") == -1:
|
||||
win32ui.SetStatusText(
|
||||
"Translating from Unix file format - please wait...", 1
|
||||
)
|
||||
return re.sub(r"\r*\n", "\r\n", data)
|
||||
else:
|
||||
return data
|
||||
|
||||
def SaveFile(self, fileName, encoding=None):
|
||||
if isRichText:
|
||||
view = self.GetFirstView()
|
||||
view.SaveTextFile(fileName, encoding=encoding)
|
||||
else: # Old style edit view window.
|
||||
self.GetFirstView().SaveFile(fileName)
|
||||
try:
|
||||
# Make sure line cache has updated info about me!
|
||||
import linecache
|
||||
|
||||
linecache.checkcache()
|
||||
except:
|
||||
pass
|
||||
|
||||
#
|
||||
# Color state stuff
|
||||
#
|
||||
def SetAllLineColors(self, color=None):
|
||||
for view in self.GetAllViews():
|
||||
view.SetAllLineColors(color)
|
||||
|
||||
def SetLineColor(self, lineNo, color):
|
||||
"Color a line of all views"
|
||||
for view in self.GetAllViews():
|
||||
view.SetLineColor(lineNo, color)
|
||||
|
||||
|
||||
# def StreamTextOut(self, data): ### This seems unreliable???
|
||||
# self.saveFileHandle.write(data)
|
||||
# return 1 # keep em coming!
|
||||
|
||||
|
||||
class EditorView(ParentEditorView):
|
||||
def __init__(self, doc):
|
||||
ParentEditorView.__init__(self, doc)
|
||||
if isRichText:
|
||||
self.SetWordWrap(win32ui.CRichEditView_WrapNone)
|
||||
|
||||
self.addToMRU = 1
|
||||
self.HookHandlers()
|
||||
self.bCheckingFile = 0
|
||||
|
||||
self.defCharFormat = GetEditorFontOption("Default Font", defaultCharacterFormat)
|
||||
|
||||
# Smart tabs override everything else if context can be worked out.
|
||||
self.bSmartTabs = GetEditorOption("Smart Tabs", 1)
|
||||
|
||||
self.tabSize = GetEditorOption("Tab Size", 8)
|
||||
self.indentSize = GetEditorOption("Indent Size", 8)
|
||||
# If next indent is at a tab position, and useTabs is set, a tab will be inserted.
|
||||
self.bUseTabs = GetEditorOption("Use Tabs", 1)
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
rc = self._obj_.OnInitialUpdate()
|
||||
self.SetDefaultCharFormat(self.defCharFormat)
|
||||
return rc
|
||||
|
||||
def CutCurLine(self):
|
||||
curLine = self._obj_.LineFromChar()
|
||||
nextLine = curLine + 1
|
||||
start = self._obj_.LineIndex(curLine)
|
||||
end = self._obj_.LineIndex(nextLine)
|
||||
if end == 0: # must be last line.
|
||||
end = start + self.end.GetLineLength(curLine)
|
||||
self._obj_.SetSel(start, end)
|
||||
self._obj_.Cut()
|
||||
|
||||
def _PrepareUserStateChange(self):
|
||||
"Return selection, lineindex, etc info, so it can be restored"
|
||||
self.SetRedraw(0)
|
||||
return self.GetModify(), self.GetSel(), self.GetFirstVisibleLine()
|
||||
|
||||
def _EndUserStateChange(self, info):
|
||||
scrollOff = info[2] - self.GetFirstVisibleLine()
|
||||
if scrollOff:
|
||||
self.LineScroll(scrollOff)
|
||||
self.SetSel(info[1])
|
||||
self.SetModify(info[0])
|
||||
self.SetRedraw(1)
|
||||
self.InvalidateRect()
|
||||
self.UpdateWindow()
|
||||
|
||||
def _UpdateUIForState(self):
|
||||
self.SetReadOnly(self.GetDocument()._IsReadOnly())
|
||||
|
||||
def SetAllLineColors(self, color=None):
|
||||
if isRichText:
|
||||
info = self._PrepareUserStateChange()
|
||||
try:
|
||||
if color is None:
|
||||
color = self.defCharFormat[4]
|
||||
self.SetSel(0, -1)
|
||||
self.SetSelectionCharFormat((win32con.CFM_COLOR, 0, 0, 0, color))
|
||||
finally:
|
||||
self._EndUserStateChange(info)
|
||||
|
||||
def SetLineColor(self, lineNo, color):
|
||||
"lineNo is the 1 based line number to set. If color is None, default color is used."
|
||||
if isRichText:
|
||||
info = self._PrepareUserStateChange()
|
||||
try:
|
||||
if color is None:
|
||||
color = self.defCharFormat[4]
|
||||
lineNo -= 1
|
||||
startIndex = self.LineIndex(lineNo)
|
||||
if startIndex != -1:
|
||||
self.SetSel(startIndex, self.LineIndex(lineNo + 1))
|
||||
self.SetSelectionCharFormat((win32con.CFM_COLOR, 0, 0, 0, color))
|
||||
finally:
|
||||
self._EndUserStateChange(info)
|
||||
|
||||
def Indent(self):
|
||||
"""Insert an indent to move the cursor to the next tab position.
|
||||
|
||||
Honors the tab size and 'use tabs' settings. Assumes the cursor is already at the
|
||||
position to be indented, and the selection is a single character (ie, not a block)
|
||||
"""
|
||||
start, end = self._obj_.GetSel()
|
||||
startLine = self._obj_.LineFromChar(start)
|
||||
line = self._obj_.GetLine(startLine)
|
||||
realCol = start - self._obj_.LineIndex(startLine)
|
||||
# Calulate the next tab stop.
|
||||
# Expand existing tabs.
|
||||
curCol = 0
|
||||
for ch in line[:realCol]:
|
||||
if ch == "\t":
|
||||
curCol = ((curCol / self.tabSize) + 1) * self.tabSize
|
||||
else:
|
||||
curCol += 1
|
||||
nextColumn = ((curCol / self.indentSize) + 1) * self.indentSize
|
||||
# print("curCol is", curCol, "nextColumn is", nextColumn)
|
||||
ins = None
|
||||
if self.bSmartTabs:
|
||||
# Look for some context.
|
||||
if realCol == 0: # Start of the line - see if the line above can tell us
|
||||
lookLine = startLine - 1
|
||||
while lookLine >= 0:
|
||||
check = self._obj_.GetLine(lookLine)[0:1]
|
||||
if check in ("\t", " "):
|
||||
ins = check
|
||||
break
|
||||
lookLine -= 1
|
||||
else: # See if the previous char can tell us
|
||||
check = line[realCol - 1]
|
||||
if check in ("\t", " "):
|
||||
ins = check
|
||||
|
||||
# Either smart tabs off, or not smart enough!
|
||||
# Use the "old style" settings.
|
||||
if ins is None:
|
||||
if self.bUseTabs and nextColumn % self.tabSize == 0:
|
||||
ins = "\t"
|
||||
else:
|
||||
ins = " "
|
||||
|
||||
if ins == " ":
|
||||
# Calc the number of spaces to take us to the next stop
|
||||
ins *= nextColumn - curCol
|
||||
|
||||
self._obj_.ReplaceSel(ins)
|
||||
|
||||
def BlockDent(self, isIndent, startLine, endLine):
|
||||
"Indent/Undent all lines specified"
|
||||
if not self.GetDocument().CheckMakeDocumentWritable():
|
||||
return 0
|
||||
tabSize = self.tabSize # hard-code for now!
|
||||
info = self._PrepareUserStateChange()
|
||||
try:
|
||||
for lineNo in range(startLine, endLine):
|
||||
pos = self._obj_.LineIndex(lineNo)
|
||||
self._obj_.SetSel(pos, pos)
|
||||
if isIndent:
|
||||
self.Indent()
|
||||
else:
|
||||
line = self._obj_.GetLine(lineNo)
|
||||
try:
|
||||
noToDel = 0
|
||||
if line[0] == "\t":
|
||||
noToDel = 1
|
||||
elif line[0] == " ":
|
||||
for noToDel in range(0, tabSize):
|
||||
if line[noToDel] != " ":
|
||||
break
|
||||
else:
|
||||
noToDel = tabSize
|
||||
if noToDel:
|
||||
self._obj_.SetSel(pos, pos + noToDel)
|
||||
self._obj_.Clear()
|
||||
except IndexError:
|
||||
pass
|
||||
finally:
|
||||
self._EndUserStateChange(info)
|
||||
self.GetDocument().SetModifiedFlag(1) # Now dirty
|
||||
self._obj_.SetSel(self.LineIndex(startLine), self.LineIndex(endLine))
|
||||
|
||||
def GotoLine(self, lineNo=None):
|
||||
try:
|
||||
if lineNo is None:
|
||||
lineNo = int(input("Enter Line Number"))
|
||||
except (ValueError, KeyboardInterrupt):
|
||||
return 0
|
||||
self.GetLineCount() # Seems to be needed when file first opened???
|
||||
charNo = self.LineIndex(lineNo - 1)
|
||||
self.SetSel(charNo)
|
||||
|
||||
def HookHandlers(self): # children can override, but should still call me!
|
||||
# self.HookAllKeyStrokes(self.OnKey)
|
||||
self.HookMessage(self.OnCheckExternalDocumentUpdated, MSG_CHECK_EXTERNAL_FILE)
|
||||
self.HookMessage(self.OnRClick, win32con.WM_RBUTTONDOWN)
|
||||
self.HookMessage(self.OnSetFocus, win32con.WM_SETFOCUS)
|
||||
self.HookMessage(self.OnKeyDown, win32con.WM_KEYDOWN)
|
||||
self.HookKeyStroke(self.OnKeyCtrlY, 25) # ^Y
|
||||
self.HookKeyStroke(self.OnKeyCtrlG, 7) # ^G
|
||||
self.HookKeyStroke(self.OnKeyTab, 9) # TAB
|
||||
self.HookKeyStroke(self.OnKeyEnter, 13) # Enter
|
||||
self.HookCommand(self.OnCmdLocateFile, ID_LOCATE_FILE)
|
||||
self.HookCommand(self.OnCmdGotoLine, ID_GOTO_LINE)
|
||||
self.HookCommand(self.OnEditPaste, afxres.ID_EDIT_PASTE)
|
||||
self.HookCommand(self.OnEditCut, afxres.ID_EDIT_CUT)
|
||||
|
||||
# Hook Handlers
|
||||
def OnSetFocus(self, msg):
|
||||
# Even though we use file change notifications, we should be very sure about it here.
|
||||
self.OnCheckExternalDocumentUpdated(msg)
|
||||
|
||||
def OnRClick(self, params):
|
||||
menu = win32ui.CreatePopupMenu()
|
||||
|
||||
# look for a module name
|
||||
line = self._obj_.GetLine().strip()
|
||||
flags = win32con.MF_STRING | win32con.MF_ENABLED
|
||||
matchResult = patImport.match(line)
|
||||
if matchResult and matchResult[0] == line:
|
||||
menu.AppendMenu(
|
||||
flags, ID_LOCATE_FILE, "&Locate %s.py" % matchResult.group("name")
|
||||
)
|
||||
menu.AppendMenu(win32con.MF_SEPARATOR)
|
||||
menu.AppendMenu(flags, win32ui.ID_EDIT_UNDO, "&Undo")
|
||||
menu.AppendMenu(win32con.MF_SEPARATOR)
|
||||
menu.AppendMenu(flags, win32ui.ID_EDIT_CUT, "Cu&t")
|
||||
menu.AppendMenu(flags, win32ui.ID_EDIT_COPY, "&Copy")
|
||||
menu.AppendMenu(flags, win32ui.ID_EDIT_PASTE, "&Paste")
|
||||
menu.AppendMenu(flags, win32con.MF_SEPARATOR)
|
||||
menu.AppendMenu(flags, win32ui.ID_EDIT_SELECT_ALL, "&Select all")
|
||||
menu.AppendMenu(flags, win32con.MF_SEPARATOR)
|
||||
menu.AppendMenu(flags, ID_GOTO_LINE, "&Goto line...")
|
||||
menu.TrackPopupMenu(params[5])
|
||||
return 0
|
||||
|
||||
def OnCmdGotoLine(self, cmd, code):
|
||||
self.GotoLine()
|
||||
return 0
|
||||
|
||||
def OnCmdLocateFile(self, cmd, code):
|
||||
modName = patImport.group("name")
|
||||
if not modName:
|
||||
return 0
|
||||
import pywin.framework.scriptutils
|
||||
|
||||
fileName = pywin.framework.scriptutils.LocatePythonFile(modName)
|
||||
if fileName is None:
|
||||
win32ui.SetStatusText("Can't locate module %s" % modName)
|
||||
else:
|
||||
win32ui.GetApp().OpenDocumentFile(fileName)
|
||||
return 0
|
||||
|
||||
# Key handlers
|
||||
def OnKeyEnter(self, key):
|
||||
if not self.GetDocument().CheckMakeDocumentWritable():
|
||||
return 0
|
||||
curLine = self._obj_.GetLine()
|
||||
self._obj_.ReplaceSel("\r\n") # insert the newline
|
||||
# If the current line indicates the next should be indented,
|
||||
# then copy the current indentation to this line.
|
||||
res = patIndent.match(curLine, 0)
|
||||
if res > 0 and curLine.strip():
|
||||
curIndent = patIndent.group(1)
|
||||
self._obj_.ReplaceSel(curIndent)
|
||||
return 0 # don't pass on
|
||||
|
||||
def OnKeyCtrlY(self, key):
|
||||
if not self.GetDocument().CheckMakeDocumentWritable():
|
||||
return 0
|
||||
self.CutCurLine()
|
||||
return 0 # don't let him have it!
|
||||
|
||||
def OnKeyCtrlG(self, key):
|
||||
self.GotoLine()
|
||||
return 0 # don't let him have it!
|
||||
|
||||
def OnKeyTab(self, key):
|
||||
if not self.GetDocument().CheckMakeDocumentWritable():
|
||||
return 0
|
||||
start, end = self._obj_.GetSel()
|
||||
if start == end: # normal TAB key
|
||||
self.Indent()
|
||||
return 0 # we handled this.
|
||||
|
||||
# Otherwise it is a block indent/dedent.
|
||||
if start > end:
|
||||
start, end = end, start # swap them.
|
||||
startLine = self._obj_.LineFromChar(start)
|
||||
endLine = self._obj_.LineFromChar(end)
|
||||
|
||||
self.BlockDent(win32api.GetKeyState(win32con.VK_SHIFT) >= 0, startLine, endLine)
|
||||
return 0
|
||||
|
||||
def OnEditPaste(self, id, code):
|
||||
# Return 1 if we can make the file editable.(or it already is!)
|
||||
return self.GetDocument().CheckMakeDocumentWritable()
|
||||
|
||||
def OnEditCut(self, id, code):
|
||||
# Return 1 if we can make the file editable.(or it already is!)
|
||||
return self.GetDocument().CheckMakeDocumentWritable()
|
||||
|
||||
def OnKeyDown(self, msg):
|
||||
key = msg[2]
|
||||
if win32api.GetKeyState(win32con.VK_CONTROL) & 0x8000:
|
||||
modList = MODIFYING_VK_KEYS_CTRL
|
||||
elif win32api.GetKeyState(win32con.VK_MENU) & 0x8000:
|
||||
modList = MODIFYING_VK_KEYS_ALT
|
||||
else:
|
||||
modList = MODIFYING_VK_KEYS
|
||||
|
||||
if key in modList:
|
||||
# Return 1 if we can make the file editable.(or it already is!)
|
||||
return self.GetDocument().CheckMakeDocumentWritable()
|
||||
return 1 # Pass it on OK
|
||||
|
||||
# def OnKey(self, key):
|
||||
# return self.GetDocument().CheckMakeDocumentWritable()
|
||||
|
||||
def OnCheckExternalDocumentUpdated(self, msg):
|
||||
if self._obj_ is None or self.bCheckingFile:
|
||||
return
|
||||
self.bCheckingFile = 1
|
||||
self.GetDocument().CheckExternalDocumentUpdated()
|
||||
self.bCheckingFile = 0
|
||||
|
||||
|
||||
from .template import EditorTemplateBase
|
||||
|
||||
|
||||
class EditorTemplate(EditorTemplateBase):
|
||||
def __init__(
|
||||
self, res=win32ui.IDR_TEXTTYPE, makeDoc=None, makeFrame=None, makeView=None
|
||||
):
|
||||
if makeDoc is None:
|
||||
makeDoc = EditorDocument
|
||||
if makeView is None:
|
||||
makeView = EditorView
|
||||
EditorTemplateBase.__init__(self, res, makeDoc, makeFrame, makeView)
|
||||
|
||||
def _CreateDocTemplate(self, resourceId):
|
||||
return win32ui.CreateRichEditDocTemplate(resourceId)
|
||||
|
||||
def CreateWin32uiDocument(self):
|
||||
return self.DoCreateRichEditDoc()
|
||||
|
||||
|
||||
def Create(fileName=None, title=None, template=None):
|
||||
return editorTemplate.OpenDocumentFile(fileName)
|
||||
|
||||
|
||||
from pywin.framework.editor import GetDefaultEditorModuleName
|
||||
|
||||
prefModule = GetDefaultEditorModuleName()
|
||||
# Initialize only if this is the "default" editor.
|
||||
if __name__ == prefModule:
|
||||
# For debugging purposes, when this module may be reloaded many times.
|
||||
try:
|
||||
win32ui.GetApp().RemoveDocTemplate(editorTemplate) # type: ignore[has-type, used-before-def]
|
||||
except (NameError, win32ui.error):
|
||||
pass
|
||||
|
||||
editorTemplate = EditorTemplate()
|
||||
win32ui.GetApp().AddDocTemplate(editorTemplate)
|
||||
@@ -0,0 +1,74 @@
|
||||
# frame.py - The MDI frame window for an editor.
|
||||
import pywin.framework.window
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
from . import ModuleBrowser
|
||||
|
||||
|
||||
class EditorFrame(pywin.framework.window.MDIChildWnd):
|
||||
def OnCreateClient(self, cp, context):
|
||||
# Create the default view as specified by the template (ie, the editor view)
|
||||
view = context.template.MakeView(context.doc)
|
||||
# Create the browser view.
|
||||
browserView = ModuleBrowser.BrowserView(context.doc)
|
||||
view2 = context.template.MakeView(context.doc)
|
||||
|
||||
splitter = win32ui.CreateSplitter()
|
||||
style = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
splitter.CreateStatic(self, 1, 2, style, win32ui.AFX_IDW_PANE_FIRST)
|
||||
sub_splitter = self.sub_splitter = win32ui.CreateSplitter()
|
||||
sub_splitter.CreateStatic(splitter, 2, 1, style, win32ui.AFX_IDW_PANE_FIRST + 1)
|
||||
|
||||
# Note we must add the default view first, so that doc.GetFirstView() returns the editor view.
|
||||
sub_splitter.CreateView(view, 1, 0, (0, 0))
|
||||
splitter.CreateView(browserView, 0, 0, (0, 0))
|
||||
sub_splitter.CreateView(view2, 0, 0, (0, 0))
|
||||
|
||||
# print("First view is", context.doc.GetFirstView())
|
||||
# print("Views are", view, view2, browserView)
|
||||
# print("Parents are", view.GetParent(), view2.GetParent(), browserView.GetParent())
|
||||
# print("Splitter is", splitter)
|
||||
# print("sub splitter is", sub_splitter)
|
||||
# Old
|
||||
# splitter.CreateStatic (self, 1, 2)
|
||||
# splitter.CreateView(view, 0, 1, (0,0)) # size ignored.
|
||||
# splitter.CreateView (browserView, 0, 0, (0, 0))
|
||||
|
||||
# Restrict the size of the browser splitter (and we can avoid filling
|
||||
# it until it is shown)
|
||||
splitter.SetColumnInfo(0, 10, 20)
|
||||
# And the active view is our default view (so it gets initial focus)
|
||||
self.SetActiveView(view)
|
||||
|
||||
def GetEditorView(self):
|
||||
# In a multi-view (eg, splitter) environment, get
|
||||
# an editor (ie, scintilla) view
|
||||
# Look for the splitter opened the most!
|
||||
if self.sub_splitter is None:
|
||||
return self.GetDlgItem(win32ui.AFX_IDW_PANE_FIRST)
|
||||
v1 = self.sub_splitter.GetPane(0, 0)
|
||||
v2 = self.sub_splitter.GetPane(1, 0)
|
||||
r1 = v1.GetWindowRect()
|
||||
r2 = v2.GetWindowRect()
|
||||
if r1[3] - r1[1] > r2[3] - r2[1]:
|
||||
return v1
|
||||
return v2
|
||||
|
||||
def GetBrowserView(self):
|
||||
# XXX - should fix this :-)
|
||||
return self.GetActiveDocument().GetAllViews()[1]
|
||||
|
||||
def OnClose(self):
|
||||
doc = self.GetActiveDocument()
|
||||
if not doc.SaveModified():
|
||||
# Cancel button selected from Save dialog, do not actually close
|
||||
# print("close cancelled")
|
||||
return 0
|
||||
## So the 'Save' dialog doesn't come up twice
|
||||
doc._obj_.SetModifiedFlag(False)
|
||||
|
||||
# Must force the module browser to close itself here (OnDestroy for the view itself is too late!)
|
||||
self.sub_splitter = None # ensure no circles!
|
||||
self.GetBrowserView().DestroyBrowser()
|
||||
return self._obj_.OnClose()
|
||||
@@ -0,0 +1,59 @@
|
||||
import os
|
||||
|
||||
import win32api
|
||||
import win32ui
|
||||
from pywin.mfc import docview
|
||||
|
||||
from . import frame
|
||||
|
||||
ParentEditorTemplate = docview.DocTemplate
|
||||
|
||||
|
||||
class EditorTemplateBase(ParentEditorTemplate):
|
||||
def __init__(
|
||||
self, res=win32ui.IDR_TEXTTYPE, makeDoc=None, makeFrame=None, makeView=None
|
||||
):
|
||||
if makeFrame is None:
|
||||
makeFrame = frame.EditorFrame
|
||||
ParentEditorTemplate.__init__(self, res, makeDoc, makeFrame, makeView)
|
||||
|
||||
def _CreateDocTemplate(self, resourceId):
|
||||
raise NotImplementedError("You must override this")
|
||||
|
||||
def CreateWin32uiDocument(self):
|
||||
raise NotImplementedError("You must override this")
|
||||
|
||||
def GetFileExtensions(self):
|
||||
return ".txt", ".py"
|
||||
|
||||
def MatchDocType(self, fileName, fileType):
|
||||
doc = self.FindOpenDocument(fileName)
|
||||
if doc:
|
||||
return doc
|
||||
ext = os.path.splitext(fileName)[1].lower()
|
||||
if ext in self.GetFileExtensions():
|
||||
return win32ui.CDocTemplate_Confidence_yesAttemptNative
|
||||
return win32ui.CDocTemplate_Confidence_maybeAttemptForeign
|
||||
|
||||
def InitialUpdateFrame(self, frame, doc, makeVisible=1):
|
||||
self._obj_.InitialUpdateFrame(frame, doc, makeVisible) # call default handler.
|
||||
doc._UpdateUIForState()
|
||||
|
||||
def GetPythonPropertyPages(self):
|
||||
"""Returns a list of property pages"""
|
||||
from . import configui
|
||||
|
||||
return [configui.EditorPropertyPage(), configui.EditorWhitespacePropertyPage()]
|
||||
|
||||
def OpenDocumentFile(self, filename, bMakeVisible=1):
|
||||
if filename is not None:
|
||||
try:
|
||||
path = os.path.split(filename)[0]
|
||||
# print("The editor is translating", "filename", "to")
|
||||
filename = win32api.FindFiles(filename)[0][8]
|
||||
filename = os.path.join(path, filename)
|
||||
# print("filename")
|
||||
except (win32api.error, IndexError) as details:
|
||||
# print("Couldn't get the full filename!", details)
|
||||
pass
|
||||
return self._obj_.OpenDocumentFile(filename, bMakeVisible)
|
||||
@@ -0,0 +1,104 @@
|
||||
# vss.py -- Source Control using Microsoft VSS.
|
||||
|
||||
# Provides routines for checking files out of VSS.
|
||||
#
|
||||
# Uses an INI file very similar to how VB integrates with VSS - even
|
||||
# as far as using the same name.
|
||||
|
||||
# The file must be named "Mssccprj.scc", and be in the format of
|
||||
# an INI file. This file may be in a parent directory, in which
|
||||
# case the project name will be built from what is specified in the
|
||||
# ini file, plus the path from the INI file to the file itself.
|
||||
#
|
||||
# The INI file should have a [Python] section, and a
|
||||
# Project=Project Name
|
||||
# and optionally
|
||||
# Database=??
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import win32api
|
||||
import win32ui
|
||||
|
||||
g_iniName = "Mssccprj.scc" # Use the same INI name as VB!
|
||||
|
||||
g_sourceSafe = None
|
||||
|
||||
|
||||
def FindVssProjectInfo(fullfname):
|
||||
"""Looks up the file system for an INI file describing the project.
|
||||
|
||||
Looking up the tree is for ni style packages.
|
||||
|
||||
Returns (projectName, pathToFileName) where pathToFileName contains
|
||||
the path from the ini file to the actual file.
|
||||
"""
|
||||
path, fnameonly = os.path.split(fullfname)
|
||||
origPath = path
|
||||
project = ""
|
||||
retPaths = [fnameonly]
|
||||
while not project:
|
||||
iniName = os.path.join(path, g_iniName)
|
||||
database = win32api.GetProfileVal("Python", "Database", "", iniName)
|
||||
project = win32api.GetProfileVal("Python", "Project", "", iniName)
|
||||
if project:
|
||||
break
|
||||
# No valid INI file in this directory - look up a level.
|
||||
path, addpath = os.path.split(path)
|
||||
if not addpath: # Root?
|
||||
break
|
||||
retPaths.insert(0, addpath)
|
||||
if not project:
|
||||
win32ui.MessageBox(
|
||||
"%s\r\n\r\nThis directory is not configured for Python/VSS" % origPath
|
||||
)
|
||||
return
|
||||
return project, "/".join(retPaths), database
|
||||
|
||||
|
||||
def CheckoutFile(fileName):
|
||||
global g_sourceSafe
|
||||
import pythoncom
|
||||
|
||||
ok = 0
|
||||
# Assumes the fileName has a complete path,
|
||||
# and that the INI file can be found in that path
|
||||
# (or a parent path if a ni style package)
|
||||
try:
|
||||
import win32com.client
|
||||
import win32com.client.gencache
|
||||
|
||||
mod = win32com.client.gencache.EnsureModule(
|
||||
"{783CD4E0-9D54-11CF-B8EE-00608CC9A71F}", 0, 5, 0
|
||||
)
|
||||
if mod is None:
|
||||
win32ui.MessageBox(
|
||||
"VSS does not appear to be installed. The TypeInfo can not be created"
|
||||
)
|
||||
return ok
|
||||
|
||||
rc = FindVssProjectInfo(fileName)
|
||||
if rc is None:
|
||||
return
|
||||
project, vssFname, database = rc
|
||||
if g_sourceSafe is None:
|
||||
g_sourceSafe = win32com.client.Dispatch("SourceSafe")
|
||||
# SS seems a bit weird. It defaults the arguments as empty strings, but
|
||||
# then complains when they are used - so we pass "Missing"
|
||||
if not database:
|
||||
database = pythoncom.Missing
|
||||
g_sourceSafe.Open(database, pythoncom.Missing, pythoncom.Missing)
|
||||
item = g_sourceSafe.VSSItem(f"$/{project}/{vssFname}")
|
||||
item.Checkout(None, fileName)
|
||||
ok = 1
|
||||
except pythoncom.com_error as exc:
|
||||
win32ui.MessageBox(exc.strerror, "Error checking out file")
|
||||
except:
|
||||
typ, val, tb = sys.exc_info()
|
||||
traceback.print_exc()
|
||||
win32ui.MessageBox(f"{typ} - {val}", "Error checking out file")
|
||||
tb = None # Cleanup a cycle
|
||||
return ok
|
||||
@@ -0,0 +1,173 @@
|
||||
# help.py - help utilities for PythonWin.
|
||||
import os
|
||||
|
||||
import regutil
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
htmlhelp_handle = None
|
||||
|
||||
html_help_command_translators = {
|
||||
win32con.HELP_CONTENTS: 1, # HH_DISPLAY_TOC
|
||||
win32con.HELP_CONTEXT: 15, # HH_HELP_CONTEXT
|
||||
win32con.HELP_FINDER: 1, # HH_DISPLAY_TOC
|
||||
}
|
||||
|
||||
|
||||
def FinalizeHelp():
|
||||
global htmlhelp_handle
|
||||
if htmlhelp_handle is not None:
|
||||
import win32help
|
||||
|
||||
try:
|
||||
# frame = win32ui.GetMainFrame().GetSafeHwnd()
|
||||
frame = 0
|
||||
win32help.HtmlHelp(frame, None, win32help.HH_UNINITIALIZE, htmlhelp_handle)
|
||||
except win32help.error:
|
||||
print("Failed to finalize htmlhelp!")
|
||||
htmlhelp_handle = None
|
||||
|
||||
|
||||
def OpenHelpFile(fileName, helpCmd=None, helpArg=None):
|
||||
"Open a help file, given a full path"
|
||||
# default help arg.
|
||||
win32ui.DoWaitCursor(1)
|
||||
try:
|
||||
if helpCmd is None:
|
||||
helpCmd = win32con.HELP_CONTENTS
|
||||
ext = os.path.splitext(fileName)[1].lower()
|
||||
if ext == ".hlp":
|
||||
win32api.WinHelp(
|
||||
win32ui.GetMainFrame().GetSafeHwnd(), fileName, helpCmd, helpArg
|
||||
)
|
||||
# XXX - using the htmlhelp API wreaks havoc with keyboard shortcuts
|
||||
# so we disable it, forcing ShellExecute, which works fine (but
|
||||
# doesn't close the help file when Pythonwin is closed.
|
||||
# Tom Heller also points out
|
||||
# https://web.archive.org/web/20070519165457/http://www.microsoft.com:80/mind/0499/faq/faq0499.asp ,
|
||||
# which may or may not be related.
|
||||
elif 0 and ext == ".chm":
|
||||
import win32help
|
||||
|
||||
global htmlhelp_handle
|
||||
helpCmd = html_help_command_translators.get(helpCmd, helpCmd)
|
||||
# frame = win32ui.GetMainFrame().GetSafeHwnd()
|
||||
frame = 0 # Don't want it overlapping ours!
|
||||
if htmlhelp_handle is None:
|
||||
htmlhelp_hwnd, htmlhelp_handle = win32help.HtmlHelp(
|
||||
frame, None, win32help.HH_INITIALIZE
|
||||
)
|
||||
win32help.HtmlHelp(frame, fileName, helpCmd, helpArg)
|
||||
else:
|
||||
# Hope that the extension is registered, and we know what to do!
|
||||
win32api.ShellExecute(0, "open", fileName, None, "", win32con.SW_SHOW)
|
||||
return fileName
|
||||
finally:
|
||||
win32ui.DoWaitCursor(-1)
|
||||
|
||||
|
||||
def ListAllHelpFiles():
|
||||
ret = []
|
||||
ret = _ListAllHelpFilesInRoot(win32con.HKEY_LOCAL_MACHINE)
|
||||
# Ensure we don't get dups.
|
||||
for item in _ListAllHelpFilesInRoot(win32con.HKEY_CURRENT_USER):
|
||||
if item not in ret:
|
||||
ret.append(item)
|
||||
return ret
|
||||
|
||||
|
||||
def _ListAllHelpFilesInRoot(root):
|
||||
"""Returns a list of (helpDesc, helpFname) for all registered help files"""
|
||||
|
||||
retList = []
|
||||
try:
|
||||
key = win32api.RegOpenKey(
|
||||
root, regutil.BuildDefaultPythonKey() + "\\Help", 0, win32con.KEY_READ
|
||||
)
|
||||
except win32api.error as exc:
|
||||
import winerror
|
||||
|
||||
if exc.winerror != winerror.ERROR_FILE_NOT_FOUND:
|
||||
raise
|
||||
return retList
|
||||
try:
|
||||
keyNo = 0
|
||||
while 1:
|
||||
try:
|
||||
helpDesc = win32api.RegEnumKey(key, keyNo)
|
||||
helpFile = win32api.RegQueryValue(key, helpDesc)
|
||||
retList.append((helpDesc, helpFile))
|
||||
keyNo += 1
|
||||
except win32api.error as exc:
|
||||
import winerror
|
||||
|
||||
if exc.winerror != winerror.ERROR_NO_MORE_ITEMS:
|
||||
raise
|
||||
break
|
||||
finally:
|
||||
win32api.RegCloseKey(key)
|
||||
return retList
|
||||
|
||||
|
||||
def SelectAndRunHelpFile():
|
||||
from pywin.dialogs import list
|
||||
|
||||
helpFiles = ListAllHelpFiles()
|
||||
if len(helpFiles) == 1:
|
||||
# only 1 help file registered - probably ours - no point asking
|
||||
index = 0
|
||||
else:
|
||||
index = list.SelectFromLists("Select Help file", helpFiles, ["Title"])
|
||||
if index is not None:
|
||||
OpenHelpFile(helpFiles[index][1])
|
||||
|
||||
|
||||
helpIDMap = None
|
||||
|
||||
|
||||
def SetHelpMenuOtherHelp(mainMenu):
|
||||
"""Modifies the main Help Menu to handle all registered help files.
|
||||
mainMenu -- The main menu to modify - usually from docTemplate.GetSharedMenu()
|
||||
"""
|
||||
|
||||
# Load all help files from the registry.
|
||||
global helpIDMap
|
||||
if helpIDMap is None:
|
||||
helpIDMap = {}
|
||||
cmdID = win32ui.ID_HELP_OTHER
|
||||
excludeList = ["Main Python Documentation", "Pythonwin Reference"]
|
||||
firstList = ListAllHelpFiles()
|
||||
# We actually want to not only exclude these entries, but
|
||||
# their help file names (as many entries may share the same name)
|
||||
excludeFnames = []
|
||||
for desc, fname in firstList:
|
||||
if desc in excludeList:
|
||||
excludeFnames.append(fname)
|
||||
|
||||
helpDescs = []
|
||||
for desc, fname in firstList:
|
||||
if fname not in excludeFnames:
|
||||
helpIDMap[cmdID] = (desc, fname)
|
||||
win32ui.GetMainFrame().HookCommand(HandleHelpOtherCommand, cmdID)
|
||||
cmdID += 1
|
||||
|
||||
helpMenu = mainMenu.GetSubMenu(
|
||||
mainMenu.GetMenuItemCount() - 1
|
||||
) # Help menu always last.
|
||||
otherHelpMenuPos = 2 # can't search for ID, as sub-menu has no ID.
|
||||
otherMenu = helpMenu.GetSubMenu(otherHelpMenuPos)
|
||||
while otherMenu.GetMenuItemCount():
|
||||
otherMenu.DeleteMenu(0, win32con.MF_BYPOSITION)
|
||||
|
||||
if helpIDMap:
|
||||
for id, (desc, fname) in helpIDMap.items():
|
||||
otherMenu.AppendMenu(win32con.MF_ENABLED | win32con.MF_STRING, id, desc)
|
||||
else:
|
||||
helpMenu.EnableMenuItem(
|
||||
otherHelpMenuPos, win32con.MF_BYPOSITION | win32con.MF_GRAYED
|
||||
)
|
||||
|
||||
|
||||
def HandleHelpOtherCommand(cmd, code):
|
||||
OpenHelpFile(helpIDMap[cmd][1])
|
||||
@@ -0,0 +1,995 @@
|
||||
##################################################################
|
||||
##
|
||||
## Interactive Shell Window
|
||||
##
|
||||
|
||||
import array
|
||||
import code
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import __main__
|
||||
import pywin.framework.app
|
||||
import pywin.scintilla.control
|
||||
import pywin.scintilla.formatter
|
||||
import pywin.scintilla.IDLEenvironment # nopycln: import # Injects fast_readline into the IDLE auto-indent extension
|
||||
import win32api
|
||||
import win32clipboard
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import afxres
|
||||
|
||||
## sequential after ID_GOTO_LINE defined in editor.py
|
||||
ID_EDIT_COPY_CODE = 0xE2002
|
||||
ID_EDIT_EXEC_CLIPBOARD = 0x2003
|
||||
|
||||
trace = pywin.scintilla.formatter.trace
|
||||
|
||||
import re
|
||||
|
||||
from . import winout
|
||||
|
||||
# from IDLE.
|
||||
_is_block_opener = re.compile(r":\s*(#.*)?$").search
|
||||
_is_block_closer = re.compile(
|
||||
r"""
|
||||
\s*
|
||||
( return
|
||||
| break
|
||||
| continue
|
||||
| raise
|
||||
| pass
|
||||
)
|
||||
\b
|
||||
""",
|
||||
re.VERBOSE,
|
||||
).match
|
||||
|
||||
tracebackHeader = b"Traceback ("
|
||||
|
||||
sectionProfile = "Interactive Window"
|
||||
valueFormatTitle = "FormatTitle"
|
||||
valueFormatInput = "FormatInput"
|
||||
valueFormatOutput = "FormatOutput"
|
||||
valueFormatOutputError = "FormatOutputError"
|
||||
|
||||
# These are defaults only. Values are read from the registry.
|
||||
formatTitle = (-536870897, 0, 220, 0, 16711680, 184, 34, "Arial")
|
||||
formatInput = (-402653169, 0, 200, 0, 0, 0, 49, "Courier New")
|
||||
formatOutput = (-402653169, 0, 200, 0, 8421376, 0, 49, "Courier New")
|
||||
formatOutputError = (-402653169, 0, 200, 0, 255, 0, 49, "Courier New")
|
||||
|
||||
try:
|
||||
sys.ps1
|
||||
except AttributeError:
|
||||
sys.ps1 = ">>> "
|
||||
sys.ps2 = "... "
|
||||
|
||||
|
||||
def LoadPreference(preference, default=""):
|
||||
return win32ui.GetProfileVal(sectionProfile, preference, default)
|
||||
|
||||
|
||||
def SavePreference(prefName, prefValue):
|
||||
win32ui.WriteProfileVal(sectionProfile, prefName, prefValue)
|
||||
|
||||
|
||||
def SaveFontPreferences():
|
||||
win32ui.WriteProfileVal(sectionProfile, valueFormatTitle, str(formatTitle))
|
||||
win32ui.WriteProfileVal(sectionProfile, valueFormatInput, str(formatInput))
|
||||
win32ui.WriteProfileVal(sectionProfile, valueFormatOutput, str(formatOutput))
|
||||
win32ui.WriteProfileVal(
|
||||
sectionProfile, valueFormatOutputError, str(formatOutputError)
|
||||
)
|
||||
|
||||
|
||||
def GetPromptPrefix(line):
|
||||
ps1 = sys.ps1
|
||||
if line[: len(ps1)] == ps1:
|
||||
return ps1
|
||||
ps2 = sys.ps2
|
||||
if line[: len(ps2)] == ps2:
|
||||
return ps2
|
||||
|
||||
|
||||
#############################################################
|
||||
#
|
||||
# Colorizer related code.
|
||||
#
|
||||
#############################################################
|
||||
STYLE_INTERACTIVE_EOL = "Interactive EOL"
|
||||
STYLE_INTERACTIVE_OUTPUT = "Interactive Output"
|
||||
STYLE_INTERACTIVE_PROMPT = "Interactive Prompt"
|
||||
STYLE_INTERACTIVE_BANNER = "Interactive Banner"
|
||||
STYLE_INTERACTIVE_ERROR = "Interactive Error"
|
||||
STYLE_INTERACTIVE_ERROR_FINALLINE = "Interactive Error (final line)"
|
||||
|
||||
INTERACTIVE_STYLES = [
|
||||
STYLE_INTERACTIVE_EOL,
|
||||
STYLE_INTERACTIVE_OUTPUT,
|
||||
STYLE_INTERACTIVE_PROMPT,
|
||||
STYLE_INTERACTIVE_BANNER,
|
||||
STYLE_INTERACTIVE_ERROR,
|
||||
STYLE_INTERACTIVE_ERROR_FINALLINE,
|
||||
]
|
||||
|
||||
FormatterParent = pywin.scintilla.formatter.PythonSourceFormatter
|
||||
|
||||
|
||||
class InteractiveFormatter(FormatterParent):
|
||||
def __init__(self, scintilla):
|
||||
FormatterParent.__init__(self, scintilla)
|
||||
self.bannerDisplayed = False
|
||||
|
||||
def SetStyles(self):
|
||||
FormatterParent.SetStyles(self)
|
||||
Style = pywin.scintilla.formatter.Style
|
||||
self.RegisterStyle(Style(STYLE_INTERACTIVE_EOL, STYLE_INTERACTIVE_PROMPT))
|
||||
self.RegisterStyle(Style(STYLE_INTERACTIVE_PROMPT, formatInput))
|
||||
self.RegisterStyle(Style(STYLE_INTERACTIVE_OUTPUT, formatOutput))
|
||||
self.RegisterStyle(Style(STYLE_INTERACTIVE_BANNER, formatTitle))
|
||||
self.RegisterStyle(Style(STYLE_INTERACTIVE_ERROR, formatOutputError))
|
||||
self.RegisterStyle(
|
||||
Style(STYLE_INTERACTIVE_ERROR_FINALLINE, STYLE_INTERACTIVE_ERROR)
|
||||
)
|
||||
|
||||
def LoadPreference(self, name, default):
|
||||
rc = win32ui.GetProfileVal("Format", name, default)
|
||||
if rc == default:
|
||||
rc = win32ui.GetProfileVal(sectionProfile, name, default)
|
||||
return rc
|
||||
|
||||
def ColorizeInteractiveCode(self, cdoc, styleStart, stylePyStart):
|
||||
lengthDoc = len(cdoc)
|
||||
if lengthDoc == 0:
|
||||
return
|
||||
state = styleStart
|
||||
# As per comments in Colorize(), we work with the raw utf8
|
||||
# bytes. To avoid too much pain, we treat each utf8 byte
|
||||
# as a latin-1 unicode character - we only use it to compare
|
||||
# against ascii chars anyway...
|
||||
chNext = cdoc[0:1].decode("latin-1")
|
||||
startSeg = 0
|
||||
i = 0
|
||||
lastState = state # debug only
|
||||
while i < lengthDoc:
|
||||
ch = chNext
|
||||
chNext = cdoc[i + 1 : i + 2].decode("latin-1")
|
||||
|
||||
# trace("ch=%r, i=%d, next=%r, state=%s" % (ch, i, chNext, state))
|
||||
if state == STYLE_INTERACTIVE_EOL:
|
||||
if ch not in "\r\n":
|
||||
self.ColorSeg(startSeg, i - 1, state)
|
||||
startSeg = i
|
||||
if ch in (str(sys.ps1)[0], str(sys.ps2)[0]):
|
||||
state = STYLE_INTERACTIVE_PROMPT
|
||||
elif cdoc[i : i + len(tracebackHeader)] == tracebackHeader:
|
||||
state = STYLE_INTERACTIVE_ERROR
|
||||
else:
|
||||
state = STYLE_INTERACTIVE_OUTPUT
|
||||
elif state == STYLE_INTERACTIVE_PROMPT:
|
||||
if ch not in sys.ps1 + sys.ps2 + " ":
|
||||
self.ColorSeg(startSeg, i - 1, state)
|
||||
startSeg = i
|
||||
if ch in "\r\n":
|
||||
state = STYLE_INTERACTIVE_EOL
|
||||
else:
|
||||
state = stylePyStart # Start coloring Python code.
|
||||
elif state in (STYLE_INTERACTIVE_OUTPUT,):
|
||||
if ch in "\r\n":
|
||||
self.ColorSeg(startSeg, i - 1, state)
|
||||
startSeg = i
|
||||
state = STYLE_INTERACTIVE_EOL
|
||||
elif state == STYLE_INTERACTIVE_ERROR:
|
||||
if ch in "\r\n" and chNext and chNext not in string.whitespace:
|
||||
# Everything including me
|
||||
self.ColorSeg(startSeg, i, state)
|
||||
startSeg = i + 1
|
||||
state = STYLE_INTERACTIVE_ERROR_FINALLINE
|
||||
elif i == 0 and ch not in string.whitespace:
|
||||
# If we are coloring from the start of a line,
|
||||
# we need this better check for the last line
|
||||
# Color up to not including me
|
||||
self.ColorSeg(startSeg, i - 1, state)
|
||||
startSeg = i
|
||||
state = STYLE_INTERACTIVE_ERROR_FINALLINE
|
||||
elif state == STYLE_INTERACTIVE_ERROR_FINALLINE:
|
||||
if ch in "\r\n":
|
||||
self.ColorSeg(startSeg, i - 1, state)
|
||||
startSeg = i
|
||||
state = STYLE_INTERACTIVE_EOL
|
||||
elif state == STYLE_INTERACTIVE_BANNER:
|
||||
if ch in "\r\n" and (chNext == "" or chNext in ">["):
|
||||
# Everything including me
|
||||
self.ColorSeg(startSeg, i - 1, state)
|
||||
startSeg = i
|
||||
state = STYLE_INTERACTIVE_EOL
|
||||
else:
|
||||
# It is a PythonColorizer state - seek past the end of the line
|
||||
# and ask the Python colorizer to color that.
|
||||
end = startSeg
|
||||
while end < lengthDoc and cdoc[end] not in b"\r\n":
|
||||
end += 1
|
||||
self.ColorizePythonCode(cdoc[:end], startSeg, state)
|
||||
stylePyStart = self.GetStringStyle(end - 1)
|
||||
if stylePyStart is None:
|
||||
stylePyStart = pywin.scintilla.formatter.STYLE_DEFAULT
|
||||
else:
|
||||
stylePyStart = stylePyStart.name
|
||||
startSeg = end
|
||||
i = end - 1 # ready for increment.
|
||||
chNext = cdoc[end : end + 1].decode("latin-1")
|
||||
state = STYLE_INTERACTIVE_EOL
|
||||
if lastState != state:
|
||||
lastState = state
|
||||
i += 1
|
||||
# and the rest
|
||||
if startSeg < i:
|
||||
self.ColorSeg(startSeg, i - 1, state)
|
||||
|
||||
def Colorize(self, start=0, end=-1):
|
||||
# scintilla's formatting is all done in terms of utf, so
|
||||
# we work with utf8 bytes instead of unicode. This magically
|
||||
# works as any extended chars found in the utf8 don't change
|
||||
# the semantics.
|
||||
stringVal = self.scintilla.GetTextRange(start, end, decode=False)
|
||||
styleStart = None
|
||||
stylePyStart = None
|
||||
if start > 1:
|
||||
# Likely we are being asked to color from the start of the line.
|
||||
# We find the last formatted character on the previous line.
|
||||
# If TQString, we continue it. Otherwise, we reset.
|
||||
look = start - 1
|
||||
while look and self.scintilla.SCIGetCharAt(look) in "\n\r":
|
||||
look -= 1
|
||||
if look and look < start - 1: # Did we find a char before the \n\r sets?
|
||||
strstyle = self.GetStringStyle(look)
|
||||
quote_char = None
|
||||
if strstyle is not None:
|
||||
if strstyle.name == pywin.scintilla.formatter.STYLE_TQSSTRING:
|
||||
quote_char = "'"
|
||||
elif strstyle.name == pywin.scintilla.formatter.STYLE_TQDSTRING:
|
||||
quote_char = '"'
|
||||
if quote_char is not None:
|
||||
# It is a TQS. If the TQS is not terminated, we
|
||||
# carry the style through.
|
||||
if look > 2:
|
||||
look_str = (
|
||||
self.scintilla.SCIGetCharAt(look - 2)
|
||||
+ self.scintilla.SCIGetCharAt(look - 1)
|
||||
+ self.scintilla.SCIGetCharAt(look)
|
||||
)
|
||||
if look_str != quote_char * 3:
|
||||
stylePyStart = strstyle.name
|
||||
if stylePyStart is None:
|
||||
stylePyStart = pywin.scintilla.formatter.STYLE_DEFAULT
|
||||
|
||||
if start > 0:
|
||||
stylenum = self.scintilla.SCIGetStyleAt(start - 1)
|
||||
styleStart = self.GetStyleByNum(stylenum).name
|
||||
elif self.bannerDisplayed:
|
||||
styleStart = STYLE_INTERACTIVE_EOL
|
||||
else:
|
||||
styleStart = STYLE_INTERACTIVE_BANNER
|
||||
self.bannerDisplayed = True
|
||||
self.scintilla.SCIStartStyling(start, 31)
|
||||
self.style_buffer = array.array("b", (0,) * len(stringVal))
|
||||
self.ColorizeInteractiveCode(stringVal, styleStart, stylePyStart)
|
||||
self.scintilla.SCISetStylingEx(self.style_buffer)
|
||||
self.style_buffer = None
|
||||
|
||||
|
||||
###############################################################
|
||||
#
|
||||
# This class handles the Python interactive interpreter.
|
||||
#
|
||||
# It uses a basic EditWindow, and does all the magic.
|
||||
# This is triggered by the enter key hander attached by the
|
||||
# start-up code. It determines if a command is to be executed
|
||||
# or continued (ie, emit "... ") by snooping around the current
|
||||
# line, looking for the prompts
|
||||
#
|
||||
class PythonwinInteractiveInterpreter(code.InteractiveInterpreter):
|
||||
def __init__(self, locals=None, globals=None):
|
||||
if locals is None:
|
||||
locals = __main__.__dict__
|
||||
if globals is None:
|
||||
globals = locals
|
||||
self.globals = globals
|
||||
code.InteractiveInterpreter.__init__(self, locals)
|
||||
|
||||
def showsyntaxerror(self, filename=None, **kwargs):
|
||||
sys.stderr.write(
|
||||
tracebackHeader.decode("ascii")
|
||||
) # So the color syntaxer recognises it.
|
||||
code.InteractiveInterpreter.showsyntaxerror(self, filename)
|
||||
|
||||
def runcode(self, code):
|
||||
try:
|
||||
exec(code, self.globals, self.locals)
|
||||
except SystemExit:
|
||||
raise
|
||||
except:
|
||||
self.showtraceback()
|
||||
|
||||
|
||||
class InteractiveCore:
|
||||
def __init__(self, banner=None):
|
||||
self.banner = banner
|
||||
|
||||
# LoadFontPreferences()
|
||||
def Init(self):
|
||||
self.oldStdOut = self.oldStdErr = None
|
||||
|
||||
# self.SetWordWrap(win32ui.CRichEditView_WrapNone)
|
||||
self.interp = PythonwinInteractiveInterpreter()
|
||||
|
||||
self.OutputGrab() # Release at cleanup.
|
||||
|
||||
if self.GetTextLength() == 0:
|
||||
if self.banner is None:
|
||||
suffix = ""
|
||||
if win32ui.debug:
|
||||
suffix = ", debug build"
|
||||
sys.stderr.write(
|
||||
f"PythonWin {sys.version} on {sys.platform}{suffix}.\n"
|
||||
)
|
||||
sys.stderr.write(
|
||||
"Portions {} - see 'Help/About PythonWin' for further copyright information.\n".format(
|
||||
win32ui.copyright
|
||||
)
|
||||
)
|
||||
else:
|
||||
sys.stderr.write(self.banner)
|
||||
rcfile = os.environ.get("PYTHONSTARTUP")
|
||||
if rcfile:
|
||||
import __main__
|
||||
|
||||
try:
|
||||
exec(
|
||||
compile(
|
||||
open(rcfile, "rb").read(), rcfile, "exec", dont_inherit=True
|
||||
),
|
||||
__main__.__dict__,
|
||||
__main__.__dict__,
|
||||
)
|
||||
except:
|
||||
sys.stderr.write(
|
||||
">>> \nError executing PYTHONSTARTUP script %r\n" % (rcfile)
|
||||
)
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
self.AppendToPrompt([])
|
||||
|
||||
def SetContext(self, globals, locals, name="Dbg"):
|
||||
oldPrompt = sys.ps1
|
||||
if globals is None:
|
||||
# Reset
|
||||
sys.ps1 = ">>> "
|
||||
sys.ps2 = "... "
|
||||
locals = globals = __main__.__dict__
|
||||
else:
|
||||
sys.ps1 = "[%s]>>> " % name
|
||||
sys.ps2 = "[%s]... " % name
|
||||
self.interp.locals = locals
|
||||
self.interp.globals = globals
|
||||
self.AppendToPrompt([], oldPrompt)
|
||||
|
||||
def GetContext(self):
|
||||
return self.interp.globals, self.interp.locals
|
||||
|
||||
def DoGetLine(self, line=-1):
|
||||
if line == -1:
|
||||
line = self.LineFromChar()
|
||||
line = self.GetLine(line)
|
||||
while line and line[-1] in ("\r", "\n"):
|
||||
line = line[:-1]
|
||||
return line
|
||||
|
||||
def AppendToPrompt(self, bufLines, oldPrompt=None):
|
||||
"Take a command and stick it at the end of the buffer (with python prompts inserted if required)."
|
||||
self.flush()
|
||||
lastLineNo = self.GetLineCount() - 1
|
||||
line = self.DoGetLine(lastLineNo)
|
||||
if oldPrompt and line == oldPrompt:
|
||||
self.SetSel(self.GetTextLength() - len(oldPrompt), self.GetTextLength())
|
||||
self.ReplaceSel(sys.ps1)
|
||||
elif line != str(sys.ps1):
|
||||
if len(line) != 0:
|
||||
self.write("\n")
|
||||
self.write(sys.ps1)
|
||||
self.flush()
|
||||
self.idle.text.mark_set("iomark", "end-1c")
|
||||
if not bufLines:
|
||||
return
|
||||
terms = (["\n" + sys.ps2] * (len(bufLines) - 1)) + [""]
|
||||
for bufLine, term in zip(bufLines, terms):
|
||||
if bufLine.strip():
|
||||
self.write(bufLine + term)
|
||||
self.flush()
|
||||
|
||||
def EnsureNoPrompt(self):
|
||||
# Get ready to write some text NOT at a Python prompt.
|
||||
self.flush()
|
||||
lastLineNo = self.GetLineCount() - 1
|
||||
line = self.DoGetLine(lastLineNo)
|
||||
if not line or line in (sys.ps1, sys.ps2):
|
||||
self.SetSel(self.GetTextLength() - len(line), self.GetTextLength())
|
||||
self.ReplaceSel("")
|
||||
else:
|
||||
# Just add a new line.
|
||||
self.write("\n")
|
||||
|
||||
def _GetSubConfigNames(self):
|
||||
return ["interactive"] # Allow [Keys:Interactive] sections to be specific
|
||||
|
||||
def HookHandlers(self):
|
||||
# Hook menu command (executed when a menu item with that ID is selected from a menu/toolbar
|
||||
self.HookCommand(self.OnSelectBlock, win32ui.ID_EDIT_SELECT_BLOCK)
|
||||
self.HookCommand(self.OnEditCopyCode, ID_EDIT_COPY_CODE)
|
||||
self.HookCommand(self.OnEditExecClipboard, ID_EDIT_EXEC_CLIPBOARD)
|
||||
mod = pywin.scintilla.IDLEenvironment.GetIDLEModule("IdleHistory")
|
||||
if mod is not None:
|
||||
self.history = mod.History(self.idle.text, "\n" + sys.ps2)
|
||||
else:
|
||||
self.history = None
|
||||
# hack for now for event handling.
|
||||
|
||||
# GetBlockBoundary takes a line number, and will return the
|
||||
# start and and line numbers of the block, and a flag indicating if the
|
||||
# block is a Python code block.
|
||||
# If the line specified has a Python prompt, then the lines are parsed
|
||||
# backwards and forwards, and the flag is true.
|
||||
# If the line does not start with a prompt, the block is searched forward
|
||||
# and backward until a prompt _is_ found, and all lines in between without
|
||||
# prompts are returned, and the flag is false.
|
||||
def GetBlockBoundary(self, lineNo):
|
||||
line = self.DoGetLine(lineNo)
|
||||
maxLineNo = self.GetLineCount() - 1
|
||||
prefix = GetPromptPrefix(line)
|
||||
if prefix is None: # Non code block
|
||||
flag = 0
|
||||
startLineNo = lineNo
|
||||
while startLineNo > 0:
|
||||
if GetPromptPrefix(self.DoGetLine(startLineNo - 1)) is not None:
|
||||
break # there _is_ a prompt
|
||||
startLineNo -= 1
|
||||
endLineNo = lineNo
|
||||
while endLineNo < maxLineNo:
|
||||
if GetPromptPrefix(self.DoGetLine(endLineNo + 1)) is not None:
|
||||
break # there _is_ a prompt
|
||||
endLineNo += 1
|
||||
else: # Code block
|
||||
flag = 1
|
||||
startLineNo = lineNo
|
||||
while startLineNo > 0 and prefix != str(sys.ps1):
|
||||
prefix = GetPromptPrefix(self.DoGetLine(startLineNo - 1))
|
||||
if prefix is None:
|
||||
break
|
||||
# there is no prompt.
|
||||
startLineNo -= 1
|
||||
endLineNo = lineNo
|
||||
while endLineNo < maxLineNo:
|
||||
prefix = GetPromptPrefix(self.DoGetLine(endLineNo + 1))
|
||||
if prefix is None:
|
||||
break # there is no prompt
|
||||
if prefix == str(sys.ps1):
|
||||
break # this is another command
|
||||
endLineNo += 1
|
||||
# continue until end of buffer, or no prompt
|
||||
return (startLineNo, endLineNo, flag)
|
||||
|
||||
def ExtractCommand(self, lines):
|
||||
start, end = lines
|
||||
retList = []
|
||||
while end >= start:
|
||||
thisLine = self.DoGetLine(end)
|
||||
promptLen = len(GetPromptPrefix(thisLine))
|
||||
retList = [thisLine[promptLen:]] + retList
|
||||
end -= 1
|
||||
return retList
|
||||
|
||||
def OutputGrab(self):
|
||||
# import win32traceutil; return
|
||||
self.oldStdOut = sys.stdout
|
||||
self.oldStdErr = sys.stderr
|
||||
sys.stdout = self
|
||||
sys.stderr = self
|
||||
self.flush()
|
||||
|
||||
def OutputRelease(self):
|
||||
# a command may have overwritten these - only restore if not.
|
||||
if self.oldStdOut is not None:
|
||||
if sys.stdout == self:
|
||||
sys.stdout = self.oldStdOut
|
||||
if self.oldStdErr is not None:
|
||||
if sys.stderr == self:
|
||||
sys.stderr = self.oldStdErr
|
||||
self.oldStdOut = None
|
||||
self.oldStdErr = None
|
||||
self.flush()
|
||||
|
||||
###################################
|
||||
#
|
||||
# Message/Command/Key Hooks.
|
||||
#
|
||||
# Enter key handler
|
||||
#
|
||||
def ProcessEnterEvent(self, event):
|
||||
# If autocompletion has been triggered, complete and do not process event
|
||||
if self.SCIAutoCActive():
|
||||
self.SCIAutoCComplete()
|
||||
self.SCICancel()
|
||||
return
|
||||
|
||||
self.SCICancel()
|
||||
# First, check for an error message
|
||||
haveGrabbedOutput = 0
|
||||
if self.HandleSpecialLine():
|
||||
return 0
|
||||
|
||||
lineNo = self.LineFromChar()
|
||||
start, end, isCode = self.GetBlockBoundary(lineNo)
|
||||
# If we are not in a code block just go to the prompt (or create a new one)
|
||||
if not isCode:
|
||||
self.AppendToPrompt([])
|
||||
win32ui.SetStatusText(win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE))
|
||||
return
|
||||
|
||||
lines = self.ExtractCommand((start, end))
|
||||
|
||||
# If we are in a code-block, but it isn't at the end of the buffer
|
||||
# then copy it to the end ready for editing and subsequent execution
|
||||
if end != self.GetLineCount() - 1:
|
||||
win32ui.SetStatusText("Press ENTER to execute command")
|
||||
self.AppendToPrompt(lines)
|
||||
self.SetSel(-2)
|
||||
return
|
||||
|
||||
# If SHIFT held down, we want new code here and now!
|
||||
bNeedIndent = (
|
||||
win32api.GetKeyState(win32con.VK_SHIFT) < 0
|
||||
or win32api.GetKeyState(win32con.VK_CONTROL) < 0
|
||||
)
|
||||
if bNeedIndent:
|
||||
self.ReplaceSel("\n")
|
||||
else:
|
||||
self.SetSel(-2)
|
||||
self.ReplaceSel("\n")
|
||||
source = "\n".join(lines)
|
||||
while source and source[-1] in "\t ":
|
||||
source = source[:-1]
|
||||
self.OutputGrab() # grab the output for the command exec.
|
||||
try:
|
||||
if self.interp.runsource(
|
||||
source, "<interactive input>"
|
||||
): # Need more input!
|
||||
bNeedIndent = 1
|
||||
else:
|
||||
# If the last line isn't empty, append a newline
|
||||
if self.history is not None:
|
||||
self.history.history_store(source)
|
||||
self.AppendToPrompt([])
|
||||
win32ui.SetStatusText(
|
||||
win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE)
|
||||
)
|
||||
# win32ui.SetStatusText('Successfully executed statement')
|
||||
finally:
|
||||
self.OutputRelease()
|
||||
if bNeedIndent:
|
||||
win32ui.SetStatusText("Ready to continue the command")
|
||||
# Now attempt correct indentation (should use IDLE?)
|
||||
curLine = self.DoGetLine(lineNo)[len(sys.ps2) :]
|
||||
pos = 0
|
||||
indent = ""
|
||||
while len(curLine) > pos and curLine[pos] in string.whitespace:
|
||||
indent += curLine[pos]
|
||||
pos += 1
|
||||
if _is_block_opener(curLine):
|
||||
indent += "\t"
|
||||
elif _is_block_closer(curLine):
|
||||
indent = indent[:-1]
|
||||
# use ReplaceSel to ensure it goes at the cursor rather than end of buffer.
|
||||
self.ReplaceSel(sys.ps2 + indent)
|
||||
return 0
|
||||
|
||||
# ESC key handler
|
||||
def ProcessEscEvent(self, event):
|
||||
# Implement a cancel.
|
||||
if self.SCIAutoCActive() or self.SCICallTipActive():
|
||||
self.SCICancel()
|
||||
else:
|
||||
win32ui.SetStatusText("Cancelled.")
|
||||
self.AppendToPrompt(("",))
|
||||
return 0
|
||||
|
||||
def OnSelectBlock(self, command, code):
|
||||
lineNo = self.LineFromChar()
|
||||
start, end, isCode = self.GetBlockBoundary(lineNo)
|
||||
startIndex = self.LineIndex(start)
|
||||
endIndex = self.LineIndex(end + 1) - 2 # skip \r + \n
|
||||
if endIndex < 0: # must be beyond end of buffer
|
||||
endIndex = -2 # self.Length()
|
||||
self.SetSel(startIndex, endIndex)
|
||||
|
||||
def OnEditCopyCode(self, command, code):
|
||||
"""Sanitizes code from interactive window, removing prompts and output,
|
||||
and inserts it in the clipboard."""
|
||||
code = self.GetSelText()
|
||||
lines = code.splitlines()
|
||||
out_lines = []
|
||||
for line in lines:
|
||||
if line.startswith(sys.ps1):
|
||||
line = line[len(sys.ps1) :]
|
||||
out_lines.append(line)
|
||||
elif line.startswith(sys.ps2):
|
||||
line = line[len(sys.ps2) :]
|
||||
out_lines.append(line)
|
||||
out_code = os.linesep.join(out_lines)
|
||||
win32clipboard.OpenClipboard()
|
||||
try:
|
||||
win32clipboard.SetClipboardData(
|
||||
win32clipboard.CF_UNICODETEXT, str(out_code)
|
||||
)
|
||||
finally:
|
||||
win32clipboard.CloseClipboard()
|
||||
|
||||
def OnEditExecClipboard(self, command, code):
|
||||
"""Executes python code directly from the clipboard."""
|
||||
win32clipboard.OpenClipboard()
|
||||
try:
|
||||
code = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
|
||||
finally:
|
||||
win32clipboard.CloseClipboard()
|
||||
|
||||
code = code.replace("\r\n", "\n") + "\n"
|
||||
try:
|
||||
o = compile(code, "<clipboard>", "exec")
|
||||
exec(o, __main__.__dict__)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
def GetRightMenuItems(self):
|
||||
# Just override parents
|
||||
ret = []
|
||||
flags = 0
|
||||
ret.append((flags, win32ui.ID_EDIT_UNDO, "&Undo"))
|
||||
ret.append(win32con.MF_SEPARATOR)
|
||||
ret.append((flags, win32ui.ID_EDIT_CUT, "Cu&t"))
|
||||
ret.append((flags, win32ui.ID_EDIT_COPY, "&Copy"))
|
||||
|
||||
start, end = self.GetSel()
|
||||
if start != end:
|
||||
ret.append((flags, ID_EDIT_COPY_CODE, "Copy code without prompts"))
|
||||
if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_UNICODETEXT):
|
||||
ret.append(
|
||||
(flags, ID_EDIT_EXEC_CLIPBOARD, "Execute python code from clipboard")
|
||||
)
|
||||
|
||||
ret.append((flags, win32ui.ID_EDIT_PASTE, "&Paste"))
|
||||
ret.append(win32con.MF_SEPARATOR)
|
||||
ret.append((flags, win32ui.ID_EDIT_SELECT_ALL, "&Select all"))
|
||||
ret.append((flags, win32ui.ID_EDIT_SELECT_BLOCK, "Select &block"))
|
||||
ret.append((flags, win32ui.ID_VIEW_WHITESPACE, "View &Whitespace"))
|
||||
return ret
|
||||
|
||||
def MDINextEvent(self, event):
|
||||
win32ui.GetMainFrame().MDINext(0)
|
||||
|
||||
def MDIPrevEvent(self, event):
|
||||
win32ui.GetMainFrame().MDINext(0)
|
||||
|
||||
def WindowBackEvent(self, event):
|
||||
parent = self.GetParentFrame()
|
||||
if parent == win32ui.GetMainFrame():
|
||||
# It is docked.
|
||||
try:
|
||||
wnd, isactive = parent.MDIGetActive()
|
||||
wnd.SetFocus()
|
||||
except win32ui.error:
|
||||
# No MDI window active!
|
||||
pass
|
||||
else:
|
||||
# Normal Window
|
||||
try:
|
||||
lastActive = self.GetParentFrame().lastActive
|
||||
# If the window is invalid, reset it.
|
||||
if lastActive is not None and (
|
||||
lastActive._obj_ is None or lastActive.GetSafeHwnd() == 0
|
||||
):
|
||||
lastActive = self.GetParentFrame().lastActive = None
|
||||
win32ui.SetStatusText("The last active Window has been closed.")
|
||||
except AttributeError:
|
||||
print("Can't find the last active window!")
|
||||
lastActive = None
|
||||
if lastActive is not None:
|
||||
lastActive.MDIActivate()
|
||||
|
||||
|
||||
class InteractiveView(InteractiveCore, winout.WindowOutputView):
|
||||
def __init__(self, doc):
|
||||
InteractiveCore.__init__(self)
|
||||
winout.WindowOutputView.__init__(self, doc)
|
||||
self.encoding = pywin.default_scintilla_encoding
|
||||
|
||||
def _MakeColorizer(self):
|
||||
return InteractiveFormatter(self)
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
winout.WindowOutputView.OnInitialUpdate(self)
|
||||
self.SetWordWrap()
|
||||
self.Init()
|
||||
|
||||
def HookHandlers(self):
|
||||
winout.WindowOutputView.HookHandlers(self)
|
||||
InteractiveCore.HookHandlers(self)
|
||||
|
||||
|
||||
class CInteractivePython(winout.WindowOutput):
|
||||
def __init__(self, makeDoc=None, makeFrame=None):
|
||||
self.IsFinalDestroy = 0
|
||||
winout.WindowOutput.__init__(
|
||||
self,
|
||||
sectionProfile,
|
||||
sectionProfile,
|
||||
winout.flags.WQ_LINE,
|
||||
1,
|
||||
None,
|
||||
makeDoc,
|
||||
makeFrame,
|
||||
InteractiveView,
|
||||
)
|
||||
self.Create()
|
||||
|
||||
def OnViewDestroy(self, view):
|
||||
if self.IsFinalDestroy:
|
||||
view.OutputRelease()
|
||||
winout.WindowOutput.OnViewDestroy(self, view)
|
||||
|
||||
def Close(self):
|
||||
self.IsFinalDestroy = 1
|
||||
winout.WindowOutput.Close(self)
|
||||
|
||||
|
||||
class InteractiveFrame(winout.WindowOutputFrame):
|
||||
def __init__(self):
|
||||
self.lastActive = None
|
||||
winout.WindowOutputFrame.__init__(self)
|
||||
|
||||
def OnMDIActivate(self, bActive, wndActive, wndDeactive):
|
||||
if bActive:
|
||||
self.lastActive = wndDeactive
|
||||
|
||||
|
||||
######################################################################
|
||||
##
|
||||
## Dockable Window Support
|
||||
##
|
||||
######################################################################
|
||||
ID_DOCKED_INTERACTIVE_CONTROLBAR = 0xE802
|
||||
|
||||
DockedInteractiveViewParent = InteractiveView
|
||||
|
||||
|
||||
class DockedInteractiveView(DockedInteractiveViewParent):
|
||||
def HookHandlers(self):
|
||||
DockedInteractiveViewParent.HookHandlers(self)
|
||||
self.HookMessage(self.OnSetFocus, win32con.WM_SETFOCUS)
|
||||
self.HookMessage(self.OnKillFocus, win32con.WM_KILLFOCUS)
|
||||
|
||||
def OnSetFocus(self, msg):
|
||||
self.GetParentFrame().SetActiveView(self)
|
||||
return 1
|
||||
|
||||
def OnKillFocus(self, msg):
|
||||
# If we are losing focus to another in this app, reset the main frame's active view.
|
||||
hwnd = wparam = msg[2]
|
||||
try:
|
||||
wnd = win32ui.CreateWindowFromHandle(hwnd)
|
||||
reset = wnd.GetTopLevelFrame() == self.GetTopLevelFrame()
|
||||
except win32ui.error:
|
||||
reset = 0 # Not my window
|
||||
if reset:
|
||||
self.GetParentFrame().SetActiveView(None)
|
||||
return 1
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
newSize = self.GetWindowPlacement()[4]
|
||||
pywin.framework.app.SaveWindowSize("Interactive Window", newSize, "docked")
|
||||
try:
|
||||
if self.GetParentFrame().GetActiveView == self:
|
||||
self.GetParentFrame().SetActiveView(None)
|
||||
except win32ui.error:
|
||||
pass
|
||||
try:
|
||||
if win32ui.GetMainFrame().GetActiveView() == self:
|
||||
win32ui.GetMainFrame().SetActiveView(None)
|
||||
except win32ui.error:
|
||||
pass
|
||||
return DockedInteractiveViewParent.OnDestroy(self, msg)
|
||||
|
||||
|
||||
class CDockedInteractivePython(CInteractivePython):
|
||||
def __init__(self, dockbar):
|
||||
self.bFirstCreated = 0
|
||||
self.dockbar = dockbar
|
||||
CInteractivePython.__init__(self)
|
||||
|
||||
def NeedRecreateWindow(self):
|
||||
if self.bCreating:
|
||||
return 0
|
||||
try:
|
||||
frame = win32ui.GetMainFrame()
|
||||
if frame.closing:
|
||||
return 0 # Dieing!
|
||||
except (win32ui.error, AttributeError):
|
||||
return 0 # The app is dieing!
|
||||
try:
|
||||
cb = frame.GetControlBar(ID_DOCKED_INTERACTIVE_CONTROLBAR)
|
||||
return not cb.IsWindowVisible()
|
||||
except win32ui.error:
|
||||
return 1 # Control bar does not exist!
|
||||
|
||||
def RecreateWindow(self):
|
||||
try:
|
||||
dockbar = win32ui.GetMainFrame().GetControlBar(
|
||||
ID_DOCKED_INTERACTIVE_CONTROLBAR
|
||||
)
|
||||
win32ui.GetMainFrame().ShowControlBar(dockbar, 1, 1)
|
||||
except win32ui.error:
|
||||
CreateDockedInteractiveWindow()
|
||||
|
||||
def Create(self):
|
||||
self.bCreating = 1
|
||||
doc = InteractiveDocument(None, self.DoCreateDoc())
|
||||
view = DockedInteractiveView(doc)
|
||||
defRect = pywin.framework.app.LoadWindowSize("Interactive Window", "docked")
|
||||
if defRect[2] - defRect[0] == 0:
|
||||
defRect = 0, 0, 500, 200
|
||||
style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.WS_BORDER
|
||||
id = 1050 # win32ui.AFX_IDW_PANE_FIRST
|
||||
view.CreateWindow(self.dockbar, id, style, defRect)
|
||||
view.OnInitialUpdate()
|
||||
self.bFirstCreated = 1
|
||||
|
||||
self.currentView = doc.GetFirstView()
|
||||
self.bCreating = 0
|
||||
if self.title:
|
||||
doc.SetTitle(self.title)
|
||||
|
||||
|
||||
# The factory we pass to the dockable window support.
|
||||
def InteractiveViewCreator(parent):
|
||||
global edit
|
||||
edit = CDockedInteractivePython(parent)
|
||||
return edit.currentView
|
||||
|
||||
|
||||
def CreateDockedInteractiveWindow():
|
||||
# Later, the DockingBar should be capable of hosting multiple
|
||||
# children.
|
||||
from pywin.docking.DockingBar import DockingBar
|
||||
|
||||
bar = DockingBar()
|
||||
creator = InteractiveViewCreator
|
||||
bar.CreateWindow(
|
||||
win32ui.GetMainFrame(),
|
||||
creator,
|
||||
"Interactive Window",
|
||||
ID_DOCKED_INTERACTIVE_CONTROLBAR,
|
||||
)
|
||||
bar.SetBarStyle(
|
||||
bar.GetBarStyle()
|
||||
| afxres.CBRS_TOOLTIPS
|
||||
| afxres.CBRS_FLYBY
|
||||
| afxres.CBRS_SIZE_DYNAMIC
|
||||
)
|
||||
bar.EnableDocking(afxres.CBRS_ALIGN_ANY)
|
||||
win32ui.GetMainFrame().DockControlBar(bar, afxres.AFX_IDW_DOCKBAR_BOTTOM)
|
||||
|
||||
|
||||
######################################################################
|
||||
#
|
||||
# The public interface to this module.
|
||||
#
|
||||
######################################################################
|
||||
# No extra functionality now, but maybe later, so
|
||||
# publicize these names.
|
||||
InteractiveDocument = winout.WindowOutputDocument
|
||||
|
||||
# We remember our one and only interactive window in the "edit" variable.
|
||||
edit = None
|
||||
|
||||
|
||||
def CreateInteractiveWindowUserPreference(makeDoc=None, makeFrame=None):
|
||||
"""Create some sort of interactive window if the user's preference say we should."""
|
||||
bCreate = LoadPreference("Show at startup", 1)
|
||||
if bCreate:
|
||||
CreateInteractiveWindow(makeDoc, makeFrame)
|
||||
|
||||
|
||||
def CreateInteractiveWindow(makeDoc=None, makeFrame=None):
|
||||
"""Create a standard or docked interactive window unconditionally"""
|
||||
assert edit is None, "Creating second interactive window!"
|
||||
bDocking = LoadPreference("Docking", 0)
|
||||
if bDocking:
|
||||
CreateDockedInteractiveWindow()
|
||||
else:
|
||||
CreateMDIInteractiveWindow(makeDoc, makeFrame)
|
||||
assert edit is not None, "Created interactive window, but did not set the global!"
|
||||
edit.currentView.SetFocus()
|
||||
|
||||
|
||||
def CreateMDIInteractiveWindow(makeDoc=None, makeFrame=None):
|
||||
"""Create a standard (non-docked) interactive window unconditionally"""
|
||||
global edit
|
||||
if makeDoc is None:
|
||||
makeDoc = InteractiveDocument
|
||||
if makeFrame is None:
|
||||
makeFrame = InteractiveFrame
|
||||
edit = CInteractivePython(makeDoc=makeDoc, makeFrame=makeFrame)
|
||||
|
||||
|
||||
def DestroyInteractiveWindow():
|
||||
"""Destroy the interactive window.
|
||||
This is different to Closing the window,
|
||||
which may automatically re-appear. Once destroyed, it can never be recreated,
|
||||
and a complete new instance must be created (which the various other helper
|
||||
functions will then do after making this call
|
||||
"""
|
||||
global edit
|
||||
if edit is not None and edit.currentView is not None:
|
||||
if edit.currentView.GetParentFrame() == win32ui.GetMainFrame():
|
||||
# It is docked - do nothing now (this is only called at shutdown!)
|
||||
pass
|
||||
else:
|
||||
# It is a standard window - call Close on the container.
|
||||
edit.Close()
|
||||
edit = None
|
||||
|
||||
|
||||
def CloseInteractiveWindow():
|
||||
"""Close the interactive window, allowing it to be re-created on demand."""
|
||||
global edit
|
||||
if edit is not None and edit.currentView is not None:
|
||||
if edit.currentView.GetParentFrame() == win32ui.GetMainFrame():
|
||||
# It is docked, just hide the dock bar.
|
||||
frame = win32ui.GetMainFrame()
|
||||
cb = frame.GetControlBar(ID_DOCKED_INTERACTIVE_CONTROLBAR)
|
||||
frame.ShowControlBar(cb, 0, 1)
|
||||
else:
|
||||
# It is a standard window - destroy the frame/view, allowing the object itself to remain.
|
||||
edit.currentView.GetParentFrame().DestroyWindow()
|
||||
|
||||
|
||||
def ToggleInteractiveWindow():
|
||||
"""If the interactive window is visible, hide it, otherwise show it."""
|
||||
if edit is None:
|
||||
CreateInteractiveWindow()
|
||||
else:
|
||||
if edit.NeedRecreateWindow():
|
||||
edit.RecreateWindow()
|
||||
else:
|
||||
# Close it, allowing a reopen.
|
||||
CloseInteractiveWindow()
|
||||
|
||||
|
||||
def ShowInteractiveWindow():
|
||||
"""Shows (or creates if necessary) an interactive window"""
|
||||
if edit is None:
|
||||
CreateInteractiveWindow()
|
||||
else:
|
||||
if edit.NeedRecreateWindow():
|
||||
edit.RecreateWindow()
|
||||
else:
|
||||
parent = edit.currentView.GetParentFrame()
|
||||
if parent == win32ui.GetMainFrame(): # It is docked.
|
||||
edit.currentView.SetFocus()
|
||||
else: # It is a "normal" window
|
||||
edit.currentView.GetParentFrame().AutoRestore()
|
||||
win32ui.GetMainFrame().MDIActivate(edit.currentView.GetParentFrame())
|
||||
|
||||
|
||||
def IsInteractiveWindowVisible():
|
||||
return edit is not None and not edit.NeedRecreateWindow()
|
||||
@@ -0,0 +1,552 @@
|
||||
# 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, docview
|
||||
|
||||
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)
|
||||
|
||||
|
||||
docview.DocTemplate._SetupSharedMenu_ = _SetupSharedMenu_ # type: ignore[method-assign]
|
||||
|
||||
|
||||
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 don't 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(
|
||||
f"Python {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: {}\n\nCommand Line: {}".format(
|
||||
fname, win32api.GetCommandLine()
|
||||
),
|
||||
"Open file for edit",
|
||||
win32con.MB_ICONERROR,
|
||||
)
|
||||
continue
|
||||
if dde:
|
||||
dde.Exec(f"win32ui.GetApp().OpenDocumentFile({fname!r})")
|
||||
else:
|
||||
win32ui.GetApp().OpenDocumentFile(par)
|
||||
elif argType == "/rundlg":
|
||||
if dde:
|
||||
dde.Exec(
|
||||
"from pywin.framework import scriptutils;scriptutils.RunScript({!r}, {!r}, 1)".format(
|
||||
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)".format(
|
||||
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"
|
||||
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" property 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:
|
||||
# Don't actually call the function with the exception handler.
|
||||
getter = template.GetPythonPropertyPages
|
||||
except AttributeError:
|
||||
# Template does not provide property pages!
|
||||
continue
|
||||
pages.extend(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 hasn't 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()
|
||||
|
||||
|
||||
thisApp = InteractivePythonApp()
|
||||
@@ -0,0 +1,65 @@
|
||||
# DDE support for Pythonwin
|
||||
#
|
||||
# Seems to work fine (in the context that IE4 seems to have broken
|
||||
# DDE on _all_ NT4 machines I have tried, but only when a "Command Prompt" window
|
||||
# is open. Strange, but true. If you have problems with this, close all Command Prompts!
|
||||
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import win32ui
|
||||
from dde import ( # nopycln: import # Re-exported for intpyapp.py
|
||||
CBF_FAIL_SELFCONNECTIONS as CBF_FAIL_SELFCONNECTIONS, # noqa: PLC0414
|
||||
CreateConversation as CreateConversation, # noqa: PLC0414
|
||||
CreateServer,
|
||||
CreateServerSystemTopic,
|
||||
error as error, # noqa: PLC0414
|
||||
)
|
||||
from pywin.mfc import object
|
||||
|
||||
|
||||
class DDESystemTopic(object.Object):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
object.Object.__init__(self, CreateServerSystemTopic())
|
||||
|
||||
def Exec(self, data):
|
||||
try:
|
||||
# print("Executing", cmd)
|
||||
self.app.OnDDECommand(data)
|
||||
except:
|
||||
t, v, tb = sys.exc_info()
|
||||
# The DDE Execution failed.
|
||||
print("Error executing DDE command.")
|
||||
traceback.print_exception(t, v, tb)
|
||||
return 0
|
||||
|
||||
|
||||
class DDEServer(object.Object):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
object.Object.__init__(self, CreateServer())
|
||||
self.topic = self.item = None
|
||||
|
||||
def CreateSystemTopic(self):
|
||||
return DDESystemTopic(self.app)
|
||||
|
||||
def Shutdown(self):
|
||||
self._obj_.Shutdown()
|
||||
self._obj_.Destroy()
|
||||
if self.topic is not None:
|
||||
self.topic.Destroy()
|
||||
self.topic = None
|
||||
if self.item is not None:
|
||||
self.item.Destroy()
|
||||
self.item = None
|
||||
|
||||
def OnCreate(self):
|
||||
return 1
|
||||
|
||||
def Status(self, msg):
|
||||
try:
|
||||
win32ui.SetStatusText(msg)
|
||||
except win32ui.error:
|
||||
pass
|
||||
@@ -0,0 +1,684 @@
|
||||
"""
|
||||
Various utilities for running/importing a script
|
||||
"""
|
||||
|
||||
import bdb
|
||||
import linecache
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import __main__
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
from pywin.mfc.docview import TreeView
|
||||
|
||||
from .cmdline import ParseArgs
|
||||
|
||||
RS_DEBUGGER_NONE = 0 # Don't run under the debugger.
|
||||
RS_DEBUGGER_STEP = 1 # Start stepping under the debugger
|
||||
RS_DEBUGGER_GO = 2 # Just run under the debugger, stopping only at break-points.
|
||||
RS_DEBUGGER_PM = (
|
||||
3 # Don't run under debugger, but do post-mortem analysis on exception.
|
||||
)
|
||||
|
||||
debugging_options = """No debugging
|
||||
Step-through in the debugger
|
||||
Run in the debugger
|
||||
Post-Mortem of unhandled exceptions""".split("\n")
|
||||
|
||||
byte_cr = b"\r"
|
||||
byte_lf = b"\n"
|
||||
byte_crlf = b"\r\n"
|
||||
|
||||
|
||||
# A dialog box for the "Run Script" command.
|
||||
class DlgRunScript(dialog.Dialog):
|
||||
"A class for the 'run script' dialog"
|
||||
|
||||
def __init__(self, bHaveDebugger):
|
||||
dialog.Dialog.__init__(self, win32ui.IDD_RUN_SCRIPT)
|
||||
self.AddDDX(win32ui.IDC_EDIT1, "script")
|
||||
self.AddDDX(win32ui.IDC_EDIT2, "args")
|
||||
self.AddDDX(win32ui.IDC_COMBO1, "debuggingType", "i")
|
||||
self.HookCommand(self.OnBrowse, win32ui.IDC_BUTTON2)
|
||||
self.bHaveDebugger = bHaveDebugger
|
||||
|
||||
def OnInitDialog(self):
|
||||
rc = dialog.Dialog.OnInitDialog(self)
|
||||
cbo = self.GetDlgItem(win32ui.IDC_COMBO1)
|
||||
for o in debugging_options:
|
||||
cbo.AddString(o)
|
||||
cbo.SetCurSel(self["debuggingType"])
|
||||
if not self.bHaveDebugger:
|
||||
cbo.EnableWindow(0)
|
||||
|
||||
def OnBrowse(self, id, code):
|
||||
if code != 0: # BN_CLICKED
|
||||
return 1
|
||||
openFlags = win32con.OFN_OVERWRITEPROMPT | win32con.OFN_FILEMUSTEXIST
|
||||
dlg = win32ui.CreateFileDialog(
|
||||
1, None, None, openFlags, "Python Scripts (*.py)|*.py||", self
|
||||
)
|
||||
dlg.SetOFNTitle("Run Script")
|
||||
if dlg.DoModal() != win32con.IDOK:
|
||||
return 0
|
||||
self["script"] = dlg.GetPathName()
|
||||
self.UpdateData(0)
|
||||
return 0
|
||||
|
||||
|
||||
def GetDebugger():
|
||||
"""Get the default Python debugger. Returns the debugger, or None.
|
||||
|
||||
It is assumed the debugger has a standard "pdb" defined interface.
|
||||
Currently always returns the 'pywin.debugger' debugger, or None
|
||||
(pdb is _not_ returned as it is not effective in this GUI environment)
|
||||
"""
|
||||
try:
|
||||
import pywin.debugger
|
||||
|
||||
return pywin.debugger
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
|
||||
def IsOnPythonPath(path):
|
||||
"Given a path only, see if it is on the Pythonpath. Assumes path is a full path spec."
|
||||
# must check that the command line arg's path is in sys.path
|
||||
for syspath in sys.path:
|
||||
try:
|
||||
# sys.path can have an empty entry.
|
||||
if syspath and win32ui.FullPath(syspath) == path:
|
||||
return 1
|
||||
except win32ui.error as details:
|
||||
print(f"Warning: The sys.path entry '{syspath}' is invalid\n{details}")
|
||||
return 0
|
||||
|
||||
|
||||
def GetPackageModuleName(fileName):
|
||||
r"""Given a filename, return (module name, new path).
|
||||
eg - given "c:\a\b\c\my.py", return ("b.c.my",None) if "c:\a" is on sys.path.
|
||||
If no package found, will return ("my", "c:\a\b\c")
|
||||
"""
|
||||
path, fname = os.path.split(fileName)
|
||||
path = origPath = win32ui.FullPath(path)
|
||||
fname = os.path.splitext(fname)[0]
|
||||
modBits = []
|
||||
newPathReturn = None
|
||||
if not IsOnPythonPath(path):
|
||||
# Module not directly on the search path - see if under a package.
|
||||
while len(path) > 3: # ie 'C:\'
|
||||
path, modBit = os.path.split(path)
|
||||
modBits.append(modBit)
|
||||
# If on path, _and_ existing package of that name loaded.
|
||||
if (
|
||||
IsOnPythonPath(path)
|
||||
and modBit in sys.modules
|
||||
and (
|
||||
os.path.exists(os.path.join(path, modBit, "__init__.py"))
|
||||
or os.path.exists(os.path.join(path, modBit, "__init__.pyc"))
|
||||
or os.path.exists(os.path.join(path, modBit, "__init__.pyo"))
|
||||
)
|
||||
):
|
||||
modBits.reverse()
|
||||
return ".".join(modBits) + "." + fname, newPathReturn
|
||||
# Not found - look a level higher
|
||||
else:
|
||||
newPathReturn = origPath
|
||||
|
||||
return fname, newPathReturn
|
||||
|
||||
|
||||
def GetActiveView():
|
||||
"""Gets the edit control (eg, EditView) with the focus, or None"""
|
||||
try:
|
||||
childFrame, bIsMaximised = win32ui.GetMainFrame().MDIGetActive()
|
||||
return childFrame.GetActiveView()
|
||||
except win32ui.error:
|
||||
return None
|
||||
|
||||
|
||||
def GetActiveEditControl():
|
||||
view = GetActiveView()
|
||||
if view is None:
|
||||
return None
|
||||
if hasattr(view, "SCIAddText"): # Is it a scintilla control?
|
||||
return view
|
||||
try:
|
||||
return view.GetRichEditCtrl()
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
return view.GetEditCtrl()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
def GetActiveEditorDocument():
|
||||
"""Returns the active editor document and view, or (None,None) if no
|
||||
active document or it's not an editor document.
|
||||
"""
|
||||
view = GetActiveView()
|
||||
if view is None or isinstance(view, TreeView):
|
||||
return (None, None)
|
||||
doc = view.GetDocument()
|
||||
if hasattr(doc, "MarkerAdd"): # Is it an Editor document?
|
||||
return doc, view
|
||||
return (None, None)
|
||||
|
||||
|
||||
def GetActiveFileName(bAutoSave=1):
|
||||
"""Gets the file name for the active frame, saving it if necessary.
|
||||
|
||||
Returns None if it can't be found, or raises KeyboardInterrupt.
|
||||
"""
|
||||
pathName = None
|
||||
active = GetActiveView()
|
||||
if active is None:
|
||||
return None
|
||||
try:
|
||||
doc = active.GetDocument()
|
||||
pathName = doc.GetPathName()
|
||||
|
||||
if bAutoSave and (
|
||||
len(pathName) > 0
|
||||
or doc.GetTitle()[:8] == "Untitled"
|
||||
or doc.GetTitle()[:6] == "Script"
|
||||
): # if not a special purpose window
|
||||
if doc.IsModified():
|
||||
try:
|
||||
doc.OnSaveDocument(pathName)
|
||||
pathName = doc.GetPathName()
|
||||
|
||||
# clear the linecache buffer
|
||||
linecache.clearcache()
|
||||
|
||||
except win32ui.error:
|
||||
raise KeyboardInterrupt
|
||||
|
||||
except (win32ui.error, AttributeError):
|
||||
pass
|
||||
if not pathName:
|
||||
return None
|
||||
return pathName
|
||||
|
||||
|
||||
lastScript = ""
|
||||
lastArgs = ""
|
||||
lastDebuggingType = RS_DEBUGGER_NONE
|
||||
|
||||
|
||||
def RunScript(defName=None, defArgs=None, bShowDialog=1, debuggingType=None):
|
||||
global lastScript, lastArgs, lastDebuggingType
|
||||
_debugger_stop_frame_ = 1 # Magic variable so the debugger will hide me!
|
||||
|
||||
# Get the debugger - may be None!
|
||||
debugger = GetDebugger()
|
||||
|
||||
if defName is None:
|
||||
try:
|
||||
pathName = GetActiveFileName()
|
||||
except KeyboardInterrupt:
|
||||
return # User cancelled save.
|
||||
else:
|
||||
pathName = defName
|
||||
if not pathName:
|
||||
pathName = lastScript
|
||||
if defArgs is None:
|
||||
args = ""
|
||||
if pathName == lastScript:
|
||||
args = lastArgs
|
||||
else:
|
||||
args = defArgs
|
||||
if debuggingType is None:
|
||||
debuggingType = lastDebuggingType
|
||||
|
||||
if not pathName or bShowDialog:
|
||||
dlg = DlgRunScript(debugger is not None)
|
||||
dlg["script"] = pathName
|
||||
dlg["args"] = args
|
||||
dlg["debuggingType"] = debuggingType
|
||||
if dlg.DoModal() != win32con.IDOK:
|
||||
return
|
||||
script = dlg["script"]
|
||||
args = dlg["args"]
|
||||
debuggingType = dlg["debuggingType"]
|
||||
if not script:
|
||||
return
|
||||
if debuggingType == RS_DEBUGGER_GO and debugger is not None:
|
||||
# This may surprise users - they select "Run under debugger", but
|
||||
# it appears not to! Only warn when they pick from the dialog!
|
||||
# First - ensure the debugger is activated to pickup any break-points
|
||||
# set in the editor.
|
||||
try:
|
||||
# Create the debugger, but _dont_ init the debugger GUI.
|
||||
rd = debugger._GetCurrentDebugger()
|
||||
except AttributeError:
|
||||
rd = None
|
||||
if rd is not None and len(rd.breaks) == 0:
|
||||
msg = "There are no active break-points.\r\n\r\nSelecting this debug option without any\r\nbreak-points is unlikely to have the desired effect\r\nas the debugger is unlikely to be invoked..\r\n\r\nWould you like to step-through in the debugger instead?"
|
||||
rc = win32ui.MessageBox(
|
||||
msg,
|
||||
win32ui.LoadString(win32ui.IDR_DEBUGGER),
|
||||
win32con.MB_YESNOCANCEL | win32con.MB_ICONINFORMATION,
|
||||
)
|
||||
if rc == win32con.IDCANCEL:
|
||||
return
|
||||
if rc == win32con.IDYES:
|
||||
debuggingType = RS_DEBUGGER_STEP
|
||||
|
||||
lastDebuggingType = debuggingType
|
||||
lastScript = script
|
||||
lastArgs = args
|
||||
else:
|
||||
script = pathName
|
||||
|
||||
# try and open the script.
|
||||
if (
|
||||
len(os.path.splitext(script)[1]) == 0
|
||||
): # check if no extension supplied, and give one.
|
||||
script += ".py"
|
||||
# If no path specified, try and locate the file
|
||||
path, fnameonly = os.path.split(script)
|
||||
if len(path) == 0:
|
||||
try:
|
||||
os.stat(fnameonly) # See if it is OK as is...
|
||||
script = fnameonly
|
||||
except OSError:
|
||||
fullScript = LocatePythonFile(script)
|
||||
if fullScript is None:
|
||||
win32ui.MessageBox("The file '%s' can not be located" % script)
|
||||
return
|
||||
script = fullScript
|
||||
else:
|
||||
path = win32ui.FullPath(path)
|
||||
if not IsOnPythonPath(path):
|
||||
sys.path.append(path)
|
||||
|
||||
# py3k fun: If we use text mode to open the file, we get \r\n
|
||||
# translated so Python allows the syntax (good!), but we get back
|
||||
# text already decoded from the default encoding (bad!) and Python
|
||||
# ignores any encoding decls (bad!). If we use binary mode we get
|
||||
# the raw bytes and Python looks at the encoding (good!) but \r\n
|
||||
# chars stay in place so Python throws a syntax error (bad!).
|
||||
# So: do the binary thing and manually normalize \r\n.
|
||||
try:
|
||||
f = open(script, "rb")
|
||||
except OSError as exc:
|
||||
win32ui.MessageBox(
|
||||
"The file could not be opened - %s (%d)" % (exc.strerror, exc.errno)
|
||||
)
|
||||
return
|
||||
|
||||
# Get the source-code - as above, normalize \r\n
|
||||
code = f.read().replace(byte_crlf, byte_lf).replace(byte_cr, byte_lf) + byte_lf
|
||||
|
||||
# Remember and hack sys.argv for the script.
|
||||
oldArgv = sys.argv
|
||||
sys.argv = ParseArgs(args)
|
||||
sys.argv.insert(0, script)
|
||||
# sys.path[0] is the path of the script
|
||||
oldPath0 = sys.path[0]
|
||||
newPath0 = os.path.split(script)[0]
|
||||
if not oldPath0: # if sys.path[0] is empty
|
||||
sys.path[0] = newPath0
|
||||
insertedPath0 = 0
|
||||
else:
|
||||
sys.path.insert(0, newPath0)
|
||||
insertedPath0 = 1
|
||||
bWorked = 0
|
||||
win32ui.DoWaitCursor(1)
|
||||
base = os.path.split(script)[1]
|
||||
# Allow windows to repaint before starting.
|
||||
win32ui.PumpWaitingMessages()
|
||||
win32ui.SetStatusText("Running script %s..." % base, 1)
|
||||
exitCode = 0
|
||||
from pywin.framework import interact
|
||||
|
||||
# Check the debugger flags
|
||||
if debugger is None and (debuggingType != RS_DEBUGGER_NONE):
|
||||
win32ui.MessageBox(
|
||||
"No debugger is installed. Debugging options have been ignored!"
|
||||
)
|
||||
debuggingType = RS_DEBUGGER_NONE
|
||||
|
||||
# Get a code object - ignore the debugger for this, as it is probably a syntax error
|
||||
# at this point
|
||||
try:
|
||||
codeObject = compile(code, script, "exec")
|
||||
except:
|
||||
# Almost certainly a syntax error!
|
||||
_HandlePythonFailure("run script", script)
|
||||
# No code object which to run/debug.
|
||||
return
|
||||
__main__.__file__ = script
|
||||
try:
|
||||
if debuggingType == RS_DEBUGGER_STEP:
|
||||
debugger.run(codeObject, __main__.__dict__, start_stepping=1)
|
||||
elif debuggingType == RS_DEBUGGER_GO:
|
||||
debugger.run(codeObject, __main__.__dict__, start_stepping=0)
|
||||
else:
|
||||
# Post mortem or no debugging
|
||||
exec(codeObject, __main__.__dict__)
|
||||
bWorked = 1
|
||||
except bdb.BdbQuit:
|
||||
# Don't print tracebacks when the debugger quit, but do print a message.
|
||||
print("Debugging session cancelled.")
|
||||
exitCode = 1
|
||||
bWorked = 1
|
||||
except SystemExit as code:
|
||||
exitCode = code
|
||||
bWorked = 1
|
||||
except KeyboardInterrupt:
|
||||
# Consider this successful, as we don't want the debugger.
|
||||
# (but we do want a traceback!)
|
||||
if interact.edit and interact.edit.currentView:
|
||||
interact.edit.currentView.EnsureNoPrompt()
|
||||
traceback.print_exc()
|
||||
if interact.edit and interact.edit.currentView:
|
||||
interact.edit.currentView.AppendToPrompt([])
|
||||
bWorked = 1
|
||||
except:
|
||||
if interact.edit and interact.edit.currentView:
|
||||
interact.edit.currentView.EnsureNoPrompt()
|
||||
traceback.print_exc()
|
||||
if interact.edit and interact.edit.currentView:
|
||||
interact.edit.currentView.AppendToPrompt([])
|
||||
if debuggingType == RS_DEBUGGER_PM:
|
||||
debugger.pm()
|
||||
del __main__.__file__
|
||||
sys.argv = oldArgv
|
||||
if insertedPath0:
|
||||
del sys.path[0]
|
||||
else:
|
||||
sys.path[0] = oldPath0
|
||||
f.close()
|
||||
if bWorked:
|
||||
win32ui.SetStatusText(f"Script '{script}' returned exit code {exitCode}")
|
||||
else:
|
||||
win32ui.SetStatusText("Exception raised while running script %s" % base)
|
||||
try:
|
||||
sys.stdout.flush()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
win32ui.DoWaitCursor(0)
|
||||
|
||||
|
||||
def ImportFile():
|
||||
"""This code looks for the current window, and determines if it can be imported. If not,
|
||||
it will prompt for a file name, and allow it to be imported."""
|
||||
try:
|
||||
pathName = GetActiveFileName()
|
||||
except KeyboardInterrupt:
|
||||
pathName = None
|
||||
|
||||
if pathName is not None:
|
||||
if os.path.splitext(pathName)[1].lower() not in (".py", ".pyw", ".pyx"):
|
||||
pathName = None
|
||||
|
||||
if pathName is None:
|
||||
openFlags = win32con.OFN_OVERWRITEPROMPT | win32con.OFN_FILEMUSTEXIST
|
||||
dlg = win32ui.CreateFileDialog(
|
||||
1, None, None, openFlags, "Python Scripts (*.py;*.pyw)|*.py;*.pyw;*.pyx||"
|
||||
)
|
||||
dlg.SetOFNTitle("Import Script")
|
||||
if dlg.DoModal() != win32con.IDOK:
|
||||
return 0
|
||||
|
||||
pathName = dlg.GetPathName()
|
||||
|
||||
# If already imported, don't look for package
|
||||
path, modName = os.path.split(pathName)
|
||||
modName, modExt = os.path.splitext(modName)
|
||||
newPath = None
|
||||
# note that some packages (*cough* email *cough*) use "lazy importers"
|
||||
# meaning sys.modules can change as a side-effect of looking at
|
||||
# module.__file__ - so we must take a copy (ie, list(items()))
|
||||
for key, mod in sys.modules.items():
|
||||
if getattr(mod, "__file__", None):
|
||||
fname = mod.__file__
|
||||
base, ext = os.path.splitext(fname)
|
||||
if ext.lower() in (".pyo", ".pyc"):
|
||||
ext = ".py"
|
||||
fname = base + ext
|
||||
if win32ui.ComparePath(fname, pathName):
|
||||
modName = key
|
||||
break
|
||||
else: # for not broken
|
||||
modName, newPath = GetPackageModuleName(pathName)
|
||||
if newPath:
|
||||
sys.path.append(newPath)
|
||||
|
||||
if modName in sys.modules:
|
||||
bNeedReload = 1
|
||||
what = "reload"
|
||||
else:
|
||||
what = "import"
|
||||
bNeedReload = 0
|
||||
|
||||
win32ui.SetStatusText(what.capitalize() + "ing module...", 1)
|
||||
win32ui.DoWaitCursor(1)
|
||||
# win32ui.GetMainFrame().BeginWaitCursor()
|
||||
|
||||
try:
|
||||
# always do an import, as it is cheap if it's already loaded. This ensures
|
||||
# it is in our name space.
|
||||
codeObj = compile("import " + modName, "<auto import>", "exec")
|
||||
except SyntaxError:
|
||||
win32ui.SetStatusText('Invalid filename for import: "' + modName + '"')
|
||||
return
|
||||
try:
|
||||
exec(codeObj, __main__.__dict__)
|
||||
mod = sys.modules.get(modName)
|
||||
if bNeedReload:
|
||||
from importlib import reload
|
||||
|
||||
mod = reload(sys.modules[modName])
|
||||
win32ui.SetStatusText(
|
||||
"Successfully "
|
||||
+ what
|
||||
+ "ed module '"
|
||||
+ modName
|
||||
+ "': %s" % getattr(mod, "__file__", "<unkown file>")
|
||||
)
|
||||
except:
|
||||
_HandlePythonFailure(what)
|
||||
win32ui.DoWaitCursor(0)
|
||||
|
||||
|
||||
def CheckFile():
|
||||
"""This code looks for the current window, and gets Python to check it
|
||||
without actually executing any code (ie, by compiling only)
|
||||
"""
|
||||
try:
|
||||
pathName = GetActiveFileName()
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
|
||||
what = "check"
|
||||
win32ui.SetStatusText(what.capitalize() + "ing module...", 1)
|
||||
win32ui.DoWaitCursor(1)
|
||||
try:
|
||||
f = open(pathName)
|
||||
except OSError as details:
|
||||
print(f"Can't open file '{pathName}' - {details}")
|
||||
return
|
||||
try:
|
||||
code = f.read() + "\n"
|
||||
finally:
|
||||
f.close()
|
||||
try:
|
||||
codeObj = compile(code, pathName, "exec")
|
||||
if RunTabNanny(pathName):
|
||||
win32ui.SetStatusText(
|
||||
"Python and the TabNanny successfully checked the file '"
|
||||
+ os.path.basename(pathName)
|
||||
+ "'"
|
||||
)
|
||||
except SyntaxError:
|
||||
_HandlePythonFailure(what, pathName)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
_HandlePythonFailure(what)
|
||||
win32ui.DoWaitCursor(0)
|
||||
|
||||
|
||||
def RunTabNanny(filename):
|
||||
import io
|
||||
|
||||
tabnanny = FindTabNanny()
|
||||
if tabnanny is None:
|
||||
win32ui.MessageBox("The TabNanny is not around, so the children can run amok!")
|
||||
return
|
||||
|
||||
# Capture the tab-nanny output
|
||||
newout = io.StringIO()
|
||||
old_out = sys.stderr, sys.stdout
|
||||
sys.stderr = sys.stdout = newout
|
||||
try:
|
||||
tabnanny.check(filename)
|
||||
finally:
|
||||
# Restore output
|
||||
sys.stderr, sys.stdout = old_out
|
||||
data = newout.getvalue()
|
||||
if data:
|
||||
try:
|
||||
lineno = data.split()[1]
|
||||
lineno = int(lineno)
|
||||
_JumpToPosition(filename, lineno)
|
||||
try: # Try and display whitespace
|
||||
GetActiveEditControl().SCISetViewWS(1)
|
||||
except:
|
||||
pass
|
||||
win32ui.SetStatusText("The TabNanny found trouble at line %d" % lineno)
|
||||
except (IndexError, TypeError, ValueError):
|
||||
print("The tab nanny complained, but I can't see where!")
|
||||
print(data)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def _JumpToPosition(fileName, lineno, col=1):
|
||||
JumpToDocument(fileName, lineno, col)
|
||||
|
||||
|
||||
def JumpToDocument(fileName, lineno=0, col=1, nChars=0, bScrollToTop=0):
|
||||
# Jump to the position in a file.
|
||||
# If lineno is <= 0, don't move the position - just open/restore.
|
||||
# if nChars > 0, select that many characters.
|
||||
# if bScrollToTop, the specified line will be moved to the top of the window
|
||||
# (eg, bScrollToTop should be false when jumping to an error line to retain the
|
||||
# context, but true when jumping to a method defn, where we want the full body.
|
||||
# Return the view which is editing the file, or None on error.
|
||||
doc = win32ui.GetApp().OpenDocumentFile(fileName)
|
||||
if doc is None:
|
||||
return None
|
||||
frame = doc.GetFirstView().GetParentFrame()
|
||||
try:
|
||||
view = frame.GetEditorView()
|
||||
if frame.GetActiveView() != view:
|
||||
frame.SetActiveView(view)
|
||||
frame.AutoRestore()
|
||||
except AttributeError: # Not an editor frame??
|
||||
view = doc.GetFirstView()
|
||||
if lineno > 0:
|
||||
charNo = view.LineIndex(lineno - 1)
|
||||
start = charNo + col - 1
|
||||
size = view.GetTextLength()
|
||||
try:
|
||||
view.EnsureCharsVisible(charNo)
|
||||
except AttributeError:
|
||||
print("Doesn't appear to be one of our views?")
|
||||
view.SetSel(min(start, size), min(start + nChars, size))
|
||||
if bScrollToTop:
|
||||
curTop = view.GetFirstVisibleLine()
|
||||
nScroll = (lineno - 1) - curTop
|
||||
view.LineScroll(nScroll, 0)
|
||||
view.SetFocus()
|
||||
return view
|
||||
|
||||
|
||||
def _HandlePythonFailure(what, syntaxErrorPathName=None):
|
||||
typ, details, tb = sys.exc_info()
|
||||
if isinstance(details, SyntaxError):
|
||||
try:
|
||||
msg, (fileName, line, col, text) = details
|
||||
if (not fileName or fileName == "<string>") and syntaxErrorPathName:
|
||||
fileName = syntaxErrorPathName
|
||||
_JumpToPosition(fileName, line, col)
|
||||
except (TypeError, ValueError):
|
||||
msg = str(details)
|
||||
win32ui.SetStatusText(f"Failed to {what} - syntax error - {msg}")
|
||||
else:
|
||||
traceback.print_exc()
|
||||
win32ui.SetStatusText(f"Failed to {what} - {details}")
|
||||
tb = None # Clean up a cycle.
|
||||
|
||||
|
||||
# Find the Python TabNanny in either the standard library or the Python Tools/Scripts directory.
|
||||
def FindTabNanny():
|
||||
try:
|
||||
return __import__("tabnanny")
|
||||
except ImportError:
|
||||
pass
|
||||
# OK - not in the standard library - go looking.
|
||||
filename = "tabnanny.py"
|
||||
try:
|
||||
path = win32api.RegQueryValue(
|
||||
win32con.HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Python\\PythonCore\\%s\\InstallPath" % (sys.winver),
|
||||
)
|
||||
except win32api.error:
|
||||
print("WARNING - The Python registry does not have an 'InstallPath' setting")
|
||||
print(" The file '%s' can not be located" % (filename))
|
||||
return None
|
||||
fname = os.path.join(path, "Tools\\Scripts\\%s" % filename)
|
||||
try:
|
||||
os.stat(fname)
|
||||
except OSError:
|
||||
print(f"WARNING - The file '{filename}' can not be located in path '{path}'")
|
||||
return None
|
||||
|
||||
tabnannyhome, tabnannybase = os.path.split(fname)
|
||||
tabnannybase = os.path.splitext(tabnannybase)[0]
|
||||
# Put tab nanny at the top of the path.
|
||||
sys.path.insert(0, tabnannyhome)
|
||||
try:
|
||||
return __import__(tabnannybase)
|
||||
finally:
|
||||
# remove the tab-nanny from the path
|
||||
del sys.path[0]
|
||||
|
||||
|
||||
def LocatePythonFile(fileName, bBrowseIfDir=1):
|
||||
"Given a file name, return a fully qualified file name, or None"
|
||||
# first look for the exact file as specified
|
||||
if not os.path.isfile(fileName):
|
||||
# Go looking!
|
||||
baseName = fileName
|
||||
for path in sys.path:
|
||||
fileName = os.path.abspath(os.path.join(path, baseName))
|
||||
if os.path.isdir(fileName):
|
||||
if bBrowseIfDir:
|
||||
d = win32ui.CreateFileDialog(
|
||||
1, "*.py", None, 0, "Python Files (*.py)|*.py|All files|*.*"
|
||||
)
|
||||
d.SetOFNInitialDir(fileName)
|
||||
rc = d.DoModal()
|
||||
if rc == win32con.IDOK:
|
||||
fileName = d.GetPathName()
|
||||
break
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
fileName += ".py"
|
||||
if os.path.isfile(fileName):
|
||||
break # Found it!
|
||||
|
||||
else: # for not broken out of
|
||||
return None
|
||||
return win32ui.FullPath(fileName)
|
||||
@@ -0,0 +1,749 @@
|
||||
# SGrepMDI is by Gordon McMillan (gmcm@hypernet.com)
|
||||
# It does basically what Find In Files does in MSVC with a couple enhancements.
|
||||
# - It saves any directories in the app's ini file (if you want to get rid
|
||||
# of them you'll have to edit the file)
|
||||
# - "Directories" can be directories,
|
||||
# - semicolon separated lists of "directories",
|
||||
# - environment variables that evaluate to "directories",
|
||||
# - registry path names that evaluate to "directories",
|
||||
# - all of which is recursive, so you can mix them all up.
|
||||
# - It is MDI, so you can 'nest' greps and return to earlier ones,
|
||||
# (ie, have multiple results open at the same time)
|
||||
# - Like FIF, double clicking a line opens an editor and takes you to the line.
|
||||
# - You can highlight text, right click and start a new grep with the selected
|
||||
# text as search pattern and same directories etc as before.
|
||||
# - You can save grep parameters (so you don't lose your hardearned pattern)
|
||||
# from File|Save
|
||||
# - You can save grep results by right clicking in the result window.
|
||||
# Hats off to Mark Hammond for providing an environment where I could cobble
|
||||
# something like this together in a couple evenings!
|
||||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog, docview, window
|
||||
|
||||
from . import scriptutils
|
||||
|
||||
|
||||
def getsubdirs(d):
|
||||
dlist = []
|
||||
flist = glob.glob(d + "\\*")
|
||||
for f in flist:
|
||||
if os.path.isdir(f):
|
||||
dlist.append(f)
|
||||
dlist += getsubdirs(f)
|
||||
return dlist
|
||||
|
||||
|
||||
class dirpath:
|
||||
def __init__(self, str, recurse=0):
|
||||
dp = str.split(";")
|
||||
dirs = {}
|
||||
for d in dp:
|
||||
if os.path.isdir(d):
|
||||
d = d.lower()
|
||||
if d not in dirs:
|
||||
dirs[d] = None
|
||||
if recurse:
|
||||
subdirs = getsubdirs(d)
|
||||
for sd in subdirs:
|
||||
sd = sd.lower()
|
||||
if sd not in dirs:
|
||||
dirs[sd] = None
|
||||
elif os.path.isfile(d):
|
||||
pass
|
||||
else:
|
||||
x = None
|
||||
if d in os.environ:
|
||||
x = dirpath(os.environ[d])
|
||||
elif d[:5] == "HKEY_":
|
||||
keystr = d.split("\\")
|
||||
try:
|
||||
root = eval("win32con." + keystr[0])
|
||||
except:
|
||||
win32ui.MessageBox(
|
||||
"Can't interpret registry key name '%s'" % keystr[0]
|
||||
)
|
||||
try:
|
||||
subkey = "\\".join(keystr[1:])
|
||||
val = win32api.RegQueryValue(root, subkey)
|
||||
if val:
|
||||
x = dirpath(val)
|
||||
else:
|
||||
win32ui.MessageBox(
|
||||
"Registry path '%s' did not return a path entry" % d
|
||||
)
|
||||
except:
|
||||
win32ui.MessageBox(
|
||||
"Can't interpret registry key value: %s" % keystr[1:]
|
||||
)
|
||||
else:
|
||||
win32ui.MessageBox("Directory '%s' not found" % d)
|
||||
if x:
|
||||
for xd in x:
|
||||
if xd not in dirs:
|
||||
dirs[xd] = None
|
||||
if recurse:
|
||||
subdirs = getsubdirs(xd)
|
||||
for sd in subdirs:
|
||||
sd = sd.lower()
|
||||
if sd not in dirs:
|
||||
dirs[sd] = None
|
||||
self.dirs = list(dirs)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.dirs[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.dirs)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.dirs[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.dirs[key]
|
||||
|
||||
def __getslice__(self, lo, hi):
|
||||
return self.dirs[lo:hi]
|
||||
|
||||
def __setslice__(self, lo, hi, seq):
|
||||
self.dirs[lo:hi] = seq
|
||||
|
||||
def __delslice__(self, lo, hi):
|
||||
del self.dirs[lo:hi]
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, (dirpath, list)):
|
||||
return self.dirs + other.dirs
|
||||
|
||||
def __radd__(self, other):
|
||||
if isinstance(other, (dirpath, list)):
|
||||
return other.dirs + self.dirs
|
||||
|
||||
|
||||
# Group(1) is the filename, group(2) is the lineno.
|
||||
regexGrep = re.compile(r"^([a-zA-Z]:[^(]*)\(([0-9]+)\)")
|
||||
|
||||
# these are the atom numbers defined by Windows for basic dialog controls
|
||||
|
||||
BUTTON = 0x80
|
||||
EDIT = 0x81
|
||||
STATIC = 0x82
|
||||
LISTBOX = 0x83
|
||||
SCROLLBAR = 0x84
|
||||
COMBOBOX = 0x85
|
||||
|
||||
|
||||
class GrepTemplate(docview.RichEditDocTemplate):
|
||||
def __init__(self):
|
||||
docview.RichEditDocTemplate.__init__(
|
||||
self, win32ui.IDR_TEXTTYPE, GrepDocument, GrepFrame, GrepView
|
||||
)
|
||||
self.SetDocStrings("\nGrep\nGrep\nGrep params (*.grep)\n.grep\n\n\n")
|
||||
win32ui.GetApp().AddDocTemplate(self)
|
||||
self.docparams = None
|
||||
|
||||
def MatchDocType(self, fileName, fileType):
|
||||
doc = self.FindOpenDocument(fileName)
|
||||
if doc:
|
||||
return doc
|
||||
ext = os.path.splitext(fileName)[1].lower()
|
||||
if ext == ".grep":
|
||||
return win32ui.CDocTemplate_Confidence_yesAttemptNative
|
||||
return win32ui.CDocTemplate_Confidence_noAttempt
|
||||
|
||||
def setParams(self, params):
|
||||
self.docparams = params
|
||||
|
||||
def readParams(self):
|
||||
tmp = self.docparams
|
||||
self.docparams = None
|
||||
return tmp
|
||||
|
||||
|
||||
class GrepFrame(window.MDIChildWnd):
|
||||
# The template and doc params will one day be removed.
|
||||
def __init__(self, wnd=None):
|
||||
window.MDIChildWnd.__init__(self, wnd)
|
||||
|
||||
|
||||
class GrepDocument(docview.RichEditDoc):
|
||||
def __init__(self, template):
|
||||
docview.RichEditDoc.__init__(self, template)
|
||||
self.dirpattern = ""
|
||||
self.filpattern = ""
|
||||
self.greppattern = ""
|
||||
self.casesensitive = 1
|
||||
self.recurse = 1
|
||||
self.verbose = 0
|
||||
|
||||
def OnOpenDocument(self, fnm):
|
||||
# this bizarre stuff with params is so right clicking in a result window
|
||||
# and starting a new grep can communicate the default parameters to the
|
||||
# new grep.
|
||||
try:
|
||||
params = open(fnm, "r").read()
|
||||
except:
|
||||
params = None
|
||||
self.setInitParams(params)
|
||||
return self.OnNewDocument()
|
||||
|
||||
def OnCloseDocument(self):
|
||||
try:
|
||||
win32ui.GetApp().DeleteIdleHandler(self.SearchFile)
|
||||
except:
|
||||
pass
|
||||
return self._obj_.OnCloseDocument()
|
||||
|
||||
def saveInitParams(self):
|
||||
# Only save the flags, not the text boxes.
|
||||
paramstr = "\t%s\t\t%d\t%d" % (
|
||||
self.filpattern,
|
||||
self.casesensitive,
|
||||
self.recurse,
|
||||
)
|
||||
win32ui.WriteProfileVal("Grep", "Params", paramstr)
|
||||
|
||||
def setInitParams(self, paramstr):
|
||||
if paramstr is None:
|
||||
paramstr = win32ui.GetProfileVal("Grep", "Params", "\t\t\t1\t0\t0")
|
||||
params = paramstr.split("\t")
|
||||
if len(params) < 3:
|
||||
params.extend([""] * (3 - len(params)))
|
||||
if len(params) < 6:
|
||||
params.extend([0] * (6 - len(params)))
|
||||
self.dirpattern = params[0]
|
||||
self.filpattern = params[1]
|
||||
self.greppattern = params[2]
|
||||
self.casesensitive = int(params[3])
|
||||
self.recurse = int(params[4])
|
||||
self.verbose = int(params[5])
|
||||
# setup some reasonable defaults.
|
||||
if not self.dirpattern:
|
||||
try:
|
||||
editor = win32ui.GetMainFrame().MDIGetActive()[0].GetEditorView()
|
||||
self.dirpattern = os.path.abspath(
|
||||
os.path.dirname(editor.GetDocument().GetPathName())
|
||||
)
|
||||
except (AttributeError, win32ui.error):
|
||||
self.dirpattern = os.getcwd()
|
||||
if not self.filpattern:
|
||||
self.filpattern = "*.py"
|
||||
|
||||
def OnNewDocument(self):
|
||||
if self.dirpattern == "":
|
||||
self.setInitParams(greptemplate.readParams())
|
||||
d = GrepDialog(
|
||||
self.dirpattern,
|
||||
self.filpattern,
|
||||
self.greppattern,
|
||||
self.casesensitive,
|
||||
self.recurse,
|
||||
self.verbose,
|
||||
)
|
||||
if d.DoModal() == win32con.IDOK:
|
||||
self.dirpattern = d["dirpattern"]
|
||||
self.filpattern = d["filpattern"]
|
||||
self.greppattern = d["greppattern"]
|
||||
self.casesensitive = d["casesensitive"]
|
||||
self.recurse = d["recursive"]
|
||||
self.verbose = d["verbose"]
|
||||
self.doSearch()
|
||||
self.saveInitParams()
|
||||
return 1
|
||||
return 0 # cancelled - return zero to stop frame creation.
|
||||
|
||||
def doSearch(self):
|
||||
self.dp = dirpath(self.dirpattern, self.recurse)
|
||||
self.SetTitle(f"Grep for {self.greppattern} in {self.filpattern}")
|
||||
# self.text = []
|
||||
self.GetFirstView().Append(f"#Search {self.dirpattern}\n")
|
||||
if self.verbose:
|
||||
self.GetFirstView().Append(f"# ={self.dp.dirs!r}\n")
|
||||
self.GetFirstView().Append(f"# Files {self.filpattern}\n")
|
||||
self.GetFirstView().Append(f"# For {self.greppattern}\n")
|
||||
self.fplist = self.filpattern.split(";")
|
||||
if self.casesensitive:
|
||||
self.pat = re.compile(self.greppattern)
|
||||
else:
|
||||
self.pat = re.compile(self.greppattern, re.IGNORECASE)
|
||||
win32ui.SetStatusText("Searching. Please wait...", 0)
|
||||
self.dpndx = self.fpndx = 0
|
||||
self.fndx = -1
|
||||
if not self.dp:
|
||||
self.GetFirstView().Append(
|
||||
"# ERROR: '%s' does not resolve to any search locations"
|
||||
% self.dirpattern
|
||||
)
|
||||
self.SetModifiedFlag(0)
|
||||
else:
|
||||
self.flist = glob.glob(self.dp[0] + "\\" + self.fplist[0])
|
||||
win32ui.GetApp().AddIdleHandler(self.SearchFile)
|
||||
|
||||
def SearchFile(self, handler, count):
|
||||
self.fndx += 1
|
||||
if self.fndx < len(self.flist):
|
||||
f = self.flist[self.fndx]
|
||||
if self.verbose:
|
||||
self.GetFirstView().Append("# .." + f + "\n")
|
||||
# Directories may match the file type pattern, and files may be removed
|
||||
# while grep is running
|
||||
if os.path.isfile(f):
|
||||
win32ui.SetStatusText("Searching " + f, 0)
|
||||
lines = open(f, "r").readlines()
|
||||
for i in range(len(lines)):
|
||||
line = lines[i]
|
||||
if self.pat.search(line) is not None:
|
||||
self.GetFirstView().Append(f"{f} ({i + 1!r}) {line}")
|
||||
else:
|
||||
self.fndx = -1
|
||||
self.fpndx += 1
|
||||
if self.fpndx < len(self.fplist):
|
||||
self.flist = glob.glob(
|
||||
self.dp[self.dpndx] + "\\" + self.fplist[self.fpndx]
|
||||
)
|
||||
else:
|
||||
self.fpndx = 0
|
||||
self.dpndx += 1
|
||||
if self.dpndx < len(self.dp):
|
||||
self.flist = glob.glob(
|
||||
self.dp[self.dpndx] + "\\" + self.fplist[self.fpndx]
|
||||
)
|
||||
else:
|
||||
win32ui.SetStatusText("Search complete.", 0)
|
||||
self.SetModifiedFlag(0) # default to not modified.
|
||||
try:
|
||||
win32ui.GetApp().DeleteIdleHandler(self.SearchFile)
|
||||
except:
|
||||
pass
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def GetParams(self):
|
||||
return "{}\t{}\t{}\t{!r}\t{!r}\t{!r}".format(
|
||||
self.dirpattern,
|
||||
self.filpattern,
|
||||
self.greppattern,
|
||||
self.casesensitive,
|
||||
self.recurse,
|
||||
self.verbose,
|
||||
)
|
||||
|
||||
def OnSaveDocument(self, filename):
|
||||
# print("OnSaveDocument() filename=", filename)
|
||||
savefile = open(filename, "wb")
|
||||
txt = self.GetParams() + "\n"
|
||||
# print("writing", txt)
|
||||
savefile.write(txt)
|
||||
savefile.close()
|
||||
self.SetModifiedFlag(0)
|
||||
return 1
|
||||
|
||||
|
||||
ID_OPEN_FILE = 0xE400
|
||||
ID_GREP = 0xE401
|
||||
ID_SAVERESULTS = 0x402
|
||||
ID_TRYAGAIN = 0x403
|
||||
|
||||
|
||||
class GrepView(docview.RichEditView):
|
||||
def __init__(self, doc):
|
||||
docview.RichEditView.__init__(self, doc)
|
||||
self.SetWordWrap(win32ui.CRichEditView_WrapNone)
|
||||
self.HookHandlers()
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
rc = self._obj_.OnInitialUpdate()
|
||||
format = (-402653169, 0, 200, 0, 0, 0, 49, "Courier New")
|
||||
self.SetDefaultCharFormat(format)
|
||||
return rc
|
||||
|
||||
def HookHandlers(self):
|
||||
self.HookMessage(self.OnRClick, win32con.WM_RBUTTONDOWN)
|
||||
self.HookCommand(self.OnCmdOpenFile, ID_OPEN_FILE)
|
||||
self.HookCommand(self.OnCmdGrep, ID_GREP)
|
||||
self.HookCommand(self.OnCmdSave, ID_SAVERESULTS)
|
||||
self.HookCommand(self.OnTryAgain, ID_TRYAGAIN)
|
||||
self.HookMessage(self.OnLDblClick, win32con.WM_LBUTTONDBLCLK)
|
||||
|
||||
def OnLDblClick(self, params):
|
||||
line = self.GetLine()
|
||||
regexGrepResult = regexGrep.match(line)
|
||||
if regexGrepResult:
|
||||
fname = regexGrepResult.group(1)
|
||||
line = int(regexGrepResult.group(2))
|
||||
scriptutils.JumpToDocument(fname, line)
|
||||
return 0 # don't pass on
|
||||
return 1 # pass it on by default.
|
||||
|
||||
def OnRClick(self, params):
|
||||
menu = win32ui.CreatePopupMenu()
|
||||
flags = win32con.MF_STRING | win32con.MF_ENABLED
|
||||
lineno = self._obj_.LineFromChar(-1) # selection or current line
|
||||
line = self._obj_.GetLine(lineno)
|
||||
regexGrepResult = regexGrep.match(line)
|
||||
if regexGrepResult:
|
||||
self.fnm = regexGrepResult.group(1)
|
||||
self.lnnum = int(regexGrepResult.group(2))
|
||||
menu.AppendMenu(flags, ID_OPEN_FILE, "&Open " + self.fnm)
|
||||
menu.AppendMenu(win32con.MF_SEPARATOR)
|
||||
menu.AppendMenu(flags, ID_TRYAGAIN, "&Try Again")
|
||||
charstart, charend = self._obj_.GetSel()
|
||||
if charstart != charend:
|
||||
linestart = self._obj_.LineIndex(lineno)
|
||||
self.sel = line[charstart - linestart : charend - linestart]
|
||||
menu.AppendMenu(flags, ID_GREP, "&Grep for " + self.sel)
|
||||
menu.AppendMenu(win32con.MF_SEPARATOR)
|
||||
menu.AppendMenu(flags, win32ui.ID_EDIT_CUT, "Cu&t")
|
||||
menu.AppendMenu(flags, win32ui.ID_EDIT_COPY, "&Copy")
|
||||
menu.AppendMenu(flags, win32ui.ID_EDIT_PASTE, "&Paste")
|
||||
menu.AppendMenu(flags, win32con.MF_SEPARATOR)
|
||||
menu.AppendMenu(flags, win32ui.ID_EDIT_SELECT_ALL, "&Select all")
|
||||
menu.AppendMenu(flags, win32con.MF_SEPARATOR)
|
||||
menu.AppendMenu(flags, ID_SAVERESULTS, "Sa&ve results")
|
||||
menu.TrackPopupMenu(params[5])
|
||||
return 0
|
||||
|
||||
def OnCmdOpenFile(self, cmd, code):
|
||||
doc = win32ui.GetApp().OpenDocumentFile(self.fnm)
|
||||
if doc:
|
||||
vw = doc.GetFirstView()
|
||||
# hope you have an editor that implements GotoLine()!
|
||||
try:
|
||||
vw.GotoLine(int(self.lnnum))
|
||||
except:
|
||||
pass
|
||||
return 0
|
||||
|
||||
def OnCmdGrep(self, cmd, code):
|
||||
if code != 0:
|
||||
return 1
|
||||
curparamsstr = self.GetDocument().GetParams()
|
||||
params = curparamsstr.split("\t")
|
||||
params[2] = self.sel
|
||||
greptemplate.setParams("\t".join(params))
|
||||
greptemplate.OpenDocumentFile()
|
||||
return 0
|
||||
|
||||
def OnTryAgain(self, cmd, code):
|
||||
if code != 0:
|
||||
return 1
|
||||
greptemplate.setParams(self.GetDocument().GetParams())
|
||||
greptemplate.OpenDocumentFile()
|
||||
return 0
|
||||
|
||||
def OnCmdSave(self, cmd, code):
|
||||
if code != 0:
|
||||
return 1
|
||||
flags = win32con.OFN_OVERWRITEPROMPT
|
||||
dlg = win32ui.CreateFileDialog(
|
||||
0, None, None, flags, "Text Files (*.txt)|*.txt||", self
|
||||
)
|
||||
dlg.SetOFNTitle("Save Results As")
|
||||
if dlg.DoModal() == win32con.IDOK:
|
||||
pn = dlg.GetPathName()
|
||||
self._obj_.SaveTextFile(pn)
|
||||
return 0
|
||||
|
||||
def Append(self, strng):
|
||||
numlines = self.GetLineCount()
|
||||
endpos = self.LineIndex(numlines - 1) + len(self.GetLine(numlines - 1))
|
||||
self.SetSel(endpos, endpos)
|
||||
self.ReplaceSel(strng)
|
||||
|
||||
|
||||
class GrepDialog(dialog.Dialog):
|
||||
def __init__(self, dp, fp, gp, cs, r, v):
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
CS = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
tmp = [
|
||||
["Grep", (0, 0, 210, 90), style, None, (8, "MS Sans Serif")],
|
||||
]
|
||||
tmp.append([STATIC, "Grep For:", -1, (7, 7, 50, 9), CS])
|
||||
tmp.append(
|
||||
[
|
||||
EDIT,
|
||||
gp,
|
||||
101,
|
||||
(52, 7, 144, 11),
|
||||
CS | win32con.WS_TABSTOP | win32con.ES_AUTOHSCROLL | win32con.WS_BORDER,
|
||||
]
|
||||
)
|
||||
tmp.append([STATIC, "Directories:", -1, (7, 20, 50, 9), CS])
|
||||
tmp.append(
|
||||
[
|
||||
EDIT,
|
||||
dp,
|
||||
102,
|
||||
(52, 20, 128, 11),
|
||||
CS | win32con.WS_TABSTOP | win32con.ES_AUTOHSCROLL | win32con.WS_BORDER,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"...",
|
||||
110,
|
||||
(182, 20, 16, 11),
|
||||
CS | win32con.BS_PUSHBUTTON | win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
tmp.append([STATIC, "File types:", -1, (7, 33, 50, 9), CS])
|
||||
tmp.append(
|
||||
[
|
||||
EDIT,
|
||||
fp,
|
||||
103,
|
||||
(52, 33, 128, 11),
|
||||
CS | win32con.WS_TABSTOP | win32con.ES_AUTOHSCROLL | win32con.WS_BORDER,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"...",
|
||||
111,
|
||||
(182, 33, 16, 11),
|
||||
CS | win32con.BS_PUSHBUTTON | win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"Case sensitive",
|
||||
104,
|
||||
(7, 45, 72, 9),
|
||||
CS
|
||||
| win32con.BS_AUTOCHECKBOX
|
||||
| win32con.BS_LEFTTEXT
|
||||
| win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"Subdirectories",
|
||||
105,
|
||||
(7, 56, 72, 9),
|
||||
CS
|
||||
| win32con.BS_AUTOCHECKBOX
|
||||
| win32con.BS_LEFTTEXT
|
||||
| win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"Verbose",
|
||||
106,
|
||||
(7, 67, 72, 9),
|
||||
CS
|
||||
| win32con.BS_AUTOCHECKBOX
|
||||
| win32con.BS_LEFTTEXT
|
||||
| win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"OK",
|
||||
win32con.IDOK,
|
||||
(166, 53, 32, 12),
|
||||
CS | win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"Cancel",
|
||||
win32con.IDCANCEL,
|
||||
(166, 67, 32, 12),
|
||||
CS | win32con.BS_PUSHBUTTON | win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
dialog.Dialog.__init__(self, tmp)
|
||||
self.AddDDX(101, "greppattern")
|
||||
self.AddDDX(102, "dirpattern")
|
||||
self.AddDDX(103, "filpattern")
|
||||
self.AddDDX(104, "casesensitive")
|
||||
self.AddDDX(105, "recursive")
|
||||
self.AddDDX(106, "verbose")
|
||||
self._obj_.data["greppattern"] = gp
|
||||
self._obj_.data["dirpattern"] = dp
|
||||
self._obj_.data["filpattern"] = fp
|
||||
self._obj_.data["casesensitive"] = cs
|
||||
self._obj_.data["recursive"] = r
|
||||
self._obj_.data["verbose"] = v
|
||||
self.HookCommand(self.OnMoreDirectories, 110)
|
||||
self.HookCommand(self.OnMoreFiles, 111)
|
||||
|
||||
def OnMoreDirectories(self, cmd, code):
|
||||
if code != 0:
|
||||
return 1
|
||||
self.getMore("Grep\\Directories", "dirpattern")
|
||||
|
||||
def OnMoreFiles(self, cmd, code):
|
||||
if code != 0:
|
||||
return 1
|
||||
self.getMore("Grep\\File Types", "filpattern")
|
||||
|
||||
def getMore(self, section, key):
|
||||
self.UpdateData(1)
|
||||
# get the items out of the ini file
|
||||
ini = win32ui.GetProfileFileName()
|
||||
secitems = win32api.GetProfileSection(section, ini)
|
||||
items = []
|
||||
for secitem in secitems:
|
||||
items.append(secitem.split("=")[1])
|
||||
dlg = GrepParamsDialog(items)
|
||||
if dlg.DoModal() == win32con.IDOK:
|
||||
itemstr = ";".join(dlg.getItems())
|
||||
self._obj_.data[key] = itemstr
|
||||
# update the ini file with dlg.getNew()
|
||||
i = 0
|
||||
newitems = dlg.getNew()
|
||||
if newitems:
|
||||
items.extend(newitems)
|
||||
for item in items:
|
||||
win32api.WriteProfileVal(section, repr(i), item, ini)
|
||||
i += 1
|
||||
self.UpdateData(0)
|
||||
|
||||
def OnOK(self):
|
||||
self.UpdateData(1)
|
||||
for id, name in (
|
||||
(101, "greppattern"),
|
||||
(102, "dirpattern"),
|
||||
(103, "filpattern"),
|
||||
):
|
||||
if not self[name]:
|
||||
self.GetDlgItem(id).SetFocus()
|
||||
win32api.MessageBeep()
|
||||
win32ui.SetStatusText("Please enter a value")
|
||||
return
|
||||
self._obj_.OnOK()
|
||||
|
||||
|
||||
class GrepParamsDialog(dialog.Dialog):
|
||||
def __init__(self, items):
|
||||
self.items = items
|
||||
self.newitems = []
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
CS = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
tmp = [
|
||||
["Grep Parameters", (0, 0, 205, 100), style, None, (8, "MS Sans Serif")],
|
||||
]
|
||||
tmp.append(
|
||||
[
|
||||
LISTBOX,
|
||||
"",
|
||||
107,
|
||||
(7, 7, 150, 72),
|
||||
CS
|
||||
| win32con.LBS_MULTIPLESEL
|
||||
| win32con.LBS_STANDARD
|
||||
| win32con.LBS_HASSTRINGS
|
||||
| win32con.WS_TABSTOP
|
||||
| win32con.LBS_NOTIFY,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"OK",
|
||||
win32con.IDOK,
|
||||
(167, 7, 32, 12),
|
||||
CS | win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"Cancel",
|
||||
win32con.IDCANCEL,
|
||||
(167, 23, 32, 12),
|
||||
CS | win32con.BS_PUSHBUTTON | win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
tmp.append([STATIC, "New:", -1, (2, 83, 15, 12), CS])
|
||||
tmp.append(
|
||||
[
|
||||
EDIT,
|
||||
"",
|
||||
108,
|
||||
(18, 83, 139, 12),
|
||||
CS | win32con.WS_TABSTOP | win32con.ES_AUTOHSCROLL | win32con.WS_BORDER,
|
||||
]
|
||||
)
|
||||
tmp.append(
|
||||
[
|
||||
BUTTON,
|
||||
"Add",
|
||||
109,
|
||||
(167, 83, 32, 12),
|
||||
CS | win32con.BS_PUSHBUTTON | win32con.WS_TABSTOP,
|
||||
]
|
||||
)
|
||||
dialog.Dialog.__init__(self, tmp)
|
||||
self.HookCommand(self.OnAddItem, 109)
|
||||
self.HookCommand(self.OnListDoubleClick, 107)
|
||||
|
||||
def OnInitDialog(self):
|
||||
lb = self.GetDlgItem(107)
|
||||
for item in self.items:
|
||||
lb.AddString(item)
|
||||
return self._obj_.OnInitDialog()
|
||||
|
||||
def OnAddItem(self, cmd, code):
|
||||
if code != 0:
|
||||
return 1
|
||||
eb = self.GetDlgItem(108)
|
||||
item = eb.GetLine(0)
|
||||
self.newitems.append(item)
|
||||
lb = self.GetDlgItem(107)
|
||||
i = lb.AddString(item)
|
||||
lb.SetSel(i, 1)
|
||||
return 1
|
||||
|
||||
def OnListDoubleClick(self, cmd, code):
|
||||
if code == win32con.LBN_DBLCLK:
|
||||
self.OnOK()
|
||||
return 1
|
||||
|
||||
def OnOK(self):
|
||||
lb = self.GetDlgItem(107)
|
||||
self.selections = lb.GetSelTextItems()
|
||||
self._obj_.OnOK()
|
||||
|
||||
def getItems(self):
|
||||
return self.selections
|
||||
|
||||
def getNew(self):
|
||||
return self.newitems
|
||||
|
||||
|
||||
try:
|
||||
win32ui.GetApp().RemoveDocTemplate(greptemplate) # type: ignore[has-type, used-before-def]
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
greptemplate = GrepTemplate()
|
||||
@@ -0,0 +1,70 @@
|
||||
# startup.py
|
||||
#
|
||||
"The main application startup code for PythonWin."
|
||||
|
||||
#
|
||||
# This does the basic command line handling.
|
||||
|
||||
# Keep this as short as possible, cos error output is only redirected if
|
||||
# this runs OK. Errors in imported modules are much better - the messages go somewhere (not any more :-)
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import win32api
|
||||
import win32ui
|
||||
|
||||
if not sys.argv:
|
||||
# Initialize sys.argv from commandline. When sys.argv is empty list (
|
||||
# different from [''] meaning "no cmd line arguments" ), then C
|
||||
# bootstrapping or another method of invocation failed to initialize
|
||||
# sys.argv and it will be done here. ( This was a workaround for a bug in
|
||||
# win32ui but is retained for other situations. )
|
||||
argv = win32api.CommandLineToArgv(win32api.GetCommandLine())
|
||||
sys.argv = argv[1:]
|
||||
if os.getcwd() not in sys.path and "." not in sys.path:
|
||||
sys.path.insert(0, os.getcwd())
|
||||
|
||||
# You may wish to redirect error output somewhere useful if you have startup errors.
|
||||
# eg, 'import win32traceutil' will do this for you.
|
||||
# import win32traceutil # Just uncomment this line to see error output!
|
||||
|
||||
# An old class I used to use - generally only useful if Pythonwin is running under MSVC
|
||||
# class DebugOutput:
|
||||
# softspace=1
|
||||
# def write(self,message):
|
||||
# win32ui.OutputDebug(message)
|
||||
# sys.stderr=sys.stdout=DebugOutput()
|
||||
|
||||
# To fix a problem with Pythonwin when started from the Pythonwin directory,
|
||||
# we update the pywin path to ensure it is absolute.
|
||||
# If it is indeed relative, it will be relative to our current directory.
|
||||
# If it's already absolute, then this will have no affect.
|
||||
import pywin
|
||||
import pywin.framework
|
||||
|
||||
# Ensure we're working on __path__ as list, not Iterable
|
||||
pywin.__path__ = list(pywin.__path__)
|
||||
pywin.framework.__path__ = list(pywin.framework.__path__)
|
||||
|
||||
pywin.__path__[0] = win32ui.FullPath(pywin.__path__[0])
|
||||
pywin.framework.__path__[0] = win32ui.FullPath(pywin.framework.__path__[0])
|
||||
|
||||
# make a few weird sys values. This is so later we can clobber sys.argv to trick
|
||||
# scripts when running under a GUI environment.
|
||||
|
||||
moduleName = "pywin.framework.intpyapp"
|
||||
sys.appargvoffset = 0
|
||||
sys.appargv = sys.argv[:]
|
||||
# Must check for /app param here.
|
||||
if len(sys.argv) >= 2 and sys.argv[0].lower() in ("/app", "-app"):
|
||||
from . import cmdline
|
||||
|
||||
moduleName = cmdline.FixArgFileName(sys.argv[1])
|
||||
sys.appargvoffset = 2
|
||||
newargv = sys.argv[sys.appargvoffset :]
|
||||
# newargv.insert(0, sys.argv[0])
|
||||
sys.argv = newargv
|
||||
|
||||
# Import the application module.
|
||||
__import__(moduleName)
|
||||
@@ -0,0 +1,172 @@
|
||||
# Copyright (c) 2000 David Abrahams. Permission to copy, use, modify, sell
|
||||
# and distribute this software is granted provided this copyright
|
||||
# notice appears in all copies. This software is provided "as is" without
|
||||
# express or implied warranty, and with no claim as to its suitability for
|
||||
# any purpose.
|
||||
"""Provides a class Stdin which can be used to emulate the regular old
|
||||
sys.stdin for the PythonWin interactive window. Right now it just pops
|
||||
up a input() dialog. With luck, someone will integrate it into the
|
||||
actual PythonWin interactive window someday.
|
||||
|
||||
WARNING: Importing this file automatically replaces sys.stdin with an
|
||||
instance of Stdin (below). This is useful because you can just open
|
||||
Stdin.py in PythonWin and hit the import button to get it set up right
|
||||
if you don't feel like changing PythonWin's source. To put things back
|
||||
the way they were, simply use this magic incantation:
|
||||
import sys
|
||||
sys.stdin = sys.stdin.real_file
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
get_input_line = input
|
||||
|
||||
|
||||
class Stdin:
|
||||
def __init__(self):
|
||||
self.real_file = sys.stdin # NOTE: Likely to be None
|
||||
self.buffer = ""
|
||||
self.closed = False
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Forward most functions to the real sys.stdin for absolute realism."""
|
||||
if self.real_file is None:
|
||||
raise AttributeError(name)
|
||||
return getattr(self.real_file, name)
|
||||
|
||||
def isatty(self):
|
||||
"""Return 1 if the file is connected to a tty(-like) device, else 0."""
|
||||
return 1
|
||||
|
||||
def read(self, size=-1):
|
||||
"""Read at most size bytes from the file (less if the read
|
||||
hits EOF or no more data is immediately available on a pipe,
|
||||
tty or similar device). If the size argument is negative or
|
||||
omitted, read all data until EOF is reached. The bytes are
|
||||
returned as a string object. An empty string is returned when
|
||||
EOF is encountered immediately. (For certain files, like ttys,
|
||||
it makes sense to continue reading after an EOF is hit.)"""
|
||||
result_size = self.__get_lines(size)
|
||||
return self.__extract_from_buffer(result_size)
|
||||
|
||||
def readline(self, size=-1):
|
||||
"""Read one entire line from the file. A trailing newline
|
||||
character is kept in the string2.6 (but may be absent when a file ends
|
||||
with an incomplete line). If the size argument is present and
|
||||
non-negative, it is a maximum byte count (including the trailing
|
||||
newline) and an incomplete line may be returned. An empty string is
|
||||
returned when EOF is hit immediately. Note: unlike stdio's fgets(),
|
||||
the returned string contains null characters ('\0') if they occurred
|
||||
in the input.
|
||||
"""
|
||||
maximum_result_size = self.__get_lines(size, lambda buffer: "\n" in buffer)
|
||||
|
||||
if "\n" in self.buffer[:maximum_result_size]:
|
||||
result_size = self.buffer.find("\n", 0, maximum_result_size) + 1
|
||||
assert result_size > 0
|
||||
else:
|
||||
result_size = maximum_result_size
|
||||
|
||||
return self.__extract_from_buffer(result_size)
|
||||
|
||||
def __extract_from_buffer(self, character_count):
|
||||
"""Remove the first character_count characters from the internal buffer and
|
||||
return them.
|
||||
"""
|
||||
result = self.buffer[:character_count]
|
||||
self.buffer = self.buffer[character_count:]
|
||||
return result
|
||||
|
||||
def __get_lines(self, desired_size, done_reading=lambda buffer: False):
|
||||
"""Keep adding lines to our internal buffer until done_reading(self.buffer)
|
||||
is true or EOF has been reached or we have desired_size bytes in the buffer.
|
||||
If desired_size < 0, we are never satisfied until we reach EOF. If done_reading
|
||||
is not supplied, it is not consulted.
|
||||
|
||||
If desired_size < 0, returns the length of the internal buffer. Otherwise,
|
||||
returns desired_size.
|
||||
"""
|
||||
while not done_reading(self.buffer) and (
|
||||
desired_size < 0 or len(self.buffer) < desired_size
|
||||
):
|
||||
try:
|
||||
self.__get_line()
|
||||
except (
|
||||
EOFError,
|
||||
KeyboardInterrupt,
|
||||
): # deal with cancellation of get_input_line dialog
|
||||
desired_size = len(self.buffer) # Be satisfied!
|
||||
|
||||
if desired_size < 0:
|
||||
return len(self.buffer)
|
||||
else:
|
||||
return desired_size
|
||||
|
||||
def __get_line(self):
|
||||
"""Grab one line from get_input_line() and append it to the buffer."""
|
||||
line = get_input_line()
|
||||
print(">>>", line) # echo input to console
|
||||
self.buffer = self.buffer + line + "\n"
|
||||
|
||||
def readlines(self, *sizehint):
|
||||
"""Read until EOF using readline() and return a list containing the lines
|
||||
thus read. If the optional sizehint argument is present, instead of
|
||||
reading up to EOF, whole lines totalling approximately sizehint bytes
|
||||
(possibly after rounding up to an internal buffer size) are read.
|
||||
"""
|
||||
result = []
|
||||
total_read = 0
|
||||
while sizehint == () or total_read < sizehint[0]:
|
||||
line = self.readline()
|
||||
if line == "":
|
||||
break
|
||||
total_read += len(line)
|
||||
result.append(line)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_input = r"""this is some test
|
||||
input that I am hoping
|
||||
~
|
||||
will be very instructive
|
||||
and when I am done
|
||||
I will have tested everything.
|
||||
Twelve and twenty blackbirds
|
||||
baked in a pie. Patty cake
|
||||
patty cake so am I.
|
||||
~
|
||||
Thirty-five niggling idiots!
|
||||
Sell you soul to the devil, baby
|
||||
"""
|
||||
|
||||
def fake_input(prompt=None):
|
||||
"""Replacement for input() which pulls lines out of global test_input.
|
||||
For testing only!
|
||||
"""
|
||||
global test_input
|
||||
if "\n" not in test_input:
|
||||
end_of_line_pos = len(test_input)
|
||||
else:
|
||||
end_of_line_pos = test_input.find("\n")
|
||||
result = test_input[:end_of_line_pos]
|
||||
test_input = test_input[end_of_line_pos + 1 :]
|
||||
if len(result) == 0 or result[0] == "~":
|
||||
raise EOFError
|
||||
return result
|
||||
|
||||
get_input_line = fake_input
|
||||
|
||||
# Some completely inadequate tests, just to make sure the code's not totally broken
|
||||
try:
|
||||
x = Stdin()
|
||||
print(x.read())
|
||||
print(x.readline())
|
||||
print(x.read(12))
|
||||
print(x.readline(47))
|
||||
print(x.readline(3))
|
||||
print(x.readlines())
|
||||
finally:
|
||||
get_input_line = input
|
||||
else:
|
||||
sys.stdin = Stdin()
|
||||
@@ -0,0 +1,279 @@
|
||||
# toolmenu.py
|
||||
|
||||
import sys
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
tools = {}
|
||||
idPos = 100
|
||||
|
||||
# The default items should no tools menu exist in the INI file.
|
||||
defaultToolMenuItems = [
|
||||
("Browser", "win32ui.GetApp().OnViewBrowse(0,0)"),
|
||||
(
|
||||
"Browse PythonPath",
|
||||
"from pywin.tools import browseProjects;browseProjects.Browse()",
|
||||
),
|
||||
("Edit Python Path", "from pywin.tools import regedit;regedit.EditRegistry()"),
|
||||
("COM Makepy utility", "from win32com.client import makepy;makepy.main()"),
|
||||
(
|
||||
"COM Browser",
|
||||
"from win32com.client import combrowse;combrowse.main(modal=False)",
|
||||
),
|
||||
(
|
||||
"Trace Collector Debugging tool",
|
||||
"from pywin.tools import TraceCollector;TraceCollector.MakeOutputWindow()",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def LoadToolMenuItems():
|
||||
# Load from the registry.
|
||||
items = []
|
||||
lookNo = 1
|
||||
while 1:
|
||||
menu = win32ui.GetProfileVal("Tools Menu\\%s" % lookNo, "", "")
|
||||
if menu == "":
|
||||
break
|
||||
cmd = win32ui.GetProfileVal("Tools Menu\\%s" % lookNo, "Command", "")
|
||||
items.append((menu, cmd))
|
||||
lookNo += 1
|
||||
|
||||
if len(items) == 0:
|
||||
items = defaultToolMenuItems
|
||||
return items
|
||||
|
||||
|
||||
def WriteToolMenuItems(items):
|
||||
# Items is a list of (menu, command)
|
||||
# Delete the entire registry tree.
|
||||
try:
|
||||
mainKey = win32ui.GetAppRegistryKey()
|
||||
toolKey = win32api.RegOpenKey(mainKey, "Tools Menu")
|
||||
except win32ui.error:
|
||||
toolKey = None
|
||||
if toolKey is not None:
|
||||
while 1:
|
||||
try:
|
||||
subkey = win32api.RegEnumKey(toolKey, 0)
|
||||
except win32api.error:
|
||||
break
|
||||
win32api.RegDeleteKey(toolKey, subkey)
|
||||
# Keys are now removed - write the new ones.
|
||||
# But first check if we have the defaults - and if so, don't write anything!
|
||||
if items == defaultToolMenuItems:
|
||||
return
|
||||
itemNo = 1
|
||||
for menu, cmd in items:
|
||||
win32ui.WriteProfileVal("Tools Menu\\%s" % itemNo, "", menu)
|
||||
win32ui.WriteProfileVal("Tools Menu\\%s" % itemNo, "Command", cmd)
|
||||
itemNo += 1
|
||||
|
||||
|
||||
def SetToolsMenu(menu, menuPos=None):
|
||||
global tools
|
||||
global idPos
|
||||
|
||||
# todo - check the menu does not already exist.
|
||||
# Create the new menu
|
||||
toolsMenu = win32ui.CreatePopupMenu()
|
||||
|
||||
# Load from the ini file.
|
||||
items = LoadToolMenuItems()
|
||||
for menuString, cmd in items:
|
||||
tools[idPos] = (menuString, cmd, menuString)
|
||||
toolsMenu.AppendMenu(
|
||||
win32con.MF_ENABLED | win32con.MF_STRING, idPos, menuString
|
||||
)
|
||||
win32ui.GetMainFrame().HookCommand(HandleToolCommand, idPos)
|
||||
idPos += 1
|
||||
|
||||
# Find the correct spot to insert the new tools menu.
|
||||
if menuPos is None:
|
||||
menuPos = menu.GetMenuItemCount() - 2
|
||||
if menuPos < 0:
|
||||
menuPos = 0
|
||||
|
||||
menu.InsertMenu(
|
||||
menuPos,
|
||||
win32con.MF_BYPOSITION
|
||||
| win32con.MF_ENABLED
|
||||
| win32con.MF_STRING
|
||||
| win32con.MF_POPUP,
|
||||
toolsMenu.GetHandle(),
|
||||
"&Tools",
|
||||
)
|
||||
|
||||
|
||||
def HandleToolCommand(cmd, code):
|
||||
import re
|
||||
import traceback
|
||||
|
||||
global tools
|
||||
(menuString, pyCmd, desc) = tools[cmd]
|
||||
win32ui.SetStatusText("Executing tool %s" % desc, 1)
|
||||
pyCmd = re.sub(r"\\n", "\n", pyCmd)
|
||||
win32ui.DoWaitCursor(1)
|
||||
oldFlag = None
|
||||
try:
|
||||
oldFlag = sys.stdout.template.writeQueueing
|
||||
sys.stdout.template.writeQueueing = 0
|
||||
except (NameError, AttributeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
exec("%s\n" % pyCmd)
|
||||
worked = 1
|
||||
except SystemExit:
|
||||
# The program raised a SystemExit - ignore it.
|
||||
worked = 1
|
||||
except:
|
||||
print("Failed to execute command:\n%s" % pyCmd)
|
||||
traceback.print_exc()
|
||||
worked = 0
|
||||
if oldFlag is not None:
|
||||
sys.stdout.template.writeQueueing = oldFlag
|
||||
win32ui.DoWaitCursor(0)
|
||||
if worked:
|
||||
text = "Completed successfully."
|
||||
else:
|
||||
text = "Error executing %s." % desc
|
||||
win32ui.SetStatusText(text, 1)
|
||||
|
||||
|
||||
# The property page for maintaing the items on the Tools menu.
|
||||
import commctrl
|
||||
from pywin.mfc import dialog
|
||||
|
||||
LVN_ENDLABELEDIT = commctrl.LVN_ENDLABELEDITW
|
||||
|
||||
|
||||
class ToolMenuPropPage(dialog.PropertyPage):
|
||||
def __init__(self):
|
||||
self.bImChangingEditControls = 0 # Am I programatically changing the controls?
|
||||
dialog.PropertyPage.__init__(self, win32ui.IDD_PP_TOOLMENU)
|
||||
|
||||
def OnInitDialog(self):
|
||||
self.editMenuCommand = self.GetDlgItem(win32ui.IDC_EDIT2)
|
||||
self.butNew = self.GetDlgItem(win32ui.IDC_BUTTON3)
|
||||
|
||||
# Now hook the change notification messages for the edit controls.
|
||||
self.HookCommand(self.OnCommandEditControls, win32ui.IDC_EDIT1)
|
||||
self.HookCommand(self.OnCommandEditControls, win32ui.IDC_EDIT2)
|
||||
|
||||
self.HookNotify(self.OnNotifyListControl, commctrl.LVN_ITEMCHANGED)
|
||||
self.HookNotify(self.OnNotifyListControlEndLabelEdit, commctrl.LVN_ENDLABELEDIT)
|
||||
|
||||
# Hook the button clicks.
|
||||
self.HookCommand(self.OnButtonNew, win32ui.IDC_BUTTON3) # New Item
|
||||
self.HookCommand(self.OnButtonDelete, win32ui.IDC_BUTTON4) # Delete item
|
||||
self.HookCommand(self.OnButtonMove, win32ui.IDC_BUTTON1) # Move up
|
||||
self.HookCommand(self.OnButtonMove, win32ui.IDC_BUTTON2) # Move down
|
||||
|
||||
# Setup the columns in the list control
|
||||
lc = self.GetDlgItem(win32ui.IDC_LIST1)
|
||||
rect = lc.GetWindowRect()
|
||||
cx = rect[2] - rect[0]
|
||||
colSize = cx / 2 - win32api.GetSystemMetrics(win32con.SM_CXBORDER) - 1
|
||||
|
||||
item = commctrl.LVCFMT_LEFT, colSize, "Menu Text"
|
||||
lc.InsertColumn(0, item)
|
||||
|
||||
item = commctrl.LVCFMT_LEFT, colSize, "Python Command"
|
||||
lc.InsertColumn(1, item)
|
||||
|
||||
# Insert the existing tools menu
|
||||
itemNo = 0
|
||||
for desc, cmd in LoadToolMenuItems():
|
||||
lc.InsertItem(itemNo, desc)
|
||||
lc.SetItemText(itemNo, 1, cmd)
|
||||
itemNo += 1
|
||||
|
||||
self.listControl = lc
|
||||
return dialog.PropertyPage.OnInitDialog(self)
|
||||
|
||||
def OnOK(self):
|
||||
# Write the menu back to the registry.
|
||||
items = []
|
||||
itemLook = 0
|
||||
while 1:
|
||||
try:
|
||||
text = self.listControl.GetItemText(itemLook, 0)
|
||||
if not text:
|
||||
break
|
||||
items.append((text, self.listControl.GetItemText(itemLook, 1)))
|
||||
except win32ui.error:
|
||||
# no more items!
|
||||
break
|
||||
itemLook += 1
|
||||
WriteToolMenuItems(items)
|
||||
return self._obj_.OnOK()
|
||||
|
||||
def OnCommandEditControls(self, id, cmd):
|
||||
# print("OnEditControls", id, cmd)
|
||||
if cmd == win32con.EN_CHANGE and not self.bImChangingEditControls:
|
||||
itemNo = self.listControl.GetNextItem(-1, commctrl.LVNI_SELECTED)
|
||||
newText = self.editMenuCommand.GetWindowText()
|
||||
self.listControl.SetItemText(itemNo, 1, newText)
|
||||
|
||||
return 0
|
||||
|
||||
def OnNotifyListControlEndLabelEdit(self, id, cmd):
|
||||
newText = self.listControl.GetEditControl().GetWindowText()
|
||||
itemNo = self.listControl.GetNextItem(-1, commctrl.LVNI_SELECTED)
|
||||
self.listControl.SetItemText(itemNo, 0, newText)
|
||||
|
||||
def OnNotifyListControl(self, id, cmd):
|
||||
# print(id, cmd)
|
||||
try:
|
||||
itemNo = self.listControl.GetNextItem(-1, commctrl.LVNI_SELECTED)
|
||||
except win32ui.error: # No selection!
|
||||
return
|
||||
|
||||
self.bImChangingEditControls = 1
|
||||
try:
|
||||
item = self.listControl.GetItem(itemNo, 1)
|
||||
self.editMenuCommand.SetWindowText(item[4])
|
||||
finally:
|
||||
self.bImChangingEditControls = 0
|
||||
|
||||
return 0 # we have handled this!
|
||||
|
||||
def OnButtonNew(self, id, cmd):
|
||||
if cmd == win32con.BN_CLICKED:
|
||||
newIndex = self.listControl.GetItemCount()
|
||||
self.listControl.InsertItem(newIndex, "Click to edit the text")
|
||||
self.listControl.EnsureVisible(newIndex, 0)
|
||||
|
||||
def OnButtonMove(self, id, cmd):
|
||||
if cmd == win32con.BN_CLICKED:
|
||||
try:
|
||||
itemNo = self.listControl.GetNextItem(-1, commctrl.LVNI_SELECTED)
|
||||
except win32ui.error:
|
||||
return
|
||||
menu = self.listControl.GetItemText(itemNo, 0)
|
||||
cmd = self.listControl.GetItemText(itemNo, 1)
|
||||
if id == win32ui.IDC_BUTTON1:
|
||||
# Move up
|
||||
if itemNo > 0:
|
||||
self.listControl.DeleteItem(itemNo)
|
||||
# reinsert it.
|
||||
self.listControl.InsertItem(itemNo - 1, menu)
|
||||
self.listControl.SetItemText(itemNo - 1, 1, cmd)
|
||||
else:
|
||||
# Move down.
|
||||
if itemNo < self.listControl.GetItemCount() - 1:
|
||||
self.listControl.DeleteItem(itemNo)
|
||||
# reinsert it.
|
||||
self.listControl.InsertItem(itemNo + 1, menu)
|
||||
self.listControl.SetItemText(itemNo + 1, 1, cmd)
|
||||
|
||||
def OnButtonDelete(self, id, cmd):
|
||||
if cmd == win32con.BN_CLICKED:
|
||||
try:
|
||||
itemNo = self.listControl.GetNextItem(-1, commctrl.LVNI_SELECTED)
|
||||
except win32ui.error: # No selection!
|
||||
return
|
||||
self.listControl.DeleteItem(itemNo)
|
||||
@@ -0,0 +1,14 @@
|
||||
# Framework Window classes.
|
||||
|
||||
# Most Pythonwin windows should use these classes rather than
|
||||
# the raw MFC ones if they want Pythonwin specific functionality.
|
||||
import pywin.mfc.window
|
||||
import win32con
|
||||
|
||||
|
||||
class MDIChildWnd(pywin.mfc.window.MDIChildWnd):
|
||||
def AutoRestore(self):
|
||||
"If the window is minimised or maximised, restore it."
|
||||
p = self.GetWindowPlacement()
|
||||
if p[1] == win32con.SW_MINIMIZE or p[1] == win32con.SW_SHOWMINIMIZED:
|
||||
self.SetWindowPlacement(p[0], win32con.SW_RESTORE, p[2], p[3], p[4])
|
||||
@@ -0,0 +1,589 @@
|
||||
# winout.py
|
||||
#
|
||||
# generic "output window"
|
||||
#
|
||||
# This Window will detect itself closing, and recreate next time output is
|
||||
# written to it.
|
||||
|
||||
# This has the option of writing output at idle time (by hooking the
|
||||
# idle message, and queueing output) or writing as each
|
||||
# write is executed.
|
||||
# Updating the window directly gives a jerky appearance as many writes
|
||||
# take place between commands, and the windows scrolls, and updates etc
|
||||
# Updating at idle-time may defer all output of a long process, giving the
|
||||
# appearence nothing is happening.
|
||||
# There is a compromise "line" mode, which will output whenever
|
||||
# a complete line is available.
|
||||
|
||||
# behaviour depends on self.writeQueueing
|
||||
|
||||
# This module is thread safe - output can originate from any thread. If any thread
|
||||
# other than the main thread attempts to print, it is always queued until next idle time
|
||||
|
||||
import queue
|
||||
import re
|
||||
|
||||
import pywin.scintilla.document
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework import app, window
|
||||
from pywin.mfc import docview
|
||||
from pywin.scintilla import scintillacon
|
||||
|
||||
debug = lambda msg: None
|
||||
# debug=win32ui.OutputDebugString
|
||||
# import win32trace;win32trace.InitWrite() # for debugging - delete me!
|
||||
# debug = win32trace.write
|
||||
# WindowOutputDocumentParent=docview.RichEditDoc
|
||||
# WindowOutputDocumentParent=docview.Document
|
||||
WindowOutputDocumentParent = pywin.scintilla.document.CScintillaDocument
|
||||
|
||||
|
||||
class flags:
|
||||
# queueing of output.
|
||||
WQ_NONE = 0
|
||||
WQ_LINE = 1
|
||||
WQ_IDLE = 2
|
||||
|
||||
|
||||
class WindowOutputDocument(WindowOutputDocumentParent):
|
||||
def SaveModified(self):
|
||||
return 1 # say it is OK to destroy my document
|
||||
|
||||
def OnSaveDocument(self, fileName):
|
||||
win32ui.SetStatusText("Saving file...", 1)
|
||||
try:
|
||||
self.SaveFile(fileName)
|
||||
except OSError as details:
|
||||
win32ui.MessageBox("Error - could not save file\r\n\r\n%s" % details)
|
||||
return 0
|
||||
win32ui.SetStatusText("Ready")
|
||||
return 1
|
||||
|
||||
|
||||
class WindowOutputFrame(window.MDIChildWnd):
|
||||
def __init__(self, wnd=None):
|
||||
window.MDIChildWnd.__init__(self, wnd)
|
||||
self.HookMessage(self.OnSizeMove, win32con.WM_SIZE)
|
||||
self.HookMessage(self.OnSizeMove, win32con.WM_MOVE)
|
||||
|
||||
def LoadFrame(self, idResource, style, wndParent, context):
|
||||
self.template = context.template
|
||||
return self._obj_.LoadFrame(idResource, style, wndParent, context)
|
||||
|
||||
def PreCreateWindow(self, cc):
|
||||
cc = self._obj_.PreCreateWindow(cc)
|
||||
if (
|
||||
self.template.defSize
|
||||
and self.template.defSize[0] != self.template.defSize[1]
|
||||
):
|
||||
rect = app.RectToCreateStructRect(self.template.defSize)
|
||||
cc = cc[0], cc[1], cc[2], cc[3], rect, cc[5], cc[6], cc[7], cc[8]
|
||||
return cc
|
||||
|
||||
def OnSizeMove(self, msg):
|
||||
# so recreate maintains position.
|
||||
# Need to map coordinates from the
|
||||
# frame windows first child.
|
||||
mdiClient = self.GetParent()
|
||||
self.template.defSize = mdiClient.ScreenToClient(self.GetWindowRect())
|
||||
|
||||
def OnDestroy(self, message):
|
||||
self.template.OnFrameDestroy(self)
|
||||
return 1
|
||||
|
||||
|
||||
class WindowOutputViewImpl:
|
||||
def __init__(self):
|
||||
self.patErrorMessage = re.compile(r'\W*File "(.*)", line ([0-9]+)')
|
||||
self.template = self.GetDocument().GetDocTemplate()
|
||||
|
||||
def HookHandlers(self):
|
||||
# Hook for the right-click menu.
|
||||
self.HookMessage(self.OnRClick, win32con.WM_RBUTTONDOWN)
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
self.template.OnViewDestroy(self)
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
self.RestoreKillBuffer()
|
||||
self.SetSel(-2) # end of buffer
|
||||
|
||||
def GetRightMenuItems(self):
|
||||
ret = []
|
||||
flags = win32con.MF_STRING | win32con.MF_ENABLED
|
||||
ret.append((flags, win32ui.ID_EDIT_COPY, "&Copy"))
|
||||
ret.append((flags, win32ui.ID_EDIT_SELECT_ALL, "&Select all"))
|
||||
return ret
|
||||
|
||||
#
|
||||
# Windows command handlers, virtuals, etc.
|
||||
#
|
||||
def OnRClick(self, params):
|
||||
paramsList = self.GetRightMenuItems()
|
||||
menu = win32ui.CreatePopupMenu()
|
||||
for appendParams in paramsList:
|
||||
if not isinstance(appendParams, tuple):
|
||||
appendParams = (appendParams,)
|
||||
menu.AppendMenu(*appendParams)
|
||||
menu.TrackPopupMenu(params[5]) # track at mouse position.
|
||||
return 0
|
||||
|
||||
# as this is often used as an output window, exeptions will often
|
||||
# be printed. Therefore, we support this functionality at this level.
|
||||
# Returns TRUE if the current line is an error message line, and will
|
||||
# jump to it. FALSE if no error (and no action taken)
|
||||
def HandleSpecialLine(self):
|
||||
from . import scriptutils
|
||||
|
||||
line = self.GetLine()
|
||||
if line[:11] == "com_error: ":
|
||||
# An OLE Exception - pull apart the exception
|
||||
# and try and locate a help file.
|
||||
try:
|
||||
import win32api
|
||||
import win32con
|
||||
|
||||
det = eval(line[line.find(":") + 1 :].strip())
|
||||
win32ui.SetStatusText("Opening help file on OLE error...")
|
||||
from . import help
|
||||
|
||||
help.OpenHelpFile(det[2][3], win32con.HELP_CONTEXT, det[2][4])
|
||||
return 1
|
||||
except win32api.error as details:
|
||||
win32ui.SetStatusText(
|
||||
"The help file could not be opened - %s" % details.strerror
|
||||
)
|
||||
return 1
|
||||
except:
|
||||
win32ui.SetStatusText(
|
||||
"Line is a COM error, but no WinHelp details can be parsed"
|
||||
)
|
||||
# Look for a Python traceback.
|
||||
matchResult = self.patErrorMessage.match(line)
|
||||
if matchResult is None:
|
||||
# No match - try the previous line
|
||||
lineNo = self.LineFromChar()
|
||||
if lineNo > 0:
|
||||
line = self.GetLine(lineNo - 1)
|
||||
matchResult = self.patErrorMessage.match(line)
|
||||
if matchResult is not None:
|
||||
# we have an error line.
|
||||
fileName = matchResult.group(1)
|
||||
if fileName[0] == "<":
|
||||
win32ui.SetStatusText("Can not load this file")
|
||||
return 1 # still was an error message.
|
||||
else:
|
||||
lineNoString = matchResult.group(2)
|
||||
# Attempt to locate the file (in case it is a relative spec)
|
||||
fileNameSpec = fileName
|
||||
fileName = scriptutils.LocatePythonFile(fileName)
|
||||
if fileName is None:
|
||||
# Don't force update, so it replaces the idle prompt.
|
||||
win32ui.SetStatusText(
|
||||
"Can't locate the file '%s'" % (fileNameSpec), 0
|
||||
)
|
||||
return 1
|
||||
|
||||
win32ui.SetStatusText(
|
||||
"Jumping to line " + lineNoString + " of file " + fileName, 1
|
||||
)
|
||||
if not scriptutils.JumpToDocument(fileName, int(lineNoString)):
|
||||
win32ui.SetStatusText("Could not open %s" % fileName)
|
||||
return 1 # still was an error message.
|
||||
return 1
|
||||
return 0 # not an error line
|
||||
|
||||
def write(self, msg):
|
||||
return self.template.write(msg)
|
||||
|
||||
def writelines(self, lines):
|
||||
for line in lines:
|
||||
self.write(line)
|
||||
|
||||
def flush(self):
|
||||
self.template.flush()
|
||||
|
||||
|
||||
class WindowOutputViewRTF(docview.RichEditView, WindowOutputViewImpl):
|
||||
def __init__(self, doc):
|
||||
docview.RichEditView.__init__(self, doc)
|
||||
WindowOutputViewImpl.__init__(self)
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
WindowOutputViewImpl.OnInitialUpdate(self)
|
||||
return docview.RichEditView.OnInitialUpdate(self)
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
WindowOutputViewImpl.OnDestroy(self, msg)
|
||||
docview.RichEditView.OnDestroy(self, msg)
|
||||
|
||||
def HookHandlers(self):
|
||||
WindowOutputViewImpl.HookHandlers(self)
|
||||
# Hook for finding and locating error messages
|
||||
self.HookMessage(self.OnLDoubleClick, win32con.WM_LBUTTONDBLCLK)
|
||||
|
||||
# docview.RichEditView.HookHandlers(self)
|
||||
|
||||
def OnLDoubleClick(self, params):
|
||||
if self.HandleSpecialLine():
|
||||
return 0 # don't pass on
|
||||
return 1 # pass it on by default.
|
||||
|
||||
def RestoreKillBuffer(self):
|
||||
if len(self.template.killBuffer):
|
||||
self.StreamIn(win32con.SF_RTF, self._StreamRTFIn)
|
||||
self.template.killBuffer = []
|
||||
|
||||
def SaveKillBuffer(self):
|
||||
self.StreamOut(win32con.SF_RTFNOOBJS, self._StreamRTFOut)
|
||||
|
||||
def _StreamRTFOut(self, data):
|
||||
self.template.killBuffer.append(data)
|
||||
return 1 # keep em coming!
|
||||
|
||||
def _StreamRTFIn(self, bytes):
|
||||
try:
|
||||
item = self.template.killBuffer[0]
|
||||
self.template.killBuffer.remove(item)
|
||||
if bytes < len(item):
|
||||
print("Warning - output buffer not big enough!")
|
||||
return item
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def dowrite(self, str):
|
||||
self.SetSel(-2)
|
||||
self.ReplaceSel(str)
|
||||
|
||||
|
||||
import pywin.scintilla.view
|
||||
|
||||
|
||||
class WindowOutputViewScintilla(
|
||||
pywin.scintilla.view.CScintillaView, WindowOutputViewImpl
|
||||
):
|
||||
def __init__(self, doc):
|
||||
pywin.scintilla.view.CScintillaView.__init__(self, doc)
|
||||
WindowOutputViewImpl.__init__(self)
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
pywin.scintilla.view.CScintillaView.OnInitialUpdate(self)
|
||||
self.SCISetMarginWidth(3)
|
||||
WindowOutputViewImpl.OnInitialUpdate(self)
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
WindowOutputViewImpl.OnDestroy(self, msg)
|
||||
pywin.scintilla.view.CScintillaView.OnDestroy(self, msg)
|
||||
|
||||
def HookHandlers(self):
|
||||
WindowOutputViewImpl.HookHandlers(self)
|
||||
pywin.scintilla.view.CScintillaView.HookHandlers(self)
|
||||
self.GetParent().HookNotify(
|
||||
self.OnScintillaDoubleClick, scintillacon.SCN_DOUBLECLICK
|
||||
)
|
||||
|
||||
## self.HookMessage(self.OnLDoubleClick,win32con.WM_LBUTTONDBLCLK)
|
||||
|
||||
def OnScintillaDoubleClick(self, std, extra):
|
||||
self.HandleSpecialLine()
|
||||
|
||||
## def OnLDoubleClick(self,params):
|
||||
## return 0 # never don't pass on
|
||||
|
||||
def RestoreKillBuffer(self):
|
||||
assert len(self.template.killBuffer) in (0, 1), "Unexpected killbuffer contents"
|
||||
if self.template.killBuffer:
|
||||
self.SCIAddText(self.template.killBuffer[0])
|
||||
self.template.killBuffer = []
|
||||
|
||||
def SaveKillBuffer(self):
|
||||
self.template.killBuffer = [self.GetTextRange(0, -1)]
|
||||
|
||||
def dowrite(self, str):
|
||||
end = self.GetTextLength()
|
||||
atEnd = end == self.GetSel()[0]
|
||||
self.SCIInsertText(str, end)
|
||||
if atEnd:
|
||||
self.SetSel(self.GetTextLength())
|
||||
|
||||
def SetWordWrap(self, bWrapOn=1):
|
||||
if bWrapOn:
|
||||
wrap_mode = scintillacon.SC_WRAP_WORD
|
||||
else:
|
||||
wrap_mode = scintillacon.SC_WRAP_NONE
|
||||
self.SCISetWrapMode(wrap_mode)
|
||||
|
||||
def _MakeColorizer(self):
|
||||
return None # No colorizer for me!
|
||||
|
||||
|
||||
WindowOutputView = WindowOutputViewScintilla
|
||||
|
||||
|
||||
# The WindowOutput class is actually an MFC template. This is a conventient way of
|
||||
# making sure that my state can exist beyond the life of the windows themselves.
|
||||
# This is primarily to support the functionality of a WindowOutput window automatically
|
||||
# being recreated if necessary when written to.
|
||||
class WindowOutput(docview.DocTemplate):
|
||||
"""Looks like a general Output Window - text can be written by the 'write' method.
|
||||
Will auto-create itself on first write, and also on next write after being closed"""
|
||||
|
||||
softspace = 1
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title=None,
|
||||
defSize=None,
|
||||
queueing=flags.WQ_LINE,
|
||||
bAutoRestore=1,
|
||||
style=None,
|
||||
makeDoc=None,
|
||||
makeFrame=None,
|
||||
makeView=None,
|
||||
):
|
||||
"""init the output window -
|
||||
Params
|
||||
title=None -- What is the title of the window
|
||||
defSize=None -- What is the default size for the window - if this
|
||||
is a string, the size will be loaded from the ini file.
|
||||
queueing = flags.WQ_LINE -- When should output be written
|
||||
bAutoRestore=1 -- Should a minimized window be restored.
|
||||
style -- Style for Window, or None for default.
|
||||
makeDoc, makeFrame, makeView -- Classes for frame, view and window respectively.
|
||||
"""
|
||||
if makeDoc is None:
|
||||
makeDoc = WindowOutputDocument
|
||||
if makeFrame is None:
|
||||
makeFrame = WindowOutputFrame
|
||||
if makeView is None:
|
||||
makeView = WindowOutputViewScintilla
|
||||
docview.DocTemplate.__init__(
|
||||
self, win32ui.IDR_PYTHONTYPE, makeDoc, makeFrame, makeView
|
||||
)
|
||||
self.SetDocStrings("\nOutput\n\nText Documents (*.txt)\n.txt\n\n\n")
|
||||
win32ui.GetApp().AddDocTemplate(self)
|
||||
self.writeQueueing = queueing
|
||||
self.errorCantRecreate = 0
|
||||
self.killBuffer = []
|
||||
self.style = style
|
||||
self.bAutoRestore = bAutoRestore
|
||||
self.title = title
|
||||
self.bCreating = 0
|
||||
self.interruptCount = 0
|
||||
if isinstance(defSize, str): # maintain size pos from ini file.
|
||||
self.iniSizeSection = defSize
|
||||
self.defSize = app.LoadWindowSize(defSize)
|
||||
self.loadedSize = self.defSize
|
||||
else:
|
||||
self.iniSizeSection = None
|
||||
self.defSize = defSize
|
||||
self.currentView = None
|
||||
self.outputQueue = queue.Queue(-1)
|
||||
self.mainThreadId = win32api.GetCurrentThreadId()
|
||||
self.idleHandlerSet = 0
|
||||
self.SetIdleHandler()
|
||||
|
||||
def __del__(self):
|
||||
self.Close()
|
||||
|
||||
def Create(self, title=None, style=None):
|
||||
self.bCreating = 1
|
||||
if title:
|
||||
self.title = title
|
||||
if style:
|
||||
self.style = style
|
||||
doc = self.OpenDocumentFile()
|
||||
if doc is None:
|
||||
return
|
||||
self.currentView = doc.GetFirstView()
|
||||
self.bCreating = 0
|
||||
if self.title:
|
||||
doc.SetTitle(self.title)
|
||||
|
||||
def Close(self):
|
||||
self.RemoveIdleHandler()
|
||||
try:
|
||||
parent = self.currentView.GetParent()
|
||||
except (AttributeError, win32ui.error): # Already closed
|
||||
return
|
||||
parent.DestroyWindow()
|
||||
|
||||
def SetTitle(self, title):
|
||||
self.title = title
|
||||
if self.currentView:
|
||||
self.currentView.GetDocument().SetTitle(self.title)
|
||||
|
||||
def OnViewDestroy(self, view):
|
||||
self.currentView.SaveKillBuffer()
|
||||
self.currentView = None
|
||||
|
||||
def OnFrameDestroy(self, frame):
|
||||
if self.iniSizeSection:
|
||||
# use GetWindowPlacement(), as it works even when min'd or max'd
|
||||
newSize = frame.GetWindowPlacement()[4]
|
||||
if self.loadedSize != newSize:
|
||||
app.SaveWindowSize(self.iniSizeSection, newSize)
|
||||
|
||||
def SetIdleHandler(self):
|
||||
if not self.idleHandlerSet:
|
||||
debug("Idle handler set\n")
|
||||
win32ui.GetApp().AddIdleHandler(self.QueueIdleHandler)
|
||||
self.idleHandlerSet = 1
|
||||
|
||||
def RemoveIdleHandler(self):
|
||||
if self.idleHandlerSet:
|
||||
debug("Idle handler reset\n")
|
||||
if win32ui.GetApp().DeleteIdleHandler(self.QueueIdleHandler) == 0:
|
||||
debug("Error deleting idle handler\n")
|
||||
self.idleHandlerSet = 0
|
||||
|
||||
def RecreateWindow(self):
|
||||
if self.errorCantRecreate:
|
||||
debug("Error = not trying again")
|
||||
return 0
|
||||
try:
|
||||
# This will fail if app shutting down
|
||||
win32ui.GetMainFrame().GetSafeHwnd()
|
||||
self.Create()
|
||||
return 1
|
||||
except (win32ui.error, AttributeError):
|
||||
self.errorCantRecreate = 1
|
||||
debug("Winout can not recreate the Window!\n")
|
||||
return 0
|
||||
|
||||
# this handles the idle message, and does the printing.
|
||||
def QueueIdleHandler(self, handler, count):
|
||||
try:
|
||||
bEmpty = self.QueueFlush(20)
|
||||
# If the queue is empty, then we are back to idle and restart interrupt logic.
|
||||
if bEmpty:
|
||||
self.interruptCount = 0
|
||||
except KeyboardInterrupt:
|
||||
# First interrupt since idle we just pass on.
|
||||
# later ones we dump the queue and give up.
|
||||
self.interruptCount += 1
|
||||
if self.interruptCount > 1:
|
||||
# Drop the queue quickly as the user is already annoyed :-)
|
||||
self.outputQueue = queue.Queue(-1)
|
||||
print("Interrupted.")
|
||||
bEmpty = 1
|
||||
else:
|
||||
raise # re-raise the error so the users exception filters up.
|
||||
return not bEmpty # More to do if not empty.
|
||||
|
||||
# Returns true if the Window needs to be recreated.
|
||||
def NeedRecreateWindow(self):
|
||||
try:
|
||||
if self.currentView is not None and self.currentView.IsWindow():
|
||||
return 0
|
||||
except (
|
||||
win32ui.error,
|
||||
AttributeError,
|
||||
): # Attribute error if the win32ui object has died.
|
||||
pass
|
||||
return 1
|
||||
|
||||
# Returns true if the Window is OK (either cos it was, or because it was recreated
|
||||
def CheckRecreateWindow(self):
|
||||
if self.bCreating:
|
||||
return 1
|
||||
if not self.NeedRecreateWindow():
|
||||
return 1
|
||||
if self.bAutoRestore:
|
||||
if self.RecreateWindow():
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def QueueFlush(self, max=None):
|
||||
# Returns true if the queue is empty after the flush
|
||||
# debug("Queueflush - %d, %d\n" % (max, self.outputQueue.qsize()))
|
||||
if self.bCreating:
|
||||
return 1
|
||||
items = []
|
||||
rc = 0
|
||||
while max is None or max > 0:
|
||||
try:
|
||||
item = self.outputQueue.get_nowait()
|
||||
items.append(item)
|
||||
except queue.Empty:
|
||||
rc = 1
|
||||
break
|
||||
if max is not None:
|
||||
max -= 1
|
||||
if len(items) != 0:
|
||||
if not self.CheckRecreateWindow():
|
||||
debug(":Recreate failed!\n")
|
||||
return 1 # In trouble - so say we have nothing to do.
|
||||
win32ui.PumpWaitingMessages() # Pump paint messages
|
||||
self.currentView.dowrite("".join(items))
|
||||
return rc
|
||||
|
||||
def HandleOutput(self, message):
|
||||
# debug("QueueOutput on thread %d, flags %d with '%s'...\n" % (win32api.GetCurrentThreadId(), self.writeQueueing, message ))
|
||||
self.outputQueue.put(message)
|
||||
if win32api.GetCurrentThreadId() != self.mainThreadId:
|
||||
pass
|
||||
# debug("not my thread - ignoring queue options!\n")
|
||||
elif self.writeQueueing == flags.WQ_LINE:
|
||||
pos = message.rfind("\n")
|
||||
if pos >= 0:
|
||||
# debug("Line queueing - forcing flush\n")
|
||||
self.QueueFlush()
|
||||
return
|
||||
elif self.writeQueueing == flags.WQ_NONE:
|
||||
# debug("WQ_NONE - flushing!\n")
|
||||
self.QueueFlush()
|
||||
return
|
||||
# Let our idle handler get it - wake it up
|
||||
try:
|
||||
win32ui.GetMainFrame().PostMessage(
|
||||
win32con.WM_USER
|
||||
) # Kick main thread off.
|
||||
except win32ui.error:
|
||||
# This can happen as the app is shutting down, so we send it to the C++ debugger
|
||||
win32api.OutputDebugString(message)
|
||||
|
||||
# delegate certain fns to my view.
|
||||
def writelines(self, lines):
|
||||
for line in lines:
|
||||
self.write(line)
|
||||
|
||||
def write(self, message):
|
||||
self.HandleOutput(message)
|
||||
|
||||
def flush(self):
|
||||
self.QueueFlush()
|
||||
|
||||
def HandleSpecialLine(self):
|
||||
self.currentView.HandleSpecialLine()
|
||||
|
||||
|
||||
def RTFWindowOutput(*args, **kw):
|
||||
kw["makeView"] = WindowOutputViewRTF
|
||||
return WindowOutput(*args, **kw)
|
||||
|
||||
|
||||
def thread_test(o):
|
||||
for i in range(5):
|
||||
o.write("Hi from thread %d\n" % (win32api.GetCurrentThreadId()))
|
||||
win32api.Sleep(100)
|
||||
|
||||
|
||||
def test():
|
||||
w = WindowOutput(queueing=flags.WQ_IDLE)
|
||||
w.write("First bit of text\n")
|
||||
import _thread
|
||||
|
||||
for i in range(5):
|
||||
w.write("Hello from the main thread\n")
|
||||
_thread.start_new(thread_test, (w,))
|
||||
for i in range(2):
|
||||
w.write("Hello from the main thread\n")
|
||||
win32api.Sleep(50)
|
||||
return w
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
@@ -0,0 +1,95 @@
|
||||
import re
|
||||
import string
|
||||
|
||||
###$ event <<expand-word>>
|
||||
###$ win <Alt-slash>
|
||||
###$ unix <Alt-slash>
|
||||
|
||||
|
||||
class AutoExpand:
|
||||
keydefs = {
|
||||
"<<expand-word>>": ["<Alt-slash>"],
|
||||
}
|
||||
|
||||
unix_keydefs = {
|
||||
"<<expand-word>>": ["<Meta-slash>"],
|
||||
}
|
||||
|
||||
menudefs = [
|
||||
(
|
||||
"edit",
|
||||
[
|
||||
("E_xpand word", "<<expand-word>>"),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
wordchars = string.ascii_letters + string.digits + "_"
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.text = editwin.text
|
||||
self.text.wordlist = None # XXX what is this?
|
||||
self.state = None
|
||||
|
||||
def expand_word_event(self, event):
|
||||
curinsert = self.text.index("insert")
|
||||
curline = self.text.get("insert linestart", "insert lineend")
|
||||
if not self.state:
|
||||
words = self.getwords()
|
||||
index = 0
|
||||
else:
|
||||
words, index, insert, line = self.state
|
||||
if insert != curinsert or line != curline:
|
||||
words = self.getwords()
|
||||
index = 0
|
||||
if not words:
|
||||
self.text.bell()
|
||||
return "break"
|
||||
word = self.getprevword()
|
||||
self.text.delete("insert - %d chars" % len(word), "insert")
|
||||
newword = words[index]
|
||||
index = (index + 1) % len(words)
|
||||
if index == 0:
|
||||
self.text.bell() # Warn we cycled around
|
||||
self.text.insert("insert", newword)
|
||||
curinsert = self.text.index("insert")
|
||||
curline = self.text.get("insert linestart", "insert lineend")
|
||||
self.state = words, index, curinsert, curline
|
||||
return "break"
|
||||
|
||||
def getwords(self):
|
||||
word = self.getprevword()
|
||||
if not word:
|
||||
return []
|
||||
before = self.text.get("1.0", "insert wordstart")
|
||||
wbefore = re.findall(r"\b" + word + r"\w+\b", before)
|
||||
del before
|
||||
after = self.text.get("insert wordend", "end")
|
||||
wafter = re.findall(r"\b" + word + r"\w+\b", after)
|
||||
del after
|
||||
if not wbefore and not wafter:
|
||||
return []
|
||||
words = []
|
||||
dict = {}
|
||||
# search backwards through words before
|
||||
wbefore.reverse()
|
||||
for w in wbefore:
|
||||
if dict.get(w):
|
||||
continue
|
||||
words.append(w)
|
||||
dict[w] = w
|
||||
# search onwards through words after
|
||||
for w in wafter:
|
||||
if dict.get(w):
|
||||
continue
|
||||
words.append(w)
|
||||
dict[w] = w
|
||||
words.append(word)
|
||||
return words
|
||||
|
||||
def getprevword(self):
|
||||
line = self.text.get("insert linestart", "insert")
|
||||
i = len(line)
|
||||
while i > 0 and line[i - 1] in self.wordchars:
|
||||
i -= 1
|
||||
return line[i:]
|
||||
@@ -0,0 +1,536 @@
|
||||
import tokenize
|
||||
|
||||
from pywin import default_scintilla_encoding
|
||||
|
||||
from . import PyParse
|
||||
|
||||
|
||||
class AutoIndent:
|
||||
menudefs = [
|
||||
(
|
||||
"edit",
|
||||
[
|
||||
None,
|
||||
("_Indent region", "<<indent-region>>"),
|
||||
("_Dedent region", "<<dedent-region>>"),
|
||||
("Comment _out region", "<<comment-region>>"),
|
||||
("U_ncomment region", "<<uncomment-region>>"),
|
||||
("Tabify region", "<<tabify-region>>"),
|
||||
("Untabify region", "<<untabify-region>>"),
|
||||
("Toggle tabs", "<<toggle-tabs>>"),
|
||||
("New indent width", "<<change-indentwidth>>"),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
keydefs = {
|
||||
"<<smart-backspace>>": ["<Key-BackSpace>"],
|
||||
"<<newline-and-indent>>": ["<Key-Return>", "<KP_Enter>"],
|
||||
"<<smart-indent>>": ["<Key-Tab>"],
|
||||
}
|
||||
|
||||
windows_keydefs = {
|
||||
"<<indent-region>>": ["<Control-bracketright>"],
|
||||
"<<dedent-region>>": ["<Control-bracketleft>"],
|
||||
"<<comment-region>>": ["<Alt-Key-3>"],
|
||||
"<<uncomment-region>>": ["<Alt-Key-4>"],
|
||||
"<<tabify-region>>": ["<Alt-Key-5>"],
|
||||
"<<untabify-region>>": ["<Alt-Key-6>"],
|
||||
"<<toggle-tabs>>": ["<Alt-Key-t>"],
|
||||
"<<change-indentwidth>>": ["<Alt-Key-u>"],
|
||||
}
|
||||
|
||||
unix_keydefs = {
|
||||
"<<indent-region>>": [
|
||||
"<Alt-bracketright>",
|
||||
"<Meta-bracketright>",
|
||||
"<Control-bracketright>",
|
||||
],
|
||||
"<<dedent-region>>": [
|
||||
"<Alt-bracketleft>",
|
||||
"<Meta-bracketleft>",
|
||||
"<Control-bracketleft>",
|
||||
],
|
||||
"<<comment-region>>": ["<Alt-Key-3>", "<Meta-Key-3>"],
|
||||
"<<uncomment-region>>": ["<Alt-Key-4>", "<Meta-Key-4>"],
|
||||
"<<tabify-region>>": ["<Alt-Key-5>", "<Meta-Key-5>"],
|
||||
"<<untabify-region>>": ["<Alt-Key-6>", "<Meta-Key-6>"],
|
||||
"<<toggle-tabs>>": ["<Alt-Key-t>"],
|
||||
"<<change-indentwidth>>": ["<Alt-Key-u>"],
|
||||
}
|
||||
|
||||
# usetabs true -> literal tab characters are used by indent and
|
||||
# dedent cmds, possibly mixed with spaces if
|
||||
# indentwidth is not a multiple of tabwidth
|
||||
# false -> tab characters are converted to spaces by indent
|
||||
# and dedent cmds, and ditto TAB keystrokes
|
||||
# indentwidth is the number of characters per logical indent level.
|
||||
# tabwidth is the display width of a literal tab character.
|
||||
# CAUTION: telling Tk to use anything other than its default
|
||||
# tab setting causes it to use an entirely different tabbing algorithm,
|
||||
# treating tab stops as fixed distances from the left margin.
|
||||
# Nobody expects this, so for now tabwidth should never be changed.
|
||||
usetabs = 1
|
||||
indentwidth = 4
|
||||
tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
|
||||
|
||||
# If context_use_ps1 is true, parsing searches back for a ps1 line;
|
||||
# else searches for a popular (if, def, ...) Python stmt.
|
||||
context_use_ps1 = 0
|
||||
|
||||
# When searching backwards for a reliable place to begin parsing,
|
||||
# first start num_context_lines[0] lines back, then
|
||||
# num_context_lines[1] lines back if that didn't work, and so on.
|
||||
# The last value should be huge (larger than the # of lines in a
|
||||
# conceivable file).
|
||||
# Making the initial values larger slows things down more often.
|
||||
num_context_lines = 50, 500, 5000000
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
self.text = editwin.text
|
||||
|
||||
def config(self, **options):
|
||||
for key, value in options.items():
|
||||
if key == "usetabs":
|
||||
self.usetabs = value
|
||||
elif key == "indentwidth":
|
||||
self.indentwidth = value
|
||||
elif key == "tabwidth":
|
||||
self.tabwidth = value
|
||||
elif key == "context_use_ps1":
|
||||
self.context_use_ps1 = value
|
||||
else:
|
||||
raise KeyError(f"bad option name: {key!r}")
|
||||
|
||||
# If ispythonsource and guess are true, guess a good value for
|
||||
# indentwidth based on file content (if possible), and if
|
||||
# indentwidth != tabwidth set usetabs false.
|
||||
# In any case, adjust the Text widget's view of what a tab
|
||||
# character means.
|
||||
|
||||
def set_indentation_params(self, ispythonsource, guess=1):
|
||||
if guess and ispythonsource:
|
||||
i = self.guess_indent()
|
||||
if 2 <= i <= 8:
|
||||
self.indentwidth = i
|
||||
if self.indentwidth != self.tabwidth:
|
||||
self.usetabs = 0
|
||||
|
||||
self.editwin.set_tabwidth(self.tabwidth)
|
||||
|
||||
def smart_backspace_event(self, event):
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
text.delete(first, last)
|
||||
text.mark_set("insert", first)
|
||||
return "break"
|
||||
# Delete whitespace left, until hitting a real char or closest
|
||||
# preceding virtual tab stop.
|
||||
chars = text.get("insert linestart", "insert")
|
||||
if chars == "":
|
||||
if text.compare("insert", ">", "1.0"):
|
||||
# easy: delete preceding newline
|
||||
text.delete("insert-1c")
|
||||
else:
|
||||
text.bell() # at start of buffer
|
||||
return "break"
|
||||
if chars[-1] not in " \t":
|
||||
# easy: delete preceding real char
|
||||
text.delete("insert-1c")
|
||||
return "break"
|
||||
# Ick. It may require *inserting* spaces if we back up over a
|
||||
# tab character! This is written to be clear, not fast.
|
||||
have = len(chars.expandtabs(self.tabwidth))
|
||||
assert have > 0
|
||||
want = int((have - 1) / self.indentwidth) * self.indentwidth
|
||||
ncharsdeleted = 0
|
||||
while 1:
|
||||
chars = chars[:-1]
|
||||
ncharsdeleted += 1
|
||||
have = len(chars.expandtabs(self.tabwidth))
|
||||
if have <= want or chars[-1] not in " \t":
|
||||
break
|
||||
text.undo_block_start()
|
||||
text.delete("insert-%dc" % ncharsdeleted, "insert")
|
||||
if have < want:
|
||||
text.insert("insert", " " * (want - have))
|
||||
text.undo_block_stop()
|
||||
return "break"
|
||||
|
||||
def smart_indent_event(self, event):
|
||||
# if intraline selection:
|
||||
# delete it
|
||||
# elif multiline selection:
|
||||
# do indent-region & return
|
||||
# indent one level
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
text.undo_block_start()
|
||||
try:
|
||||
if first and last:
|
||||
if index2line(first) != index2line(last):
|
||||
return self.indent_region_event(event)
|
||||
text.delete(first, last)
|
||||
text.mark_set("insert", first)
|
||||
prefix = text.get("insert linestart", "insert")
|
||||
raw, effective = classifyws(prefix, self.tabwidth)
|
||||
if raw == len(prefix):
|
||||
# only whitespace to the left
|
||||
self.reindent_to(effective + self.indentwidth)
|
||||
else:
|
||||
if self.usetabs:
|
||||
pad = "\t"
|
||||
else:
|
||||
effective = len(prefix.expandtabs(self.tabwidth))
|
||||
n = self.indentwidth
|
||||
pad = " " * (n - effective % n)
|
||||
text.insert("insert", pad)
|
||||
text.see("insert")
|
||||
return "break"
|
||||
finally:
|
||||
text.undo_block_stop()
|
||||
|
||||
def newline_and_indent_event(self, event):
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
text.undo_block_start()
|
||||
try:
|
||||
if first and last:
|
||||
text.delete(first, last)
|
||||
text.mark_set("insert", first)
|
||||
line = text.get("insert linestart", "insert")
|
||||
i, n = 0, len(line)
|
||||
while i < n and line[i] in " \t":
|
||||
i += 1
|
||||
if i == n:
|
||||
# the cursor is in or at leading indentation; just inject
|
||||
# an empty line at the start and strip space from current line
|
||||
text.delete("insert - %d chars" % i, "insert")
|
||||
text.insert("insert linestart", "\n")
|
||||
return "break"
|
||||
indent = line[:i]
|
||||
# strip whitespace before insert point
|
||||
i = 0
|
||||
while line and line[-1] in " \t":
|
||||
line = line[:-1]
|
||||
i += 1
|
||||
if i:
|
||||
text.delete("insert - %d chars" % i, "insert")
|
||||
# strip whitespace after insert point
|
||||
while text.get("insert") in " \t":
|
||||
text.delete("insert")
|
||||
# start new line
|
||||
text.insert("insert", "\n")
|
||||
|
||||
# adjust indentation for continuations and block
|
||||
# open/close first need to find the last stmt
|
||||
lno = index2line(text.index("insert"))
|
||||
y = PyParse.Parser(self.indentwidth, self.tabwidth)
|
||||
for context in self.num_context_lines:
|
||||
startat = max(lno - context, 1)
|
||||
startatindex = f"{startat!r}.0"
|
||||
rawtext = text.get(startatindex, "insert")
|
||||
y.set_str(rawtext)
|
||||
bod = y.find_good_parse_start(
|
||||
self.context_use_ps1, self._build_char_in_string_func(startatindex)
|
||||
)
|
||||
if bod is not None or startat == 1:
|
||||
break
|
||||
y.set_lo(bod or 0)
|
||||
c = y.get_continuation_type()
|
||||
if c != PyParse.C_NONE:
|
||||
# The current stmt hasn't ended yet.
|
||||
if c == PyParse.C_STRING:
|
||||
# inside a string; just mimic the current indent
|
||||
text.insert("insert", indent)
|
||||
elif c == PyParse.C_BRACKET:
|
||||
# line up with the first (if any) element of the
|
||||
# last open bracket structure; else indent one
|
||||
# level beyond the indent of the line with the
|
||||
# last open bracket
|
||||
self.reindent_to(y.compute_bracket_indent())
|
||||
elif c == PyParse.C_BACKSLASH:
|
||||
# if more than one line in this stmt already, just
|
||||
# mimic the current indent; else if initial line
|
||||
# has a start on an assignment stmt, indent to
|
||||
# beyond leftmost =; else to beyond first chunk of
|
||||
# non-whitespace on initial line
|
||||
if y.get_num_lines_in_stmt() > 1:
|
||||
text.insert("insert", indent)
|
||||
else:
|
||||
self.reindent_to(y.compute_backslash_indent())
|
||||
else:
|
||||
raise ValueError(f"bogus continuation type {c!r}")
|
||||
return "break"
|
||||
|
||||
# This line starts a brand new stmt; indent relative to
|
||||
# indentation of initial line of closest preceding
|
||||
# interesting stmt.
|
||||
indent = y.get_base_indent_string()
|
||||
text.insert("insert", indent)
|
||||
if y.is_block_opener():
|
||||
self.smart_indent_event(event)
|
||||
elif indent and y.is_block_closer():
|
||||
self.smart_backspace_event(event)
|
||||
return "break"
|
||||
finally:
|
||||
text.see("insert")
|
||||
text.undo_block_stop()
|
||||
|
||||
auto_indent = newline_and_indent_event
|
||||
|
||||
# Our editwin provides a is_char_in_string function that works
|
||||
# with a Tk text index, but PyParse only knows about offsets into
|
||||
# a string. This builds a function for PyParse that accepts an
|
||||
# offset.
|
||||
|
||||
def _build_char_in_string_func(self, startindex):
|
||||
def inner(offset, _startindex=startindex, _icis=self.editwin.is_char_in_string):
|
||||
return _icis(_startindex + "+%dc" % offset)
|
||||
|
||||
return inner
|
||||
|
||||
def indent_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = classifyws(line, self.tabwidth)
|
||||
effective += self.indentwidth
|
||||
lines[pos] = self._make_blanks(effective) + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def dedent_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = classifyws(line, self.tabwidth)
|
||||
effective = max(effective - self.indentwidth, 0)
|
||||
lines[pos] = self._make_blanks(effective) + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def comment_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines) - 1):
|
||||
line = lines[pos]
|
||||
lines[pos] = "##" + line
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def uncomment_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if not line:
|
||||
continue
|
||||
if line[:2] == "##":
|
||||
line = line[2:]
|
||||
elif line[:1] == "#":
|
||||
line = line[1:]
|
||||
lines[pos] = line
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def tabify_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
tabwidth = self._asktabwidth()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = classifyws(line, tabwidth)
|
||||
ntabs, nspaces = divmod(effective, tabwidth)
|
||||
lines[pos] = "\t" * ntabs + " " * nspaces + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def untabify_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
tabwidth = self._asktabwidth()
|
||||
for pos in range(len(lines)):
|
||||
lines[pos] = lines[pos].expandtabs(tabwidth)
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def toggle_tabs_event(self, event):
|
||||
if self.editwin.askyesno(
|
||||
"Toggle tabs",
|
||||
"Turn tabs " + ("on", "off")[self.usetabs] + "?",
|
||||
parent=self.text,
|
||||
):
|
||||
self.usetabs = not self.usetabs
|
||||
return "break"
|
||||
|
||||
# XXX this isn't bound to anything -- see class tabwidth comments
|
||||
def change_tabwidth_event(self, event):
|
||||
new = self._asktabwidth()
|
||||
if new != self.tabwidth:
|
||||
self.tabwidth = new
|
||||
self.set_indentation_params(0, guess=0)
|
||||
return "break"
|
||||
|
||||
def change_indentwidth_event(self, event):
|
||||
new = self.editwin.askinteger(
|
||||
"Indent width",
|
||||
"New indent width (1-16)",
|
||||
parent=self.text,
|
||||
initialvalue=self.indentwidth,
|
||||
minvalue=1,
|
||||
maxvalue=16,
|
||||
)
|
||||
if new and new != self.indentwidth:
|
||||
self.indentwidth = new
|
||||
return "break"
|
||||
|
||||
def get_region(self):
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
head = text.index(first + " linestart")
|
||||
tail = text.index(last + "-1c lineend +1c")
|
||||
else:
|
||||
head = text.index("insert linestart")
|
||||
tail = text.index("insert lineend +1c")
|
||||
chars = text.get(head, tail)
|
||||
lines = chars.split("\n")
|
||||
return head, tail, chars, lines
|
||||
|
||||
def set_region(self, head, tail, chars, lines):
|
||||
text = self.text
|
||||
newchars = "\n".join(lines)
|
||||
if newchars == chars:
|
||||
text.bell()
|
||||
return
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
text.mark_set("insert", head)
|
||||
text.undo_block_start()
|
||||
text.delete(head, tail)
|
||||
text.insert(head, newchars)
|
||||
text.undo_block_stop()
|
||||
text.tag_add("sel", head, "insert")
|
||||
|
||||
# Make string that displays as n leading blanks.
|
||||
|
||||
def _make_blanks(self, n):
|
||||
if self.usetabs:
|
||||
ntabs, nspaces = divmod(n, self.tabwidth)
|
||||
return "\t" * ntabs + " " * nspaces
|
||||
else:
|
||||
return " " * n
|
||||
|
||||
# Delete from beginning of line to insert point, then reinsert
|
||||
# column logical (meaning use tabs if appropriate) spaces.
|
||||
|
||||
def reindent_to(self, column):
|
||||
text = self.text
|
||||
text.undo_block_start()
|
||||
if text.compare("insert linestart", "!=", "insert"):
|
||||
text.delete("insert linestart", "insert")
|
||||
if column:
|
||||
text.insert("insert", self._make_blanks(column))
|
||||
text.undo_block_stop()
|
||||
|
||||
def _asktabwidth(self):
|
||||
return (
|
||||
self.editwin.askinteger(
|
||||
"Tab width",
|
||||
"Spaces per tab?",
|
||||
parent=self.text,
|
||||
initialvalue=self.tabwidth,
|
||||
minvalue=1,
|
||||
maxvalue=16,
|
||||
)
|
||||
or self.tabwidth
|
||||
)
|
||||
|
||||
# Guess indentwidth from text content.
|
||||
# Return guessed indentwidth. This should not be believed unless
|
||||
# it's in a reasonable range (e.g., it will be 0 if no indented
|
||||
# blocks are found).
|
||||
|
||||
def guess_indent(self):
|
||||
opener, indented = IndentSearcher(self.text, self.tabwidth).run()
|
||||
if opener and indented:
|
||||
raw, indentsmall = classifyws(opener, self.tabwidth)
|
||||
raw, indentlarge = classifyws(indented, self.tabwidth)
|
||||
else:
|
||||
indentsmall = indentlarge = 0
|
||||
return indentlarge - indentsmall
|
||||
|
||||
|
||||
# "line.col" -> line, as an int
|
||||
def index2line(index):
|
||||
return int(float(index))
|
||||
|
||||
|
||||
# Look at the leading whitespace in s.
|
||||
# Return pair (# of leading ws characters,
|
||||
# effective # of leading blanks after expanding
|
||||
# tabs to width tabwidth)
|
||||
|
||||
|
||||
def classifyws(s, tabwidth):
|
||||
raw = effective = 0
|
||||
for ch in s:
|
||||
if ch == " ":
|
||||
raw += 1
|
||||
effective += 1
|
||||
elif ch == "\t":
|
||||
raw += 1
|
||||
effective = (effective // tabwidth + 1) * tabwidth
|
||||
else:
|
||||
break
|
||||
return raw, effective
|
||||
|
||||
|
||||
class IndentSearcher:
|
||||
# .run() chews over the Text widget, looking for a block opener
|
||||
# and the stmt following it. Returns a pair,
|
||||
# (line containing block opener, line containing stmt)
|
||||
# Either or both may be None.
|
||||
|
||||
def __init__(self, text, tabwidth):
|
||||
self.text = text
|
||||
self.tabwidth = tabwidth
|
||||
self.i = self.finished = 0
|
||||
self.blkopenline = self.indentedline = None
|
||||
|
||||
def readline(self):
|
||||
if self.finished:
|
||||
val = ""
|
||||
else:
|
||||
i = self.i = self.i + 1
|
||||
mark = f"{i!r}.0"
|
||||
if self.text.compare(mark, ">=", "end"):
|
||||
val = ""
|
||||
else:
|
||||
val = self.text.get(mark, mark + " lineend+1c")
|
||||
# hrm - not sure this is correct - the source code may have
|
||||
# an encoding declared, but the data will *always* be in
|
||||
# default_scintilla_encoding - so if anyone looks at the encoding decl
|
||||
# in the source they will be wrong. I think. Maybe. Or something...
|
||||
return val.encode(default_scintilla_encoding)
|
||||
|
||||
def run(self):
|
||||
OPENERS = ("class", "def", "for", "if", "try", "while")
|
||||
INDENT = tokenize.INDENT
|
||||
NAME = tokenize.NAME
|
||||
|
||||
save_tabsize = tokenize.tabsize
|
||||
tokenize.tabsize = self.tabwidth
|
||||
try:
|
||||
try:
|
||||
for typ, token, start, end, line in tokenize.tokenize(self.readline):
|
||||
if typ == NAME and token in OPENERS:
|
||||
self.blkopenline = line
|
||||
elif typ == INDENT and self.blkopenline:
|
||||
self.indentedline = line
|
||||
break
|
||||
|
||||
except (tokenize.TokenError, IndentationError):
|
||||
# since we cut off the tokenizer early, we can trigger
|
||||
# spurious errors
|
||||
pass
|
||||
finally:
|
||||
tokenize.tabsize = save_tabsize
|
||||
return self.blkopenline, self.indentedline
|
||||
@@ -0,0 +1,216 @@
|
||||
# CallTips.py - An IDLE extension that provides "Call Tips" - ie, a floating window that
|
||||
# displays parameter information as you open parens.
|
||||
|
||||
import inspect
|
||||
import string
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
||||
class CallTips:
|
||||
menudefs = []
|
||||
|
||||
keydefs = {
|
||||
"<<paren-open>>": ["<Key-parenleft>"],
|
||||
"<<paren-close>>": ["<Key-parenright>"],
|
||||
"<<check-calltip-cancel>>": ["<KeyRelease>"],
|
||||
"<<calltip-cancel>>": ["<ButtonPress>", "<Key-Escape>"],
|
||||
}
|
||||
|
||||
windows_keydefs = {}
|
||||
|
||||
unix_keydefs = {}
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
self.text = editwin.text
|
||||
self.calltip = None
|
||||
if hasattr(self.text, "make_calltip_window"):
|
||||
self._make_calltip_window = self.text.make_calltip_window
|
||||
else:
|
||||
self._make_calltip_window = self._make_tk_calltip_window
|
||||
|
||||
def close(self):
|
||||
self._make_calltip_window = None
|
||||
|
||||
# Makes a Tk based calltip window. Used by IDLE, but not Pythonwin.
|
||||
# See __init__ above for how this is used.
|
||||
def _make_tk_calltip_window(self):
|
||||
import CallTipWindow
|
||||
|
||||
return CallTipWindow.CallTip(self.text)
|
||||
|
||||
def _remove_calltip_window(self):
|
||||
if self.calltip:
|
||||
self.calltip.hidetip()
|
||||
self.calltip = None
|
||||
|
||||
def paren_open_event(self, event):
|
||||
self._remove_calltip_window()
|
||||
arg_text = get_arg_text(self.get_object_at_cursor())
|
||||
if arg_text:
|
||||
self.calltip_start = self.text.index("insert")
|
||||
self.calltip = self._make_calltip_window()
|
||||
self.calltip.showtip(arg_text)
|
||||
return "" # so the event is handled normally.
|
||||
|
||||
def paren_close_event(self, event):
|
||||
# Now just hides, but later we should check if other
|
||||
# paren'd expressions remain open.
|
||||
self._remove_calltip_window()
|
||||
return "" # so the event is handled normally.
|
||||
|
||||
def check_calltip_cancel_event(self, event):
|
||||
if self.calltip:
|
||||
# If we have moved before the start of the calltip,
|
||||
# or off the calltip line, then cancel the tip.
|
||||
# (Later need to be smarter about multi-line, etc)
|
||||
if self.text.compare(
|
||||
"insert", "<=", self.calltip_start
|
||||
) or self.text.compare("insert", ">", self.calltip_start + " lineend"):
|
||||
self._remove_calltip_window()
|
||||
return "" # so the event is handled normally.
|
||||
|
||||
def calltip_cancel_event(self, event):
|
||||
self._remove_calltip_window()
|
||||
return "" # so the event is handled normally.
|
||||
|
||||
def get_object_at_cursor(
|
||||
self,
|
||||
wordchars="._"
|
||||
+ string.ascii_uppercase
|
||||
+ string.ascii_lowercase
|
||||
+ string.digits,
|
||||
):
|
||||
# XXX - This needs to be moved to a better place
|
||||
# so the "." attribute lookup code can also use it.
|
||||
text = self.text
|
||||
chars = text.get("insert linestart", "insert")
|
||||
i = len(chars)
|
||||
while i and chars[i - 1] in wordchars:
|
||||
i -= 1
|
||||
word = chars[i:]
|
||||
if word:
|
||||
# How is this for a hack!
|
||||
import __main__
|
||||
|
||||
namespace = sys.modules.copy()
|
||||
namespace.update(__main__.__dict__)
|
||||
try:
|
||||
return eval(word, namespace)
|
||||
except:
|
||||
pass
|
||||
return None # Can't find an object.
|
||||
|
||||
|
||||
def _find_constructor(class_ob):
|
||||
# Given a class object, return a function object used for the
|
||||
# constructor (ie, __init__() ) or None if we can't find one.
|
||||
try:
|
||||
return class_ob.__init__
|
||||
except AttributeError:
|
||||
for base in class_ob.__bases__:
|
||||
rc = _find_constructor(base)
|
||||
if rc is not None:
|
||||
return rc
|
||||
return None
|
||||
|
||||
|
||||
def get_arg_text(ob):
|
||||
# Get a string describing the arguments for the given object.
|
||||
argText = ""
|
||||
if ob is not None:
|
||||
if inspect.isclass(ob):
|
||||
# Look for the highest __init__ in the class chain.
|
||||
fob = _find_constructor(ob)
|
||||
if fob is None:
|
||||
fob = lambda: None
|
||||
else:
|
||||
fob = ob
|
||||
if inspect.isfunction(fob) or inspect.ismethod(fob):
|
||||
try:
|
||||
argText = str(inspect.signature(fob))
|
||||
except:
|
||||
print("Failed to format the args")
|
||||
traceback.print_exc()
|
||||
# See if we can use the docstring
|
||||
if hasattr(ob, "__doc__"):
|
||||
doc = ob.__doc__
|
||||
try:
|
||||
doc = doc.strip()
|
||||
pos = doc.find("\n")
|
||||
except AttributeError:
|
||||
## New style classes may have __doc__ slot without actually
|
||||
## having a string assigned to it
|
||||
pass
|
||||
else:
|
||||
if pos < 0 or pos > 70:
|
||||
pos = 70
|
||||
if argText:
|
||||
argText += "\n"
|
||||
argText += doc[:pos]
|
||||
|
||||
return argText
|
||||
|
||||
|
||||
#################################################
|
||||
#
|
||||
# Test code
|
||||
#
|
||||
if __name__ == "__main__":
|
||||
|
||||
def t1():
|
||||
"()"
|
||||
|
||||
def t2(a, b=None):
|
||||
"(a, b=None)"
|
||||
|
||||
def t3(a, *args):
|
||||
"(a, *args)"
|
||||
|
||||
def t4(*args):
|
||||
"(*args)"
|
||||
|
||||
def t5(a, *args):
|
||||
"(a, *args)"
|
||||
|
||||
def t6(a, b=None, *args, **kw):
|
||||
"(a, b=None, *args, **kw)"
|
||||
|
||||
class TC:
|
||||
"(self, a=None, *b)"
|
||||
|
||||
def __init__(self, a=None, *b):
|
||||
"(self, a=None, *b)"
|
||||
|
||||
def t1(self):
|
||||
"(self)"
|
||||
|
||||
def t2(self, a, b=None):
|
||||
"(self, a, b=None)"
|
||||
|
||||
def t3(self, a, *args):
|
||||
"(self, a, *args)"
|
||||
|
||||
def t4(self, *args):
|
||||
"(self, *args)"
|
||||
|
||||
def t5(self, a, *args):
|
||||
"(self, a, *args)"
|
||||
|
||||
def t6(self, a, b=None, *args, **kw):
|
||||
"(self, a, b=None, *args, **kw)"
|
||||
|
||||
def test(tests):
|
||||
failed = []
|
||||
for t in tests:
|
||||
expected = t.__doc__ + "\n" + t.__doc__
|
||||
if get_arg_text(t) != expected:
|
||||
failed.append(t)
|
||||
print(f"{t} - expected {expected!r}, but got {get_arg_text(t)!r}")
|
||||
print("%d of %d tests failed" % (len(failed), len(tests)))
|
||||
|
||||
tc = TC()
|
||||
tests = t1, t2, t3, t4, t5, t6, TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6
|
||||
|
||||
test(tests)
|
||||
@@ -0,0 +1,166 @@
|
||||
# Extension to format a paragraph
|
||||
|
||||
# Does basic, standard text formatting, and also understands Python
|
||||
# comment blocks. Thus, for editing Python source code, this
|
||||
# extension is really only suitable for reformatting these comment
|
||||
# blocks or triple-quoted strings.
|
||||
|
||||
# Known problems with comment reformatting:
|
||||
# * If there is a selection marked, and the first line of the
|
||||
# selection is not complete, the block will probably not be detected
|
||||
# as comments, and will have the normal "text formatting" rules
|
||||
# applied.
|
||||
# * If a comment block has leading whitespace that mixes tabs and
|
||||
# spaces, they will not be considered part of the same block.
|
||||
# * Fancy comments, like this bulleted list, aren't handled :-)
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class FormatParagraph:
|
||||
menudefs = [
|
||||
(
|
||||
"edit",
|
||||
[
|
||||
("Format Paragraph", "<<format-paragraph>>"),
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
keydefs = {
|
||||
"<<format-paragraph>>": ["<Alt-q>"],
|
||||
}
|
||||
|
||||
unix_keydefs = {
|
||||
"<<format-paragraph>>": ["<Meta-q>"],
|
||||
}
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
def close(self):
|
||||
self.editwin = None
|
||||
|
||||
def format_paragraph_event(self, event):
|
||||
text = self.editwin.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
data = text.get(first, last)
|
||||
comment_header = ""
|
||||
else:
|
||||
first, last, comment_header, data = find_paragraph(
|
||||
text, text.index("insert")
|
||||
)
|
||||
if comment_header:
|
||||
# Reformat the comment lines - convert to text sans header.
|
||||
lines = data.split("\n")
|
||||
lines = map(lambda st, l=len(comment_header): st[l:], lines)
|
||||
data = "\n".join(lines)
|
||||
# Reformat to 70 chars or a 20 char width, whichever is greater.
|
||||
format_width = max(70 - len(comment_header), 20)
|
||||
newdata = reformat_paragraph(data, format_width)
|
||||
# re-split and re-insert the comment header.
|
||||
newdata = newdata.split("\n")
|
||||
# If the block ends in a \n, we don't want the comment
|
||||
# prefix inserted after it. (I'm not sure it makes sense to
|
||||
# reformat a comment block that isn't made of complete
|
||||
# lines, but whatever!) Can't think of a clean soltution,
|
||||
# so we hack away
|
||||
block_suffix = ""
|
||||
if not newdata[-1]:
|
||||
block_suffix = "\n"
|
||||
newdata = newdata[:-1]
|
||||
builder = lambda item, prefix=comment_header: prefix + item
|
||||
newdata = "\n".join([builder(d) for d in newdata]) + block_suffix
|
||||
else:
|
||||
# Just a normal text format
|
||||
newdata = reformat_paragraph(data)
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
if newdata != data:
|
||||
text.mark_set("insert", first)
|
||||
text.undo_block_start()
|
||||
text.delete(first, last)
|
||||
text.insert(first, newdata)
|
||||
text.undo_block_stop()
|
||||
else:
|
||||
text.mark_set("insert", last)
|
||||
text.see("insert")
|
||||
|
||||
|
||||
def find_paragraph(text, mark):
|
||||
lineno, col = list(map(int, mark.split(".")))
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
while text.compare("%d.0" % lineno, "<", "end") and is_all_white(line):
|
||||
lineno += 1
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
first_lineno = lineno
|
||||
comment_header = get_comment_header(line)
|
||||
comment_header_len = len(comment_header)
|
||||
while get_comment_header(line) == comment_header and not is_all_white(
|
||||
line[comment_header_len:]
|
||||
):
|
||||
lineno += 1
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
last = "%d.0" % lineno
|
||||
# Search back to beginning of paragraph
|
||||
lineno = first_lineno - 1
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
while (
|
||||
lineno > 0
|
||||
and get_comment_header(line) == comment_header
|
||||
and not is_all_white(line[comment_header_len:])
|
||||
):
|
||||
lineno -= 1
|
||||
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
|
||||
first = "%d.0" % (lineno + 1)
|
||||
return first, last, comment_header, text.get(first, last)
|
||||
|
||||
|
||||
def reformat_paragraph(data, limit=70):
|
||||
lines = data.split("\n")
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n and is_all_white(lines[i]):
|
||||
i += 1
|
||||
if i >= n:
|
||||
return data
|
||||
indent1 = get_indent(lines[i])
|
||||
if i + 1 < n and not is_all_white(lines[i + 1]):
|
||||
indent2 = get_indent(lines[i + 1])
|
||||
else:
|
||||
indent2 = indent1
|
||||
new = lines[:i]
|
||||
partial = indent1
|
||||
while i < n and not is_all_white(lines[i]):
|
||||
# XXX Should take double space after period (etc.) into account
|
||||
words = re.split(r"(\s+)", lines[i])
|
||||
for j in range(0, len(words), 2):
|
||||
word = words[j]
|
||||
if not word:
|
||||
continue # Can happen when line ends in whitespace
|
||||
if len((partial + word).expandtabs()) > limit and partial != indent1:
|
||||
new.append(partial.rstrip())
|
||||
partial = indent2
|
||||
partial += word + " "
|
||||
if j + 1 < len(words) and words[j + 1] != " ":
|
||||
partial += " "
|
||||
i += 1
|
||||
new.append(partial.rstrip())
|
||||
# XXX Should reformat remaining paragraphs as well
|
||||
new.extend(lines[i:])
|
||||
return "\n".join(new)
|
||||
|
||||
|
||||
def is_all_white(line):
|
||||
return re.match(r"^\s*$", line) is not None
|
||||
|
||||
|
||||
def get_indent(line):
|
||||
return re.match(r"^(\s*)", line).group()
|
||||
|
||||
|
||||
def get_comment_header(line):
|
||||
m = re.match(r"^(\s*#*)", line)
|
||||
if m is None:
|
||||
return ""
|
||||
return m.group(1)
|
||||
@@ -0,0 +1,87 @@
|
||||
class History:
|
||||
def __init__(self, text, output_sep="\n"):
|
||||
self.text = text
|
||||
self.history = []
|
||||
self.history_prefix = None
|
||||
self.history_pointer = None
|
||||
self.output_sep = output_sep
|
||||
text.bind("<<history-previous>>", self.history_prev)
|
||||
text.bind("<<history-next>>", self.history_next)
|
||||
|
||||
def history_next(self, event):
|
||||
self.history_do(0)
|
||||
return "break"
|
||||
|
||||
def history_prev(self, event):
|
||||
self.history_do(1)
|
||||
return "break"
|
||||
|
||||
def _get_source(self, start, end):
|
||||
# Get source code from start index to end index. Lines in the
|
||||
# text control may be separated by sys.ps2 .
|
||||
lines = self.text.get(start, end).split(self.output_sep)
|
||||
return "\n".join(lines)
|
||||
|
||||
def _put_source(self, where, source):
|
||||
output = self.output_sep.join(source.split("\n"))
|
||||
self.text.insert(where, output)
|
||||
|
||||
def history_do(self, reverse):
|
||||
nhist = len(self.history)
|
||||
pointer = self.history_pointer
|
||||
prefix = self.history_prefix
|
||||
if pointer is not None and prefix is not None:
|
||||
if (
|
||||
self.text.compare("insert", "!=", "end-1c")
|
||||
or self._get_source("iomark", "end-1c") != self.history[pointer]
|
||||
):
|
||||
pointer = prefix = None
|
||||
if pointer is None or prefix is None:
|
||||
prefix = self._get_source("iomark", "end-1c")
|
||||
if reverse:
|
||||
pointer = nhist
|
||||
else:
|
||||
pointer = -1
|
||||
nprefix = len(prefix)
|
||||
while 1:
|
||||
if reverse:
|
||||
pointer -= 1
|
||||
else:
|
||||
pointer += 1
|
||||
if pointer < 0 or pointer >= nhist:
|
||||
self.text.bell()
|
||||
if self._get_source("iomark", "end-1c") != prefix:
|
||||
self.text.delete("iomark", "end-1c")
|
||||
self._put_source("iomark", prefix)
|
||||
pointer = prefix = None
|
||||
break
|
||||
item = self.history[pointer]
|
||||
if item[:nprefix] == prefix and len(item) > nprefix:
|
||||
self.text.delete("iomark", "end-1c")
|
||||
self._put_source("iomark", item)
|
||||
break
|
||||
self.text.mark_set("insert", "end-1c")
|
||||
self.text.see("insert")
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.history_pointer = pointer
|
||||
self.history_prefix = prefix
|
||||
|
||||
def history_store(self, source):
|
||||
source = source.strip()
|
||||
if len(source) > 2:
|
||||
# avoid duplicates
|
||||
try:
|
||||
self.history.remove(source)
|
||||
except ValueError:
|
||||
pass
|
||||
self.history.append(source)
|
||||
self.history_pointer = None
|
||||
self.history_prefix = None
|
||||
|
||||
def recall(self, s):
|
||||
s = s.strip()
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.text.delete("iomark", "end-1c")
|
||||
self.text.mark_set("insert", "end-1c")
|
||||
self.text.insert("insert", s)
|
||||
self.text.see("insert")
|
||||
@@ -0,0 +1,585 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Reason last stmt is continued (or C_NONE if it's not).
|
||||
C_NONE, C_BACKSLASH, C_STRING, C_BRACKET = list(range(4))
|
||||
|
||||
if 0: # for throwaway debugging output
|
||||
|
||||
def dump(*stuff):
|
||||
sys.__stdout__.write(" ".join(map(str, stuff)) + "\n")
|
||||
|
||||
|
||||
# Find what looks like the start of a popular stmt.
|
||||
|
||||
_synchre = re.compile(
|
||||
r"""
|
||||
^
|
||||
[ \t]*
|
||||
(?: if
|
||||
| for
|
||||
| while
|
||||
| else
|
||||
| def
|
||||
| return
|
||||
| assert
|
||||
| break
|
||||
| class
|
||||
| continue
|
||||
| elif
|
||||
| try
|
||||
| except
|
||||
| raise
|
||||
| import
|
||||
)
|
||||
\b
|
||||
""",
|
||||
re.VERBOSE | re.MULTILINE,
|
||||
).search
|
||||
|
||||
# Match blank line or non-indenting comment line.
|
||||
|
||||
_junkre = re.compile(
|
||||
r"""
|
||||
[ \t]*
|
||||
(?: \# \S .* )?
|
||||
\n
|
||||
""",
|
||||
re.VERBOSE,
|
||||
).match
|
||||
|
||||
# Match any flavor of string; the terminating quote is optional
|
||||
# so that we're robust in the face of incomplete program text.
|
||||
|
||||
_match_stringre = re.compile(
|
||||
r"""
|
||||
\""" [^"\\]* (?:
|
||||
(?: \\. | "(?!"") )
|
||||
[^"\\]*
|
||||
)*
|
||||
(?: \""" )?
|
||||
|
||||
| " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
|
||||
|
||||
| ''' [^'\\]* (?:
|
||||
(?: \\. | '(?!'') )
|
||||
[^'\\]*
|
||||
)*
|
||||
(?: ''' )?
|
||||
|
||||
| ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
|
||||
""",
|
||||
re.VERBOSE | re.DOTALL,
|
||||
).match
|
||||
|
||||
# Match a line that starts with something interesting;
|
||||
# used to find the first item of a bracket structure.
|
||||
|
||||
_itemre = re.compile(
|
||||
r"""
|
||||
[ \t]*
|
||||
[^\s#\\] # if we match, m.end()-1 is the interesting char
|
||||
""",
|
||||
re.VERBOSE,
|
||||
).match
|
||||
|
||||
# Match start of stmts that should be followed by a dedent.
|
||||
|
||||
_closere = re.compile(
|
||||
r"""
|
||||
\s*
|
||||
(?: return
|
||||
| break
|
||||
| continue
|
||||
| raise
|
||||
| pass
|
||||
)
|
||||
\b
|
||||
""",
|
||||
re.VERBOSE,
|
||||
).match
|
||||
|
||||
# Chew up non-special chars as quickly as possible. If match is
|
||||
# successful, m.end() less 1 is the index of the last boring char
|
||||
# matched. If match is unsuccessful, the string starts with an
|
||||
# interesting char.
|
||||
|
||||
_chew_ordinaryre = re.compile(
|
||||
r"""
|
||||
[^[\](){}#'"\\]+
|
||||
""",
|
||||
re.VERBOSE,
|
||||
).match
|
||||
|
||||
# Build translation table to map uninteresting chars to "x", open
|
||||
# brackets to "(", and close brackets to ")".
|
||||
|
||||
_tran = ["x"] * 256
|
||||
for ch in "({[":
|
||||
_tran[ord(ch)] = "("
|
||||
for ch in ")}]":
|
||||
_tran[ord(ch)] = ")"
|
||||
for ch in "\"'\\\n#":
|
||||
_tran[ord(ch)] = ch
|
||||
del ch
|
||||
|
||||
|
||||
class Parser:
|
||||
def __init__(self, indentwidth, tabwidth):
|
||||
self.indentwidth = indentwidth
|
||||
self.tabwidth = tabwidth
|
||||
|
||||
def set_str(self, str):
|
||||
assert len(str) == 0 or str[-1] == "\n", f"Oops - have str {str!r}"
|
||||
self.str = str
|
||||
self.study_level = 0
|
||||
|
||||
# Return index of a good place to begin parsing, as close to the
|
||||
# end of the string as possible. This will be the start of some
|
||||
# popular stmt like "if" or "def". Return None if none found:
|
||||
# the caller should pass more prior context then, if possible, or
|
||||
# if not (the entire program text up until the point of interest
|
||||
# has already been tried) pass 0 to set_lo.
|
||||
#
|
||||
# This will be reliable iff given a reliable is_char_in_string
|
||||
# function, meaning that when it says "no", it's absolutely
|
||||
# guaranteed that the char is not in a string.
|
||||
#
|
||||
# Ack, hack: in the shell window this kills us, because there's
|
||||
# no way to tell the differences between output, >>> etc and
|
||||
# user input. Indeed, IDLE's first output line makes the rest
|
||||
# look like it's in an unclosed paren!:
|
||||
# Python X.X.X (#0, Apr 13 1999, ...
|
||||
|
||||
def find_good_parse_start(self, use_ps1, is_char_in_string=None):
|
||||
str, pos = self.str, None
|
||||
if use_ps1:
|
||||
# shell window
|
||||
ps1 = "\n" + sys.ps1
|
||||
i = str.rfind(ps1)
|
||||
if i >= 0:
|
||||
pos = i + len(ps1)
|
||||
# make it look like there's a newline instead
|
||||
# of ps1 at the start -- hacking here once avoids
|
||||
# repeated hackery later
|
||||
self.str = str[: pos - 1] + "\n" + str[pos:]
|
||||
return pos
|
||||
|
||||
# File window -- real work.
|
||||
if not is_char_in_string:
|
||||
# no clue -- make the caller pass everything
|
||||
return None
|
||||
|
||||
# Peek back from the end for a good place to start,
|
||||
# but don't try too often; pos will be left None, or
|
||||
# bumped to a legitimate synch point.
|
||||
limit = len(str)
|
||||
for tries in range(5):
|
||||
i = str.rfind(":\n", 0, limit)
|
||||
if i < 0:
|
||||
break
|
||||
i = str.rfind("\n", 0, i) + 1 # start of colon line
|
||||
m = _synchre(str, i, limit)
|
||||
if m and not is_char_in_string(m.start()):
|
||||
pos = m.start()
|
||||
break
|
||||
limit = i
|
||||
if pos is None:
|
||||
# Nothing looks like a block-opener, or stuff does
|
||||
# but is_char_in_string keeps returning true; most likely
|
||||
# we're in or near a giant string, the colorizer hasn't
|
||||
# caught up enough to be helpful, or there simply *aren't*
|
||||
# any interesting stmts. In any of these cases we're
|
||||
# going to have to parse the whole thing to be sure, so
|
||||
# give it one last try from the start, but stop wasting
|
||||
# time here regardless of the outcome.
|
||||
m = _synchre(str)
|
||||
if m and not is_char_in_string(m.start()):
|
||||
pos = m.start()
|
||||
return pos
|
||||
|
||||
# Peeking back worked; look forward until _synchre no longer
|
||||
# matches.
|
||||
i = pos + 1
|
||||
while 1:
|
||||
m = _synchre(str, i)
|
||||
if m:
|
||||
s, i = m.span()
|
||||
if not is_char_in_string(s):
|
||||
pos = s
|
||||
else:
|
||||
break
|
||||
return pos
|
||||
|
||||
# Throw away the start of the string. Intended to be called with
|
||||
# find_good_parse_start's result.
|
||||
|
||||
def set_lo(self, lo):
|
||||
assert lo == 0 or self.str[lo - 1] == "\n"
|
||||
if lo > 0:
|
||||
self.str = self.str[lo:]
|
||||
|
||||
# As quickly as humanly possible <wink>, find the line numbers (0-
|
||||
# based) of the non-continuation lines.
|
||||
# Creates self.{goodlines, continuation}.
|
||||
|
||||
def _study1(self):
|
||||
if self.study_level >= 1:
|
||||
return
|
||||
self.study_level = 1
|
||||
|
||||
# Map all uninteresting characters to "x", all open brackets
|
||||
# to "(", all close brackets to ")", then collapse runs of
|
||||
# uninteresting characters. This can cut the number of chars
|
||||
# by a factor of 10-40, and so greatly speed the following loop.
|
||||
str = self.str
|
||||
str = str.translate(_tran)
|
||||
str = str.replace("xxxxxxxx", "x")
|
||||
str = str.replace("xxxx", "x")
|
||||
str = str.replace("xx", "x")
|
||||
str = str.replace("xx", "x")
|
||||
str = str.replace("\nx", "\n")
|
||||
# note that replacing x\n with \n would be incorrect, because
|
||||
# x may be preceded by a backslash
|
||||
|
||||
# March over the squashed version of the program, accumulating
|
||||
# the line numbers of non-continued stmts, and determining
|
||||
# whether & why the last stmt is a continuation.
|
||||
continuation = C_NONE
|
||||
level = lno = 0 # level is nesting level; lno is line number
|
||||
self.goodlines = goodlines = [0]
|
||||
push_good = goodlines.append
|
||||
i, n = 0, len(str)
|
||||
while i < n:
|
||||
ch = str[i]
|
||||
i += 1
|
||||
|
||||
# cases are checked in decreasing order of frequency
|
||||
if ch == "x":
|
||||
continue
|
||||
|
||||
if ch == "\n":
|
||||
lno += 1
|
||||
if level == 0:
|
||||
push_good(lno)
|
||||
# else we're in an unclosed bracket structure
|
||||
continue
|
||||
|
||||
if ch == "(":
|
||||
level += 1
|
||||
continue
|
||||
|
||||
if ch == ")":
|
||||
if level:
|
||||
level -= 1
|
||||
# else the program is invalid, but we can't complain
|
||||
continue
|
||||
|
||||
if ch == '"' or ch == "'":
|
||||
# consume the string
|
||||
quote = ch
|
||||
if str[i - 1 : i + 2] == quote * 3:
|
||||
quote *= 3
|
||||
w = len(quote) - 1
|
||||
i += w
|
||||
while i < n:
|
||||
ch = str[i]
|
||||
i += 1
|
||||
|
||||
if ch == "x":
|
||||
continue
|
||||
|
||||
if str[i - 1 : i + w] == quote:
|
||||
i += w
|
||||
break
|
||||
|
||||
if ch == "\n":
|
||||
lno += 1
|
||||
if w == 0:
|
||||
# unterminated single-quoted string
|
||||
if level == 0:
|
||||
push_good(lno)
|
||||
break
|
||||
continue
|
||||
|
||||
if ch == "\\":
|
||||
assert i < n
|
||||
if str[i] == "\n":
|
||||
lno += 1
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# else comment char or paren inside string
|
||||
|
||||
else:
|
||||
# didn't break out of the loop, so we're still
|
||||
# inside a string
|
||||
continuation = C_STRING
|
||||
continue # with outer loop
|
||||
|
||||
if ch == "#":
|
||||
# consume the comment
|
||||
i = str.find("\n", i)
|
||||
assert i >= 0
|
||||
continue
|
||||
|
||||
assert ch == "\\"
|
||||
assert i < n
|
||||
if str[i] == "\n":
|
||||
lno += 1
|
||||
if i + 1 == n:
|
||||
continuation = C_BACKSLASH
|
||||
i += 1
|
||||
|
||||
# The last stmt may be continued for all 3 reasons.
|
||||
# String continuation takes precedence over bracket
|
||||
# continuation, which beats backslash continuation.
|
||||
if continuation != C_STRING and level > 0:
|
||||
continuation = C_BRACKET
|
||||
self.continuation = continuation
|
||||
|
||||
# Push the final line number as a sentinel value, regardless of
|
||||
# whether it's continued.
|
||||
assert (continuation == C_NONE) == (goodlines[-1] == lno)
|
||||
if goodlines[-1] != lno:
|
||||
push_good(lno)
|
||||
|
||||
def get_continuation_type(self):
|
||||
self._study1()
|
||||
return self.continuation
|
||||
|
||||
# study1 was sufficient to determine the continuation status,
|
||||
# but doing more requires looking at every character. study2
|
||||
# does this for the last interesting statement in the block.
|
||||
# Creates:
|
||||
# self.stmt_start, stmt_end
|
||||
# slice indices of last interesting stmt
|
||||
# self.lastch
|
||||
# last non-whitespace character before optional trailing
|
||||
# comment
|
||||
# self.lastopenbracketpos
|
||||
# if continuation is C_BRACKET, index of last open bracket
|
||||
|
||||
def _study2(self):
|
||||
if self.study_level >= 2:
|
||||
return
|
||||
self._study1()
|
||||
self.study_level = 2
|
||||
|
||||
# Set p and q to slice indices of last interesting stmt.
|
||||
str, goodlines = self.str, self.goodlines
|
||||
i = len(goodlines) - 1
|
||||
p = len(str) # index of newest line
|
||||
while i:
|
||||
assert p
|
||||
# p is the index of the stmt at line number goodlines[i].
|
||||
# Move p back to the stmt at line number goodlines[i-1].
|
||||
q = p
|
||||
for nothing in range(goodlines[i - 1], goodlines[i]):
|
||||
# tricky: sets p to 0 if no preceding newline
|
||||
p = str.rfind("\n", 0, p - 1) + 1
|
||||
# The stmt str[p:q] isn't a continuation, but may be blank
|
||||
# or a non-indenting comment line.
|
||||
if _junkre(str, p):
|
||||
i -= 1
|
||||
else:
|
||||
break
|
||||
if i == 0:
|
||||
# nothing but junk!
|
||||
assert p == 0
|
||||
q = p
|
||||
self.stmt_start, self.stmt_end = p, q
|
||||
|
||||
# Analyze this stmt, to find the last open bracket (if any)
|
||||
# and last interesting character (if any).
|
||||
lastch = ""
|
||||
stack = [] # stack of open bracket indices
|
||||
push_stack = stack.append
|
||||
while p < q:
|
||||
# suck up all except ()[]{}'"#\\
|
||||
m = _chew_ordinaryre(str, p, q)
|
||||
if m:
|
||||
# we skipped at least one boring char
|
||||
newp = m.end()
|
||||
# back up over totally boring whitespace
|
||||
i = newp - 1 # index of last boring char
|
||||
while i >= p and str[i] in " \t\n":
|
||||
i -= 1
|
||||
if i >= p:
|
||||
lastch = str[i]
|
||||
p = newp
|
||||
if p >= q:
|
||||
break
|
||||
|
||||
ch = str[p]
|
||||
|
||||
if ch in "([{":
|
||||
push_stack(p)
|
||||
lastch = ch
|
||||
p += 1
|
||||
continue
|
||||
|
||||
if ch in ")]}":
|
||||
if stack:
|
||||
del stack[-1]
|
||||
lastch = ch
|
||||
p += 1
|
||||
continue
|
||||
|
||||
if ch == '"' or ch == "'":
|
||||
# consume string
|
||||
# Note that study1 did this with a Python loop, but
|
||||
# we use a regexp here; the reason is speed in both
|
||||
# cases; the string may be huge, but study1 pre-squashed
|
||||
# strings to a couple of characters per line. study1
|
||||
# also needed to keep track of newlines, and we don't
|
||||
# have to.
|
||||
lastch = ch
|
||||
p = _match_stringre(str, p, q).end()
|
||||
continue
|
||||
|
||||
if ch == "#":
|
||||
# consume comment and trailing newline
|
||||
p = str.find("\n", p, q) + 1
|
||||
assert p > 0
|
||||
continue
|
||||
|
||||
assert ch == "\\"
|
||||
p += 1 # beyond backslash
|
||||
assert p < q
|
||||
if str[p] != "\n":
|
||||
# the program is invalid, but can't complain
|
||||
lastch = ch + str[p]
|
||||
p += 1 # beyond escaped char
|
||||
|
||||
# end while p < q:
|
||||
|
||||
self.lastch = lastch
|
||||
if stack:
|
||||
self.lastopenbracketpos = stack[-1]
|
||||
|
||||
# Assuming continuation is C_BRACKET, return the number
|
||||
# of spaces the next line should be indented.
|
||||
|
||||
def compute_bracket_indent(self):
|
||||
self._study2()
|
||||
assert self.continuation == C_BRACKET
|
||||
j = self.lastopenbracketpos
|
||||
str = self.str
|
||||
n = len(str)
|
||||
origi = i = str.rfind("\n", 0, j) + 1
|
||||
j += 1 # one beyond open bracket
|
||||
# find first list item; set i to start of its line
|
||||
while j < n:
|
||||
m = _itemre(str, j)
|
||||
if m:
|
||||
j = m.end() - 1 # index of first interesting char
|
||||
extra = 0
|
||||
break
|
||||
else:
|
||||
# this line is junk; advance to next line
|
||||
i = j = str.find("\n", j) + 1
|
||||
else:
|
||||
# nothing interesting follows the bracket;
|
||||
# reproduce the bracket line's indentation + a level
|
||||
j = i = origi
|
||||
while str[j] in " \t":
|
||||
j += 1
|
||||
extra = self.indentwidth
|
||||
return len(str[i:j].expandtabs(self.tabwidth)) + extra
|
||||
|
||||
# Return number of physical lines in last stmt (whether or not
|
||||
# it's an interesting stmt! this is intended to be called when
|
||||
# continuation is C_BACKSLASH).
|
||||
|
||||
def get_num_lines_in_stmt(self):
|
||||
self._study1()
|
||||
goodlines = self.goodlines
|
||||
return goodlines[-1] - goodlines[-2]
|
||||
|
||||
# Assuming continuation is C_BACKSLASH, return the number of spaces
|
||||
# the next line should be indented. Also assuming the new line is
|
||||
# the first one following the initial line of the stmt.
|
||||
|
||||
def compute_backslash_indent(self):
|
||||
self._study2()
|
||||
assert self.continuation == C_BACKSLASH
|
||||
str = self.str
|
||||
i = self.stmt_start
|
||||
while str[i] in " \t":
|
||||
i += 1
|
||||
startpos = i
|
||||
|
||||
# See whether the initial line starts an assignment stmt; i.e.,
|
||||
# look for an = operator
|
||||
endpos = str.find("\n", startpos) + 1
|
||||
found = level = 0
|
||||
while i < endpos:
|
||||
ch = str[i]
|
||||
if ch in "([{":
|
||||
level += 1
|
||||
i += 1
|
||||
elif ch in ")]}":
|
||||
if level:
|
||||
level -= 1
|
||||
i += 1
|
||||
elif ch == '"' or ch == "'":
|
||||
i = _match_stringre(str, i, endpos).end()
|
||||
elif ch == "#":
|
||||
break
|
||||
elif (
|
||||
level == 0
|
||||
and ch == "="
|
||||
and (i == 0 or str[i - 1] not in "=<>!")
|
||||
and str[i + 1] != "="
|
||||
):
|
||||
found = 1
|
||||
break
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if found:
|
||||
# found a legit =, but it may be the last interesting
|
||||
# thing on the line
|
||||
i += 1 # move beyond the =
|
||||
found = re.match(r"\s*\\", str[i:endpos]) is None
|
||||
|
||||
if not found:
|
||||
# oh well ... settle for moving beyond the first chunk
|
||||
# of non-whitespace chars
|
||||
i = startpos
|
||||
while str[i] not in " \t\n":
|
||||
i += 1
|
||||
|
||||
return len(str[self.stmt_start : i].expandtabs(self.tabwidth)) + 1
|
||||
|
||||
# Return the leading whitespace on the initial line of the last
|
||||
# interesting stmt.
|
||||
|
||||
def get_base_indent_string(self):
|
||||
self._study2()
|
||||
i, n = self.stmt_start, self.stmt_end
|
||||
j = i
|
||||
str = self.str
|
||||
while j < n and str[j] in " \t":
|
||||
j += 1
|
||||
return str[i:j]
|
||||
|
||||
# Did the last interesting stmt open a block?
|
||||
|
||||
def is_block_opener(self):
|
||||
self._study2()
|
||||
return self.lastch == ":"
|
||||
|
||||
# Did the last interesting stmt close a block?
|
||||
|
||||
def is_block_closer(self):
|
||||
self._study2()
|
||||
return _closere(self.str, self.stmt_start) is not None
|
||||
|
||||
# index of last open bracket ({[, or None if none
|
||||
lastopenbracketpos = None
|
||||
|
||||
def get_last_open_bracket_pos(self):
|
||||
self._study2()
|
||||
return self.lastopenbracketpos
|
||||
@@ -0,0 +1 @@
|
||||
# This file denotes the directory as a Python package.
|
||||
@@ -0,0 +1,79 @@
|
||||
"""Support for ActiveX control hosting in Pythonwin."""
|
||||
|
||||
import win32ui
|
||||
import win32uiole
|
||||
|
||||
from . import window
|
||||
|
||||
|
||||
class Control(window.Wnd):
|
||||
"""An ActiveX control base class. A new class must be derived from both
|
||||
this class and the Events class. See the demos for more details.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.__dict__["_dispobj_"] = None
|
||||
window.Wnd.__init__(self)
|
||||
|
||||
def _GetControlCLSID(self):
|
||||
return self.CLSID
|
||||
|
||||
def _GetDispatchClass(self):
|
||||
return self.default_interface
|
||||
|
||||
def _GetEventMap(self):
|
||||
return self.default_source._dispid_to_func_
|
||||
|
||||
def CreateControl(self, windowTitle, style, rect, parent, id, lic_string=None):
|
||||
clsid = str(self._GetControlCLSID())
|
||||
self.__dict__["_obj_"] = win32ui.CreateControl(
|
||||
clsid, windowTitle, style, rect, parent, id, None, False, lic_string
|
||||
)
|
||||
klass = self._GetDispatchClass()
|
||||
dispobj = klass(win32uiole.GetIDispatchForWindow(self._obj_))
|
||||
self.HookOleEvents()
|
||||
self.__dict__["_dispobj_"] = dispobj
|
||||
|
||||
def HookOleEvents(self):
|
||||
dict = self._GetEventMap()
|
||||
for dispid, methodName in dict.items():
|
||||
if hasattr(self, methodName):
|
||||
self._obj_.HookOleEvent(getattr(self, methodName), dispid)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# Delegate attributes to the windows and the Dispatch object for this class
|
||||
try:
|
||||
return window.Wnd.__getattr__(self, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
return getattr(self._dispobj_, attr)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
if hasattr(self.__dict__, attr):
|
||||
self.__dict__[attr] = value
|
||||
return
|
||||
try:
|
||||
if self._dispobj_:
|
||||
self._dispobj_.__setattr__(attr, value)
|
||||
return
|
||||
except AttributeError:
|
||||
pass
|
||||
self.__dict__[attr] = value
|
||||
|
||||
|
||||
def MakeControlClass(controlClass, name=None):
|
||||
"""Given a CoClass in a generated .py file, this function will return a Class
|
||||
object which can be used as an OCX control.
|
||||
|
||||
This function is used when you do not want to handle any events from the OCX
|
||||
control. If you need events, then you should derive a class from both the
|
||||
activex.Control class and the CoClass
|
||||
"""
|
||||
if name is None:
|
||||
name = controlClass.__name__
|
||||
return type("OCX" + name, (Control, controlClass), {})
|
||||
|
||||
|
||||
def MakeControlInstance(controlClass, name=None):
|
||||
"""As for MakeControlClass(), but returns an instance of the class."""
|
||||
return MakeControlClass(controlClass, name)()
|
||||
@@ -0,0 +1,501 @@
|
||||
# Generated by h2py from stdin
|
||||
TCS_MULTILINE = 0x0200
|
||||
CBRS_ALIGN_LEFT = 0x1000
|
||||
CBRS_ALIGN_TOP = 0x2000
|
||||
CBRS_ALIGN_RIGHT = 0x4000
|
||||
CBRS_ALIGN_BOTTOM = 0x8000
|
||||
CBRS_ALIGN_ANY = 0xF000
|
||||
CBRS_BORDER_LEFT = 0x0100
|
||||
CBRS_BORDER_TOP = 0x0200
|
||||
CBRS_BORDER_RIGHT = 0x0400
|
||||
CBRS_BORDER_BOTTOM = 0x0800
|
||||
CBRS_BORDER_ANY = 0x0F00
|
||||
CBRS_TOOLTIPS = 0x0010
|
||||
CBRS_FLYBY = 0x0020
|
||||
CBRS_FLOAT_MULTI = 0x0040
|
||||
CBRS_BORDER_3D = 0x0080
|
||||
CBRS_HIDE_INPLACE = 0x0008
|
||||
CBRS_SIZE_DYNAMIC = 0x0004
|
||||
CBRS_SIZE_FIXED = 0x0002
|
||||
CBRS_FLOATING = 0x0001
|
||||
CBRS_GRIPPER = 0x00400000
|
||||
CBRS_ORIENT_HORZ = CBRS_ALIGN_TOP | CBRS_ALIGN_BOTTOM
|
||||
CBRS_ORIENT_VERT = CBRS_ALIGN_LEFT | CBRS_ALIGN_RIGHT
|
||||
CBRS_ORIENT_ANY = CBRS_ORIENT_HORZ | CBRS_ORIENT_VERT
|
||||
CBRS_ALL = 0xFFFF
|
||||
CBRS_NOALIGN = 0x00000000
|
||||
CBRS_LEFT = CBRS_ALIGN_LEFT | CBRS_BORDER_RIGHT
|
||||
CBRS_TOP = CBRS_ALIGN_TOP | CBRS_BORDER_BOTTOM
|
||||
CBRS_RIGHT = CBRS_ALIGN_RIGHT | CBRS_BORDER_LEFT
|
||||
CBRS_BOTTOM = CBRS_ALIGN_BOTTOM | CBRS_BORDER_TOP
|
||||
SBPS_NORMAL = 0x0000
|
||||
SBPS_NOBORDERS = 0x0100
|
||||
SBPS_POPOUT = 0x0200
|
||||
SBPS_OWNERDRAW = 0x1000
|
||||
SBPS_DISABLED = 0x04000000
|
||||
SBPS_STRETCH = 0x08000000
|
||||
ID_INDICATOR_EXT = 0xE700
|
||||
ID_INDICATOR_CAPS = 0xE701
|
||||
ID_INDICATOR_NUM = 0xE702
|
||||
ID_INDICATOR_SCRL = 0xE703
|
||||
ID_INDICATOR_OVR = 0xE704
|
||||
ID_INDICATOR_REC = 0xE705
|
||||
ID_INDICATOR_KANA = 0xE706
|
||||
ID_SEPARATOR = 0
|
||||
AFX_IDW_CONTROLBAR_FIRST = 0xE800
|
||||
AFX_IDW_CONTROLBAR_LAST = 0xE8FF
|
||||
AFX_IDW_TOOLBAR = 0xE800
|
||||
AFX_IDW_STATUS_BAR = 0xE801
|
||||
AFX_IDW_PREVIEW_BAR = 0xE802
|
||||
AFX_IDW_RESIZE_BAR = 0xE803
|
||||
AFX_IDW_DOCKBAR_TOP = 0xE81B
|
||||
AFX_IDW_DOCKBAR_LEFT = 0xE81C
|
||||
AFX_IDW_DOCKBAR_RIGHT = 0xE81D
|
||||
AFX_IDW_DOCKBAR_BOTTOM = 0xE81E
|
||||
AFX_IDW_DOCKBAR_FLOAT = 0xE81F
|
||||
|
||||
|
||||
def AFX_CONTROLBAR_MASK(nIDC):
|
||||
return 1 << (nIDC - AFX_IDW_CONTROLBAR_FIRST)
|
||||
|
||||
|
||||
AFX_IDW_PANE_FIRST = 0xE900
|
||||
AFX_IDW_PANE_LAST = 0xE9FF
|
||||
AFX_IDW_HSCROLL_FIRST = 0xEA00
|
||||
AFX_IDW_VSCROLL_FIRST = 0xEA10
|
||||
AFX_IDW_SIZE_BOX = 0xEA20
|
||||
AFX_IDW_PANE_SAVE = 0xEA21
|
||||
AFX_IDS_APP_TITLE = 0xE000
|
||||
AFX_IDS_IDLEMESSAGE = 0xE001
|
||||
AFX_IDS_HELPMODEMESSAGE = 0xE002
|
||||
AFX_IDS_APP_TITLE_EMBEDDING = 0xE003
|
||||
AFX_IDS_COMPANY_NAME = 0xE004
|
||||
AFX_IDS_OBJ_TITLE_INPLACE = 0xE005
|
||||
ID_FILE_NEW = 0xE100
|
||||
ID_FILE_OPEN = 0xE101
|
||||
ID_FILE_CLOSE = 0xE102
|
||||
ID_FILE_SAVE = 0xE103
|
||||
ID_FILE_SAVE_AS = 0xE104
|
||||
ID_FILE_PAGE_SETUP = 0xE105
|
||||
ID_FILE_PRINT_SETUP = 0xE106
|
||||
ID_FILE_PRINT = 0xE107
|
||||
ID_FILE_PRINT_DIRECT = 0xE108
|
||||
ID_FILE_PRINT_PREVIEW = 0xE109
|
||||
ID_FILE_UPDATE = 0xE10A
|
||||
ID_FILE_SAVE_COPY_AS = 0xE10B
|
||||
ID_FILE_SEND_MAIL = 0xE10C
|
||||
ID_FILE_MRU_FIRST = 0xE110
|
||||
ID_FILE_MRU_FILE1 = 0xE110
|
||||
ID_FILE_MRU_FILE2 = 0xE111
|
||||
ID_FILE_MRU_FILE3 = 0xE112
|
||||
ID_FILE_MRU_FILE4 = 0xE113
|
||||
ID_FILE_MRU_FILE5 = 0xE114
|
||||
ID_FILE_MRU_FILE6 = 0xE115
|
||||
ID_FILE_MRU_FILE7 = 0xE116
|
||||
ID_FILE_MRU_FILE8 = 0xE117
|
||||
ID_FILE_MRU_FILE9 = 0xE118
|
||||
ID_FILE_MRU_FILE10 = 0xE119
|
||||
ID_FILE_MRU_FILE11 = 0xE11A
|
||||
ID_FILE_MRU_FILE12 = 0xE11B
|
||||
ID_FILE_MRU_FILE13 = 0xE11C
|
||||
ID_FILE_MRU_FILE14 = 0xE11D
|
||||
ID_FILE_MRU_FILE15 = 0xE11E
|
||||
ID_FILE_MRU_FILE16 = 0xE11F
|
||||
ID_FILE_MRU_LAST = 0xE11F
|
||||
ID_EDIT_CLEAR = 0xE120
|
||||
ID_EDIT_CLEAR_ALL = 0xE121
|
||||
ID_EDIT_COPY = 0xE122
|
||||
ID_EDIT_CUT = 0xE123
|
||||
ID_EDIT_FIND = 0xE124
|
||||
ID_EDIT_PASTE = 0xE125
|
||||
ID_EDIT_PASTE_LINK = 0xE126
|
||||
ID_EDIT_PASTE_SPECIAL = 0xE127
|
||||
ID_EDIT_REPEAT = 0xE128
|
||||
ID_EDIT_REPLACE = 0xE129
|
||||
ID_EDIT_SELECT_ALL = 0xE12A
|
||||
ID_EDIT_UNDO = 0xE12B
|
||||
ID_EDIT_REDO = 0xE12C
|
||||
ID_WINDOW_NEW = 0xE130
|
||||
ID_WINDOW_ARRANGE = 0xE131
|
||||
ID_WINDOW_CASCADE = 0xE132
|
||||
ID_WINDOW_TILE_HORZ = 0xE133
|
||||
ID_WINDOW_TILE_VERT = 0xE134
|
||||
ID_WINDOW_SPLIT = 0xE135
|
||||
AFX_IDM_WINDOW_FIRST = 0xE130
|
||||
AFX_IDM_WINDOW_LAST = 0xE13F
|
||||
AFX_IDM_FIRST_MDICHILD = 0xFF00
|
||||
ID_APP_ABOUT = 0xE140
|
||||
ID_APP_EXIT = 0xE141
|
||||
ID_HELP_INDEX = 0xE142
|
||||
ID_HELP_FINDER = 0xE143
|
||||
ID_HELP_USING = 0xE144
|
||||
ID_CONTEXT_HELP = 0xE145
|
||||
ID_HELP = 0xE146
|
||||
ID_DEFAULT_HELP = 0xE147
|
||||
ID_NEXT_PANE = 0xE150
|
||||
ID_PREV_PANE = 0xE151
|
||||
ID_FORMAT_FONT = 0xE160
|
||||
ID_OLE_INSERT_NEW = 0xE200
|
||||
ID_OLE_EDIT_LINKS = 0xE201
|
||||
ID_OLE_EDIT_CONVERT = 0xE202
|
||||
ID_OLE_EDIT_CHANGE_ICON = 0xE203
|
||||
ID_OLE_EDIT_PROPERTIES = 0xE204
|
||||
ID_OLE_VERB_FIRST = 0xE210
|
||||
ID_OLE_VERB_LAST = 0xE21F
|
||||
AFX_ID_PREVIEW_CLOSE = 0xE300
|
||||
AFX_ID_PREVIEW_NUMPAGE = 0xE301
|
||||
AFX_ID_PREVIEW_NEXT = 0xE302
|
||||
AFX_ID_PREVIEW_PREV = 0xE303
|
||||
AFX_ID_PREVIEW_PRINT = 0xE304
|
||||
AFX_ID_PREVIEW_ZOOMIN = 0xE305
|
||||
AFX_ID_PREVIEW_ZOOMOUT = 0xE306
|
||||
ID_VIEW_TOOLBAR = 0xE800
|
||||
ID_VIEW_STATUS_BAR = 0xE801
|
||||
ID_RECORD_FIRST = 0xE900
|
||||
ID_RECORD_LAST = 0xE901
|
||||
ID_RECORD_NEXT = 0xE902
|
||||
ID_RECORD_PREV = 0xE903
|
||||
IDC_STATIC = -1
|
||||
AFX_IDS_SCFIRST = 0xEF00
|
||||
AFX_IDS_SCSIZE = 0xEF00
|
||||
AFX_IDS_SCMOVE = 0xEF01
|
||||
AFX_IDS_SCMINIMIZE = 0xEF02
|
||||
AFX_IDS_SCMAXIMIZE = 0xEF03
|
||||
AFX_IDS_SCNEXTWINDOW = 0xEF04
|
||||
AFX_IDS_SCPREVWINDOW = 0xEF05
|
||||
AFX_IDS_SCCLOSE = 0xEF06
|
||||
AFX_IDS_SCRESTORE = 0xEF12
|
||||
AFX_IDS_SCTASKLIST = 0xEF13
|
||||
AFX_IDS_MDICHILD = 0xEF1F
|
||||
AFX_IDS_DESKACCESSORY = 0xEFDA
|
||||
AFX_IDS_OPENFILE = 0xF000
|
||||
AFX_IDS_SAVEFILE = 0xF001
|
||||
AFX_IDS_ALLFILTER = 0xF002
|
||||
AFX_IDS_UNTITLED = 0xF003
|
||||
AFX_IDS_SAVEFILECOPY = 0xF004
|
||||
AFX_IDS_PREVIEW_CLOSE = 0xF005
|
||||
AFX_IDS_UNNAMED_FILE = 0xF006
|
||||
AFX_IDS_ABOUT = 0xF010
|
||||
AFX_IDS_HIDE = 0xF011
|
||||
AFX_IDP_NO_ERROR_AVAILABLE = 0xF020
|
||||
AFX_IDS_NOT_SUPPORTED_EXCEPTION = 0xF021
|
||||
AFX_IDS_RESOURCE_EXCEPTION = 0xF022
|
||||
AFX_IDS_MEMORY_EXCEPTION = 0xF023
|
||||
AFX_IDS_USER_EXCEPTION = 0xF024
|
||||
AFX_IDS_PRINTONPORT = 0xF040
|
||||
AFX_IDS_ONEPAGE = 0xF041
|
||||
AFX_IDS_TWOPAGE = 0xF042
|
||||
AFX_IDS_PRINTPAGENUM = 0xF043
|
||||
AFX_IDS_PREVIEWPAGEDESC = 0xF044
|
||||
AFX_IDS_PRINTDEFAULTEXT = 0xF045
|
||||
AFX_IDS_PRINTDEFAULT = 0xF046
|
||||
AFX_IDS_PRINTFILTER = 0xF047
|
||||
AFX_IDS_PRINTCAPTION = 0xF048
|
||||
AFX_IDS_PRINTTOFILE = 0xF049
|
||||
AFX_IDS_OBJECT_MENUITEM = 0xF080
|
||||
AFX_IDS_EDIT_VERB = 0xF081
|
||||
AFX_IDS_ACTIVATE_VERB = 0xF082
|
||||
AFX_IDS_CHANGE_LINK = 0xF083
|
||||
AFX_IDS_AUTO = 0xF084
|
||||
AFX_IDS_MANUAL = 0xF085
|
||||
AFX_IDS_FROZEN = 0xF086
|
||||
AFX_IDS_ALL_FILES = 0xF087
|
||||
AFX_IDS_SAVE_MENU = 0xF088
|
||||
AFX_IDS_UPDATE_MENU = 0xF089
|
||||
AFX_IDS_SAVE_AS_MENU = 0xF08A
|
||||
AFX_IDS_SAVE_COPY_AS_MENU = 0xF08B
|
||||
AFX_IDS_EXIT_MENU = 0xF08C
|
||||
AFX_IDS_UPDATING_ITEMS = 0xF08D
|
||||
AFX_IDS_METAFILE_FORMAT = 0xF08E
|
||||
AFX_IDS_DIB_FORMAT = 0xF08F
|
||||
AFX_IDS_BITMAP_FORMAT = 0xF090
|
||||
AFX_IDS_LINKSOURCE_FORMAT = 0xF091
|
||||
AFX_IDS_EMBED_FORMAT = 0xF092
|
||||
AFX_IDS_PASTELINKEDTYPE = 0xF094
|
||||
AFX_IDS_UNKNOWNTYPE = 0xF095
|
||||
AFX_IDS_RTF_FORMAT = 0xF096
|
||||
AFX_IDS_TEXT_FORMAT = 0xF097
|
||||
AFX_IDS_INVALID_CURRENCY = 0xF098
|
||||
AFX_IDS_INVALID_DATETIME = 0xF099
|
||||
AFX_IDS_INVALID_DATETIMESPAN = 0xF09A
|
||||
AFX_IDP_INVALID_FILENAME = 0xF100
|
||||
AFX_IDP_FAILED_TO_OPEN_DOC = 0xF101
|
||||
AFX_IDP_FAILED_TO_SAVE_DOC = 0xF102
|
||||
AFX_IDP_ASK_TO_SAVE = 0xF103
|
||||
AFX_IDP_FAILED_TO_CREATE_DOC = 0xF104
|
||||
AFX_IDP_FILE_TOO_LARGE = 0xF105
|
||||
AFX_IDP_FAILED_TO_START_PRINT = 0xF106
|
||||
AFX_IDP_FAILED_TO_LAUNCH_HELP = 0xF107
|
||||
AFX_IDP_INTERNAL_FAILURE = 0xF108
|
||||
AFX_IDP_COMMAND_FAILURE = 0xF109
|
||||
AFX_IDP_FAILED_MEMORY_ALLOC = 0xF10A
|
||||
AFX_IDP_PARSE_INT = 0xF110
|
||||
AFX_IDP_PARSE_REAL = 0xF111
|
||||
AFX_IDP_PARSE_INT_RANGE = 0xF112
|
||||
AFX_IDP_PARSE_REAL_RANGE = 0xF113
|
||||
AFX_IDP_PARSE_STRING_SIZE = 0xF114
|
||||
AFX_IDP_PARSE_RADIO_BUTTON = 0xF115
|
||||
AFX_IDP_PARSE_BYTE = 0xF116
|
||||
AFX_IDP_PARSE_UINT = 0xF117
|
||||
AFX_IDP_PARSE_DATETIME = 0xF118
|
||||
AFX_IDP_PARSE_CURRENCY = 0xF119
|
||||
AFX_IDP_FAILED_INVALID_FORMAT = 0xF120
|
||||
AFX_IDP_FAILED_INVALID_PATH = 0xF121
|
||||
AFX_IDP_FAILED_DISK_FULL = 0xF122
|
||||
AFX_IDP_FAILED_ACCESS_READ = 0xF123
|
||||
AFX_IDP_FAILED_ACCESS_WRITE = 0xF124
|
||||
AFX_IDP_FAILED_IO_ERROR_READ = 0xF125
|
||||
AFX_IDP_FAILED_IO_ERROR_WRITE = 0xF126
|
||||
AFX_IDP_STATIC_OBJECT = 0xF180
|
||||
AFX_IDP_FAILED_TO_CONNECT = 0xF181
|
||||
AFX_IDP_SERVER_BUSY = 0xF182
|
||||
AFX_IDP_BAD_VERB = 0xF183
|
||||
AFX_IDP_FAILED_TO_NOTIFY = 0xF185
|
||||
AFX_IDP_FAILED_TO_LAUNCH = 0xF186
|
||||
AFX_IDP_ASK_TO_UPDATE = 0xF187
|
||||
AFX_IDP_FAILED_TO_UPDATE = 0xF188
|
||||
AFX_IDP_FAILED_TO_REGISTER = 0xF189
|
||||
AFX_IDP_FAILED_TO_AUTO_REGISTER = 0xF18A
|
||||
AFX_IDP_FAILED_TO_CONVERT = 0xF18B
|
||||
AFX_IDP_GET_NOT_SUPPORTED = 0xF18C
|
||||
AFX_IDP_SET_NOT_SUPPORTED = 0xF18D
|
||||
AFX_IDP_ASK_TO_DISCARD = 0xF18E
|
||||
AFX_IDP_FAILED_TO_CREATE = 0xF18F
|
||||
AFX_IDP_FAILED_MAPI_LOAD = 0xF190
|
||||
AFX_IDP_INVALID_MAPI_DLL = 0xF191
|
||||
AFX_IDP_FAILED_MAPI_SEND = 0xF192
|
||||
AFX_IDP_FILE_NONE = 0xF1A0
|
||||
AFX_IDP_FILE_GENERIC = 0xF1A1
|
||||
AFX_IDP_FILE_NOT_FOUND = 0xF1A2
|
||||
AFX_IDP_FILE_BAD_PATH = 0xF1A3
|
||||
AFX_IDP_FILE_TOO_MANY_OPEN = 0xF1A4
|
||||
AFX_IDP_FILE_ACCESS_DENIED = 0xF1A5
|
||||
AFX_IDP_FILE_INVALID_FILE = 0xF1A6
|
||||
AFX_IDP_FILE_REMOVE_CURRENT = 0xF1A7
|
||||
AFX_IDP_FILE_DIR_FULL = 0xF1A8
|
||||
AFX_IDP_FILE_BAD_SEEK = 0xF1A9
|
||||
AFX_IDP_FILE_HARD_IO = 0xF1AA
|
||||
AFX_IDP_FILE_SHARING = 0xF1AB
|
||||
AFX_IDP_FILE_LOCKING = 0xF1AC
|
||||
AFX_IDP_FILE_DISKFULL = 0xF1AD
|
||||
AFX_IDP_FILE_EOF = 0xF1AE
|
||||
AFX_IDP_ARCH_NONE = 0xF1B0
|
||||
AFX_IDP_ARCH_GENERIC = 0xF1B1
|
||||
AFX_IDP_ARCH_READONLY = 0xF1B2
|
||||
AFX_IDP_ARCH_ENDOFFILE = 0xF1B3
|
||||
AFX_IDP_ARCH_WRITEONLY = 0xF1B4
|
||||
AFX_IDP_ARCH_BADINDEX = 0xF1B5
|
||||
AFX_IDP_ARCH_BADCLASS = 0xF1B6
|
||||
AFX_IDP_ARCH_BADSCHEMA = 0xF1B7
|
||||
AFX_IDS_OCC_SCALEUNITS_PIXELS = 0xF1C0
|
||||
AFX_IDS_STATUS_FONT = 0xF230
|
||||
AFX_IDS_TOOLTIP_FONT = 0xF231
|
||||
AFX_IDS_UNICODE_FONT = 0xF232
|
||||
AFX_IDS_MINI_FONT = 0xF233
|
||||
AFX_IDP_SQL_FIRST = 0xF280
|
||||
AFX_IDP_SQL_CONNECT_FAIL = 0xF281
|
||||
AFX_IDP_SQL_RECORDSET_FORWARD_ONLY = 0xF282
|
||||
AFX_IDP_SQL_EMPTY_COLUMN_LIST = 0xF283
|
||||
AFX_IDP_SQL_FIELD_SCHEMA_MISMATCH = 0xF284
|
||||
AFX_IDP_SQL_ILLEGAL_MODE = 0xF285
|
||||
AFX_IDP_SQL_MULTIPLE_ROWS_AFFECTED = 0xF286
|
||||
AFX_IDP_SQL_NO_CURRENT_RECORD = 0xF287
|
||||
AFX_IDP_SQL_NO_ROWS_AFFECTED = 0xF288
|
||||
AFX_IDP_SQL_RECORDSET_READONLY = 0xF289
|
||||
AFX_IDP_SQL_SQL_NO_TOTAL = 0xF28A
|
||||
AFX_IDP_SQL_ODBC_LOAD_FAILED = 0xF28B
|
||||
AFX_IDP_SQL_DYNASET_NOT_SUPPORTED = 0xF28C
|
||||
AFX_IDP_SQL_SNAPSHOT_NOT_SUPPORTED = 0xF28D
|
||||
AFX_IDP_SQL_API_CONFORMANCE = 0xF28E
|
||||
AFX_IDP_SQL_SQL_CONFORMANCE = 0xF28F
|
||||
AFX_IDP_SQL_NO_DATA_FOUND = 0xF290
|
||||
AFX_IDP_SQL_ROW_UPDATE_NOT_SUPPORTED = 0xF291
|
||||
AFX_IDP_SQL_ODBC_V2_REQUIRED = 0xF292
|
||||
AFX_IDP_SQL_NO_POSITIONED_UPDATES = 0xF293
|
||||
AFX_IDP_SQL_LOCK_MODE_NOT_SUPPORTED = 0xF294
|
||||
AFX_IDP_SQL_DATA_TRUNCATED = 0xF295
|
||||
AFX_IDP_SQL_ROW_FETCH = 0xF296
|
||||
AFX_IDP_SQL_INCORRECT_ODBC = 0xF297
|
||||
AFX_IDP_SQL_UPDATE_DELETE_FAILED = 0xF298
|
||||
AFX_IDP_SQL_DYNAMIC_CURSOR_NOT_SUPPORTED = 0xF299
|
||||
AFX_IDP_DAO_FIRST = 0xF2A0
|
||||
AFX_IDP_DAO_ENGINE_INITIALIZATION = 0xF2A0
|
||||
AFX_IDP_DAO_DFX_BIND = 0xF2A1
|
||||
AFX_IDP_DAO_OBJECT_NOT_OPEN = 0xF2A2
|
||||
AFX_IDP_DAO_ROWTOOSHORT = 0xF2A3
|
||||
AFX_IDP_DAO_BADBINDINFO = 0xF2A4
|
||||
AFX_IDP_DAO_COLUMNUNAVAILABLE = 0xF2A5
|
||||
AFX_IDC_LISTBOX = 100
|
||||
AFX_IDC_CHANGE = 101
|
||||
AFX_IDC_PRINT_DOCNAME = 201
|
||||
AFX_IDC_PRINT_PRINTERNAME = 202
|
||||
AFX_IDC_PRINT_PORTNAME = 203
|
||||
AFX_IDC_PRINT_PAGENUM = 204
|
||||
ID_APPLY_NOW = 0x3021
|
||||
ID_WIZBACK = 0x3023
|
||||
ID_WIZNEXT = 0x3024
|
||||
ID_WIZFINISH = 0x3025
|
||||
AFX_IDC_TAB_CONTROL = 0x3020
|
||||
AFX_IDD_FILEOPEN = 28676
|
||||
AFX_IDD_FILESAVE = 28677
|
||||
AFX_IDD_FONT = 28678
|
||||
AFX_IDD_COLOR = 28679
|
||||
AFX_IDD_PRINT = 28680
|
||||
AFX_IDD_PRINTSETUP = 28681
|
||||
AFX_IDD_FIND = 28682
|
||||
AFX_IDD_REPLACE = 28683
|
||||
AFX_IDD_NEWTYPEDLG = 30721
|
||||
AFX_IDD_PRINTDLG = 30722
|
||||
AFX_IDD_PREVIEW_TOOLBAR = 30723
|
||||
AFX_IDD_PREVIEW_SHORTTOOLBAR = 30731
|
||||
AFX_IDD_INSERTOBJECT = 30724
|
||||
AFX_IDD_CHANGEICON = 30725
|
||||
AFX_IDD_CONVERT = 30726
|
||||
AFX_IDD_PASTESPECIAL = 30727
|
||||
AFX_IDD_EDITLINKS = 30728
|
||||
AFX_IDD_FILEBROWSE = 30729
|
||||
AFX_IDD_BUSY = 30730
|
||||
AFX_IDD_OBJECTPROPERTIES = 30732
|
||||
AFX_IDD_CHANGESOURCE = 30733
|
||||
AFX_IDC_CONTEXTHELP = 30977
|
||||
AFX_IDC_MAGNIFY = 30978
|
||||
AFX_IDC_SMALLARROWS = 30979
|
||||
AFX_IDC_HSPLITBAR = 30980
|
||||
AFX_IDC_VSPLITBAR = 30981
|
||||
AFX_IDC_NODROPCRSR = 30982
|
||||
AFX_IDC_TRACKNWSE = 30983
|
||||
AFX_IDC_TRACKNESW = 30984
|
||||
AFX_IDC_TRACKNS = 30985
|
||||
AFX_IDC_TRACKWE = 30986
|
||||
AFX_IDC_TRACK4WAY = 30987
|
||||
AFX_IDC_MOVE4WAY = 30988
|
||||
AFX_IDB_MINIFRAME_MENU = 30994
|
||||
AFX_IDB_CHECKLISTBOX_NT = 30995
|
||||
AFX_IDB_CHECKLISTBOX_95 = 30996
|
||||
AFX_IDR_PREVIEW_ACCEL = 30997
|
||||
AFX_IDI_STD_MDIFRAME = 31233
|
||||
AFX_IDI_STD_FRAME = 31234
|
||||
AFX_IDC_FONTPROP = 1000
|
||||
AFX_IDC_FONTNAMES = 1001
|
||||
AFX_IDC_FONTSTYLES = 1002
|
||||
AFX_IDC_FONTSIZES = 1003
|
||||
AFX_IDC_STRIKEOUT = 1004
|
||||
AFX_IDC_UNDERLINE = 1005
|
||||
AFX_IDC_SAMPLEBOX = 1006
|
||||
AFX_IDC_COLOR_BLACK = 1100
|
||||
AFX_IDC_COLOR_WHITE = 1101
|
||||
AFX_IDC_COLOR_RED = 1102
|
||||
AFX_IDC_COLOR_GREEN = 1103
|
||||
AFX_IDC_COLOR_BLUE = 1104
|
||||
AFX_IDC_COLOR_YELLOW = 1105
|
||||
AFX_IDC_COLOR_MAGENTA = 1106
|
||||
AFX_IDC_COLOR_CYAN = 1107
|
||||
AFX_IDC_COLOR_GRAY = 1108
|
||||
AFX_IDC_COLOR_LIGHTGRAY = 1109
|
||||
AFX_IDC_COLOR_DARKRED = 1110
|
||||
AFX_IDC_COLOR_DARKGREEN = 1111
|
||||
AFX_IDC_COLOR_DARKBLUE = 1112
|
||||
AFX_IDC_COLOR_LIGHTBROWN = 1113
|
||||
AFX_IDC_COLOR_DARKMAGENTA = 1114
|
||||
AFX_IDC_COLOR_DARKCYAN = 1115
|
||||
AFX_IDC_COLORPROP = 1116
|
||||
AFX_IDC_SYSTEMCOLORS = 1117
|
||||
AFX_IDC_PROPNAME = 1201
|
||||
AFX_IDC_PICTURE = 1202
|
||||
AFX_IDC_BROWSE = 1203
|
||||
AFX_IDC_CLEAR = 1204
|
||||
AFX_IDD_PROPPAGE_COLOR = 32257
|
||||
AFX_IDD_PROPPAGE_FONT = 32258
|
||||
AFX_IDD_PROPPAGE_PICTURE = 32259
|
||||
AFX_IDB_TRUETYPE = 32384
|
||||
AFX_IDS_PROPPAGE_UNKNOWN = 0xFE01
|
||||
AFX_IDS_COLOR_DESKTOP = 0xFE04
|
||||
AFX_IDS_COLOR_APPWORKSPACE = 0xFE05
|
||||
AFX_IDS_COLOR_WNDBACKGND = 0xFE06
|
||||
AFX_IDS_COLOR_WNDTEXT = 0xFE07
|
||||
AFX_IDS_COLOR_MENUBAR = 0xFE08
|
||||
AFX_IDS_COLOR_MENUTEXT = 0xFE09
|
||||
AFX_IDS_COLOR_ACTIVEBAR = 0xFE0A
|
||||
AFX_IDS_COLOR_INACTIVEBAR = 0xFE0B
|
||||
AFX_IDS_COLOR_ACTIVETEXT = 0xFE0C
|
||||
AFX_IDS_COLOR_INACTIVETEXT = 0xFE0D
|
||||
AFX_IDS_COLOR_ACTIVEBORDER = 0xFE0E
|
||||
AFX_IDS_COLOR_INACTIVEBORDER = 0xFE0F
|
||||
AFX_IDS_COLOR_WNDFRAME = 0xFE10
|
||||
AFX_IDS_COLOR_SCROLLBARS = 0xFE11
|
||||
AFX_IDS_COLOR_BTNFACE = 0xFE12
|
||||
AFX_IDS_COLOR_BTNSHADOW = 0xFE13
|
||||
AFX_IDS_COLOR_BTNTEXT = 0xFE14
|
||||
AFX_IDS_COLOR_BTNHIGHLIGHT = 0xFE15
|
||||
AFX_IDS_COLOR_DISABLEDTEXT = 0xFE16
|
||||
AFX_IDS_COLOR_HIGHLIGHT = 0xFE17
|
||||
AFX_IDS_COLOR_HIGHLIGHTTEXT = 0xFE18
|
||||
AFX_IDS_REGULAR = 0xFE19
|
||||
AFX_IDS_BOLD = 0xFE1A
|
||||
AFX_IDS_ITALIC = 0xFE1B
|
||||
AFX_IDS_BOLDITALIC = 0xFE1C
|
||||
AFX_IDS_SAMPLETEXT = 0xFE1D
|
||||
AFX_IDS_DISPLAYSTRING_FONT = 0xFE1E
|
||||
AFX_IDS_DISPLAYSTRING_COLOR = 0xFE1F
|
||||
AFX_IDS_DISPLAYSTRING_PICTURE = 0xFE20
|
||||
AFX_IDS_PICTUREFILTER = 0xFE21
|
||||
AFX_IDS_PICTYPE_UNKNOWN = 0xFE22
|
||||
AFX_IDS_PICTYPE_NONE = 0xFE23
|
||||
AFX_IDS_PICTYPE_BITMAP = 0xFE24
|
||||
AFX_IDS_PICTYPE_METAFILE = 0xFE25
|
||||
AFX_IDS_PICTYPE_ICON = 0xFE26
|
||||
AFX_IDS_COLOR_PPG = 0xFE28
|
||||
AFX_IDS_COLOR_PPG_CAPTION = 0xFE29
|
||||
AFX_IDS_FONT_PPG = 0xFE2A
|
||||
AFX_IDS_FONT_PPG_CAPTION = 0xFE2B
|
||||
AFX_IDS_PICTURE_PPG = 0xFE2C
|
||||
AFX_IDS_PICTURE_PPG_CAPTION = 0xFE2D
|
||||
AFX_IDS_PICTUREBROWSETITLE = 0xFE30
|
||||
AFX_IDS_BORDERSTYLE_0 = 0xFE31
|
||||
AFX_IDS_BORDERSTYLE_1 = 0xFE32
|
||||
AFX_IDS_VERB_EDIT = 0xFE40
|
||||
AFX_IDS_VERB_PROPERTIES = 0xFE41
|
||||
AFX_IDP_PICTURECANTOPEN = 0xFE83
|
||||
AFX_IDP_PICTURECANTLOAD = 0xFE84
|
||||
AFX_IDP_PICTURETOOLARGE = 0xFE85
|
||||
AFX_IDP_PICTUREREADFAILED = 0xFE86
|
||||
AFX_IDP_E_ILLEGALFUNCTIONCALL = 0xFEA0
|
||||
AFX_IDP_E_OVERFLOW = 0xFEA1
|
||||
AFX_IDP_E_OUTOFMEMORY = 0xFEA2
|
||||
AFX_IDP_E_DIVISIONBYZERO = 0xFEA3
|
||||
AFX_IDP_E_OUTOFSTRINGSPACE = 0xFEA4
|
||||
AFX_IDP_E_OUTOFSTACKSPACE = 0xFEA5
|
||||
AFX_IDP_E_BADFILENAMEORNUMBER = 0xFEA6
|
||||
AFX_IDP_E_FILENOTFOUND = 0xFEA7
|
||||
AFX_IDP_E_BADFILEMODE = 0xFEA8
|
||||
AFX_IDP_E_FILEALREADYOPEN = 0xFEA9
|
||||
AFX_IDP_E_DEVICEIOERROR = 0xFEAA
|
||||
AFX_IDP_E_FILEALREADYEXISTS = 0xFEAB
|
||||
AFX_IDP_E_BADRECORDLENGTH = 0xFEAC
|
||||
AFX_IDP_E_DISKFULL = 0xFEAD
|
||||
AFX_IDP_E_BADRECORDNUMBER = 0xFEAE
|
||||
AFX_IDP_E_BADFILENAME = 0xFEAF
|
||||
AFX_IDP_E_TOOMANYFILES = 0xFEB0
|
||||
AFX_IDP_E_DEVICEUNAVAILABLE = 0xFEB1
|
||||
AFX_IDP_E_PERMISSIONDENIED = 0xFEB2
|
||||
AFX_IDP_E_DISKNOTREADY = 0xFEB3
|
||||
AFX_IDP_E_PATHFILEACCESSERROR = 0xFEB4
|
||||
AFX_IDP_E_PATHNOTFOUND = 0xFEB5
|
||||
AFX_IDP_E_INVALIDPATTERNSTRING = 0xFEB6
|
||||
AFX_IDP_E_INVALIDUSEOFNULL = 0xFEB7
|
||||
AFX_IDP_E_INVALIDFILEFORMAT = 0xFEB8
|
||||
AFX_IDP_E_INVALIDPROPERTYVALUE = 0xFEB9
|
||||
AFX_IDP_E_INVALIDPROPERTYARRAYINDEX = 0xFEBA
|
||||
AFX_IDP_E_SETNOTSUPPORTEDATRUNTIME = 0xFEBB
|
||||
AFX_IDP_E_SETNOTSUPPORTED = 0xFEBC
|
||||
AFX_IDP_E_NEEDPROPERTYARRAYINDEX = 0xFEBD
|
||||
AFX_IDP_E_SETNOTPERMITTED = 0xFEBE
|
||||
AFX_IDP_E_GETNOTSUPPORTEDATRUNTIME = 0xFEBF
|
||||
AFX_IDP_E_GETNOTSUPPORTED = 0xFEC0
|
||||
AFX_IDP_E_PROPERTYNOTFOUND = 0xFEC1
|
||||
AFX_IDP_E_INVALIDCLIPBOARDFORMAT = 0xFEC2
|
||||
AFX_IDP_E_INVALIDPICTURE = 0xFEC3
|
||||
AFX_IDP_E_PRINTERERROR = 0xFEC4
|
||||
AFX_IDP_E_CANTSAVEFILETOTEMP = 0xFEC5
|
||||
AFX_IDP_E_SEARCHTEXTNOTFOUND = 0xFEC6
|
||||
AFX_IDP_E_REPLACEMENTSTOOLONG = 0xFEC7
|
||||
@@ -0,0 +1,277 @@
|
||||
""" \
|
||||
Base class for Dialogs. Also contains a few useful utility functions
|
||||
"""
|
||||
|
||||
# dialog.py
|
||||
# Python class for Dialog Boxes in PythonWin.
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
from . import window
|
||||
|
||||
|
||||
def dllFromDll(dllid):
|
||||
"given a 'dll' (maybe a dll, filename, etc), return a DLL object"
|
||||
if dllid is None:
|
||||
return None
|
||||
elif isinstance(dllid, str):
|
||||
return win32ui.LoadLibrary(dllid)
|
||||
else:
|
||||
try:
|
||||
dllid.GetFileName()
|
||||
except AttributeError:
|
||||
raise TypeError("DLL parameter must be None, a filename or a dll object")
|
||||
return dllid
|
||||
|
||||
|
||||
class Dialog(window.Wnd):
|
||||
"Base class for a dialog"
|
||||
|
||||
def __init__(self, id, dllid=None):
|
||||
"""id is the resource ID, or a template
|
||||
dllid may be None, a dll object, or a string with a dll name"""
|
||||
# must take a reference to the DLL until InitDialog.
|
||||
self.dll = dllFromDll(dllid)
|
||||
if isinstance(id, list): # a template
|
||||
dlg = win32ui.CreateDialogIndirect(id)
|
||||
else:
|
||||
dlg = win32ui.CreateDialog(id, self.dll)
|
||||
window.Wnd.__init__(self, dlg)
|
||||
self.HookCommands()
|
||||
self.bHaveInit = None
|
||||
|
||||
def HookCommands(self):
|
||||
pass
|
||||
|
||||
def OnAttachedObjectDeath(self):
|
||||
self.data = self._obj_.data
|
||||
window.Wnd.OnAttachedObjectDeath(self)
|
||||
|
||||
# provide virtuals.
|
||||
def OnOK(self):
|
||||
self._obj_.OnOK()
|
||||
|
||||
def OnCancel(self):
|
||||
self._obj_.OnCancel()
|
||||
|
||||
def OnInitDialog(self):
|
||||
self.bHaveInit = 1
|
||||
if self._obj_.data:
|
||||
self._obj_.UpdateData(0)
|
||||
return 1 # I did NOT set focus to a child window.
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
self.dll = None # theoretically not needed if object destructs normally.
|
||||
|
||||
# DDX support
|
||||
def AddDDX(self, *args):
|
||||
self._obj_.datalist.append(args)
|
||||
|
||||
# Make a dialog object look like a dictionary for the DDX support
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.data[key]
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
self._obj_.data[key] = item # self.UpdateData(0)
|
||||
|
||||
def keys(self):
|
||||
return self.data.keys()
|
||||
|
||||
def items(self):
|
||||
return self.data.items()
|
||||
|
||||
def values(self):
|
||||
return self.data.values()
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.data
|
||||
|
||||
has_key = __contains__
|
||||
|
||||
|
||||
class PrintDialog(Dialog):
|
||||
"Base class for a print dialog"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pInfo,
|
||||
dlgID,
|
||||
printSetupOnly=0,
|
||||
flags=(
|
||||
win32ui.PD_ALLPAGES
|
||||
| win32ui.PD_USEDEVMODECOPIES
|
||||
| win32ui.PD_NOPAGENUMS
|
||||
| win32ui.PD_HIDEPRINTTOFILE
|
||||
| win32ui.PD_NOSELECTION
|
||||
),
|
||||
parent=None,
|
||||
dllid=None,
|
||||
):
|
||||
self.dll = dllFromDll(dllid)
|
||||
if isinstance(dlgID, list): # a template
|
||||
raise TypeError("dlgID parameter must be an integer resource ID")
|
||||
dlg = win32ui.CreatePrintDialog(dlgID, printSetupOnly, flags, parent, self.dll)
|
||||
window.Wnd.__init__(self, dlg)
|
||||
self.HookCommands()
|
||||
self.bHaveInit = None
|
||||
self.pInfo = pInfo
|
||||
# init values (if PrintSetup is called, values still available)
|
||||
flags = pInfo.GetFlags()
|
||||
self["toFile"] = flags & win32ui.PD_PRINTTOFILE != 0
|
||||
self["direct"] = pInfo.GetDirect()
|
||||
self["preview"] = pInfo.GetPreview()
|
||||
self["continuePrinting"] = pInfo.GetContinuePrinting()
|
||||
self["curPage"] = pInfo.GetCurPage()
|
||||
self["numPreviewPages"] = pInfo.GetNumPreviewPages()
|
||||
self["userData"] = pInfo.GetUserData()
|
||||
self["draw"] = pInfo.GetDraw()
|
||||
self["pageDesc"] = pInfo.GetPageDesc()
|
||||
self["minPage"] = pInfo.GetMinPage()
|
||||
self["maxPage"] = pInfo.GetMaxPage()
|
||||
self["offsetPage"] = pInfo.GetOffsetPage()
|
||||
self["fromPage"] = pInfo.GetFromPage()
|
||||
self["toPage"] = pInfo.GetToPage()
|
||||
# these values updated after OnOK
|
||||
self["copies"] = 0
|
||||
self["deviceName"] = ""
|
||||
self["driverName"] = ""
|
||||
self["printAll"] = 0
|
||||
self["printCollate"] = 0
|
||||
self["printRange"] = 0
|
||||
self["printSelection"] = 0
|
||||
|
||||
def OnInitDialog(self):
|
||||
self.pInfo.CreatePrinterDC() # This also sets the hDC of the pInfo structure.
|
||||
return self._obj_.OnInitDialog()
|
||||
|
||||
def OnCancel(self):
|
||||
del self.pInfo
|
||||
|
||||
def OnOK(self):
|
||||
"""DoModal has finished. Can now access the users choices"""
|
||||
self._obj_.OnOK()
|
||||
pInfo = self.pInfo
|
||||
# user values
|
||||
flags = pInfo.GetFlags()
|
||||
self["toFile"] = flags & win32ui.PD_PRINTTOFILE != 0
|
||||
self["direct"] = pInfo.GetDirect()
|
||||
self["preview"] = pInfo.GetPreview()
|
||||
self["continuePrinting"] = pInfo.GetContinuePrinting()
|
||||
self["curPage"] = pInfo.GetCurPage()
|
||||
self["numPreviewPages"] = pInfo.GetNumPreviewPages()
|
||||
self["userData"] = pInfo.GetUserData()
|
||||
self["draw"] = pInfo.GetDraw()
|
||||
self["pageDesc"] = pInfo.GetPageDesc()
|
||||
self["minPage"] = pInfo.GetMinPage()
|
||||
self["maxPage"] = pInfo.GetMaxPage()
|
||||
self["offsetPage"] = pInfo.GetOffsetPage()
|
||||
self["fromPage"] = pInfo.GetFromPage()
|
||||
self["toPage"] = pInfo.GetToPage()
|
||||
self["copies"] = pInfo.GetCopies()
|
||||
self["deviceName"] = pInfo.GetDeviceName()
|
||||
self["driverName"] = pInfo.GetDriverName()
|
||||
self["printAll"] = pInfo.PrintAll()
|
||||
self["printCollate"] = pInfo.PrintCollate()
|
||||
self["printRange"] = pInfo.PrintRange()
|
||||
self["printSelection"] = pInfo.PrintSelection()
|
||||
del self.pInfo
|
||||
|
||||
|
||||
class PropertyPage(Dialog):
|
||||
"Base class for a Property Page"
|
||||
|
||||
def __init__(self, id, dllid=None, caption=0):
|
||||
"""id is the resource ID
|
||||
dllid may be None, a dll object, or a string with a dll name"""
|
||||
|
||||
self.dll = dllFromDll(dllid)
|
||||
if self.dll:
|
||||
oldRes = win32ui.SetResource(self.dll)
|
||||
if isinstance(id, list):
|
||||
dlg = win32ui.CreatePropertyPageIndirect(id)
|
||||
else:
|
||||
dlg = win32ui.CreatePropertyPage(id, caption)
|
||||
if self.dll:
|
||||
win32ui.SetResource(oldRes)
|
||||
# don't call dialog init!
|
||||
window.Wnd.__init__(self, dlg)
|
||||
self.HookCommands()
|
||||
|
||||
|
||||
class PropertySheet(window.Wnd):
|
||||
def __init__(self, caption, dll=None, pageList=None): # parent=None, style,etc):
|
||||
"Initialize a property sheet. pageList is a list of ID's"
|
||||
# must take a reference to the DLL until InitDialog.
|
||||
self.dll = dllFromDll(dll)
|
||||
self.sheet = win32ui.CreatePropertySheet(caption)
|
||||
window.Wnd.__init__(self, self.sheet)
|
||||
if not pageList is None:
|
||||
self.AddPage(pageList)
|
||||
|
||||
def OnInitDialog(self):
|
||||
return self._obj_.OnInitDialog()
|
||||
|
||||
def DoModal(self):
|
||||
if self.dll:
|
||||
oldRes = win32ui.SetResource(self.dll)
|
||||
rc = self.sheet.DoModal()
|
||||
if self.dll:
|
||||
win32ui.SetResource(oldRes)
|
||||
return rc
|
||||
|
||||
def AddPage(self, pages):
|
||||
if self.dll:
|
||||
oldRes = win32ui.SetResource(self.dll)
|
||||
try: # try list style access
|
||||
pages[0]
|
||||
isSeq = 1
|
||||
except (TypeError, KeyError):
|
||||
isSeq = 0
|
||||
if isSeq:
|
||||
for page in pages:
|
||||
self.DoAddSinglePage(page)
|
||||
else:
|
||||
self.DoAddSinglePage(pages)
|
||||
if self.dll:
|
||||
win32ui.SetResource(oldRes)
|
||||
|
||||
def DoAddSinglePage(self, page):
|
||||
"Page may be page, or int ID. Assumes DLL setup"
|
||||
if isinstance(page, int):
|
||||
self.sheet.AddPage(win32ui.CreatePropertyPage(page))
|
||||
else:
|
||||
self.sheet.AddPage(page)
|
||||
|
||||
|
||||
# define some app utility functions.
|
||||
def GetSimpleInput(prompt, defValue="", title=None):
|
||||
"""displays a dialog, and returns a string, or None if cancelled.
|
||||
args prompt, defValue='', title=main frames title"""
|
||||
# uses a simple dialog to return a string object.
|
||||
if title is None:
|
||||
title = win32ui.GetMainFrame().GetWindowText()
|
||||
|
||||
class DlgSimpleInput(Dialog):
|
||||
def __init__(self, prompt, defValue, title):
|
||||
self.title = title
|
||||
Dialog.__init__(self, win32ui.IDD_SIMPLE_INPUT)
|
||||
self.AddDDX(win32ui.IDC_EDIT1, "result")
|
||||
self.AddDDX(win32ui.IDC_PROMPT1, "prompt")
|
||||
self._obj_.data["result"] = defValue
|
||||
self._obj_.data["prompt"] = prompt
|
||||
|
||||
def OnInitDialog(self):
|
||||
self.SetWindowText(self.title)
|
||||
return Dialog.OnInitDialog(self)
|
||||
|
||||
dlg = DlgSimpleInput(prompt, defValue, title)
|
||||
if dlg.DoModal() != win32con.IDOK:
|
||||
return None
|
||||
return dlg["result"]
|
||||
@@ -0,0 +1,151 @@
|
||||
# document and view classes for MFC.
|
||||
import win32ui
|
||||
|
||||
from . import object, window
|
||||
|
||||
|
||||
class View(window.Wnd):
|
||||
def __init__(self, initobj):
|
||||
window.Wnd.__init__(self, initobj)
|
||||
|
||||
def OnInitialUpdate(self):
|
||||
pass
|
||||
|
||||
|
||||
# Simple control based views.
|
||||
class CtrlView(View):
|
||||
def __init__(self, doc, wndclass, style=0):
|
||||
View.__init__(self, win32ui.CreateCtrlView(doc, wndclass, style))
|
||||
|
||||
|
||||
class EditView(CtrlView):
|
||||
def __init__(self, doc):
|
||||
View.__init__(self, win32ui.CreateEditView(doc))
|
||||
|
||||
|
||||
class RichEditView(CtrlView):
|
||||
def __init__(self, doc):
|
||||
View.__init__(self, win32ui.CreateRichEditView(doc))
|
||||
|
||||
|
||||
class ListView(CtrlView):
|
||||
def __init__(self, doc):
|
||||
View.__init__(self, win32ui.CreateListView(doc))
|
||||
|
||||
|
||||
class TreeView(CtrlView):
|
||||
def __init__(self, doc):
|
||||
View.__init__(self, win32ui.CreateTreeView(doc))
|
||||
|
||||
|
||||
# Other more advanced views.
|
||||
class ScrollView(View):
|
||||
def __init__(self, doc):
|
||||
View.__init__(self, win32ui.CreateView(doc))
|
||||
|
||||
|
||||
class FormView(View):
|
||||
def __init__(self, doc, id):
|
||||
View.__init__(self, win32ui.CreateFormView(doc, id))
|
||||
|
||||
|
||||
class Document(object.CmdTarget):
|
||||
def __init__(self, template, docobj=None):
|
||||
if docobj is None:
|
||||
docobj = template.DoCreateDoc()
|
||||
object.CmdTarget.__init__(self, docobj)
|
||||
|
||||
|
||||
class RichEditDoc(object.CmdTarget):
|
||||
def __init__(self, template):
|
||||
object.CmdTarget.__init__(self, template.DoCreateRichEditDoc())
|
||||
|
||||
|
||||
class CreateContext:
|
||||
"A transient base class used as a CreateContext"
|
||||
|
||||
def __init__(self, template, doc=None):
|
||||
self.template = template
|
||||
self.doc = doc
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
self.doc = None
|
||||
self.template = None
|
||||
|
||||
|
||||
class DocTemplate(object.CmdTarget):
|
||||
def __init__(
|
||||
self, resourceId=None, MakeDocument=None, MakeFrame=None, MakeView=None
|
||||
):
|
||||
if resourceId is None:
|
||||
resourceId = win32ui.IDR_PYTHONTYPE
|
||||
object.CmdTarget.__init__(self, self._CreateDocTemplate(resourceId))
|
||||
self.MakeDocument = MakeDocument
|
||||
self.MakeFrame = MakeFrame
|
||||
self.MakeView = MakeView
|
||||
self._SetupSharedMenu_()
|
||||
|
||||
def _SetupSharedMenu_(self):
|
||||
pass # to be overridden by each "app"
|
||||
|
||||
def _CreateDocTemplate(self, resourceId):
|
||||
return win32ui.CreateDocTemplate(resourceId)
|
||||
|
||||
def __del__(self):
|
||||
object.CmdTarget.__del__(self)
|
||||
|
||||
def CreateCreateContext(self, doc=None):
|
||||
return CreateContext(self, doc)
|
||||
|
||||
def CreateNewFrame(self, doc):
|
||||
makeFrame = self.MakeFrame
|
||||
if makeFrame is None:
|
||||
makeFrame = window.MDIChildWnd
|
||||
wnd = makeFrame()
|
||||
context = self.CreateCreateContext(doc)
|
||||
wnd.LoadFrame(
|
||||
self.GetResourceID(), -1, None, context
|
||||
) # triggers OnCreateClient...
|
||||
return wnd
|
||||
|
||||
def CreateNewDocument(self):
|
||||
makeDocument = self.MakeDocument
|
||||
if makeDocument is None:
|
||||
makeDocument = Document
|
||||
return makeDocument(self)
|
||||
|
||||
def CreateView(self, frame, context):
|
||||
makeView = self.MakeView
|
||||
if makeView is None:
|
||||
makeView = EditView
|
||||
view = makeView(context.doc)
|
||||
view.CreateWindow(frame)
|
||||
|
||||
|
||||
class RichEditDocTemplate(DocTemplate):
|
||||
def __init__(
|
||||
self, resourceId=None, MakeDocument=None, MakeFrame=None, MakeView=None
|
||||
):
|
||||
if MakeView is None:
|
||||
MakeView = RichEditView
|
||||
if MakeDocument is None:
|
||||
MakeDocument = RichEditDoc
|
||||
DocTemplate.__init__(self, resourceId, MakeDocument, MakeFrame, MakeView)
|
||||
|
||||
def _CreateDocTemplate(self, resourceId):
|
||||
return win32ui.CreateRichEditDocTemplate(resourceId)
|
||||
|
||||
|
||||
def t():
|
||||
class FormTemplate(DocTemplate):
|
||||
def CreateView(self, frame, context):
|
||||
makeView = self.MakeView
|
||||
# view = FormView(context.doc, win32ui.IDD_PROPDEMO1)
|
||||
view = ListView(context.doc)
|
||||
view.CreateWindow(frame)
|
||||
|
||||
t = FormTemplate()
|
||||
return t.OpenDocumentFile(None)
|
||||
@@ -0,0 +1,66 @@
|
||||
# MFC base classes.
|
||||
|
||||
import win32ui
|
||||
|
||||
|
||||
class Object:
|
||||
def __init__(self, initObj=None):
|
||||
self.__dict__["_obj_"] = initObj
|
||||
# self._obj_ = initObj
|
||||
if initObj is not None:
|
||||
initObj.AttachObject(self)
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def __getattr__(
|
||||
self, attr
|
||||
): # Make this object look like the underlying win32ui one.
|
||||
# During cleanup __dict__ is not available, causing recursive death.
|
||||
if not attr.startswith("__"):
|
||||
try:
|
||||
o = self.__dict__["_obj_"]
|
||||
if o is not None:
|
||||
return getattr(o, attr)
|
||||
# Only raise this error for non "internal" names -
|
||||
# Python may be calling __len__, __bool__, etc, so
|
||||
# we don't want this exception
|
||||
if attr[0] != "_" and attr[-1] != "_":
|
||||
raise win32ui.error("The MFC object has died.")
|
||||
except KeyError:
|
||||
# No _obj_ at all - don't report MFC object died when there isn't one!
|
||||
pass
|
||||
raise AttributeError(attr)
|
||||
|
||||
def OnAttachedObjectDeath(self):
|
||||
# print("object", self.__class__.__name__, "dieing")
|
||||
self._obj_ = None
|
||||
|
||||
def close(self):
|
||||
if "_obj_" in self.__dict__:
|
||||
if self._obj_ is not None:
|
||||
self._obj_.AttachObject(None)
|
||||
self._obj_ = None
|
||||
|
||||
|
||||
class CmdTarget(Object):
|
||||
def __init__(self, initObj):
|
||||
Object.__init__(self, initObj)
|
||||
|
||||
def HookNotifyRange(self, handler, firstID, lastID):
|
||||
oldhandlers = []
|
||||
for i in range(firstID, lastID + 1):
|
||||
oldhandlers.append(self.HookNotify(handler, i))
|
||||
return oldhandlers
|
||||
|
||||
def HookCommandRange(self, handler, firstID, lastID):
|
||||
oldhandlers = []
|
||||
for i in range(firstID, lastID + 1):
|
||||
oldhandlers.append(self.HookCommand(handler, i))
|
||||
return oldhandlers
|
||||
|
||||
def HookCommandUpdateRange(self, handler, firstID, lastID):
|
||||
oldhandlers = []
|
||||
for i in range(firstID, lastID + 1):
|
||||
oldhandlers.append(self.HookCommandUpdate(handler, i))
|
||||
return oldhandlers
|
||||
@@ -0,0 +1,25 @@
|
||||
# Thread and application objects
|
||||
|
||||
import win32ui
|
||||
|
||||
from . import object
|
||||
|
||||
|
||||
class WinThread(object.CmdTarget):
|
||||
def __init__(self, initObj=None):
|
||||
if initObj is None:
|
||||
initObj = win32ui.CreateThread()
|
||||
object.CmdTarget.__init__(self, initObj)
|
||||
|
||||
def InitInstance(self):
|
||||
pass # Default None/0 return indicates success for InitInstance()
|
||||
|
||||
def ExitInstance(self):
|
||||
pass
|
||||
|
||||
|
||||
class WinApp(WinThread):
|
||||
def __init__(self, initApp=None):
|
||||
if initApp is None:
|
||||
initApp = win32ui.GetApp()
|
||||
WinThread.__init__(self, initApp)
|
||||
@@ -0,0 +1,50 @@
|
||||
# The MFCish window classes.
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
from . import object
|
||||
|
||||
|
||||
class Wnd(object.CmdTarget):
|
||||
def __init__(self, initobj=None):
|
||||
object.CmdTarget.__init__(self, initobj)
|
||||
if self._obj_:
|
||||
self._obj_.HookMessage(self.OnDestroy, win32con.WM_DESTROY)
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
pass
|
||||
|
||||
|
||||
# NOTE NOTE - This facility is currently disabled in Pythonwin!!!!!
|
||||
# Note - to process all messages for your window, add the following method
|
||||
# to a derived class. This code provides default message handling (ie, is
|
||||
# identical, except presumably in speed, as if the method did not exist at
|
||||
# all, so presumably will be modified to test for specific messages to be
|
||||
# useful!
|
||||
# def WindowProc(self, msg, wParam, lParam):
|
||||
# rc, lResult = self._obj_.OnWndMsg(msg, wParam, lParam)
|
||||
# if not rc: lResult = self._obj_.DefWindowProc(msg, wParam, lParam)
|
||||
# return lResult
|
||||
|
||||
|
||||
class FrameWnd(Wnd):
|
||||
def __init__(self, wnd):
|
||||
Wnd.__init__(self, wnd)
|
||||
|
||||
|
||||
class MDIChildWnd(FrameWnd):
|
||||
def __init__(self, wnd=None):
|
||||
if wnd is None:
|
||||
wnd = win32ui.CreateMDIChild()
|
||||
FrameWnd.__init__(self, wnd)
|
||||
|
||||
def OnCreateClient(self, cp, context):
|
||||
if context is not None and context.template is not None:
|
||||
context.template.CreateView(self, context)
|
||||
|
||||
|
||||
class MDIFrameWnd(FrameWnd):
|
||||
def __init__(self, wnd=None):
|
||||
if wnd is None:
|
||||
wnd = win32ui.CreateMDIFrame()
|
||||
FrameWnd.__init__(self, wnd)
|
||||
@@ -0,0 +1,593 @@
|
||||
# Code that allows Pythonwin to pretend it is IDLE
|
||||
# (at least as far as most IDLE extensions are concerned)
|
||||
|
||||
import string
|
||||
import sys
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin import default_scintilla_encoding
|
||||
from pywin.mfc.dialog import GetSimpleInput
|
||||
|
||||
wordchars = string.ascii_uppercase + string.ascii_lowercase + string.digits
|
||||
|
||||
|
||||
class TextError(Exception): # When a TclError would normally be raised.
|
||||
pass
|
||||
|
||||
|
||||
class EmptyRange(Exception): # Internally raised.
|
||||
pass
|
||||
|
||||
|
||||
def GetIDLEModule(module):
|
||||
try:
|
||||
# First get it from Pythonwin it is exists.
|
||||
modname = "pywin.idle." + module
|
||||
__import__(modname)
|
||||
except ImportError as details:
|
||||
msg = (
|
||||
f"The IDLE extension '{module}' can not be located.\r\n\r\n"
|
||||
"Please correct the installation and restart the"
|
||||
f" application.\r\n\r\n{details}"
|
||||
)
|
||||
win32ui.MessageBox(msg)
|
||||
return None
|
||||
mod = sys.modules[modname]
|
||||
mod.TclError = TextError # A hack that can go soon!
|
||||
return mod
|
||||
|
||||
|
||||
# A class that is injected into the IDLE auto-indent extension.
|
||||
# It allows for decent performance when opening a new file,
|
||||
# as auto-indent uses the tokenizer module to determine indents.
|
||||
# The default AutoIndent readline method works OK, but it goes through
|
||||
# this layer of Tk index indirection for every single line. For large files
|
||||
# without indents (and even small files with indents :-) it was pretty slow!
|
||||
def fast_readline(self):
|
||||
if self.finished:
|
||||
val = ""
|
||||
else:
|
||||
if "_scint_lines" not in self.__dict__:
|
||||
# XXX - note - assumes this is only called once the file is loaded!
|
||||
self._scint_lines = self.text.edit.GetTextRange().split("\n")
|
||||
sl = self._scint_lines
|
||||
i = self.i = self.i + 1
|
||||
if i >= len(sl):
|
||||
val = ""
|
||||
else:
|
||||
val = sl[i] + "\n"
|
||||
return val.encode(default_scintilla_encoding)
|
||||
|
||||
|
||||
try:
|
||||
GetIDLEModule("AutoIndent").IndentSearcher.readline = fast_readline
|
||||
except AttributeError: # GetIDLEModule may return None
|
||||
pass
|
||||
|
||||
|
||||
# A class that attempts to emulate an IDLE editor window.
|
||||
# Construct with a Pythonwin view.
|
||||
class IDLEEditorWindow:
|
||||
def __init__(self, edit):
|
||||
self.edit = edit
|
||||
self.text = TkText(edit)
|
||||
self.extensions = {}
|
||||
self.extension_menus = {}
|
||||
|
||||
def close(self):
|
||||
self.edit = self.text = None
|
||||
self.extension_menus = None
|
||||
try:
|
||||
for ext in self.extensions.values():
|
||||
closer = getattr(ext, "close", None)
|
||||
if closer is not None:
|
||||
closer()
|
||||
finally:
|
||||
self.extensions = {}
|
||||
|
||||
def IDLEExtension(self, extension):
|
||||
ext = self.extensions.get(extension)
|
||||
if ext is not None:
|
||||
return ext
|
||||
mod = GetIDLEModule(extension)
|
||||
if mod is None:
|
||||
return None
|
||||
klass = getattr(mod, extension)
|
||||
ext = self.extensions[extension] = klass(self)
|
||||
# Find and bind all the events defined in the extension.
|
||||
events = [item for item in dir(klass) if item[-6:] == "_event"]
|
||||
for event in events:
|
||||
name = "<<{}>>".format(event[:-6].replace("_", "-"))
|
||||
self.edit.bindings.bind(name, getattr(ext, event))
|
||||
return ext
|
||||
|
||||
def GetMenuItems(self, menu_name):
|
||||
# Get all menu items for the menu name (eg, "edit")
|
||||
bindings = self.edit.bindings
|
||||
ret = []
|
||||
for ext in self.extensions.values():
|
||||
menudefs = getattr(ext, "menudefs", [])
|
||||
for name, items in menudefs:
|
||||
if name == menu_name:
|
||||
for text, event in [item for item in items if item is not None]:
|
||||
text = text.replace("&", "&&")
|
||||
text = text.replace("_", "&")
|
||||
ret.append((text, event))
|
||||
return ret
|
||||
|
||||
######################################################################
|
||||
# The IDLE "Virtual UI" methods that are exposed to the IDLE extensions.
|
||||
#
|
||||
def askinteger(
|
||||
self, caption, prompt, parent=None, initialvalue=0, minvalue=None, maxvalue=None
|
||||
):
|
||||
while 1:
|
||||
rc = GetSimpleInput(prompt, str(initialvalue), caption)
|
||||
if rc is None:
|
||||
return 0 # Correct "cancel" semantics?
|
||||
err = None
|
||||
try:
|
||||
rc = int(rc)
|
||||
except ValueError:
|
||||
err = "Please enter an integer"
|
||||
if not err and minvalue is not None and rc < minvalue:
|
||||
err = f"Please enter an integer greater then or equal to {minvalue}"
|
||||
if not err and maxvalue is not None and rc > maxvalue:
|
||||
err = f"Please enter an integer less then or equal to {maxvalue}"
|
||||
if err:
|
||||
win32ui.MessageBox(err, caption, win32con.MB_OK)
|
||||
continue
|
||||
return rc
|
||||
|
||||
def askyesno(self, caption, prompt, parent=None):
|
||||
return win32ui.MessageBox(prompt, caption, win32con.MB_YESNO) == win32con.IDYES
|
||||
|
||||
######################################################################
|
||||
# The IDLE "Virtual Text Widget" methods that are exposed to the IDLE extensions.
|
||||
#
|
||||
|
||||
# Is character at text_index in a Python string? Return 0 for
|
||||
# "guaranteed no", true for anything else.
|
||||
def is_char_in_string(self, text_index):
|
||||
# A helper for the code analyser - we need internal knowledge of
|
||||
# the colorizer to get this information
|
||||
# This assumes the colorizer has got to this point!
|
||||
text_index = self.text._getoffset(text_index)
|
||||
c = self.text.edit._GetColorizer()
|
||||
if c and c.GetStringStyle(text_index) is None:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
# If a selection is defined in the text widget, return
|
||||
# (start, end) as Tkinter text indices, otherwise return
|
||||
# (None, None)
|
||||
def get_selection_indices(self):
|
||||
try:
|
||||
first = self.text.index("sel.first")
|
||||
last = self.text.index("sel.last")
|
||||
return first, last
|
||||
except TextError:
|
||||
return None, None
|
||||
|
||||
def set_tabwidth(self, width):
|
||||
self.edit.SCISetTabWidth(width)
|
||||
|
||||
def get_tabwidth(self):
|
||||
return self.edit.GetTabWidth()
|
||||
|
||||
|
||||
# A class providing the generic "Call Tips" interface
|
||||
class CallTips:
|
||||
def __init__(self, edit):
|
||||
self.edit = edit
|
||||
|
||||
def showtip(self, tip_text):
|
||||
self.edit.SCICallTipShow(tip_text)
|
||||
|
||||
def hidetip(self):
|
||||
self.edit.SCICallTipCancel()
|
||||
|
||||
|
||||
########################################
|
||||
#
|
||||
# Helpers for the TkText emulation.
|
||||
def TkOffsetToIndex(offset, edit):
|
||||
lineoff = 0
|
||||
# May be 1 > actual end if we pretended there was a trailing '\n'
|
||||
offset = min(offset, edit.GetTextLength())
|
||||
line = edit.LineFromChar(offset)
|
||||
lineIndex = edit.LineIndex(line)
|
||||
return "%d.%d" % (line + 1, offset - lineIndex)
|
||||
|
||||
|
||||
def _NextTok(str, pos):
|
||||
# Returns (token, endPos)
|
||||
end = len(str)
|
||||
if pos >= end:
|
||||
return None, 0
|
||||
while pos < end and str[pos] in string.whitespace:
|
||||
pos += 1
|
||||
# Special case for +-
|
||||
if str[pos] in "+-":
|
||||
return str[pos], pos + 1
|
||||
# Digits also a special case.
|
||||
endPos = pos
|
||||
while endPos < end and str[endPos] in string.digits + ".":
|
||||
endPos += 1
|
||||
if pos != endPos:
|
||||
return str[pos:endPos], endPos
|
||||
endPos = pos
|
||||
while endPos < end and str[endPos] not in string.whitespace + string.digits + "+-":
|
||||
endPos += 1
|
||||
if pos != endPos:
|
||||
return str[pos:endPos], endPos
|
||||
return None, 0
|
||||
|
||||
|
||||
def TkIndexToOffset(bm, edit, marks):
|
||||
base, nextTokPos = _NextTok(bm, 0)
|
||||
if base is None:
|
||||
raise ValueError("Empty bookmark ID!")
|
||||
if base.find(".") > 0:
|
||||
try:
|
||||
line, col = base.split(".", 2)
|
||||
if col == "first" or col == "last":
|
||||
# Tag name
|
||||
if line != "sel":
|
||||
raise ValueError("Tags aren't here!")
|
||||
sel = edit.GetSel()
|
||||
if sel[0] == sel[1]:
|
||||
raise EmptyRange
|
||||
if col == "first":
|
||||
pos = sel[0]
|
||||
else:
|
||||
pos = sel[1]
|
||||
else:
|
||||
# Lines are 1 based for tkinter
|
||||
line = int(line) - 1
|
||||
if line > edit.GetLineCount():
|
||||
pos = edit.GetTextLength() + 1
|
||||
else:
|
||||
pos = edit.LineIndex(line)
|
||||
if pos == -1:
|
||||
pos = edit.GetTextLength()
|
||||
pos += int(col)
|
||||
except (ValueError, IndexError):
|
||||
raise ValueError("Unexpected literal in '%s'" % base)
|
||||
elif base == "insert":
|
||||
pos = edit.GetSel()[0]
|
||||
elif base == "end":
|
||||
pos = edit.GetTextLength()
|
||||
# Pretend there is a trailing '\n' if necessary
|
||||
if pos and edit.SCIGetCharAt(pos - 1) != "\n":
|
||||
pos += 1
|
||||
else:
|
||||
try:
|
||||
pos = marks[base]
|
||||
except KeyError:
|
||||
raise ValueError("Unsupported base offset or undefined mark '%s'" % base)
|
||||
|
||||
while 1:
|
||||
word, nextTokPos = _NextTok(bm, nextTokPos)
|
||||
if word is None:
|
||||
break
|
||||
if word in ("+", "-"):
|
||||
num, nextTokPos = _NextTok(bm, nextTokPos)
|
||||
if num is None:
|
||||
raise ValueError("+/- operator needs 2 args")
|
||||
what, nextTokPos = _NextTok(bm, nextTokPos)
|
||||
if what is None:
|
||||
raise ValueError("+/- operator needs 2 args")
|
||||
if what[0] != "c":
|
||||
raise ValueError("+/- only supports chars")
|
||||
if word == "+":
|
||||
pos += int(num)
|
||||
else:
|
||||
pos -= int(num)
|
||||
elif word == "wordstart":
|
||||
while pos > 0 and edit.SCIGetCharAt(pos - 1) in wordchars:
|
||||
pos -= 1
|
||||
elif word == "wordend":
|
||||
end = edit.GetTextLength()
|
||||
while pos < end and edit.SCIGetCharAt(pos) in wordchars:
|
||||
pos += 1
|
||||
elif word == "linestart":
|
||||
while pos > 0 and edit.SCIGetCharAt(pos - 1) not in "\n\r":
|
||||
pos -= 1
|
||||
elif word == "lineend":
|
||||
end = edit.GetTextLength()
|
||||
while pos < end and edit.SCIGetCharAt(pos) not in "\n\r":
|
||||
pos += 1
|
||||
else:
|
||||
raise ValueError("Unsupported relative offset '%s'" % word)
|
||||
return max(pos, 0) # Tkinter is tollerant of -ve indexes - we aren't
|
||||
|
||||
|
||||
# A class that resembles an IDLE (ie, a Tk) text widget.
|
||||
# Construct with an edit object (eg, an editor view)
|
||||
class TkText:
|
||||
def __init__(self, edit):
|
||||
self.calltips = None
|
||||
self.edit = edit
|
||||
self.marks = {}
|
||||
|
||||
## def __getattr__(self, attr):
|
||||
## if attr=="tk": return self # So text.tk.call works.
|
||||
## if attr=="master": return None # ditto!
|
||||
## raise AttributeError, attr
|
||||
## def __getitem__(self, item):
|
||||
## if item=="tabs":
|
||||
## size = self.edit.GetTabWidth()
|
||||
## if size==8: return "" # Tk default
|
||||
## return size # correct semantics?
|
||||
## elif item=="font": # Used for measurements we don't need to do!
|
||||
## return "Don't know the font"
|
||||
## raise IndexError, "Invalid index '%s'" % item
|
||||
def make_calltip_window(self):
|
||||
if self.calltips is None:
|
||||
self.calltips = CallTips(self.edit)
|
||||
return self.calltips
|
||||
|
||||
def _getoffset(self, index):
|
||||
return TkIndexToOffset(index, self.edit, self.marks)
|
||||
|
||||
def _getindex(self, off):
|
||||
return TkOffsetToIndex(off, self.edit)
|
||||
|
||||
def _fix_indexes(self, start, end):
|
||||
# first some magic to handle skipping over utf8 extended chars.
|
||||
while start > 0 and ord(self.edit.SCIGetCharAt(start)) & 0xC0 == 0x80:
|
||||
start -= 1
|
||||
while (
|
||||
end < self.edit.GetTextLength()
|
||||
and ord(self.edit.SCIGetCharAt(end)) & 0xC0 == 0x80
|
||||
):
|
||||
end += 1
|
||||
# now handling fixing \r\n->\n disparities...
|
||||
if (
|
||||
start > 0
|
||||
and self.edit.SCIGetCharAt(start) == "\n"
|
||||
and self.edit.SCIGetCharAt(start - 1) == "\r"
|
||||
):
|
||||
start -= 1
|
||||
if (
|
||||
end < self.edit.GetTextLength()
|
||||
and self.edit.SCIGetCharAt(end - 1) == "\r"
|
||||
and self.edit.SCIGetCharAt(end) == "\n"
|
||||
):
|
||||
end += 1
|
||||
return start, end
|
||||
|
||||
## def get_tab_width(self):
|
||||
## return self.edit.GetTabWidth()
|
||||
## def call(self, *rest):
|
||||
## # Crap to support Tk measurement hacks for tab widths
|
||||
## if rest[0] != "font" or rest[1] != "measure":
|
||||
## raise ValueError, "Unsupport call type"
|
||||
## return len(rest[5])
|
||||
## def configure(self, **kw):
|
||||
## for name, val in kw.items():
|
||||
## if name=="tabs":
|
||||
## self.edit.SCISetTabWidth(int(val))
|
||||
## else:
|
||||
## raise ValueError, "Unsupported configuration item %s" % kw
|
||||
def bind(self, binding, handler):
|
||||
self.edit.bindings.bind(binding, handler)
|
||||
|
||||
def get(self, start, end=None):
|
||||
try:
|
||||
start = self._getoffset(start)
|
||||
if end is None:
|
||||
end = start + 1
|
||||
else:
|
||||
end = self._getoffset(end)
|
||||
except EmptyRange:
|
||||
return ""
|
||||
# Simple semantic checks to conform to the Tk text interface
|
||||
if end <= start:
|
||||
return ""
|
||||
max = self.edit.GetTextLength()
|
||||
checkEnd = 0
|
||||
if end > max:
|
||||
end = max
|
||||
checkEnd = 1
|
||||
start, end = self._fix_indexes(start, end)
|
||||
ret = self.edit.GetTextRange(start, end)
|
||||
# pretend a trailing '\n' exists if necessary.
|
||||
if checkEnd and (not ret or ret[-1] != "\n"):
|
||||
ret += "\n"
|
||||
return ret.replace("\r", "")
|
||||
|
||||
def index(self, spec):
|
||||
try:
|
||||
return self._getindex(self._getoffset(spec))
|
||||
except EmptyRange:
|
||||
return ""
|
||||
|
||||
def insert(self, pos, text):
|
||||
try:
|
||||
pos = self._getoffset(pos)
|
||||
except EmptyRange:
|
||||
raise TextError("Empty range")
|
||||
self.edit.SetSel((pos, pos))
|
||||
# IDLE only deals with "\n" - we will be nicer
|
||||
|
||||
bits = text.split("\n")
|
||||
self.edit.SCIAddText(bits[0])
|
||||
for bit in bits[1:]:
|
||||
self.edit.SCINewline()
|
||||
self.edit.SCIAddText(bit)
|
||||
|
||||
def delete(self, start, end=None):
|
||||
try:
|
||||
start = self._getoffset(start)
|
||||
if end is not None:
|
||||
end = self._getoffset(end)
|
||||
except EmptyRange:
|
||||
raise TextError("Empty range")
|
||||
# If end is specified and == start, then we must delete nothing.
|
||||
if start == end:
|
||||
return
|
||||
# If end is not specified, delete one char
|
||||
if end is None:
|
||||
end = start + 1
|
||||
else:
|
||||
# Tk says not to delete in this case, but our control would.
|
||||
if end < start:
|
||||
return
|
||||
if start == self.edit.GetTextLength():
|
||||
return # Nothing to delete.
|
||||
old = self.edit.GetSel()[0] # Lose a selection
|
||||
# Hack for partial '\r\n' and UTF-8 char removal
|
||||
start, end = self._fix_indexes(start, end)
|
||||
self.edit.SetSel((start, end))
|
||||
self.edit.Clear()
|
||||
if old >= start and old < end:
|
||||
old = start
|
||||
elif old >= end:
|
||||
old -= end - start
|
||||
self.edit.SetSel(old)
|
||||
|
||||
def bell(self):
|
||||
win32api.MessageBeep()
|
||||
|
||||
def see(self, pos):
|
||||
# Most commands we use in Scintilla actually force the selection
|
||||
# to be seen, making this unnecessary.
|
||||
pass
|
||||
|
||||
def mark_set(self, name, pos):
|
||||
try:
|
||||
pos = self._getoffset(pos)
|
||||
except EmptyRange:
|
||||
raise TextError("Empty range '%s'" % pos)
|
||||
if name == "insert":
|
||||
self.edit.SetSel(pos)
|
||||
else:
|
||||
self.marks[name] = pos
|
||||
|
||||
def tag_add(self, name, start, end):
|
||||
if name != "sel":
|
||||
raise ValueError("Only sel tag is supported")
|
||||
try:
|
||||
start = self._getoffset(start)
|
||||
end = self._getoffset(end)
|
||||
except EmptyRange:
|
||||
raise TextError("Empty range")
|
||||
self.edit.SetSel(start, end)
|
||||
|
||||
def tag_remove(self, name, start, end):
|
||||
if name != "sel" or start != "1.0" or end != "end":
|
||||
raise ValueError("Can't remove this tag")
|
||||
# Turn the sel into a cursor
|
||||
self.edit.SetSel(self.edit.GetSel()[0])
|
||||
|
||||
def compare(self, i1, op, i2):
|
||||
try:
|
||||
i1 = self._getoffset(i1)
|
||||
except EmptyRange:
|
||||
i1 = ""
|
||||
try:
|
||||
i2 = self._getoffset(i2)
|
||||
except EmptyRange:
|
||||
i2 = ""
|
||||
return eval("%d%s%d" % (i1, op, i2))
|
||||
|
||||
def undo_block_start(self):
|
||||
self.edit.SCIBeginUndoAction()
|
||||
|
||||
def undo_block_stop(self):
|
||||
self.edit.SCIEndUndoAction()
|
||||
|
||||
|
||||
######################################################################
|
||||
#
|
||||
# Test related code.
|
||||
#
|
||||
######################################################################
|
||||
def TestCheck(index, edit, expected=None):
|
||||
rc = TkIndexToOffset(index, edit, {})
|
||||
if rc != expected:
|
||||
print("ERROR: Index", index, ", expected", expected, "but got", rc)
|
||||
|
||||
|
||||
def TestGet(fr, to, t, expected):
|
||||
got = t.get(fr, to)
|
||||
if got != expected:
|
||||
print(f"ERROR: get({fr!r}, {to!r}) expected {expected!r}, but got {got!r}")
|
||||
|
||||
|
||||
def test():
|
||||
import pywin.framework.editor
|
||||
|
||||
d = pywin.framework.editor.editorTemplate.OpenDocumentFile(None)
|
||||
e = d.GetFirstView()
|
||||
t = TkText(e)
|
||||
e.SCIAddText("hi there how\nare you today\r\nI hope you are well")
|
||||
e.SetSel((4, 4))
|
||||
|
||||
skip = """
|
||||
TestCheck("insert", e, 4)
|
||||
TestCheck("insert wordstart", e, 3)
|
||||
TestCheck("insert wordend", e, 8)
|
||||
TestCheck("insert linestart", e, 0)
|
||||
TestCheck("insert lineend", e, 12)
|
||||
TestCheck("insert + 4 chars", e, 8)
|
||||
TestCheck("insert +4c", e, 8)
|
||||
TestCheck("insert - 2 chars", e, 2)
|
||||
TestCheck("insert -2c", e, 2)
|
||||
TestCheck("insert-2c", e, 2)
|
||||
TestCheck("insert-2 c", e, 2)
|
||||
TestCheck("insert- 2c", e, 2)
|
||||
TestCheck("1.1", e, 1)
|
||||
TestCheck("1.0", e, 0)
|
||||
TestCheck("2.0", e, 13)
|
||||
try:
|
||||
TestCheck("sel.first", e, 0)
|
||||
print("*** sel.first worked with an empty selection")
|
||||
except TextError:
|
||||
pass
|
||||
e.SetSel((4,5))
|
||||
TestCheck("sel.first- 2c", e, 2)
|
||||
TestCheck("sel.last- 2c", e, 3)
|
||||
"""
|
||||
# Check EOL semantics
|
||||
e.SetSel((4, 4))
|
||||
TestGet("insert lineend", "insert lineend +1c", t, "\n")
|
||||
e.SetSel((20, 20))
|
||||
TestGet("insert lineend", "insert lineend +1c", t, "\n")
|
||||
e.SetSel((35, 35))
|
||||
TestGet("insert lineend", "insert lineend +1c", t, "\n")
|
||||
|
||||
|
||||
class IDLEWrapper:
|
||||
def __init__(self, control):
|
||||
self.text = control
|
||||
|
||||
|
||||
def IDLETest(extension):
|
||||
import os
|
||||
import sys
|
||||
|
||||
modname = "pywin.idle." + extension
|
||||
__import__(modname)
|
||||
mod = sys.modules[modname]
|
||||
mod.TclError = TextError
|
||||
klass = getattr(mod, extension)
|
||||
|
||||
# Create a new Scintilla Window.
|
||||
import pywin.framework.editor
|
||||
|
||||
d = pywin.framework.editor.editorTemplate.OpenDocumentFile(None)
|
||||
v = d.GetFirstView()
|
||||
fname = os.path.splitext(__file__)[0] + ".py"
|
||||
v.SCIAddText(open(fname).read())
|
||||
d.SetModifiedFlag(0)
|
||||
r = klass(IDLEWrapper(TkText(v)))
|
||||
return r
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
@@ -0,0 +1 @@
|
||||
# package init.
|
||||
@@ -0,0 +1,180 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import traceback
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
from . import ( # nopycln: import # Injects fast_readline into the IDLE auto-indent extension
|
||||
IDLEenvironment,
|
||||
)
|
||||
|
||||
HANDLER_ARGS_GUESS = 0
|
||||
HANDLER_ARGS_NATIVE = 1
|
||||
HANDLER_ARGS_IDLE = 2
|
||||
HANDLER_ARGS_EXTENSION = 3
|
||||
|
||||
next_id = 5000
|
||||
|
||||
event_to_commands: dict[str, int] = {} # dict of event names to IDs
|
||||
command_to_events: dict[int, str] = {} # dict of IDs to event names
|
||||
|
||||
|
||||
def assign_command_id(event, id=0):
|
||||
global next_id
|
||||
if id == 0:
|
||||
id = event_to_commands.get(event, 0)
|
||||
if id == 0:
|
||||
id = next_id
|
||||
next_id += 1
|
||||
# Only map the ones we allocated - specified ones are assumed to have a handler
|
||||
command_to_events[id] = event
|
||||
event_to_commands[event] = id
|
||||
return id
|
||||
|
||||
|
||||
class SendCommandHandler:
|
||||
def __init__(self, cmd):
|
||||
self.cmd = cmd
|
||||
|
||||
def __call__(self, *args):
|
||||
win32ui.GetMainFrame().SendMessage(win32con.WM_COMMAND, self.cmd)
|
||||
|
||||
|
||||
class Binding:
|
||||
def __init__(self, handler, handler_args_type):
|
||||
self.handler = handler
|
||||
self.handler_args_type = handler_args_type
|
||||
|
||||
|
||||
class BindingsManager:
|
||||
def __init__(self, parent_view):
|
||||
self.parent_view = parent_view
|
||||
self.bindings = {} # dict of Binding instances.
|
||||
self.keymap = {}
|
||||
|
||||
def prepare_configure(self):
|
||||
self.keymap = {}
|
||||
|
||||
def complete_configure(self):
|
||||
for id in command_to_events:
|
||||
self.parent_view.HookCommand(self._OnCommand, id)
|
||||
|
||||
def close(self):
|
||||
self.parent_view = self.bindings = self.keymap = None
|
||||
|
||||
def report_error(self, problem):
|
||||
try:
|
||||
win32ui.SetStatusText(problem, 1)
|
||||
except win32ui.error:
|
||||
# No status bar!
|
||||
print(problem)
|
||||
|
||||
def update_keymap(self, keymap):
|
||||
self.keymap.update(keymap)
|
||||
|
||||
def bind(self, event, handler, handler_args_type=HANDLER_ARGS_GUESS, cid=0):
|
||||
if handler is None:
|
||||
handler = SendCommandHandler(cid)
|
||||
self.bindings[event] = self._new_binding(handler, handler_args_type)
|
||||
self.bind_command(event, cid)
|
||||
|
||||
def bind_command(self, event, id=0):
|
||||
"Binds an event to a Windows control/command ID"
|
||||
id = assign_command_id(event, id)
|
||||
return id
|
||||
|
||||
def get_command_id(self, event):
|
||||
id = event_to_commands.get(event)
|
||||
if id is None:
|
||||
# See if we even have an event of that name!?
|
||||
if event not in self.bindings:
|
||||
return None
|
||||
id = self.bind_command(event)
|
||||
return id
|
||||
|
||||
def _OnCommand(self, id, code):
|
||||
event = command_to_events.get(id)
|
||||
if event is None:
|
||||
self.report_error("No event associated with event ID %d" % id)
|
||||
return 1
|
||||
return self.fire(event)
|
||||
|
||||
def _new_binding(self, event, handler_args_type):
|
||||
return Binding(event, handler_args_type)
|
||||
|
||||
def _get_IDLE_handler(self, ext, handler):
|
||||
try:
|
||||
instance = self.parent_view.idle.IDLEExtension(ext)
|
||||
name = handler.replace("-", "_") + "_event"
|
||||
return getattr(instance, name)
|
||||
except (ImportError, AttributeError):
|
||||
msg = f"Can not find event '{handler}' in IDLE extension '{ext}'"
|
||||
self.report_error(msg)
|
||||
return None
|
||||
|
||||
def fire(self, event, event_param=None):
|
||||
# Fire the specified event. Result is native Pythonwin result
|
||||
# (ie, 1==pass one, 0 or None==handled)
|
||||
|
||||
# First look up the event directly - if there, we are set.
|
||||
binding = self.bindings.get(event)
|
||||
if binding is None:
|
||||
# If possible, find it!
|
||||
# A native method name
|
||||
handler = getattr(self.parent_view, event + "Event", None)
|
||||
if handler is None:
|
||||
# Can't decide if I should report an error??
|
||||
self.report_error("The event name '%s' can not be found." % event)
|
||||
# Either way, just let the default handlers grab it.
|
||||
return 1
|
||||
binding = self._new_binding(handler, HANDLER_ARGS_NATIVE)
|
||||
# Cache it.
|
||||
self.bindings[event] = binding
|
||||
|
||||
handler_args_type = binding.handler_args_type
|
||||
# Now actually fire it.
|
||||
if handler_args_type == HANDLER_ARGS_GUESS:
|
||||
# Can't be native, as natives are never added with "guess".
|
||||
# Must be extension or IDLE.
|
||||
if event[0] == "<":
|
||||
handler_args_type = HANDLER_ARGS_IDLE
|
||||
else:
|
||||
handler_args_type = HANDLER_ARGS_EXTENSION
|
||||
try:
|
||||
if handler_args_type == HANDLER_ARGS_EXTENSION:
|
||||
args = self.parent_view.idle, event_param
|
||||
else:
|
||||
args = (event_param,)
|
||||
rc = binding.handler(*args)
|
||||
if handler_args_type == HANDLER_ARGS_IDLE:
|
||||
# Convert to our return code.
|
||||
if rc in (None, "break"):
|
||||
rc = 0
|
||||
else:
|
||||
rc = 1
|
||||
except:
|
||||
message = "Firing event '%s' failed." % event
|
||||
print(message)
|
||||
traceback.print_exc()
|
||||
self.report_error(message)
|
||||
rc = 1 # Let any default handlers have a go!
|
||||
return rc
|
||||
|
||||
def fire_key_event(self, msg):
|
||||
key = msg[2]
|
||||
keyState = 0
|
||||
if win32api.GetKeyState(win32con.VK_CONTROL) & 0x8000:
|
||||
keyState |= win32con.RIGHT_CTRL_PRESSED | win32con.LEFT_CTRL_PRESSED
|
||||
if win32api.GetKeyState(win32con.VK_SHIFT) & 0x8000:
|
||||
keyState |= win32con.SHIFT_PRESSED
|
||||
if win32api.GetKeyState(win32con.VK_MENU) & 0x8000:
|
||||
keyState |= win32con.LEFT_ALT_PRESSED | win32con.RIGHT_ALT_PRESSED
|
||||
keyinfo = key, keyState
|
||||
# Special hacks for the dead-char key on non-US keyboards.
|
||||
# (XXX - which do not work :-(
|
||||
event = self.keymap.get(keyinfo)
|
||||
if event is None:
|
||||
return 1
|
||||
return self.fire(event, None)
|
||||
@@ -0,0 +1,363 @@
|
||||
# config.py - deals with loading configuration information.
|
||||
|
||||
# Loads config data from a .cfg file. Also caches the compiled
|
||||
# data back into a .cfc file.
|
||||
|
||||
# If you are wondering how to avoid needing .cfg files (eg,
|
||||
# if you are freezing Pythonwin etc) I suggest you create a
|
||||
# .py file, and put the config info in a docstring. Then
|
||||
# pass a CStringIO file (rather than a filename) to the
|
||||
# config manager.
|
||||
import glob
|
||||
import importlib.util
|
||||
import marshal
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
|
||||
import pywin
|
||||
import win32api
|
||||
|
||||
from . import keycodes
|
||||
|
||||
debugging = 0
|
||||
if debugging:
|
||||
import win32traceutil # Some trace statements fire before the interactive window is open.
|
||||
|
||||
def trace(*args):
|
||||
sys.stderr.write(" ".join(map(str, args)) + "\n")
|
||||
|
||||
else:
|
||||
trace = lambda *args: None
|
||||
|
||||
compiled_config_version = 3
|
||||
|
||||
|
||||
def split_line(line, lineno):
|
||||
comment_pos = line.find("#")
|
||||
if comment_pos >= 0:
|
||||
line = line[:comment_pos]
|
||||
sep_pos = line.rfind("=")
|
||||
if sep_pos == -1:
|
||||
if line.strip():
|
||||
print(f"Warning: Line {lineno}: {line!r} is an invalid entry")
|
||||
return None, None
|
||||
return "", ""
|
||||
return line[:sep_pos].strip(), line[sep_pos + 1 :].strip()
|
||||
|
||||
|
||||
def get_section_header(line):
|
||||
# Returns the section if the line is a section header, else None
|
||||
if line[0] == "[":
|
||||
end = line.find("]")
|
||||
if end == -1:
|
||||
end = len(line)
|
||||
rc = line[1:end].lower()
|
||||
try:
|
||||
i = rc.index(":")
|
||||
return rc[:i], rc[i + 1 :]
|
||||
except ValueError:
|
||||
return rc, ""
|
||||
return None, None
|
||||
|
||||
|
||||
def find_config_file(f):
|
||||
return os.path.join(next(iter(pywin.__path__)), f + ".cfg")
|
||||
|
||||
|
||||
def find_config_files():
|
||||
return [
|
||||
os.path.split(x)[1]
|
||||
for x in [
|
||||
os.path.splitext(x)[0]
|
||||
for x in glob.glob(os.path.join(next(iter(pywin.__path__)), "*.cfg"))
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
def __init__(self, f):
|
||||
self.filename = "unknown"
|
||||
self.last_error = None
|
||||
self.key_to_events = {}
|
||||
b_close = False
|
||||
if hasattr(f, "readline"):
|
||||
fp = f
|
||||
self.filename = "<config string>"
|
||||
compiled_name = None
|
||||
else:
|
||||
try:
|
||||
f = find_config_file(f)
|
||||
src_stat = os.stat(f)
|
||||
except OSError:
|
||||
self.report_error("Config file '%s' not found" % f)
|
||||
return
|
||||
self.filename = f
|
||||
self.basename = os.path.basename(f)
|
||||
trace("Loading configuration", self.basename)
|
||||
compiled_name = os.path.splitext(f)[0] + ".cfc"
|
||||
try:
|
||||
cf = open(compiled_name, "rb")
|
||||
try:
|
||||
ver = marshal.load(cf)
|
||||
ok = compiled_config_version == ver
|
||||
if ok:
|
||||
kblayoutname = marshal.load(cf)
|
||||
magic = marshal.load(cf)
|
||||
size = marshal.load(cf)
|
||||
mtime = marshal.load(cf)
|
||||
if (
|
||||
magic == importlib.util.MAGIC_NUMBER
|
||||
and win32api.GetKeyboardLayoutName() == kblayoutname
|
||||
and src_stat[stat.ST_MTIME] == mtime
|
||||
and src_stat[stat.ST_SIZE] == size
|
||||
):
|
||||
self.cache = marshal.load(cf)
|
||||
trace("Configuration loaded cached", compiled_name)
|
||||
return # We are ready to roll!
|
||||
finally:
|
||||
cf.close()
|
||||
except (OSError, EOFError):
|
||||
pass
|
||||
fp = open(f)
|
||||
b_close = True
|
||||
self.cache = {}
|
||||
lineno = 1
|
||||
line = fp.readline()
|
||||
while line:
|
||||
# Skip to the next section (maybe already there!)
|
||||
section, subsection = get_section_header(line)
|
||||
while line and section is None:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
lineno += 1
|
||||
section, subsection = get_section_header(line)
|
||||
if not line:
|
||||
break
|
||||
|
||||
if section == "keys":
|
||||
line, lineno = self._load_keys(subsection, fp, lineno)
|
||||
elif section == "extensions":
|
||||
line, lineno = self._load_extensions(subsection, fp, lineno)
|
||||
elif section == "idle extensions":
|
||||
line, lineno = self._load_idle_extensions(subsection, fp, lineno)
|
||||
elif section == "general":
|
||||
line, lineno = self._load_general(subsection, fp, lineno)
|
||||
else:
|
||||
self.report_error(
|
||||
f"Unrecognised section header '{section}:{subsection}'"
|
||||
)
|
||||
line = fp.readline()
|
||||
lineno += 1
|
||||
if b_close:
|
||||
fp.close()
|
||||
# Check critical data.
|
||||
if not self.cache.get("keys"):
|
||||
self.report_error("No keyboard definitions were loaded")
|
||||
if not self.last_error and compiled_name:
|
||||
try:
|
||||
cf = open(compiled_name, "wb")
|
||||
marshal.dump(compiled_config_version, cf)
|
||||
marshal.dump(win32api.GetKeyboardLayoutName(), cf)
|
||||
marshal.dump(importlib.util.MAGIC_NUMBER, cf)
|
||||
marshal.dump(src_stat[stat.ST_SIZE], cf)
|
||||
marshal.dump(src_stat[stat.ST_MTIME], cf)
|
||||
marshal.dump(self.cache, cf)
|
||||
cf.close()
|
||||
except (OSError, EOFError):
|
||||
pass # Ignore errors - may be read only.
|
||||
|
||||
def configure(self, editor, subsections=None):
|
||||
# Execute the extension code, and find any events.
|
||||
# First, we "recursively" connect any we are based on.
|
||||
if subsections is None:
|
||||
subsections = []
|
||||
subsections = [""] + subsections
|
||||
general = self.get_data("general")
|
||||
if general:
|
||||
parents = general.get("based on", [])
|
||||
for parent in parents:
|
||||
trace("Configuration based on", parent, "- loading.")
|
||||
parent = self.__class__(parent)
|
||||
parent.configure(editor, subsections)
|
||||
if parent.last_error:
|
||||
self.report_error(parent.last_error)
|
||||
|
||||
bindings = editor.bindings
|
||||
codeob = self.get_data("extension code")
|
||||
if codeob is not None:
|
||||
ns = {}
|
||||
try:
|
||||
exec(codeob, ns)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.report_error("Executing extension code failed")
|
||||
ns = None
|
||||
if ns:
|
||||
num = 0
|
||||
for name, func in ns.items():
|
||||
if isinstance(func, types.FunctionType) and name[:1] != "_":
|
||||
bindings.bind(name, func)
|
||||
num += 1
|
||||
trace("Configuration Extension code loaded", num, "events")
|
||||
# Load the idle extensions
|
||||
for subsection in subsections:
|
||||
for ext in self.get_data("idle extensions", {}).get(subsection, []):
|
||||
try:
|
||||
editor.idle.IDLEExtension(ext)
|
||||
trace("Loaded IDLE extension", ext)
|
||||
except:
|
||||
self.report_error("Can not load the IDLE extension '%s'" % ext)
|
||||
|
||||
# Now bind up the key-map (remembering a reverse map
|
||||
subsection_keymap = self.get_data("keys")
|
||||
num_bound = 0
|
||||
for subsection in subsections:
|
||||
keymap = subsection_keymap.get(subsection, {})
|
||||
bindings.update_keymap(keymap)
|
||||
num_bound += len(keymap)
|
||||
trace("Configuration bound", num_bound, "keys")
|
||||
|
||||
def get_key_binding(self, event, subsections=None):
|
||||
if subsections is None:
|
||||
subsections = []
|
||||
subsections = [""] + subsections
|
||||
|
||||
subsection_keymap = self.get_data("keys")
|
||||
for subsection in subsections:
|
||||
map = self.key_to_events.get(subsection)
|
||||
if map is None: # Build it
|
||||
keymap = subsection_keymap.get(subsection, {})
|
||||
map = {map_event: key_info for key_info, map_event in keymap.items()}
|
||||
self.key_to_events[subsection] = map
|
||||
|
||||
info = map.get(event)
|
||||
if info is not None:
|
||||
return keycodes.make_key_name(info[0], info[1])
|
||||
return None
|
||||
|
||||
def report_error(self, msg):
|
||||
self.last_error = msg
|
||||
print(f"Error in {self.filename}: {msg}")
|
||||
|
||||
def report_warning(self, msg):
|
||||
print(f"Warning in {self.filename}: {msg}")
|
||||
|
||||
def _readline(self, fp, lineno, bStripComments=1):
|
||||
line = fp.readline()
|
||||
lineno += 1
|
||||
if line:
|
||||
bBreak = (
|
||||
get_section_header(line)[0] is not None
|
||||
) # A new section is starting
|
||||
if bStripComments and not bBreak:
|
||||
pos = line.find("#")
|
||||
if pos >= 0:
|
||||
line = line[:pos] + "\n"
|
||||
else:
|
||||
bBreak = 1
|
||||
return line, lineno, bBreak
|
||||
|
||||
def get_data(self, name, default=None):
|
||||
return self.cache.get(name, default)
|
||||
|
||||
def _save_data(self, name, data):
|
||||
self.cache[name] = data
|
||||
return data
|
||||
|
||||
def _load_general(self, sub_section, fp, lineno):
|
||||
map = {}
|
||||
while 1:
|
||||
line, lineno, bBreak = self._readline(fp, lineno)
|
||||
if bBreak:
|
||||
break
|
||||
|
||||
key, val = split_line(line, lineno)
|
||||
if not key:
|
||||
continue
|
||||
key = key.lower()
|
||||
l = map.get(key, [])
|
||||
l.append(val)
|
||||
map[key] = l
|
||||
self._save_data("general", map)
|
||||
return line, lineno
|
||||
|
||||
def _load_keys(self, sub_section, fp, lineno):
|
||||
# Builds a nested dictionary of
|
||||
# (scancode, flags) = event_name
|
||||
main_map = self.get_data("keys", {})
|
||||
map = main_map.get(sub_section, {})
|
||||
while 1:
|
||||
line, lineno, bBreak = self._readline(fp, lineno)
|
||||
if bBreak:
|
||||
break
|
||||
|
||||
key, event = split_line(line, lineno)
|
||||
if not event:
|
||||
continue
|
||||
sc, flag = keycodes.parse_key_name(key)
|
||||
if sc is None:
|
||||
self.report_warning("Line %d: Invalid key name '%s'" % (lineno, key))
|
||||
else:
|
||||
map[sc, flag] = event
|
||||
main_map[sub_section] = map
|
||||
self._save_data("keys", main_map)
|
||||
return line, lineno
|
||||
|
||||
def _load_extensions(self, sub_section, fp, lineno):
|
||||
start_lineno = lineno
|
||||
lines = []
|
||||
while 1:
|
||||
line, lineno, bBreak = self._readline(fp, lineno, 0)
|
||||
if bBreak:
|
||||
break
|
||||
lines.append(line)
|
||||
try:
|
||||
c = compile(
|
||||
"\n" * start_lineno + "".join(lines), # produces correct tracebacks
|
||||
self.filename,
|
||||
"exec",
|
||||
)
|
||||
self._save_data("extension code", c)
|
||||
except SyntaxError as details:
|
||||
errlineno = details.lineno + start_lineno
|
||||
# Should handle syntax errors better here, and offset the lineno.
|
||||
self.report_error(
|
||||
"Compiling extension code failed:\r\nFile: %s\r\nLine %d\r\n%s"
|
||||
% (details.filename, errlineno, details.msg)
|
||||
)
|
||||
return line, lineno
|
||||
|
||||
def _load_idle_extensions(self, sub_section, fp, lineno):
|
||||
extension_map = self.get_data("idle extensions")
|
||||
if extension_map is None:
|
||||
extension_map = {}
|
||||
extensions = []
|
||||
while 1:
|
||||
line, lineno, bBreak = self._readline(fp, lineno)
|
||||
if bBreak:
|
||||
break
|
||||
line = line.strip()
|
||||
if line:
|
||||
extensions.append(line)
|
||||
extension_map[sub_section] = extensions
|
||||
self._save_data("idle extensions", extension_map)
|
||||
return line, lineno
|
||||
|
||||
|
||||
def test():
|
||||
import time
|
||||
|
||||
start = time.clock()
|
||||
f = "default"
|
||||
cm = ConfigManager(f)
|
||||
map = cm.get_data("keys")
|
||||
took = time.clock() - start
|
||||
print(f"Loaded {len(map)} items in {took:.4f} secs")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
@@ -0,0 +1,291 @@
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.mfc import dialog
|
||||
|
||||
# Used to indicate that style should use default color
|
||||
from win32con import CLR_INVALID
|
||||
|
||||
from . import scintillacon
|
||||
|
||||
######################################################
|
||||
# Property Page for syntax formatting options
|
||||
|
||||
# The standard 16 color VGA palette should always be possible
|
||||
paletteVGA = (
|
||||
("Black", win32api.RGB(0, 0, 0)),
|
||||
("Navy", win32api.RGB(0, 0, 128)),
|
||||
("Green", win32api.RGB(0, 128, 0)),
|
||||
("Cyan", win32api.RGB(0, 128, 128)),
|
||||
("Maroon", win32api.RGB(128, 0, 0)),
|
||||
("Purple", win32api.RGB(128, 0, 128)),
|
||||
("Olive", win32api.RGB(128, 128, 0)),
|
||||
("Gray", win32api.RGB(128, 128, 128)),
|
||||
("Silver", win32api.RGB(192, 192, 192)),
|
||||
("Blue", win32api.RGB(0, 0, 255)),
|
||||
("Lime", win32api.RGB(0, 255, 0)),
|
||||
("Aqua", win32api.RGB(0, 255, 255)),
|
||||
("Red", win32api.RGB(255, 0, 0)),
|
||||
("Fuchsia", win32api.RGB(255, 0, 255)),
|
||||
("Yellow", win32api.RGB(255, 255, 0)),
|
||||
("White", win32api.RGB(255, 255, 255)),
|
||||
# and a few others will generally be possible.
|
||||
("DarkGrey", win32api.RGB(64, 64, 64)),
|
||||
("PurpleBlue", win32api.RGB(64, 64, 192)),
|
||||
("DarkGreen", win32api.RGB(0, 96, 0)),
|
||||
("DarkOlive", win32api.RGB(128, 128, 64)),
|
||||
("MediumBlue", win32api.RGB(0, 0, 192)),
|
||||
("DarkNavy", win32api.RGB(0, 0, 96)),
|
||||
("Magenta", win32api.RGB(96, 0, 96)),
|
||||
("OffWhite", win32api.RGB(255, 255, 220)),
|
||||
("LightPurple", win32api.RGB(220, 220, 255)),
|
||||
("<Default>", win32con.CLR_INVALID),
|
||||
)
|
||||
|
||||
|
||||
class ScintillaFormatPropertyPage(dialog.PropertyPage):
|
||||
def __init__(self, scintillaClass=None, caption=0):
|
||||
self.scintillaClass = scintillaClass
|
||||
dialog.PropertyPage.__init__(self, win32ui.IDD_PP_FORMAT, caption=caption)
|
||||
|
||||
def OnInitDialog(self):
|
||||
try:
|
||||
if self.scintillaClass is None:
|
||||
from . import control
|
||||
|
||||
sc = control.CScintillaEdit
|
||||
else:
|
||||
sc = self.scintillaClass
|
||||
|
||||
self.scintilla = sc()
|
||||
style = win32con.WS_CHILD | win32con.WS_VISIBLE | win32con.ES_MULTILINE
|
||||
# Convert the rect size
|
||||
rect = self.MapDialogRect((5, 5, 120, 75))
|
||||
self.scintilla.CreateWindow(style, rect, self, 111)
|
||||
self.HookNotify(self.OnBraceMatch, scintillacon.SCN_UPDATEUI)
|
||||
self.scintilla.HookKeyStroke(self.OnEsc, 27)
|
||||
self.scintilla.SCISetViewWS(1)
|
||||
self.pos_bstart = self.pos_bend = self.pos_bbad = 0
|
||||
|
||||
colorizer = self.scintilla._GetColorizer()
|
||||
text = colorizer.GetSampleText()
|
||||
items = text.split("|", 2)
|
||||
pos = len(items[0])
|
||||
self.scintilla.SCIAddText("".join(items))
|
||||
self.scintilla.SetSel(pos, pos)
|
||||
self.scintilla.ApplyFormattingStyles()
|
||||
self.styles = self.scintilla._GetColorizer().styles
|
||||
|
||||
self.cbo = self.GetDlgItem(win32ui.IDC_COMBO1)
|
||||
for c in paletteVGA:
|
||||
self.cbo.AddString(c[0])
|
||||
|
||||
self.cboBoldItalic = self.GetDlgItem(win32ui.IDC_COMBO2)
|
||||
for item in ("Bold Italic", "Bold", "Italic", "Regular"):
|
||||
self.cboBoldItalic.InsertString(0, item)
|
||||
|
||||
self.butIsDefault = self.GetDlgItem(win32ui.IDC_CHECK1)
|
||||
self.butIsDefaultBackground = self.GetDlgItem(win32ui.IDC_CHECK2)
|
||||
self.listbox = self.GetDlgItem(win32ui.IDC_LIST1)
|
||||
self.HookCommand(self.OnListCommand, win32ui.IDC_LIST1)
|
||||
names = sorted(self.styles)
|
||||
for name in names:
|
||||
if self.styles[name].aliased is None:
|
||||
self.listbox.AddString(name)
|
||||
self.listbox.SetCurSel(0)
|
||||
|
||||
idc = win32ui.IDC_RADIO1
|
||||
if not self.scintilla._GetColorizer().bUseFixed:
|
||||
idc = win32ui.IDC_RADIO2
|
||||
self.GetDlgItem(idc).SetCheck(1)
|
||||
self.UpdateUIForStyle(self.styles[names[0]])
|
||||
|
||||
self.scintilla.HookFormatter(self)
|
||||
self.HookCommand(self.OnButDefaultFixedFont, win32ui.IDC_BUTTON1)
|
||||
self.HookCommand(self.OnButDefaultPropFont, win32ui.IDC_BUTTON2)
|
||||
self.HookCommand(self.OnButThisFont, win32ui.IDC_BUTTON3)
|
||||
self.HookCommand(self.OnButUseDefaultFont, win32ui.IDC_CHECK1)
|
||||
self.HookCommand(self.OnButThisBackground, win32ui.IDC_BUTTON4)
|
||||
self.HookCommand(self.OnButUseDefaultBackground, win32ui.IDC_CHECK2)
|
||||
self.HookCommand(self.OnStyleUIChanged, win32ui.IDC_COMBO1)
|
||||
self.HookCommand(self.OnStyleUIChanged, win32ui.IDC_COMBO2)
|
||||
self.HookCommand(self.OnButFixedOrDefault, win32ui.IDC_RADIO1)
|
||||
self.HookCommand(self.OnButFixedOrDefault, win32ui.IDC_RADIO2)
|
||||
except:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
def OnEsc(self, ch):
|
||||
self.GetParent().EndDialog(win32con.IDCANCEL)
|
||||
|
||||
def OnBraceMatch(self, std, extra):
|
||||
import pywin.scintilla.view
|
||||
|
||||
pywin.scintilla.view.DoBraceMatch(self.scintilla)
|
||||
|
||||
def GetSelectedStyle(self):
|
||||
return self.styles[self.listbox.GetText(self.listbox.GetCurSel())]
|
||||
|
||||
def _DoButDefaultFont(self, extra_flags, attr):
|
||||
baseFormat = getattr(self.scintilla._GetColorizer(), attr)
|
||||
flags = (
|
||||
extra_flags
|
||||
| win32con.CF_SCREENFONTS
|
||||
| win32con.CF_EFFECTS
|
||||
| win32con.CF_FORCEFONTEXIST
|
||||
)
|
||||
d = win32ui.CreateFontDialog(baseFormat, flags, None, self)
|
||||
if d.DoModal() == win32con.IDOK:
|
||||
setattr(self.scintilla._GetColorizer(), attr, d.GetCharFormat())
|
||||
self.OnStyleUIChanged(0, win32con.BN_CLICKED)
|
||||
|
||||
def OnButDefaultFixedFont(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
self._DoButDefaultFont(win32con.CF_FIXEDPITCHONLY, "baseFormatFixed")
|
||||
return 1
|
||||
|
||||
def OnButDefaultPropFont(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
self._DoButDefaultFont(win32con.CF_SCALABLEONLY, "baseFormatProp")
|
||||
return 1
|
||||
|
||||
def OnButFixedOrDefault(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
bUseFixed = id == win32ui.IDC_RADIO1
|
||||
self.GetDlgItem(win32ui.IDC_RADIO1).GetCheck() != 0
|
||||
self.scintilla._GetColorizer().bUseFixed = bUseFixed
|
||||
self.scintilla.ApplyFormattingStyles(0)
|
||||
return 1
|
||||
|
||||
def OnButThisFont(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
flags = (
|
||||
win32con.CF_SCREENFONTS
|
||||
| win32con.CF_EFFECTS
|
||||
| win32con.CF_FORCEFONTEXIST
|
||||
)
|
||||
style = self.GetSelectedStyle()
|
||||
# If the selected style is based on the default, we need to apply
|
||||
# the default to it.
|
||||
def_format = self.scintilla._GetColorizer().GetDefaultFormat()
|
||||
format = style.GetCompleteFormat(def_format)
|
||||
d = win32ui.CreateFontDialog(format, flags, None, self)
|
||||
if d.DoModal() == win32con.IDOK:
|
||||
style.format = d.GetCharFormat()
|
||||
self.scintilla.ApplyFormattingStyles(0)
|
||||
return 1
|
||||
|
||||
def OnButUseDefaultFont(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
isDef = self.butIsDefault.GetCheck()
|
||||
self.GetDlgItem(win32ui.IDC_BUTTON3).EnableWindow(not isDef)
|
||||
if isDef: # Being reset to the default font.
|
||||
style = self.GetSelectedStyle()
|
||||
style.ForceAgainstDefault()
|
||||
self.UpdateUIForStyle(style)
|
||||
self.scintilla.ApplyFormattingStyles(0)
|
||||
else:
|
||||
# User wants to override default -
|
||||
# do nothing!
|
||||
pass
|
||||
|
||||
def OnButThisBackground(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
style = self.GetSelectedStyle()
|
||||
bg = win32api.RGB(0xFF, 0xFF, 0xFF)
|
||||
if style.background != CLR_INVALID:
|
||||
bg = style.background
|
||||
d = win32ui.CreateColorDialog(bg, 0, self)
|
||||
if d.DoModal() == win32con.IDOK:
|
||||
style.background = d.GetColor()
|
||||
self.scintilla.ApplyFormattingStyles(0)
|
||||
return 1
|
||||
|
||||
def OnButUseDefaultBackground(self, id, code):
|
||||
if code == win32con.BN_CLICKED:
|
||||
isDef = self.butIsDefaultBackground.GetCheck()
|
||||
self.GetDlgItem(win32ui.IDC_BUTTON4).EnableWindow(not isDef)
|
||||
if isDef: # Being reset to the default color
|
||||
style = self.GetSelectedStyle()
|
||||
style.background = style.default_background
|
||||
self.UpdateUIForStyle(style)
|
||||
self.scintilla.ApplyFormattingStyles(0)
|
||||
else:
|
||||
# User wants to override default -
|
||||
# do nothing!
|
||||
pass
|
||||
|
||||
def OnListCommand(self, id, code):
|
||||
if code == win32con.LBN_SELCHANGE:
|
||||
style = self.GetSelectedStyle()
|
||||
self.UpdateUIForStyle(style)
|
||||
return 1
|
||||
|
||||
def UpdateUIForStyle(self, style):
|
||||
format = style.format
|
||||
sel = 0
|
||||
for c in paletteVGA:
|
||||
if format[4] == c[1]:
|
||||
# print("Style", style.name, "is", c[0])
|
||||
break
|
||||
sel += 1
|
||||
else:
|
||||
sel = -1
|
||||
self.cbo.SetCurSel(sel)
|
||||
self.butIsDefault.SetCheck(style.IsBasedOnDefault())
|
||||
self.GetDlgItem(win32ui.IDC_BUTTON3).EnableWindow(not style.IsBasedOnDefault())
|
||||
|
||||
self.butIsDefaultBackground.SetCheck(
|
||||
style.background == style.default_background
|
||||
)
|
||||
self.GetDlgItem(win32ui.IDC_BUTTON4).EnableWindow(
|
||||
style.background != style.default_background
|
||||
)
|
||||
|
||||
bold = format[1] & win32con.CFE_BOLD != 0
|
||||
italic = format[1] & win32con.CFE_ITALIC != 0
|
||||
self.cboBoldItalic.SetCurSel(bold * 2 + italic)
|
||||
|
||||
def OnStyleUIChanged(self, id, code):
|
||||
if code in [win32con.BN_CLICKED, win32con.CBN_SELCHANGE]:
|
||||
style = self.GetSelectedStyle()
|
||||
self.ApplyUIFormatToStyle(style)
|
||||
self.scintilla.ApplyFormattingStyles(0)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def ApplyUIFormatToStyle(self, style):
|
||||
format = style.format
|
||||
color = paletteVGA[self.cbo.GetCurSel()]
|
||||
effect = 0
|
||||
sel = self.cboBoldItalic.GetCurSel()
|
||||
if sel == 0:
|
||||
effect = 0
|
||||
elif sel == 1:
|
||||
effect = win32con.CFE_ITALIC
|
||||
elif sel == 2:
|
||||
effect = win32con.CFE_BOLD
|
||||
else:
|
||||
effect = win32con.CFE_BOLD | win32con.CFE_ITALIC
|
||||
maskFlags = (
|
||||
format[0] | win32con.CFM_COLOR | win32con.CFM_BOLD | win32con.CFM_ITALIC
|
||||
)
|
||||
style.format = (
|
||||
maskFlags,
|
||||
effect,
|
||||
style.format[2],
|
||||
style.format[3],
|
||||
color[1],
|
||||
) + style.format[5:]
|
||||
|
||||
def OnOK(self):
|
||||
self.scintilla._GetColorizer().SavePreferences()
|
||||
return 1
|
||||
|
||||
|
||||
def test():
|
||||
page = ScintillaFormatPropertyPage()
|
||||
sheet = dialog.PropertySheet("Test")
|
||||
sheet.AddPage(page)
|
||||
sheet.CreateWindow()
|
||||
@@ -0,0 +1,565 @@
|
||||
# An Python interface to the Scintilla control.
|
||||
#
|
||||
# Exposes Python classes that allow you to use Scintilla as
|
||||
# a "standard" MFC edit control (eg, control.GetTextLength(), control.GetSel()
|
||||
# plus many Scintilla specific features (eg control.SCIAddStyledText())
|
||||
|
||||
import array
|
||||
import os
|
||||
import struct
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin import default_scintilla_encoding
|
||||
from pywin.mfc import window
|
||||
|
||||
from . import scintillacon
|
||||
|
||||
# Load Scintilla.dll to get access to the control.
|
||||
# We expect to find this in the same directory as win32ui.pyd
|
||||
dllid = None
|
||||
if win32ui.debug: # If running _d version of Pythonwin...
|
||||
try:
|
||||
dllid = win32api.LoadLibrary(
|
||||
os.path.join(os.path.split(win32ui.__file__)[0], "Scintilla_d.DLL")
|
||||
)
|
||||
except (
|
||||
win32api.error
|
||||
): # Not there - we don't _need_ a debug ver, so ignore this error.
|
||||
pass
|
||||
if dllid is None:
|
||||
try:
|
||||
dllid = win32api.LoadLibrary(
|
||||
os.path.join(os.path.split(win32ui.__file__)[0], "Scintilla.DLL")
|
||||
)
|
||||
except win32api.error:
|
||||
pass
|
||||
if dllid is None:
|
||||
# Still not there - let's see if Windows can find it by searching?
|
||||
dllid = win32api.LoadLibrary("Scintilla.DLL")
|
||||
|
||||
null_byte = b"\0"
|
||||
|
||||
## These are from Richedit.h - need to add to win32con or commctrl
|
||||
EM_GETTEXTRANGE = 1099
|
||||
EM_EXLINEFROMCHAR = 1078
|
||||
EM_FINDTEXTEX = 1103
|
||||
EM_GETSELTEXT = 1086
|
||||
EM_EXSETSEL = win32con.WM_USER + 55
|
||||
|
||||
|
||||
class ScintillaNotification:
|
||||
def __init__(self, **args):
|
||||
self.__dict__.update(args)
|
||||
|
||||
|
||||
class ScintillaControlInterface:
|
||||
def SCIUnpackNotifyMessage(self, msg):
|
||||
format = "iiiiPiiiPPiiii"
|
||||
bytes = win32ui.GetBytes(msg, struct.calcsize(format))
|
||||
(
|
||||
position,
|
||||
ch,
|
||||
modifiers,
|
||||
modificationType,
|
||||
text_ptr,
|
||||
length,
|
||||
linesAdded,
|
||||
msg,
|
||||
wParam,
|
||||
lParam,
|
||||
line,
|
||||
foldLevelNow,
|
||||
foldLevelPrev,
|
||||
margin,
|
||||
) = struct.unpack(format, bytes)
|
||||
return ScintillaNotification(
|
||||
position=position,
|
||||
ch=ch,
|
||||
modifiers=modifiers,
|
||||
modificationType=modificationType,
|
||||
text_ptr=text_ptr,
|
||||
length=length,
|
||||
linesAdded=linesAdded,
|
||||
msg=msg,
|
||||
wParam=wParam,
|
||||
lParam=lParam,
|
||||
line=line,
|
||||
foldLevelNow=foldLevelNow,
|
||||
foldLevelPrev=foldLevelPrev,
|
||||
margin=margin,
|
||||
)
|
||||
|
||||
def SCIAddText(self, text):
|
||||
self.SendMessage(
|
||||
scintillacon.SCI_ADDTEXT, text.encode(default_scintilla_encoding)
|
||||
)
|
||||
|
||||
def SCIAddStyledText(self, text, style=None):
|
||||
# If style is None, text is assumed to be a "native" Scintilla buffer.
|
||||
# If style is specified, text is a normal string, and the style is
|
||||
# assumed to apply to the entire string.
|
||||
if style is not None:
|
||||
text = list(map(lambda char, style=style: char + chr(style), text))
|
||||
text = "".join(text)
|
||||
self.SendMessage(
|
||||
scintillacon.SCI_ADDSTYLEDTEXT, text.encode(default_scintilla_encoding)
|
||||
)
|
||||
|
||||
def SCIInsertText(self, text, pos=-1):
|
||||
# SCIInsertText allows unicode or bytes - but if they are bytes,
|
||||
# the caller must ensure it is encoded correctly.
|
||||
if isinstance(text, str):
|
||||
text = text.encode(default_scintilla_encoding)
|
||||
self.SendScintilla(scintillacon.SCI_INSERTTEXT, pos, text + null_byte)
|
||||
|
||||
def SCISetSavePoint(self):
|
||||
self.SendScintilla(scintillacon.SCI_SETSAVEPOINT)
|
||||
|
||||
def SCISetUndoCollection(self, collectFlag):
|
||||
self.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, collectFlag)
|
||||
|
||||
def SCIBeginUndoAction(self):
|
||||
self.SendScintilla(scintillacon.SCI_BEGINUNDOACTION)
|
||||
|
||||
def SCIEndUndoAction(self):
|
||||
self.SendScintilla(scintillacon.SCI_ENDUNDOACTION)
|
||||
|
||||
def SCIGetCurrentPos(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETCURRENTPOS)
|
||||
|
||||
def SCIGetCharAt(self, pos):
|
||||
# Must ensure char is unsigned!
|
||||
return chr(self.SendScintilla(scintillacon.SCI_GETCHARAT, pos) & 0xFF)
|
||||
|
||||
def SCIGotoLine(self, line):
|
||||
self.SendScintilla(scintillacon.SCI_GOTOLINE, line)
|
||||
|
||||
def SCIBraceMatch(self, pos, maxReStyle):
|
||||
return self.SendScintilla(scintillacon.SCI_BRACEMATCH, pos, maxReStyle)
|
||||
|
||||
def SCIBraceHighlight(self, pos, posOpposite):
|
||||
return self.SendScintilla(scintillacon.SCI_BRACEHIGHLIGHT, pos, posOpposite)
|
||||
|
||||
def SCIBraceBadHighlight(self, pos):
|
||||
return self.SendScintilla(scintillacon.SCI_BRACEBADLIGHT, pos)
|
||||
|
||||
####################################
|
||||
# Styling
|
||||
# def SCIColourise(self, start=0, end=-1):
|
||||
# NOTE - dependent on of we use builtin lexer, so handled below.
|
||||
def SCIGetEndStyled(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETENDSTYLED)
|
||||
|
||||
def SCIStyleSetFore(self, num, v):
|
||||
return self.SendScintilla(scintillacon.SCI_STYLESETFORE, num, v)
|
||||
|
||||
def SCIStyleSetBack(self, num, v):
|
||||
return self.SendScintilla(scintillacon.SCI_STYLESETBACK, num, v)
|
||||
|
||||
def SCIStyleSetEOLFilled(self, num, v):
|
||||
return self.SendScintilla(scintillacon.SCI_STYLESETEOLFILLED, num, v)
|
||||
|
||||
def SCIStyleSetFont(self, num, name, characterset=0):
|
||||
buff = (name + "\0").encode(default_scintilla_encoding)
|
||||
self.SendScintilla(scintillacon.SCI_STYLESETFONT, num, buff)
|
||||
self.SendScintilla(scintillacon.SCI_STYLESETCHARACTERSET, num, characterset)
|
||||
|
||||
def SCIStyleSetBold(self, num, bBold):
|
||||
self.SendScintilla(scintillacon.SCI_STYLESETBOLD, num, bBold)
|
||||
|
||||
def SCIStyleSetItalic(self, num, bItalic):
|
||||
self.SendScintilla(scintillacon.SCI_STYLESETITALIC, num, bItalic)
|
||||
|
||||
def SCIStyleSetSize(self, num, size):
|
||||
self.SendScintilla(scintillacon.SCI_STYLESETSIZE, num, size)
|
||||
|
||||
def SCIGetViewWS(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETVIEWWS)
|
||||
|
||||
def SCISetViewWS(self, val):
|
||||
self.SendScintilla(scintillacon.SCI_SETVIEWWS, not (val == 0))
|
||||
self.InvalidateRect()
|
||||
|
||||
def SCISetIndentationGuides(self, val):
|
||||
self.SendScintilla(scintillacon.SCI_SETINDENTATIONGUIDES, val)
|
||||
|
||||
def SCIGetIndentationGuides(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETINDENTATIONGUIDES)
|
||||
|
||||
def SCISetIndent(self, val):
|
||||
self.SendScintilla(scintillacon.SCI_SETINDENT, val)
|
||||
|
||||
def SCIGetIndent(self, val):
|
||||
return self.SendScintilla(scintillacon.SCI_GETINDENT)
|
||||
|
||||
def SCIGetViewEOL(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETVIEWEOL)
|
||||
|
||||
def SCISetViewEOL(self, val):
|
||||
self.SendScintilla(scintillacon.SCI_SETVIEWEOL, not (val == 0))
|
||||
self.InvalidateRect()
|
||||
|
||||
def SCISetTabWidth(self, width):
|
||||
self.SendScintilla(scintillacon.SCI_SETTABWIDTH, width, 0)
|
||||
|
||||
def SCIStartStyling(self, pos, mask):
|
||||
self.SendScintilla(scintillacon.SCI_STARTSTYLING, pos, mask)
|
||||
|
||||
def SCISetStyling(self, pos, attr):
|
||||
self.SendScintilla(scintillacon.SCI_SETSTYLING, pos, attr)
|
||||
|
||||
def SCISetStylingEx(self, ray): # ray is an array.
|
||||
address, length = ray.buffer_info()
|
||||
self.SendScintilla(scintillacon.SCI_SETSTYLINGEX, length, address)
|
||||
|
||||
def SCIGetStyleAt(self, pos):
|
||||
return self.SendScintilla(scintillacon.SCI_GETSTYLEAT, pos)
|
||||
|
||||
def SCISetMarginWidth(self, width):
|
||||
self.SendScintilla(scintillacon.SCI_SETMARGINWIDTHN, 1, width)
|
||||
|
||||
def SCISetMarginWidthN(self, n, width):
|
||||
self.SendScintilla(scintillacon.SCI_SETMARGINWIDTHN, n, width)
|
||||
|
||||
def SCISetFoldFlags(self, flags):
|
||||
self.SendScintilla(scintillacon.SCI_SETFOLDFLAGS, flags)
|
||||
|
||||
# Markers
|
||||
def SCIMarkerDefineAll(self, markerNum, markerType, fore, back):
|
||||
self.SCIMarkerDefine(markerNum, markerType)
|
||||
self.SCIMarkerSetFore(markerNum, fore)
|
||||
self.SCIMarkerSetBack(markerNum, back)
|
||||
|
||||
def SCIMarkerDefine(self, markerNum, markerType):
|
||||
self.SendScintilla(scintillacon.SCI_MARKERDEFINE, markerNum, markerType)
|
||||
|
||||
def SCIMarkerSetFore(self, markerNum, fore):
|
||||
self.SendScintilla(scintillacon.SCI_MARKERSETFORE, markerNum, fore)
|
||||
|
||||
def SCIMarkerSetBack(self, markerNum, back):
|
||||
self.SendScintilla(scintillacon.SCI_MARKERSETBACK, markerNum, back)
|
||||
|
||||
def SCIMarkerAdd(self, lineNo, markerNum):
|
||||
self.SendScintilla(scintillacon.SCI_MARKERADD, lineNo, markerNum)
|
||||
|
||||
def SCIMarkerDelete(self, lineNo, markerNum):
|
||||
self.SendScintilla(scintillacon.SCI_MARKERDELETE, lineNo, markerNum)
|
||||
|
||||
def SCIMarkerDeleteAll(self, markerNum=-1):
|
||||
self.SendScintilla(scintillacon.SCI_MARKERDELETEALL, markerNum)
|
||||
|
||||
def SCIMarkerGet(self, lineNo):
|
||||
return self.SendScintilla(scintillacon.SCI_MARKERGET, lineNo)
|
||||
|
||||
def SCIMarkerNext(self, lineNo, markerNum):
|
||||
return self.SendScintilla(scintillacon.SCI_MARKERNEXT, lineNo, markerNum)
|
||||
|
||||
def SCICancel(self):
|
||||
self.SendScintilla(scintillacon.SCI_CANCEL)
|
||||
|
||||
# AutoComplete
|
||||
def SCIAutoCShow(self, text):
|
||||
if isinstance(text, (list, tuple)):
|
||||
text = " ".join(text)
|
||||
buff = (text + "\0").encode(default_scintilla_encoding)
|
||||
return self.SendScintilla(scintillacon.SCI_AUTOCSHOW, 0, buff)
|
||||
|
||||
def SCIAutoCCancel(self):
|
||||
self.SendScintilla(scintillacon.SCI_AUTOCCANCEL)
|
||||
|
||||
def SCIAutoCActive(self):
|
||||
return self.SendScintilla(scintillacon.SCI_AUTOCACTIVE)
|
||||
|
||||
def SCIAutoCComplete(self):
|
||||
return self.SendScintilla(scintillacon.SCI_AUTOCCOMPLETE)
|
||||
|
||||
def SCIAutoCStops(self, stops):
|
||||
buff = (stops + "\0").encode(default_scintilla_encoding)
|
||||
self.SendScintilla(scintillacon.SCI_AUTOCSTOPS, 0, buff)
|
||||
|
||||
def SCIAutoCSetAutoHide(self, hide):
|
||||
self.SendScintilla(scintillacon.SCI_AUTOCSETAUTOHIDE, hide)
|
||||
|
||||
def SCIAutoCSetFillups(self, fillups):
|
||||
self.SendScintilla(scintillacon.SCI_AUTOCSETFILLUPS, fillups)
|
||||
|
||||
# Call tips
|
||||
def SCICallTipShow(self, text, pos=-1):
|
||||
if pos == -1:
|
||||
pos = self.GetSel()[0]
|
||||
buff = (text + "\0").encode(default_scintilla_encoding)
|
||||
self.SendScintilla(scintillacon.SCI_CALLTIPSHOW, pos, buff)
|
||||
|
||||
def SCICallTipCancel(self):
|
||||
self.SendScintilla(scintillacon.SCI_CALLTIPCANCEL)
|
||||
|
||||
def SCICallTipActive(self):
|
||||
return self.SendScintilla(scintillacon.SCI_CALLTIPACTIVE)
|
||||
|
||||
def SCICallTipPosStart(self):
|
||||
return self.SendScintilla(scintillacon.SCI_CALLTIPPOSSTART)
|
||||
|
||||
def SCINewline(self):
|
||||
self.SendScintilla(scintillacon.SCI_NEWLINE)
|
||||
|
||||
# Lexer etc
|
||||
def SCISetKeywords(self, keywords, kw_list_no=0):
|
||||
buff = (keywords + "\0").encode(default_scintilla_encoding)
|
||||
self.SendScintilla(scintillacon.SCI_SETKEYWORDS, kw_list_no, buff)
|
||||
|
||||
def SCISetProperty(self, name, value):
|
||||
name_buff = array.array("b", (name + "\0").encode(default_scintilla_encoding))
|
||||
val_buff = array.array(
|
||||
"b", (str(value) + "\0").encode(default_scintilla_encoding)
|
||||
)
|
||||
address_name_buffer = name_buff.buffer_info()[0]
|
||||
address_val_buffer = val_buff.buffer_info()[0]
|
||||
self.SendScintilla(
|
||||
scintillacon.SCI_SETPROPERTY, address_name_buffer, address_val_buffer
|
||||
)
|
||||
|
||||
def SCISetStyleBits(self, nbits):
|
||||
self.SendScintilla(scintillacon.SCI_SETSTYLEBITS, nbits)
|
||||
|
||||
# Folding
|
||||
def SCIGetFoldLevel(self, lineno):
|
||||
return self.SendScintilla(scintillacon.SCI_GETFOLDLEVEL, lineno)
|
||||
|
||||
def SCIToggleFold(self, lineno):
|
||||
return self.SendScintilla(scintillacon.SCI_TOGGLEFOLD, lineno)
|
||||
|
||||
def SCIEnsureVisible(self, lineno):
|
||||
self.SendScintilla(scintillacon.SCI_ENSUREVISIBLE, lineno)
|
||||
|
||||
def SCIGetFoldExpanded(self, lineno):
|
||||
return self.SendScintilla(scintillacon.SCI_GETFOLDEXPANDED, lineno)
|
||||
|
||||
# right edge
|
||||
def SCISetEdgeColumn(self, edge):
|
||||
self.SendScintilla(scintillacon.SCI_SETEDGECOLUMN, edge)
|
||||
|
||||
def SCIGetEdgeColumn(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETEDGECOLUMN)
|
||||
|
||||
def SCISetEdgeMode(self, mode):
|
||||
self.SendScintilla(scintillacon.SCI_SETEDGEMODE, mode)
|
||||
|
||||
def SCIGetEdgeMode(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETEDGEMODE)
|
||||
|
||||
def SCISetEdgeColor(self, color):
|
||||
self.SendScintilla(scintillacon.SCI_SETEDGECOLOUR, color)
|
||||
|
||||
def SCIGetEdgeColor(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETEDGECOLOR)
|
||||
|
||||
# Multi-doc
|
||||
def SCIGetDocPointer(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETDOCPOINTER)
|
||||
|
||||
def SCISetDocPointer(self, p):
|
||||
return self.SendScintilla(scintillacon.SCI_SETDOCPOINTER, 0, p)
|
||||
|
||||
def SCISetWrapMode(self, mode):
|
||||
return self.SendScintilla(scintillacon.SCI_SETWRAPMODE, mode)
|
||||
|
||||
def SCIGetWrapMode(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETWRAPMODE)
|
||||
|
||||
|
||||
class CScintillaEditInterface(ScintillaControlInterface):
|
||||
def close(self):
|
||||
self.colorizer = None
|
||||
|
||||
def Clear(self):
|
||||
self.SendScintilla(win32con.WM_CLEAR)
|
||||
|
||||
def FindText(self, flags, range, findText):
|
||||
"""LPARAM for EM_FINDTEXTEX:
|
||||
typedef struct _findtextex {
|
||||
CHARRANGE chrg;
|
||||
LPCTSTR lpstrText;
|
||||
CHARRANGE chrgText;} FINDTEXTEX;
|
||||
typedef struct _charrange {
|
||||
LONG cpMin;
|
||||
LONG cpMax;} CHARRANGE;
|
||||
"""
|
||||
findtextex_fmt = "llPll"
|
||||
## Scintilla does not handle unicode in EM_FINDTEXT msg (FINDTEXTEX struct)
|
||||
txt_buff = (findText + "\0").encode(default_scintilla_encoding)
|
||||
txt_array = array.array("b", txt_buff)
|
||||
ft_buff = struct.pack(
|
||||
findtextex_fmt, range[0], range[1], txt_array.buffer_info()[0], 0, 0
|
||||
)
|
||||
ft_array = array.array("b", ft_buff)
|
||||
rc = self.SendScintilla(EM_FINDTEXTEX, flags, ft_array.buffer_info()[0])
|
||||
ftUnpacked = struct.unpack(findtextex_fmt, ft_array)
|
||||
return rc, (ftUnpacked[3], ftUnpacked[4])
|
||||
|
||||
def GetSel(self):
|
||||
currentPos = self.SendScintilla(scintillacon.SCI_GETCURRENTPOS)
|
||||
anchorPos = self.SendScintilla(scintillacon.SCI_GETANCHOR)
|
||||
if currentPos < anchorPos:
|
||||
return (currentPos, anchorPos)
|
||||
else:
|
||||
return (anchorPos, currentPos)
|
||||
return currentPos
|
||||
|
||||
def GetSelText(self):
|
||||
start, end = self.GetSel()
|
||||
txtBuf = array.array("b", null_byte * (end - start + 1))
|
||||
addressTxtBuf = txtBuf.buffer_info()[0]
|
||||
# EM_GETSELTEXT is documented as returning the number of chars
|
||||
# not including the NULL, but scintilla includes the NULL. A
|
||||
# quick glance at the scintilla impl doesn't make this
|
||||
# obvious - the NULL is included in the 'selection' object
|
||||
# and reflected in the length of that 'selection' object.
|
||||
# I expect that is a bug in scintilla and may be fixed by now,
|
||||
# but we just blindly assume that the last char is \0 and
|
||||
# strip it.
|
||||
self.SendScintilla(EM_GETSELTEXT, 0, addressTxtBuf)
|
||||
return txtBuf.tobytes()[:-1].decode(default_scintilla_encoding)
|
||||
|
||||
def SetSel(self, start=0, end=None):
|
||||
if isinstance(start, tuple):
|
||||
assert (
|
||||
end is None
|
||||
), "If you pass a point in the first param, the second must be None"
|
||||
start, end = start
|
||||
elif end is None:
|
||||
end = start
|
||||
if start < 0:
|
||||
start = self.GetTextLength()
|
||||
if end < 0:
|
||||
end = self.GetTextLength()
|
||||
assert start <= self.GetTextLength(), "The start postion is invalid (%d/%d)" % (
|
||||
start,
|
||||
self.GetTextLength(),
|
||||
)
|
||||
assert end <= self.GetTextLength(), "The end postion is invalid (%d/%d)" % (
|
||||
end,
|
||||
self.GetTextLength(),
|
||||
)
|
||||
cr = struct.pack("ll", start, end)
|
||||
crBuff = array.array("b", cr)
|
||||
addressCrBuff = crBuff.buffer_info()[0]
|
||||
rc = self.SendScintilla(EM_EXSETSEL, 0, addressCrBuff)
|
||||
|
||||
def GetLineCount(self):
|
||||
return self.SendScintilla(win32con.EM_GETLINECOUNT)
|
||||
|
||||
def LineFromChar(self, charPos=-1):
|
||||
if charPos == -1:
|
||||
charPos = self.GetSel()[0]
|
||||
assert (
|
||||
charPos >= 0 and charPos <= self.GetTextLength()
|
||||
), f"The charPos postion ({charPos}) is invalid (max={self.GetTextLength()})"
|
||||
# return self.SendScintilla(EM_EXLINEFROMCHAR, charPos)
|
||||
# EM_EXLINEFROMCHAR puts charPos in lParam, not wParam
|
||||
return self.SendScintilla(EM_EXLINEFROMCHAR, 0, charPos)
|
||||
|
||||
def LineIndex(self, line):
|
||||
return self.SendScintilla(win32con.EM_LINEINDEX, line)
|
||||
|
||||
def ScrollCaret(self):
|
||||
return self.SendScintilla(win32con.EM_SCROLLCARET)
|
||||
|
||||
def GetCurLineNumber(self):
|
||||
return self.LineFromChar(self.SCIGetCurrentPos())
|
||||
|
||||
def GetTextLength(self):
|
||||
return self.SendScintilla(scintillacon.SCI_GETTEXTLENGTH)
|
||||
|
||||
def GetTextRange(self, start=0, end=-1, decode=True):
|
||||
if end == -1:
|
||||
end = self.SendScintilla(scintillacon.SCI_GETTEXTLENGTH)
|
||||
assert end >= start, "Negative index requested (%d/%d)" % (start, end)
|
||||
assert (
|
||||
start >= 0 and start <= self.GetTextLength()
|
||||
), "The start postion is invalid"
|
||||
assert end >= 0 and end <= self.GetTextLength(), "The end postion is invalid"
|
||||
initer = null_byte * (end - start + 1)
|
||||
buff = array.array("b", initer)
|
||||
addressBuffer = buff.buffer_info()[0]
|
||||
tr = struct.pack("llP", start, end, addressBuffer)
|
||||
trBuff = array.array("b", tr)
|
||||
addressTrBuff = trBuff.buffer_info()[0]
|
||||
num_bytes = self.SendScintilla(EM_GETTEXTRANGE, 0, addressTrBuff)
|
||||
ret = buff.tobytes()[:num_bytes]
|
||||
if decode:
|
||||
ret = ret.decode(default_scintilla_encoding)
|
||||
return ret
|
||||
|
||||
def ReplaceSel(self, str):
|
||||
buff = (str + "\0").encode(default_scintilla_encoding)
|
||||
self.SendScintilla(scintillacon.SCI_REPLACESEL, 0, buff)
|
||||
|
||||
def GetLine(self, line=-1):
|
||||
if line == -1:
|
||||
line = self.GetCurLineNumber()
|
||||
start = self.LineIndex(line)
|
||||
end = self.LineIndex(line + 1)
|
||||
return self.GetTextRange(start, end)
|
||||
|
||||
def SetReadOnly(self, flag=1):
|
||||
return self.SendScintilla(win32con.EM_SETREADONLY, flag)
|
||||
|
||||
def LineScroll(self, lines, cols=0):
|
||||
return self.SendScintilla(win32con.EM_LINESCROLL, cols, lines)
|
||||
|
||||
def GetFirstVisibleLine(self):
|
||||
return self.SendScintilla(win32con.EM_GETFIRSTVISIBLELINE)
|
||||
|
||||
def SetWordWrap(self, mode):
|
||||
if mode != win32ui.CRichEditView_WrapNone:
|
||||
raise ValueError("We don't support word-wrap (I don't think :-)")
|
||||
|
||||
|
||||
class CScintillaColorEditInterface(CScintillaEditInterface):
|
||||
################################
|
||||
# Plug-in colorizer support
|
||||
def _GetColorizer(self):
|
||||
if not hasattr(self, "colorizer"):
|
||||
self.colorizer = self._MakeColorizer()
|
||||
return self.colorizer
|
||||
|
||||
def _MakeColorizer(self):
|
||||
# Give parent a chance to hook.
|
||||
parent_func = getattr(self.GetParentFrame(), "_MakeColorizer", None)
|
||||
if parent_func is not None:
|
||||
return parent_func()
|
||||
from . import formatter
|
||||
|
||||
## return formatter.PythonSourceFormatter(self)
|
||||
return formatter.BuiltinPythonSourceFormatter(self)
|
||||
|
||||
def Colorize(self, start=0, end=-1):
|
||||
c = self._GetColorizer()
|
||||
if c is not None:
|
||||
c.Colorize(start, end)
|
||||
|
||||
def ApplyFormattingStyles(self, bReload=1):
|
||||
c = self._GetColorizer()
|
||||
if c is not None:
|
||||
c.ApplyFormattingStyles(bReload)
|
||||
|
||||
# The Parent window will normally hook
|
||||
def HookFormatter(self, parent=None):
|
||||
c = self._GetColorizer()
|
||||
if c is not None: # No need if we have no color!
|
||||
c.HookFormatter(parent)
|
||||
|
||||
|
||||
class CScintillaEdit(window.Wnd, CScintillaColorEditInterface):
|
||||
def __init__(self, wnd=None):
|
||||
if wnd is None:
|
||||
wnd = win32ui.CreateWnd()
|
||||
window.Wnd.__init__(self, wnd)
|
||||
|
||||
def SendScintilla(self, msg, w=0, l=0):
|
||||
return self.SendMessage(msg, w, l)
|
||||
|
||||
def CreateWindow(self, style, rect, parent, id):
|
||||
self._obj_.CreateWindow("Scintilla", "Scintilla", style, rect, parent, id, None)
|
||||
@@ -0,0 +1,312 @@
|
||||
import codecs
|
||||
import re
|
||||
import string
|
||||
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin import default_scintilla_encoding
|
||||
from pywin.mfc import docview
|
||||
|
||||
from . import scintillacon
|
||||
|
||||
crlf_bytes = b"\r\n"
|
||||
lf_bytes = b"\n"
|
||||
|
||||
# re from pep263 - but we use it both on bytes and strings.
|
||||
re_encoding_bytes = re.compile(rb"coding[:=]\s*([-\w.]+)")
|
||||
re_encoding_text = re.compile(r"coding[:=]\s*([-\w.]+)")
|
||||
|
||||
ParentScintillaDocument = docview.Document
|
||||
|
||||
|
||||
class CScintillaDocument(ParentScintillaDocument):
|
||||
"A SyntEdit document."
|
||||
|
||||
def __init__(self, *args):
|
||||
self.bom = None # the BOM, if any, read from the file.
|
||||
# the encoding we detected from the source. Might have
|
||||
# detected via the BOM or an encoding decl. Note that in
|
||||
# the latter case (ie, while self.bom is None), it can't be
|
||||
# trusted - the user may have edited the encoding decl between
|
||||
# open and save.
|
||||
self.source_encoding = None
|
||||
ParentScintillaDocument.__init__(self, *args)
|
||||
|
||||
def DeleteContents(self):
|
||||
pass
|
||||
|
||||
def OnOpenDocument(self, filename):
|
||||
# init data members
|
||||
# print("Opening", filename)
|
||||
self.SetPathName(filename) # Must set this early!
|
||||
try:
|
||||
# load the text as binary we can get smart
|
||||
# about detecting any existing EOL conventions.
|
||||
f = open(filename, "rb")
|
||||
try:
|
||||
self._LoadTextFromFile(f)
|
||||
finally:
|
||||
f.close()
|
||||
except OSError:
|
||||
rc = win32ui.MessageBox(
|
||||
"Could not load the file from %s\n\nDo you want to create a new file?"
|
||||
% filename,
|
||||
"Pythonwin",
|
||||
win32con.MB_YESNO | win32con.MB_ICONWARNING,
|
||||
)
|
||||
if rc == win32con.IDNO:
|
||||
return 0
|
||||
assert rc == win32con.IDYES, rc
|
||||
try:
|
||||
f = open(filename, "wb+")
|
||||
try:
|
||||
self._LoadTextFromFile(f)
|
||||
finally:
|
||||
f.close()
|
||||
except OSError as e:
|
||||
rc = win32ui.MessageBox("Cannot create the file %s" % filename)
|
||||
return 1
|
||||
|
||||
def SaveFile(self, fileName, encoding=None):
|
||||
view = self.GetFirstView()
|
||||
ok = view.SaveTextFile(fileName, encoding=encoding)
|
||||
if ok:
|
||||
view.SCISetSavePoint()
|
||||
return ok
|
||||
|
||||
def ApplyFormattingStyles(self):
|
||||
self._ApplyOptionalToViews("ApplyFormattingStyles")
|
||||
|
||||
# #####################
|
||||
# File related functions
|
||||
# Helper to transfer text from the MFC document to the control.
|
||||
def _LoadTextFromFile(self, f):
|
||||
# detect EOL mode - we don't support \r only - so find the
|
||||
# first '\n' and guess based on the char before.
|
||||
l = f.readline()
|
||||
l2 = f.readline()
|
||||
# If line ends with \r\n or has no line ending, use CRLF.
|
||||
if l.endswith(crlf_bytes) or not l.endswith(lf_bytes):
|
||||
eol_mode = scintillacon.SC_EOL_CRLF
|
||||
else:
|
||||
eol_mode = scintillacon.SC_EOL_LF
|
||||
|
||||
# Detect the encoding - first look for a BOM, and if not found,
|
||||
# look for a pep263 encoding declaration.
|
||||
for bom, encoding in (
|
||||
(codecs.BOM_UTF8, "utf8"),
|
||||
(codecs.BOM_UTF16_LE, "utf_16_le"),
|
||||
(codecs.BOM_UTF16_BE, "utf_16_be"),
|
||||
):
|
||||
if l.startswith(bom):
|
||||
self.bom = bom
|
||||
self.source_encoding = encoding
|
||||
l = l[len(bom) :] # remove it.
|
||||
break
|
||||
else:
|
||||
# no bom detected - look for pep263 encoding decl.
|
||||
for look in (l, l2):
|
||||
# Note we are looking at raw bytes here: so
|
||||
# both the re itself uses bytes and the result
|
||||
# is bytes - but we need the result as a string.
|
||||
match = re_encoding_bytes.search(look)
|
||||
if match is not None:
|
||||
self.source_encoding = match.group(1).decode("ascii")
|
||||
break
|
||||
|
||||
# reading by lines would be too slow? Maybe we can use the
|
||||
# incremental encoders? For now just stick with loading the
|
||||
# entire file in memory.
|
||||
text = l + l2 + f.read()
|
||||
|
||||
# Translate from source encoding to UTF-8 bytes for Scintilla
|
||||
source_encoding = self.source_encoding
|
||||
# If we don't know an encoding, try utf-8 - if that fails we will
|
||||
# fallback to latin-1 to treat it as bytes...
|
||||
if source_encoding is None:
|
||||
source_encoding = "utf-8"
|
||||
# we could optimize this by avoiding utf8 to-ing and from-ing,
|
||||
# but then we would lose the ability to handle invalid utf8
|
||||
# (and even then, the use of encoding aliases makes this tricky)
|
||||
# To create an invalid utf8 file:
|
||||
# >>> open(filename, "wb").write(codecs.BOM_UTF8+"bad \xa9har\r\n")
|
||||
try:
|
||||
dec = text.decode(source_encoding)
|
||||
except UnicodeError:
|
||||
print(
|
||||
"WARNING: Failed to decode bytes from '%s' encoding - treating as latin1"
|
||||
% source_encoding
|
||||
)
|
||||
dec = text.decode("latin1")
|
||||
except LookupError:
|
||||
print(
|
||||
"WARNING: Invalid encoding '%s' specified - treating as latin1"
|
||||
% source_encoding
|
||||
)
|
||||
dec = text.decode("latin1")
|
||||
# and put it back as utf8 - this shouldn't fail.
|
||||
text = dec.encode(default_scintilla_encoding)
|
||||
|
||||
view = self.GetFirstView()
|
||||
if view.IsWindow():
|
||||
# Turn off undo collection while loading
|
||||
view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 0, 0)
|
||||
# Make sure the control isn't read-only
|
||||
view.SetReadOnly(0)
|
||||
view.SendScintilla(scintillacon.SCI_CLEARALL)
|
||||
view.SendMessage(scintillacon.SCI_ADDTEXT, text)
|
||||
view.SendScintilla(scintillacon.SCI_SETUNDOCOLLECTION, 1, 0)
|
||||
view.SendScintilla(win32con.EM_EMPTYUNDOBUFFER, 0, 0)
|
||||
# set EOL mode
|
||||
view.SendScintilla(scintillacon.SCI_SETEOLMODE, eol_mode)
|
||||
|
||||
def _SaveTextToFile(self, view, filename, encoding=None):
|
||||
s = view.GetTextRange() # already decoded from scintilla's encoding
|
||||
source_encoding = encoding
|
||||
if source_encoding is None:
|
||||
if self.bom:
|
||||
source_encoding = self.source_encoding
|
||||
else:
|
||||
# no BOM - look for an encoding.
|
||||
bits = re.split(r"[\r\n]+", s, 3)
|
||||
for look in bits[:-1]:
|
||||
match = re_encoding_text.search(look)
|
||||
if match is not None:
|
||||
source_encoding = match.group(1)
|
||||
self.source_encoding = source_encoding
|
||||
break
|
||||
|
||||
if source_encoding is None:
|
||||
source_encoding = "utf-8"
|
||||
|
||||
## encode data before opening file so script is not lost if encoding fails
|
||||
file_contents = s.encode(source_encoding)
|
||||
# Open in binary mode as scintilla itself ensures the
|
||||
# line endings are already appropriate
|
||||
f = open(filename, "wb")
|
||||
try:
|
||||
if self.bom:
|
||||
f.write(self.bom)
|
||||
f.write(file_contents)
|
||||
finally:
|
||||
f.close()
|
||||
self.SetModifiedFlag(0)
|
||||
|
||||
def FinalizeViewCreation(self, view):
|
||||
pass
|
||||
|
||||
def HookViewNotifications(self, view):
|
||||
parent = view.GetParentFrame()
|
||||
parent.HookNotify(
|
||||
ViewNotifyDelegate(self, "OnBraceMatch"), scintillacon.SCN_UPDATEUI
|
||||
)
|
||||
parent.HookNotify(
|
||||
ViewNotifyDelegate(self, "OnMarginClick"), scintillacon.SCN_MARGINCLICK
|
||||
)
|
||||
parent.HookNotify(
|
||||
ViewNotifyDelegate(self, "OnNeedShown"), scintillacon.SCN_NEEDSHOWN
|
||||
)
|
||||
|
||||
parent.HookNotify(
|
||||
DocumentNotifyDelegate(self, "OnSavePointReached"),
|
||||
scintillacon.SCN_SAVEPOINTREACHED,
|
||||
)
|
||||
parent.HookNotify(
|
||||
DocumentNotifyDelegate(self, "OnSavePointLeft"),
|
||||
scintillacon.SCN_SAVEPOINTLEFT,
|
||||
)
|
||||
parent.HookNotify(
|
||||
DocumentNotifyDelegate(self, "OnModifyAttemptRO"),
|
||||
scintillacon.SCN_MODIFYATTEMPTRO,
|
||||
)
|
||||
# Tell scintilla what characters should abort auto-complete.
|
||||
view.SCIAutoCStops(string.whitespace + "()[]:;+-/*=\\?'!#@$%^&,<>\"'|")
|
||||
|
||||
if view != self.GetFirstView():
|
||||
view.SCISetDocPointer(self.GetFirstView().SCIGetDocPointer())
|
||||
|
||||
def OnSavePointReached(self, std, extra):
|
||||
self.SetModifiedFlag(0)
|
||||
|
||||
def OnSavePointLeft(self, std, extra):
|
||||
self.SetModifiedFlag(1)
|
||||
|
||||
def OnModifyAttemptRO(self, std, extra):
|
||||
self.MakeDocumentWritable()
|
||||
|
||||
# All Marker functions are 1 based.
|
||||
def MarkerAdd(self, lineNo, marker):
|
||||
self.GetEditorView().SCIMarkerAdd(lineNo - 1, marker)
|
||||
|
||||
def MarkerCheck(self, lineNo, marker):
|
||||
v = self.GetEditorView()
|
||||
lineNo -= 1 # Make 0 based
|
||||
markerState = v.SCIMarkerGet(lineNo)
|
||||
return markerState & (1 << marker) != 0
|
||||
|
||||
def MarkerToggle(self, lineNo, marker):
|
||||
v = self.GetEditorView()
|
||||
if self.MarkerCheck(lineNo, marker):
|
||||
v.SCIMarkerDelete(lineNo - 1, marker)
|
||||
else:
|
||||
v.SCIMarkerAdd(lineNo - 1, marker)
|
||||
|
||||
def MarkerDelete(self, lineNo, marker):
|
||||
self.GetEditorView().SCIMarkerDelete(lineNo - 1, marker)
|
||||
|
||||
def MarkerDeleteAll(self, marker):
|
||||
self.GetEditorView().SCIMarkerDeleteAll(marker)
|
||||
|
||||
def MarkerGetNext(self, lineNo, marker):
|
||||
return self.GetEditorView().SCIMarkerNext(lineNo - 1, 1 << marker) + 1
|
||||
|
||||
def MarkerAtLine(self, lineNo, marker):
|
||||
markerState = self.GetEditorView().SCIMarkerGet(lineNo - 1)
|
||||
return markerState & (1 << marker)
|
||||
|
||||
# Helper for reflecting functions to views.
|
||||
def _ApplyToViews(self, funcName, *args):
|
||||
for view in self.GetAllViews():
|
||||
func = getattr(view, funcName)
|
||||
func(*args)
|
||||
|
||||
def _ApplyOptionalToViews(self, funcName, *args):
|
||||
for view in self.GetAllViews():
|
||||
func = getattr(view, funcName, None)
|
||||
if func is not None:
|
||||
func(*args)
|
||||
|
||||
def GetEditorView(self):
|
||||
# Find the first frame with a view,
|
||||
# then ask it to give the editor view
|
||||
# as it knows which one is "active"
|
||||
try:
|
||||
frame_gev = self.GetFirstView().GetParentFrame().GetEditorView
|
||||
except AttributeError:
|
||||
return self.GetFirstView()
|
||||
return frame_gev()
|
||||
|
||||
|
||||
# Delegate to the correct view, based on the control that sent it.
|
||||
class ViewNotifyDelegate:
|
||||
def __init__(self, doc, name):
|
||||
self.doc = doc
|
||||
self.name = name
|
||||
|
||||
def __call__(self, std, extra):
|
||||
(hwndFrom, idFrom, code) = std
|
||||
for v in self.doc.GetAllViews():
|
||||
if v.GetSafeHwnd() == hwndFrom:
|
||||
return getattr(v, self.name)(*(std, extra))
|
||||
|
||||
|
||||
# Delegate to the document, but only from a single view (as each view sends it seperately)
|
||||
class DocumentNotifyDelegate:
|
||||
def __init__(self, doc, name):
|
||||
self.doc = doc
|
||||
self.delegate = getattr(doc, name)
|
||||
|
||||
def __call__(self, std, extra):
|
||||
(hwndFrom, idFrom, code) = std
|
||||
if hwndFrom == self.doc.GetEditorView().GetSafeHwnd():
|
||||
self.delegate(*(std, extra))
|
||||
@@ -0,0 +1,511 @@
|
||||
# find.py - Find and Replace
|
||||
from __future__ import annotations
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
from pywin.framework import scriptutils
|
||||
from pywin.mfc import afxres, dialog
|
||||
|
||||
FOUND_NOTHING = 0
|
||||
FOUND_NORMAL = 1
|
||||
FOUND_LOOPED_BACK = 2
|
||||
FOUND_NEXT_FILE = 3
|
||||
|
||||
|
||||
class SearchParams:
|
||||
def __init__(self, other=None):
|
||||
if other is None:
|
||||
self.__dict__["findText"] = ""
|
||||
self.__dict__["replaceText"] = ""
|
||||
self.__dict__["matchCase"] = 0
|
||||
self.__dict__["matchWords"] = 0
|
||||
self.__dict__["acrossFiles"] = 0
|
||||
self.__dict__["remember"] = 1
|
||||
self.__dict__["sel"] = (-1, -1)
|
||||
self.__dict__["keepDialogOpen"] = 0
|
||||
else:
|
||||
self.__dict__.update(other.__dict__)
|
||||
|
||||
# Helper so we can't misspell attributes :-)
|
||||
def __setattr__(self, attr, val):
|
||||
if not hasattr(self, attr):
|
||||
raise AttributeError(attr)
|
||||
self.__dict__[attr] = val
|
||||
|
||||
|
||||
curDialog = None
|
||||
lastSearch = defaultSearch = SearchParams()
|
||||
searchHistory: list[str] = []
|
||||
|
||||
|
||||
def ShowFindDialog():
|
||||
_ShowDialog(FindDialog)
|
||||
|
||||
|
||||
def ShowReplaceDialog():
|
||||
_ShowDialog(ReplaceDialog)
|
||||
|
||||
|
||||
def _ShowDialog(dlgClass):
|
||||
global curDialog
|
||||
if curDialog is not None:
|
||||
if curDialog.__class__ != dlgClass:
|
||||
curDialog.DestroyWindow()
|
||||
curDialog = None
|
||||
else:
|
||||
curDialog.SetFocus()
|
||||
if curDialog is None:
|
||||
curDialog = dlgClass()
|
||||
curDialog.CreateWindow()
|
||||
|
||||
|
||||
def FindNext():
|
||||
params = SearchParams(lastSearch)
|
||||
params.sel = (-1, -1)
|
||||
if not params.findText:
|
||||
ShowFindDialog()
|
||||
else:
|
||||
return _FindIt(None, params)
|
||||
|
||||
|
||||
def _GetControl(control=None):
|
||||
if control is None:
|
||||
control = scriptutils.GetActiveEditControl()
|
||||
return control
|
||||
|
||||
|
||||
def _FindIt(control, searchParams):
|
||||
global lastSearch, defaultSearch
|
||||
control = _GetControl(control)
|
||||
if control is None:
|
||||
return FOUND_NOTHING
|
||||
|
||||
# Move to the next char, so we find the next one.
|
||||
flags = 0
|
||||
if searchParams.matchWords:
|
||||
flags |= win32con.FR_WHOLEWORD
|
||||
if searchParams.matchCase:
|
||||
flags |= win32con.FR_MATCHCASE
|
||||
if searchParams.sel == (-1, -1):
|
||||
sel = control.GetSel()
|
||||
# If the position is the same as we found last time,
|
||||
# then we assume it is a "FindNext"
|
||||
if sel == lastSearch.sel:
|
||||
sel = sel[0] + 1, sel[0] + 1
|
||||
else:
|
||||
sel = searchParams.sel
|
||||
|
||||
if sel[0] == sel[1]:
|
||||
sel = sel[0], control.GetTextLength()
|
||||
|
||||
rc = FOUND_NOTHING
|
||||
# (Old edit control will fail here!)
|
||||
posFind, foundSel = control.FindText(flags, sel, searchParams.findText)
|
||||
lastSearch = SearchParams(searchParams)
|
||||
if posFind >= 0:
|
||||
rc = FOUND_NORMAL
|
||||
lineno = control.LineFromChar(posFind)
|
||||
control.SCIEnsureVisible(lineno)
|
||||
control.SetSel(foundSel)
|
||||
control.SetFocus()
|
||||
win32ui.SetStatusText(win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE))
|
||||
if rc == FOUND_NOTHING and lastSearch.acrossFiles:
|
||||
# Loop around all documents. First find this document.
|
||||
try:
|
||||
try:
|
||||
doc = control.GetDocument()
|
||||
except AttributeError:
|
||||
try:
|
||||
doc = control.GetParent().GetDocument()
|
||||
except AttributeError:
|
||||
print("Can't find a document for the control!")
|
||||
doc = None
|
||||
if doc is not None:
|
||||
template = doc.GetDocTemplate()
|
||||
alldocs = template.GetDocumentList()
|
||||
mypos = lookpos = alldocs.index(doc)
|
||||
while 1:
|
||||
lookpos = (lookpos + 1) % len(alldocs)
|
||||
if lookpos == mypos:
|
||||
break
|
||||
view = alldocs[lookpos].GetFirstView()
|
||||
posFind, foundSel = view.FindText(
|
||||
flags, (0, view.GetTextLength()), searchParams.findText
|
||||
)
|
||||
if posFind >= 0:
|
||||
nChars = foundSel[1] - foundSel[0]
|
||||
lineNo = view.LineFromChar(posFind) # zero based.
|
||||
lineStart = view.LineIndex(lineNo)
|
||||
colNo = posFind - lineStart # zero based.
|
||||
scriptutils.JumpToDocument(
|
||||
alldocs[lookpos].GetPathName(),
|
||||
lineNo + 1,
|
||||
colNo + 1,
|
||||
nChars,
|
||||
)
|
||||
rc = FOUND_NEXT_FILE
|
||||
break
|
||||
except win32ui.error:
|
||||
pass
|
||||
if rc == FOUND_NOTHING:
|
||||
# Loop around this control - attempt to find from the start of the control.
|
||||
posFind, foundSel = control.FindText(
|
||||
flags, (0, sel[0] - 1), searchParams.findText
|
||||
)
|
||||
if posFind >= 0:
|
||||
control.SCIEnsureVisible(control.LineFromChar(foundSel[0]))
|
||||
control.SetSel(foundSel)
|
||||
control.SetFocus()
|
||||
win32ui.SetStatusText("Not found! Searching from the top of the file.")
|
||||
rc = FOUND_LOOPED_BACK
|
||||
else:
|
||||
lastSearch.sel = -1, -1
|
||||
win32ui.SetStatusText("Can not find '%s'" % searchParams.findText)
|
||||
|
||||
if rc != FOUND_NOTHING:
|
||||
lastSearch.sel = foundSel
|
||||
|
||||
if lastSearch.remember:
|
||||
defaultSearch = lastSearch
|
||||
|
||||
# track search history
|
||||
try:
|
||||
ix = searchHistory.index(searchParams.findText)
|
||||
except ValueError:
|
||||
if len(searchHistory) > 50:
|
||||
searchHistory[50:] = []
|
||||
else:
|
||||
del searchHistory[ix]
|
||||
searchHistory.insert(0, searchParams.findText)
|
||||
|
||||
return rc
|
||||
|
||||
|
||||
def _ReplaceIt(control):
|
||||
control = _GetControl(control)
|
||||
statusText = "Can not find '%s'." % lastSearch.findText
|
||||
rc = FOUND_NOTHING
|
||||
if control is not None and lastSearch.sel != (-1, -1):
|
||||
control.ReplaceSel(lastSearch.replaceText)
|
||||
rc = FindNext()
|
||||
if rc != FOUND_NOTHING:
|
||||
statusText = win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE)
|
||||
win32ui.SetStatusText(statusText)
|
||||
return rc
|
||||
|
||||
|
||||
class FindReplaceDialog(dialog.Dialog):
|
||||
def __init__(self):
|
||||
dialog.Dialog.__init__(self, self._GetDialogTemplate())
|
||||
self.HookCommand(self.OnFindNext, 109)
|
||||
|
||||
def OnInitDialog(self):
|
||||
self.editFindText = self.GetDlgItem(102)
|
||||
self.butMatchWords = self.GetDlgItem(105)
|
||||
self.butMatchCase = self.GetDlgItem(107)
|
||||
self.butKeepDialogOpen = self.GetDlgItem(115)
|
||||
self.butAcrossFiles = self.GetDlgItem(116)
|
||||
self.butRemember = self.GetDlgItem(117)
|
||||
|
||||
self.editFindText.SetWindowText(defaultSearch.findText)
|
||||
control = _GetControl()
|
||||
# _GetControl only gets normal MDI windows; if the interactive
|
||||
# window is docked and no document open, we get None.
|
||||
if control:
|
||||
# If we have a selection, default to that.
|
||||
sel = control.GetSelText()
|
||||
if len(sel) != 0:
|
||||
self.editFindText.SetWindowText(sel)
|
||||
if defaultSearch.remember:
|
||||
defaultSearch.findText = sel
|
||||
for hist in searchHistory:
|
||||
self.editFindText.AddString(hist)
|
||||
|
||||
if hasattr(self.editFindText, "SetEditSel"):
|
||||
self.editFindText.SetEditSel(0, -1)
|
||||
else:
|
||||
self.editFindText.SetSel(0, -1)
|
||||
self.butMatchWords.SetCheck(defaultSearch.matchWords)
|
||||
self.butMatchCase.SetCheck(defaultSearch.matchCase)
|
||||
self.butKeepDialogOpen.SetCheck(defaultSearch.keepDialogOpen)
|
||||
self.butAcrossFiles.SetCheck(defaultSearch.acrossFiles)
|
||||
self.butRemember.SetCheck(defaultSearch.remember)
|
||||
return dialog.Dialog.OnInitDialog(self)
|
||||
|
||||
def OnDestroy(self, msg):
|
||||
global curDialog
|
||||
curDialog = None
|
||||
return dialog.Dialog.OnDestroy(self, msg)
|
||||
|
||||
def DoFindNext(self):
|
||||
params = SearchParams()
|
||||
params.findText = self.editFindText.GetWindowText()
|
||||
params.matchCase = self.butMatchCase.GetCheck()
|
||||
params.matchWords = self.butMatchWords.GetCheck()
|
||||
params.acrossFiles = self.butAcrossFiles.GetCheck()
|
||||
params.remember = self.butRemember.GetCheck()
|
||||
return _FindIt(None, params)
|
||||
|
||||
def OnFindNext(self, id, code):
|
||||
if code != 0: # BN_CLICKED
|
||||
# 3d controls (python.exe + start_pythonwin.pyw) send
|
||||
# other notification codes
|
||||
return 1 #
|
||||
if not self.editFindText.GetWindowText():
|
||||
win32api.MessageBeep()
|
||||
return 1
|
||||
if self.DoFindNext() != FOUND_NOTHING:
|
||||
if not self.butKeepDialogOpen.GetCheck():
|
||||
self.DestroyWindow()
|
||||
|
||||
|
||||
class FindDialog(FindReplaceDialog):
|
||||
def _GetDialogTemplate(self):
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
visible = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
dt = [
|
||||
["Find", (0, 2, 240, 75), style, None, (8, "MS Sans Serif")],
|
||||
["Static", "Fi&nd What:", 101, (5, 8, 40, 10), visible],
|
||||
[
|
||||
"ComboBox",
|
||||
"",
|
||||
102,
|
||||
(50, 7, 120, 120),
|
||||
visible
|
||||
| win32con.WS_BORDER
|
||||
| win32con.WS_TABSTOP
|
||||
| win32con.WS_VSCROLL
|
||||
| win32con.CBS_DROPDOWN
|
||||
| win32con.CBS_AUTOHSCROLL,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Match &whole word only",
|
||||
105,
|
||||
(5, 23, 100, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Match &case",
|
||||
107,
|
||||
(5, 33, 100, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Keep &dialog open",
|
||||
115,
|
||||
(5, 43, 100, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Across &open files",
|
||||
116,
|
||||
(5, 52, 100, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"&Remember as default search",
|
||||
117,
|
||||
(5, 61, 150, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"&Find Next",
|
||||
109,
|
||||
(185, 5, 50, 14),
|
||||
visible | win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Cancel",
|
||||
win32con.IDCANCEL,
|
||||
(185, 23, 50, 14),
|
||||
visible | win32con.WS_TABSTOP,
|
||||
],
|
||||
]
|
||||
return dt
|
||||
|
||||
|
||||
class ReplaceDialog(FindReplaceDialog):
|
||||
def _GetDialogTemplate(self):
|
||||
style = (
|
||||
win32con.DS_MODALFRAME
|
||||
| win32con.WS_POPUP
|
||||
| win32con.WS_VISIBLE
|
||||
| win32con.WS_CAPTION
|
||||
| win32con.WS_SYSMENU
|
||||
| win32con.DS_SETFONT
|
||||
)
|
||||
visible = win32con.WS_CHILD | win32con.WS_VISIBLE
|
||||
dt = [
|
||||
["Replace", (0, 2, 240, 95), style, 0, (8, "MS Sans Serif")],
|
||||
["Static", "Fi&nd What:", 101, (5, 8, 40, 10), visible],
|
||||
[
|
||||
"ComboBox",
|
||||
"",
|
||||
102,
|
||||
(60, 7, 110, 120),
|
||||
visible
|
||||
| win32con.WS_BORDER
|
||||
| win32con.WS_TABSTOP
|
||||
| win32con.WS_VSCROLL
|
||||
| win32con.CBS_DROPDOWN
|
||||
| win32con.CBS_AUTOHSCROLL,
|
||||
],
|
||||
["Static", "Re&place with:", 103, (5, 25, 50, 10), visible],
|
||||
[
|
||||
"ComboBox",
|
||||
"",
|
||||
104,
|
||||
(60, 24, 110, 120),
|
||||
visible
|
||||
| win32con.WS_BORDER
|
||||
| win32con.WS_TABSTOP
|
||||
| win32con.WS_VSCROLL
|
||||
| win32con.CBS_DROPDOWN
|
||||
| win32con.CBS_AUTOHSCROLL,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Match &whole word only",
|
||||
105,
|
||||
(5, 42, 100, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Match &case",
|
||||
107,
|
||||
(5, 52, 100, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Keep &dialog open",
|
||||
115,
|
||||
(5, 62, 100, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Across &open files",
|
||||
116,
|
||||
(5, 72, 100, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"&Remember as default search",
|
||||
117,
|
||||
(5, 81, 150, 10),
|
||||
visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"&Find Next",
|
||||
109,
|
||||
(185, 5, 50, 14),
|
||||
visible | win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"&Replace",
|
||||
110,
|
||||
(185, 23, 50, 14),
|
||||
visible | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Replace &All",
|
||||
111,
|
||||
(185, 41, 50, 14),
|
||||
visible | win32con.WS_TABSTOP,
|
||||
],
|
||||
[
|
||||
"Button",
|
||||
"Cancel",
|
||||
win32con.IDCANCEL,
|
||||
(185, 59, 50, 14),
|
||||
visible | win32con.WS_TABSTOP,
|
||||
],
|
||||
]
|
||||
return dt
|
||||
|
||||
def OnInitDialog(self):
|
||||
rc = FindReplaceDialog.OnInitDialog(self)
|
||||
self.HookCommand(self.OnReplace, 110)
|
||||
self.HookCommand(self.OnReplaceAll, 111)
|
||||
self.HookMessage(self.OnActivate, win32con.WM_ACTIVATE)
|
||||
self.editReplaceText = self.GetDlgItem(104)
|
||||
self.editReplaceText.SetWindowText(lastSearch.replaceText)
|
||||
if hasattr(self.editReplaceText, "SetEditSel"):
|
||||
self.editReplaceText.SetEditSel(0, -1)
|
||||
else:
|
||||
self.editReplaceText.SetSel(0, -1)
|
||||
self.butReplace = self.GetDlgItem(110)
|
||||
self.butReplaceAll = self.GetDlgItem(111)
|
||||
self.CheckButtonStates()
|
||||
return rc # 0 when focus set
|
||||
|
||||
def CheckButtonStates(self):
|
||||
# We can do a "Replace" or "Replace All" if the current selection
|
||||
# is the same as the search text.
|
||||
ft = self.editFindText.GetWindowText()
|
||||
control = _GetControl()
|
||||
# bCanReplace = len(ft)>0 and control.GetSelText() == ft
|
||||
bCanReplace = control is not None and lastSearch.sel == control.GetSel()
|
||||
self.butReplace.EnableWindow(bCanReplace)
|
||||
|
||||
# self.butReplaceAll.EnableWindow(bCanReplace)
|
||||
|
||||
def OnActivate(self, msg):
|
||||
wparam = msg[2]
|
||||
fActive = win32api.LOWORD(wparam)
|
||||
if fActive != win32con.WA_INACTIVE:
|
||||
self.CheckButtonStates()
|
||||
|
||||
def OnFindNext(self, id, code):
|
||||
if code != 0:
|
||||
return 1
|
||||
self.DoFindNext()
|
||||
self.CheckButtonStates()
|
||||
|
||||
def OnReplace(self, id, code):
|
||||
if code != 0:
|
||||
return 1
|
||||
lastSearch.replaceText = self.editReplaceText.GetWindowText()
|
||||
_ReplaceIt(None)
|
||||
|
||||
def OnReplaceAll(self, id, code):
|
||||
if code != 0:
|
||||
return 1
|
||||
control = _GetControl(None)
|
||||
if control is not None:
|
||||
control.SetSel(0)
|
||||
num = 0
|
||||
if self.DoFindNext() == FOUND_NORMAL:
|
||||
num = 1
|
||||
lastSearch.replaceText = self.editReplaceText.GetWindowText()
|
||||
while _ReplaceIt(control) == FOUND_NORMAL:
|
||||
num += 1
|
||||
|
||||
win32ui.SetStatusText("Replaced %d occurrences" % num)
|
||||
if num > 0 and not self.butKeepDialogOpen.GetCheck():
|
||||
self.DestroyWindow()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ShowFindDialog()
|
||||
@@ -0,0 +1,704 @@
|
||||
# Does Python source formatting for Scintilla controls.
|
||||
import array
|
||||
import string
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32ui
|
||||
|
||||
from . import scintillacon
|
||||
|
||||
WM_KICKIDLE = 0x036A
|
||||
|
||||
# Used to indicate that style should use default color
|
||||
from win32con import CLR_INVALID
|
||||
|
||||
debugging = 0
|
||||
if debugging:
|
||||
# Output must go to another process else the result of
|
||||
# the printing itself will trigger again trigger a trace.
|
||||
|
||||
import win32trace
|
||||
import win32traceutil
|
||||
|
||||
def trace(*args):
|
||||
win32trace.write(" ".join(map(str, args)) + "\n")
|
||||
|
||||
else:
|
||||
trace = lambda *args: None
|
||||
|
||||
|
||||
class Style:
|
||||
"""Represents a single format"""
|
||||
|
||||
def __init__(self, name, format, background=CLR_INVALID):
|
||||
self.name = name # Name the format representes eg, "String", "Class"
|
||||
# Default background for each style is only used when there are no
|
||||
# saved settings (generally on first startup)
|
||||
self.background = self.default_background = background
|
||||
if isinstance(format, str):
|
||||
self.aliased = format
|
||||
self.format = None
|
||||
else:
|
||||
self.format = format
|
||||
self.aliased = None
|
||||
self.stylenum = None # Not yet registered.
|
||||
|
||||
def IsBasedOnDefault(self):
|
||||
return len(self.format) == 5
|
||||
|
||||
# If the currently extended font defintion matches the
|
||||
# default format, restore the format to the "simple" format.
|
||||
def NormalizeAgainstDefault(self, defaultFormat):
|
||||
if self.IsBasedOnDefault():
|
||||
return 0 # No more to do, and not changed.
|
||||
bIsDefault = (
|
||||
self.format[7] == defaultFormat[7] and self.format[2] == defaultFormat[2]
|
||||
)
|
||||
if bIsDefault:
|
||||
self.ForceAgainstDefault()
|
||||
return bIsDefault
|
||||
|
||||
def ForceAgainstDefault(self):
|
||||
self.format = self.format[:5]
|
||||
|
||||
def GetCompleteFormat(self, defaultFormat):
|
||||
# Get the complete style after applying any relevant defaults.
|
||||
if len(self.format) == 5: # It is a default one
|
||||
fmt = self.format + defaultFormat[5:]
|
||||
else:
|
||||
fmt = self.format
|
||||
flags = (
|
||||
win32con.CFM_BOLD
|
||||
| win32con.CFM_CHARSET
|
||||
| win32con.CFM_COLOR
|
||||
| win32con.CFM_FACE
|
||||
| win32con.CFM_ITALIC
|
||||
| win32con.CFM_SIZE
|
||||
)
|
||||
return (flags,) + fmt[1:]
|
||||
|
||||
|
||||
# The Formatter interface
|
||||
# used primarily when the actual formatting is done by Scintilla!
|
||||
class FormatterBase:
|
||||
def __init__(self, scintilla):
|
||||
self.scintilla = scintilla
|
||||
self.baseFormatFixed = (-402653169, 0, 200, 0, 0, 0, 49, "Courier New")
|
||||
self.baseFormatProp = (-402653169, 0, 200, 0, 0, 0, 49, "Arial")
|
||||
self.bUseFixed = 1
|
||||
self.styles = {} # Indexed by name
|
||||
self.styles_by_id = {} # Indexed by allocated ID.
|
||||
self.SetStyles()
|
||||
|
||||
def HookFormatter(self, parent=None):
|
||||
raise NotImplementedError
|
||||
|
||||
# Used by the IDLE extensions to quickly determine if a character is a string.
|
||||
def GetStringStyle(self, pos):
|
||||
try:
|
||||
style = self.styles_by_id[self.scintilla.SCIGetStyleAt(pos)]
|
||||
except KeyError:
|
||||
# A style we don't know about - probably not even a .py file - can't be a string
|
||||
return None
|
||||
if style.name in self.string_style_names:
|
||||
return style
|
||||
return None
|
||||
|
||||
def RegisterStyle(self, style, stylenum):
|
||||
assert stylenum is not None, "We must have a style number"
|
||||
assert style.stylenum is None, "Style has already been registered"
|
||||
assert stylenum not in self.styles, "We are reusing a style number!"
|
||||
style.stylenum = stylenum
|
||||
self.styles[style.name] = style
|
||||
self.styles_by_id[stylenum] = style
|
||||
|
||||
def SetStyles(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def GetSampleText(self):
|
||||
return "Sample Text for the Format Dialog"
|
||||
|
||||
def GetDefaultFormat(self):
|
||||
if self.bUseFixed:
|
||||
return self.baseFormatFixed
|
||||
return self.baseFormatProp
|
||||
|
||||
# Update the control with the new style format.
|
||||
def _ReformatStyle(self, style):
|
||||
## Selection (background only for now)
|
||||
## Passing False for WPARAM to SCI_SETSELBACK is documented as resetting to scintilla default,
|
||||
## but does not work - selection background is not visible at all.
|
||||
## Default value in SPECIAL_STYLES taken from scintilla source.
|
||||
if style.name == STYLE_SELECTION:
|
||||
clr = style.background
|
||||
self.scintilla.SendScintilla(scintillacon.SCI_SETSELBACK, True, clr)
|
||||
|
||||
## Can't change font for selection, but could set color
|
||||
## However, the font color dropbox has no option for default, and thus would
|
||||
## always override syntax coloring
|
||||
## clr = style.format[4]
|
||||
## self.scintilla.SendScintilla(scintillacon.SCI_SETSELFORE, clr != CLR_INVALID, clr)
|
||||
return
|
||||
|
||||
assert style.stylenum is not None, "Unregistered style."
|
||||
# print("Reformat style", style.name, style.stylenum)
|
||||
scintilla = self.scintilla
|
||||
stylenum = style.stylenum
|
||||
# Now we have the style number, indirect for the actual style.
|
||||
if style.aliased is not None:
|
||||
style = self.styles[style.aliased]
|
||||
f = style.format
|
||||
if style.IsBasedOnDefault():
|
||||
baseFormat = self.GetDefaultFormat()
|
||||
else:
|
||||
baseFormat = f
|
||||
scintilla.SCIStyleSetFore(stylenum, f[4])
|
||||
scintilla.SCIStyleSetFont(stylenum, baseFormat[7], baseFormat[5])
|
||||
if f[1] & 1:
|
||||
scintilla.SCIStyleSetBold(stylenum, 1)
|
||||
else:
|
||||
scintilla.SCIStyleSetBold(stylenum, 0)
|
||||
if f[1] & 2:
|
||||
scintilla.SCIStyleSetItalic(stylenum, 1)
|
||||
else:
|
||||
scintilla.SCIStyleSetItalic(stylenum, 0)
|
||||
scintilla.SCIStyleSetSize(stylenum, int(baseFormat[2] / 20))
|
||||
scintilla.SCIStyleSetEOLFilled(stylenum, 1) # Only needed for unclosed strings.
|
||||
|
||||
## Default style background to whitespace background if set,
|
||||
## otherwise use system window color
|
||||
bg = style.background
|
||||
if bg == CLR_INVALID:
|
||||
bg = self.styles[STYLE_DEFAULT].background
|
||||
if bg == CLR_INVALID:
|
||||
bg = win32api.GetSysColor(win32con.COLOR_WINDOW)
|
||||
scintilla.SCIStyleSetBack(stylenum, bg)
|
||||
|
||||
def GetStyleByNum(self, stylenum):
|
||||
return self.styles_by_id[stylenum]
|
||||
|
||||
def ApplyFormattingStyles(self, bReload=1):
|
||||
if bReload:
|
||||
self.LoadPreferences()
|
||||
baseFormat = self.GetDefaultFormat()
|
||||
defaultStyle = Style("default", baseFormat)
|
||||
defaultStyle.stylenum = scintillacon.STYLE_DEFAULT
|
||||
self._ReformatStyle(defaultStyle)
|
||||
for style in self.styles.values():
|
||||
if style.aliased is None:
|
||||
style.NormalizeAgainstDefault(baseFormat)
|
||||
self._ReformatStyle(style)
|
||||
self.scintilla.InvalidateRect()
|
||||
|
||||
# Some functions for loading and saving preferences. By default
|
||||
# an INI file (well, MFC maps this to the registry) is used.
|
||||
def LoadPreferences(self):
|
||||
self.baseFormatFixed = eval(
|
||||
self.LoadPreference("Base Format Fixed", str(self.baseFormatFixed))
|
||||
)
|
||||
self.baseFormatProp = eval(
|
||||
self.LoadPreference("Base Format Proportional", str(self.baseFormatProp))
|
||||
)
|
||||
self.bUseFixed = int(self.LoadPreference("Use Fixed", 1))
|
||||
|
||||
for style in self.styles.values():
|
||||
new = self.LoadPreference(style.name, str(style.format))
|
||||
try:
|
||||
style.format = eval(new)
|
||||
except:
|
||||
print("Error loading style data for", style.name)
|
||||
# Use "vanilla" background hardcoded in PYTHON_STYLES if no settings in registry
|
||||
style.background = int(
|
||||
self.LoadPreference(
|
||||
style.name + " background", style.default_background
|
||||
)
|
||||
)
|
||||
|
||||
def LoadPreference(self, name, default):
|
||||
return win32ui.GetProfileVal("Format", name, default)
|
||||
|
||||
def SavePreferences(self):
|
||||
self.SavePreference("Base Format Fixed", str(self.baseFormatFixed))
|
||||
self.SavePreference("Base Format Proportional", str(self.baseFormatProp))
|
||||
self.SavePreference("Use Fixed", self.bUseFixed)
|
||||
for style in self.styles.values():
|
||||
if style.aliased is None:
|
||||
self.SavePreference(style.name, str(style.format))
|
||||
bg_name = style.name + " background"
|
||||
self.SavePreference(bg_name, style.background)
|
||||
|
||||
def SavePreference(self, name, value):
|
||||
win32ui.WriteProfileVal("Format", name, value)
|
||||
|
||||
|
||||
# An abstract formatter
|
||||
# For all formatters we actually implement here.
|
||||
# (as opposed to those formatters built in to Scintilla)
|
||||
class Formatter(FormatterBase):
|
||||
def __init__(self, scintilla):
|
||||
self.bCompleteWhileIdle = 0
|
||||
self.bHaveIdleHandler = 0 # Don't currently have an idle handle
|
||||
self.nextstylenum = 0
|
||||
FormatterBase.__init__(self, scintilla)
|
||||
|
||||
def HookFormatter(self, parent=None):
|
||||
if parent is None:
|
||||
parent = self.scintilla.GetParent() # was GetParentFrame()!?
|
||||
parent.HookNotify(self.OnStyleNeeded, scintillacon.SCN_STYLENEEDED)
|
||||
|
||||
def OnStyleNeeded(self, std, extra):
|
||||
notify = self.scintilla.SCIUnpackNotifyMessage(extra)
|
||||
endStyledChar = self.scintilla.SendScintilla(scintillacon.SCI_GETENDSTYLED)
|
||||
lineEndStyled = self.scintilla.LineFromChar(endStyledChar)
|
||||
endStyled = self.scintilla.LineIndex(lineEndStyled)
|
||||
# print(
|
||||
# "endPosPaint",
|
||||
# endPosPaint,
|
||||
# "endStyledChar",
|
||||
# endStyledChar,
|
||||
# "lineEndStyled",
|
||||
# lineEndStyled,
|
||||
# "endStyled",
|
||||
# endStyled,
|
||||
# )
|
||||
self.Colorize(endStyled, notify.position)
|
||||
|
||||
def ColorSeg(self, start, end, styleName):
|
||||
end += 1
|
||||
# assert end-start>=0, "Can't have negative styling"
|
||||
stylenum = self.styles[styleName].stylenum
|
||||
while start < end:
|
||||
self.style_buffer[start] = stylenum
|
||||
start += 1
|
||||
# self.scintilla.SCISetStyling(end - start + 1, stylenum)
|
||||
|
||||
def RegisterStyle(self, style, stylenum=None):
|
||||
if stylenum is None:
|
||||
stylenum = self.nextstylenum
|
||||
self.nextstylenum += 1
|
||||
FormatterBase.RegisterStyle(self, style, stylenum)
|
||||
|
||||
def ColorizeString(self, str, styleStart):
|
||||
raise RuntimeError("You must override this method")
|
||||
|
||||
def Colorize(self, start=0, end=-1):
|
||||
scintilla = self.scintilla
|
||||
# scintilla's formatting is all done in terms of utf, so
|
||||
# we work with utf8 bytes instead of unicode. This magically
|
||||
# works as any extended chars found in the utf8 don't change
|
||||
# the semantics.
|
||||
stringVal = scintilla.GetTextRange(start, end, decode=False)
|
||||
if start > 0:
|
||||
stylenum = scintilla.SCIGetStyleAt(start - 1)
|
||||
styleStart = self.GetStyleByNum(stylenum).name
|
||||
else:
|
||||
styleStart = None
|
||||
# trace("Coloring", start, end, end-start, len(stringVal), styleStart, self.scintilla.SCIGetCharAt(start))
|
||||
scintilla.SCIStartStyling(start, 31)
|
||||
self.style_buffer = array.array("b", (0,) * len(stringVal))
|
||||
self.ColorizeString(stringVal, styleStart)
|
||||
scintilla.SCISetStylingEx(self.style_buffer)
|
||||
self.style_buffer = None
|
||||
# trace("After styling, end styled is", self.scintilla.SCIGetEndStyled())
|
||||
if (
|
||||
self.bCompleteWhileIdle
|
||||
and not self.bHaveIdleHandler
|
||||
and end != -1
|
||||
and end < scintilla.GetTextLength()
|
||||
):
|
||||
self.bHaveIdleHandler = 1
|
||||
win32ui.GetApp().AddIdleHandler(self.DoMoreColoring)
|
||||
# Kicking idle makes the app seem slower when initially repainting!
|
||||
|
||||
# win32ui.GetMainFrame().PostMessage(WM_KICKIDLE, 0, 0)
|
||||
|
||||
def DoMoreColoring(self, handler, count):
|
||||
try:
|
||||
scintilla = self.scintilla
|
||||
endStyled = scintilla.SCIGetEndStyled()
|
||||
lineStartStyled = scintilla.LineFromChar(endStyled)
|
||||
start = scintilla.LineIndex(lineStartStyled)
|
||||
end = scintilla.LineIndex(lineStartStyled + 1)
|
||||
textlen = scintilla.GetTextLength()
|
||||
if end < 0:
|
||||
end = textlen
|
||||
|
||||
finished = end >= textlen
|
||||
self.Colorize(start, end)
|
||||
except (win32ui.error, AttributeError):
|
||||
# Window may have closed before we finished - no big deal!
|
||||
finished = 1
|
||||
|
||||
if finished:
|
||||
self.bHaveIdleHandler = 0
|
||||
win32ui.GetApp().DeleteIdleHandler(handler)
|
||||
return not finished
|
||||
|
||||
|
||||
# A Formatter that knows how to format Python source
|
||||
from keyword import iskeyword, kwlist
|
||||
|
||||
wordstarts = "_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
wordchars = "._0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
operators = "%^&*()-+=|{}[]:;<>,/?!.~"
|
||||
|
||||
STYLE_DEFAULT = "Whitespace"
|
||||
STYLE_COMMENT = "Comment"
|
||||
STYLE_COMMENT_BLOCK = "Comment Blocks"
|
||||
STYLE_NUMBER = "Number"
|
||||
STYLE_STRING = "String"
|
||||
STYLE_SQSTRING = "SQ String"
|
||||
STYLE_TQSSTRING = "TQS String"
|
||||
STYLE_TQDSTRING = "TQD String"
|
||||
STYLE_KEYWORD = "Keyword"
|
||||
STYLE_CLASS = "Class"
|
||||
STYLE_METHOD = "Method"
|
||||
STYLE_OPERATOR = "Operator"
|
||||
STYLE_IDENTIFIER = "Identifier"
|
||||
STYLE_BRACE = "Brace/Paren - matching"
|
||||
STYLE_BRACEBAD = "Brace/Paren - unmatched"
|
||||
STYLE_STRINGEOL = "String with no terminator"
|
||||
STYLE_LINENUMBER = "Line numbers"
|
||||
STYLE_INDENTGUIDE = "Indent guide"
|
||||
STYLE_SELECTION = "Selection"
|
||||
|
||||
STRING_STYLES = [
|
||||
STYLE_STRING,
|
||||
STYLE_SQSTRING,
|
||||
STYLE_TQSSTRING,
|
||||
STYLE_TQDSTRING,
|
||||
STYLE_STRINGEOL,
|
||||
]
|
||||
|
||||
# These styles can have any ID - they are not special to scintilla itself.
|
||||
# However, if we use the built-in lexer, then we must use its style numbers
|
||||
# so in that case, they _are_ special.
|
||||
# (name, format, background, scintilla id)
|
||||
PYTHON_STYLES = [
|
||||
(STYLE_DEFAULT, (0, 0, 200, 0, 0x808080), CLR_INVALID, scintillacon.SCE_P_DEFAULT),
|
||||
(
|
||||
STYLE_COMMENT,
|
||||
(0, 2, 200, 0, 0x008000),
|
||||
CLR_INVALID,
|
||||
scintillacon.SCE_P_COMMENTLINE,
|
||||
),
|
||||
(
|
||||
STYLE_COMMENT_BLOCK,
|
||||
(0, 2, 200, 0, 0x808080),
|
||||
CLR_INVALID,
|
||||
scintillacon.SCE_P_COMMENTBLOCK,
|
||||
),
|
||||
(STYLE_NUMBER, (0, 0, 200, 0, 0x808000), CLR_INVALID, scintillacon.SCE_P_NUMBER),
|
||||
(STYLE_STRING, (0, 0, 200, 0, 0x008080), CLR_INVALID, scintillacon.SCE_P_STRING),
|
||||
(STYLE_SQSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_CHARACTER),
|
||||
(STYLE_TQSSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_TRIPLE),
|
||||
(STYLE_TQDSTRING, STYLE_STRING, CLR_INVALID, scintillacon.SCE_P_TRIPLEDOUBLE),
|
||||
(STYLE_STRINGEOL, (0, 0, 200, 0, 0x000000), 0x008080, scintillacon.SCE_P_STRINGEOL),
|
||||
(STYLE_KEYWORD, (0, 1, 200, 0, 0x800000), CLR_INVALID, scintillacon.SCE_P_WORD),
|
||||
(STYLE_CLASS, (0, 1, 200, 0, 0xFF0000), CLR_INVALID, scintillacon.SCE_P_CLASSNAME),
|
||||
(STYLE_METHOD, (0, 1, 200, 0, 0x808000), CLR_INVALID, scintillacon.SCE_P_DEFNAME),
|
||||
(
|
||||
STYLE_OPERATOR,
|
||||
(0, 0, 200, 0, 0x000000),
|
||||
CLR_INVALID,
|
||||
scintillacon.SCE_P_OPERATOR,
|
||||
),
|
||||
(
|
||||
STYLE_IDENTIFIER,
|
||||
(0, 0, 200, 0, 0x000000),
|
||||
CLR_INVALID,
|
||||
scintillacon.SCE_P_IDENTIFIER,
|
||||
),
|
||||
]
|
||||
|
||||
# These styles _always_ have this specific style number, regardless of
|
||||
# internal or external formatter.
|
||||
SPECIAL_STYLES = [
|
||||
(STYLE_BRACE, (0, 0, 200, 0, 0x000000), 0xFFFF80, scintillacon.STYLE_BRACELIGHT),
|
||||
(STYLE_BRACEBAD, (0, 0, 200, 0, 0x000000), 0x8EA5F2, scintillacon.STYLE_BRACEBAD),
|
||||
(
|
||||
STYLE_LINENUMBER,
|
||||
(0, 0, 200, 0, 0x000000),
|
||||
win32api.GetSysColor(win32con.COLOR_3DFACE),
|
||||
scintillacon.STYLE_LINENUMBER,
|
||||
),
|
||||
(
|
||||
STYLE_INDENTGUIDE,
|
||||
(0, 0, 200, 0, 0x000000),
|
||||
CLR_INVALID,
|
||||
scintillacon.STYLE_INDENTGUIDE,
|
||||
),
|
||||
## Not actually a style; requires special handling to send appropriate messages to scintilla
|
||||
(
|
||||
STYLE_SELECTION,
|
||||
(0, 0, 200, 0, CLR_INVALID),
|
||||
win32api.RGB(0xC0, 0xC0, 0xC0),
|
||||
999999,
|
||||
),
|
||||
]
|
||||
|
||||
PythonSampleCode = """\
|
||||
# Some Python
|
||||
class Sample(Super):
|
||||
def Fn(self):
|
||||
\tself.v = 1024
|
||||
dest = 'dest.html'
|
||||
x = func(a + 1)|)
|
||||
s = "I forget...
|
||||
## A large
|
||||
## comment block"""
|
||||
|
||||
|
||||
class PythonSourceFormatter(Formatter):
|
||||
string_style_names = STRING_STYLES
|
||||
|
||||
def GetSampleText(self):
|
||||
return PythonSampleCode
|
||||
|
||||
def LoadStyles(self):
|
||||
pass
|
||||
|
||||
def SetStyles(self):
|
||||
for name, format, bg, ignore in PYTHON_STYLES:
|
||||
self.RegisterStyle(Style(name, format, bg))
|
||||
for name, format, bg, sc_id in SPECIAL_STYLES:
|
||||
self.RegisterStyle(Style(name, format, bg), sc_id)
|
||||
|
||||
def ClassifyWord(self, cdoc, start, end, prevWord):
|
||||
word = cdoc[start : end + 1].decode("latin-1")
|
||||
attr = STYLE_IDENTIFIER
|
||||
if prevWord == "class":
|
||||
attr = STYLE_CLASS
|
||||
elif prevWord == "def":
|
||||
attr = STYLE_METHOD
|
||||
elif word[0] in string.digits:
|
||||
attr = STYLE_NUMBER
|
||||
elif iskeyword(word):
|
||||
attr = STYLE_KEYWORD
|
||||
self.ColorSeg(start, end, attr)
|
||||
return word
|
||||
|
||||
def ColorizeString(self, str, styleStart):
|
||||
if styleStart is None:
|
||||
styleStart = STYLE_DEFAULT
|
||||
return self.ColorizePythonCode(str, 0, styleStart)
|
||||
|
||||
def ColorizePythonCode(self, cdoc, charStart, styleStart):
|
||||
# Straight translation of C++, should do better
|
||||
lengthDoc = len(cdoc)
|
||||
if lengthDoc <= charStart:
|
||||
return
|
||||
prevWord = ""
|
||||
state = styleStart
|
||||
chPrev = chPrev2 = chPrev3 = " "
|
||||
chNext2 = chNext = cdoc[charStart : charStart + 1].decode("latin-1")
|
||||
startSeg = i = charStart
|
||||
while i < lengthDoc:
|
||||
ch = chNext
|
||||
chNext = " "
|
||||
if i + 1 < lengthDoc:
|
||||
chNext = cdoc[i + 1 : i + 2].decode("latin-1")
|
||||
chNext2 = " "
|
||||
if i + 2 < lengthDoc:
|
||||
chNext2 = cdoc[i + 2 : i + 3].decode("latin-1")
|
||||
if state == STYLE_DEFAULT:
|
||||
if ch in wordstarts:
|
||||
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
|
||||
state = STYLE_KEYWORD
|
||||
startSeg = i
|
||||
elif ch == "#":
|
||||
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
|
||||
if chNext == "#":
|
||||
state = STYLE_COMMENT_BLOCK
|
||||
else:
|
||||
state = STYLE_COMMENT
|
||||
startSeg = i
|
||||
elif ch == '"':
|
||||
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
|
||||
startSeg = i
|
||||
state = STYLE_COMMENT
|
||||
if chNext == '"' and chNext2 == '"':
|
||||
i += 2
|
||||
state = STYLE_TQDSTRING
|
||||
ch = " "
|
||||
chPrev = " "
|
||||
chNext = " "
|
||||
if i + 1 < lengthDoc:
|
||||
chNext = cdoc[i + 1]
|
||||
else:
|
||||
state = STYLE_STRING
|
||||
elif ch == "'":
|
||||
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
|
||||
startSeg = i
|
||||
state = STYLE_COMMENT
|
||||
if chNext == "'" and chNext2 == "'":
|
||||
i += 2
|
||||
state = STYLE_TQSSTRING
|
||||
ch = " "
|
||||
chPrev = " "
|
||||
chNext = " "
|
||||
if i + 1 < lengthDoc:
|
||||
chNext = cdoc[i + 1]
|
||||
else:
|
||||
state = STYLE_SQSTRING
|
||||
elif ch in operators:
|
||||
self.ColorSeg(startSeg, i - 1, STYLE_DEFAULT)
|
||||
self.ColorSeg(i, i, STYLE_OPERATOR)
|
||||
startSeg = i + 1
|
||||
elif state == STYLE_KEYWORD:
|
||||
if ch not in wordchars:
|
||||
prevWord = self.ClassifyWord(cdoc, startSeg, i - 1, prevWord)
|
||||
state = STYLE_DEFAULT
|
||||
startSeg = i
|
||||
if ch == "#":
|
||||
if chNext == "#":
|
||||
state = STYLE_COMMENT_BLOCK
|
||||
else:
|
||||
state = STYLE_COMMENT
|
||||
elif ch == '"':
|
||||
if chNext == '"' and chNext2 == '"':
|
||||
i += 2
|
||||
state = STYLE_TQDSTRING
|
||||
ch = " "
|
||||
chPrev = " "
|
||||
chNext = " "
|
||||
if i + 1 < lengthDoc:
|
||||
chNext = cdoc[i + 1]
|
||||
else:
|
||||
state = STYLE_STRING
|
||||
elif ch == "'":
|
||||
if chNext == "'" and chNext2 == "'":
|
||||
i += 2
|
||||
state = STYLE_TQSSTRING
|
||||
ch = " "
|
||||
chPrev = " "
|
||||
chNext = " "
|
||||
if i + 1 < lengthDoc:
|
||||
chNext = cdoc[i + 1]
|
||||
else:
|
||||
state = STYLE_SQSTRING
|
||||
elif ch in operators:
|
||||
self.ColorSeg(startSeg, i, STYLE_OPERATOR)
|
||||
startSeg = i + 1
|
||||
elif state == STYLE_COMMENT or state == STYLE_COMMENT_BLOCK:
|
||||
if ch == "\r" or ch == "\n":
|
||||
self.ColorSeg(startSeg, i - 1, state)
|
||||
state = STYLE_DEFAULT
|
||||
startSeg = i
|
||||
elif state == STYLE_STRING:
|
||||
if ch == "\\":
|
||||
if chNext == '"' or chNext == "'" or chNext == "\\":
|
||||
i += 1
|
||||
ch = chNext
|
||||
chNext = " "
|
||||
if i + 1 < lengthDoc:
|
||||
chNext = cdoc[i + 1]
|
||||
elif ch == '"':
|
||||
self.ColorSeg(startSeg, i, STYLE_STRING)
|
||||
state = STYLE_DEFAULT
|
||||
startSeg = i + 1
|
||||
elif state == STYLE_SQSTRING:
|
||||
if ch == "\\":
|
||||
if chNext == '"' or chNext == "'" or chNext == "\\":
|
||||
i += 1
|
||||
ch = chNext
|
||||
chNext = " "
|
||||
if i + 1 < lengthDoc:
|
||||
chNext = cdoc[i + 1]
|
||||
elif ch == "'":
|
||||
self.ColorSeg(startSeg, i, STYLE_SQSTRING)
|
||||
state = STYLE_DEFAULT
|
||||
startSeg = i + 1
|
||||
elif state == STYLE_TQSSTRING:
|
||||
if ch == "'" and chPrev == "'" and chPrev2 == "'" and chPrev3 != "\\":
|
||||
self.ColorSeg(startSeg, i, STYLE_TQSSTRING)
|
||||
state = STYLE_DEFAULT
|
||||
startSeg = i + 1
|
||||
elif (
|
||||
state == STYLE_TQDSTRING
|
||||
and ch == '"'
|
||||
and chPrev == '"'
|
||||
and chPrev2 == '"'
|
||||
and chPrev3 != "\\"
|
||||
):
|
||||
self.ColorSeg(startSeg, i, STYLE_TQDSTRING)
|
||||
state = STYLE_DEFAULT
|
||||
startSeg = i + 1
|
||||
chPrev3 = chPrev2
|
||||
chPrev2 = chPrev
|
||||
chPrev = ch
|
||||
i += 1
|
||||
if startSeg < lengthDoc:
|
||||
if state == STYLE_KEYWORD:
|
||||
self.ClassifyWord(cdoc, startSeg, lengthDoc - 1, prevWord)
|
||||
else:
|
||||
self.ColorSeg(startSeg, lengthDoc - 1, state)
|
||||
|
||||
|
||||
# These taken from the SciTE properties file.
|
||||
source_formatter_extensions = [
|
||||
(".py .pys .pyw".split(), scintillacon.SCLEX_PYTHON),
|
||||
(".html .htm .asp .shtml".split(), scintillacon.SCLEX_HTML),
|
||||
(
|
||||
"c .cc .cpp .cxx .h .hh .hpp .hxx .idl .odl .php3 .phtml .inc .js".split(),
|
||||
scintillacon.SCLEX_CPP,
|
||||
),
|
||||
(".vbs .frm .ctl .cls".split(), scintillacon.SCLEX_VB),
|
||||
(".pl .pm .cgi .pod".split(), scintillacon.SCLEX_PERL),
|
||||
(".sql .spec .body .sps .spb .sf .sp".split(), scintillacon.SCLEX_SQL),
|
||||
(".tex .sty".split(), scintillacon.SCLEX_LATEX),
|
||||
(".xml .xul".split(), scintillacon.SCLEX_XML),
|
||||
(".err".split(), scintillacon.SCLEX_ERRORLIST),
|
||||
(".mak".split(), scintillacon.SCLEX_MAKEFILE),
|
||||
(".bat .cmd".split(), scintillacon.SCLEX_BATCH),
|
||||
]
|
||||
|
||||
|
||||
class BuiltinSourceFormatter(FormatterBase):
|
||||
# A class that represents a formatter built-in to Scintilla
|
||||
def __init__(self, scintilla, ext):
|
||||
self.ext = ext
|
||||
FormatterBase.__init__(self, scintilla)
|
||||
|
||||
def Colorize(self, start=0, end=-1):
|
||||
self.scintilla.SendScintilla(scintillacon.SCI_COLOURISE, start, end)
|
||||
|
||||
def RegisterStyle(self, style, stylenum=None):
|
||||
assert style.stylenum is None, "Style has already been registered"
|
||||
if stylenum is None:
|
||||
stylenum = self.nextstylenum
|
||||
self.nextstylenum += 1
|
||||
assert self.styles.get(stylenum) is None, "We are reusing a style number!"
|
||||
style.stylenum = stylenum
|
||||
self.styles[style.name] = style
|
||||
self.styles_by_id[stylenum] = style
|
||||
|
||||
def HookFormatter(self, parent=None):
|
||||
sc = self.scintilla
|
||||
for exts, formatter in source_formatter_extensions:
|
||||
if self.ext in exts:
|
||||
formatter_use = formatter
|
||||
break
|
||||
else:
|
||||
formatter_use = scintillacon.SCLEX_PYTHON
|
||||
sc.SendScintilla(scintillacon.SCI_SETLEXER, formatter_use)
|
||||
keywords = " ".join(kwlist)
|
||||
sc.SCISetKeywords(keywords)
|
||||
|
||||
|
||||
class BuiltinPythonSourceFormatter(BuiltinSourceFormatter):
|
||||
sci_lexer_name = scintillacon.SCLEX_PYTHON
|
||||
string_style_names = STRING_STYLES
|
||||
|
||||
def __init__(self, sc, ext=".py"):
|
||||
BuiltinSourceFormatter.__init__(self, sc, ext)
|
||||
|
||||
def SetStyles(self):
|
||||
for name, format, bg, sc_id in PYTHON_STYLES:
|
||||
self.RegisterStyle(Style(name, format, bg), sc_id)
|
||||
for name, format, bg, sc_id in SPECIAL_STYLES:
|
||||
self.RegisterStyle(Style(name, format, bg), sc_id)
|
||||
|
||||
def GetSampleText(self):
|
||||
return PythonSampleCode
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user