diff options
Diffstat (limited to 'test/XiteWin.py')
-rw-r--r-- | test/XiteWin.py | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/test/XiteWin.py b/test/XiteWin.py new file mode 100644 index 000000000..71234cbd6 --- /dev/null +++ b/test/XiteWin.py @@ -0,0 +1,650 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import os, sys, unittest + +import ctypes +from ctypes import wintypes +from ctypes import c_int, c_ulong, c_char_p, c_wchar_p, c_ushort +user32=ctypes.windll.user32 +gdi32=ctypes.windll.gdi32 +kernel32=ctypes.windll.kernel32 +from MessageNumbers import msgs, sgsm + +import XiteMenu + +scintillaDirectory = ".." +scintillaIncludeDirectory = os.path.join(scintillaDirectory, "include") +sys.path.append(scintillaIncludeDirectory) +import Face + +scintillaBinDirectory = os.path.join(scintillaDirectory, "bin") +os.environ['PATH'] = os.environ['PATH'] + ";" + scintillaBinDirectory +#print(os.environ['PATH']) + +WFUNC = ctypes.WINFUNCTYPE(c_int, c_int, c_int, c_int, c_int) + +WS_CHILD = 0x40000000 +WS_CLIPCHILDREN = 0x2000000 +WS_OVERLAPPEDWINDOW = 0xcf0000 +WS_VISIBLE = 0x10000000 +WS_HSCROLL = 0x100000 +WS_VSCROLL = 0x200000 +WA_INACTIVE = 0 +MF_POPUP = 16 +MF_SEPARATOR = 0x800 +IDYES = 6 +OFN_HIDEREADONLY = 4 +MB_OK = 0 +MB_YESNOCANCEL = 3 +MF_CHECKED = 8 +MF_UNCHECKED = 0 +SW_SHOW = 5 +PM_REMOVE = 1 + +VK_SHIFT = 16 +VK_CONTROL = 17 +VK_MENU = 18 + +class OPENFILENAME(ctypes.Structure): + _fields_ = (("lStructSize", c_int), + ("hwndOwner", c_int), + ("hInstance", c_int), + ("lpstrFilter", c_wchar_p), + ("lpstrCustomFilter", c_char_p), + ("nMaxCustFilter", c_int), + ("nFilterIndex", c_int), + ("lpstrFile", c_wchar_p), + ("nMaxFile", c_int), + ("lpstrFileTitle", c_wchar_p), + ("nMaxFileTitle", c_int), + ("lpstrInitialDir", c_wchar_p), + ("lpstrTitle", c_wchar_p), + ("flags", c_int), + ("nFileOffset", c_ushort), + ("nFileExtension", c_ushort), + ("lpstrDefExt", c_char_p), + ("lCustData", c_int), + ("lpfnHook", c_char_p), + ("lpTemplateName", c_char_p), + ("pvReserved", c_char_p), + ("dwReserved", c_int), + ("flagsEx", c_int)) + + def __init__(self, win, title): + ctypes.Structure.__init__(self) + self.lStructSize = ctypes.sizeof(OPENFILENAME) + self.nMaxFile = 1024 + self.hwndOwner = win + self.lpstrTitle = title + self.Flags = OFN_HIDEREADONLY + +trace = False +#~ trace = True + +def WindowSize(w): + rc = ctypes.wintypes.RECT() + user32.GetClientRect(w, ctypes.byref(rc)) + return rc.right - rc.left, rc.bottom - rc.top + +def IsKeyDown(key): + return (user32.GetKeyState(key) & 0x8000) != 0 + +def KeyTranslate(w): + tr = { 9: "Tab", 0xD:"Enter", 0x1B: "Esc" } + if w in tr: + return tr[w] + elif ord("A") <= w <= ord("Z"): + return chr(w) + elif 0x70 <= w <= 0x7b: + return "F" + str(w-0x70+1) + else: + return "Unknown_" + hex(w) + +class WNDCLASS(ctypes.Structure): + _fields_= (\ + ('style', c_int), + ('lpfnWndProc', WFUNC), + ('cls_extra', c_int), + ('wnd_extra', c_int), + ('hInst', c_int), + ('hIcon', c_int), + ('hCursor', c_int), + ('hbrBackground', c_int), + ('menu_name', c_wchar_p), + ('lpzClassName', c_wchar_p), + ) + +class XTEXTRANGE(ctypes.Structure): + _fields_= (\ + ('cpMin', c_int), + ('cpMax', c_int), + ('lpstrText', c_char_p), + ) + +class TEXTRANGE(ctypes.Structure): + _fields_= (\ + ('cpMin', c_int), + ('cpMax', c_int), + ('lpstrText', ctypes.POINTER(ctypes.c_char)), + ) + +class FINDTEXT(ctypes.Structure): + _fields_= (\ + ('cpMin', c_int), + ('cpMax', c_int), + ('lpstrText', c_char_p), + ('cpMinText', c_int), + ('cpMaxText', c_int), + ) + +hinst = ctypes.windll.kernel32.GetModuleHandleW(0) + +def RegisterClass(name, func, background = 0): + # register a window class for toplevel windows. + wc = WNDCLASS() + wc.style = 0 + wc.lpfnWndProc = func + wc.cls_extra = 0 + wc.wnd_extra = 0 + wc.hInst = hinst + wc.hIcon = 0 + wc.hCursor = 0 + wc.hbrBackground = background + wc.menu_name = 0 + wc.lpzClassName = name + user32.RegisterClassW(ctypes.byref(wc)) + +class SciCall: + def __init__(self, fn, ptr, msg): + self._fn = fn + self._ptr = ptr + self._msg = msg + def __call__(self, w=0, l=0): + return self._fn(self._ptr, self._msg, w, l) + +class Scintilla: + def __init__(self, face, hwndParent, hinstance): + self.__dict__["face"] = face + self.__dict__["used"] = set() + self.__dict__["all"] = set() + # The k member is for accessing constants as a dictionary + self.__dict__["k"] = {} + for f in face.features: + self.all.add(f) + if face.features[f]["FeatureType"] == "val": + self.k[f] = int(self.face.features[f]["Value"], 0) + elif face.features[f]["FeatureType"] == "evt": + self.k["SCN_"+f] = int(self.face.features[f]["Value"], 0) + # Get the function first as that also loads the DLL + self.__dict__["_scifn"] = ctypes.windll.SciLexer.Scintilla_DirectFunction + self.__dict__["_hwnd"] = user32.CreateWindowExW(0, + "Scintilla", "Source", + WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_CLIPCHILDREN, + 0, 0, 100, 100, hwndParent, 0, hinstance, 0) + self.__dict__["_sciptr"] = user32.SendMessageW(self._hwnd, + int(self.face.features["GetDirectPointer"]["Value"], 0), 0,0) + user32.ShowWindow(self._hwnd, SW_SHOW) + def __getattr__(self, name): + if name in self.face.features: + self.used.add(name) + feature = self.face.features[name] + value = int(feature["Value"], 0) + #~ print("Feature", name, feature) + if feature["FeatureType"] == "val": + self.__dict__[name] = value + return value + else: + return SciCall(self._scifn, self._sciptr, value) + elif ("Get" + name) in self.face.features: + self.used.add("Get" + name) + feature = self.face.features["Get" + name] + value = int(feature["Value"], 0) + if feature["FeatureType"] == "get" and \ + not name.startswith("Get") and \ + not feature["Param1Type"] and \ + not feature["Param2Type"] and \ + feature["ReturnType"] in ["bool", "int", "position"]: + #~ print("property", feature) + return self._scifn(self._sciptr, value, 0, 0) + elif name.startswith("SCN_") and name in self.k: + self.used.add(name) + feature = self.face.features[name[4:]] + value = int(feature["Value"], 0) + #~ print("Feature", name, feature) + if feature["FeatureType"] == "val": + return value + raise AttributeError(name) + def __setattr__(self, name, val): + if ("Set" + name) in self.face.features: + self.used.add("Set" + name) + feature = self.face.features["Set" + name] + value = int(feature["Value"], 0) + #~ print("setproperty", feature) + if feature["FeatureType"] == "set" and not name.startswith("Set"): + if feature["Param1Type"] in ["bool", "int", "position"]: + return self._scifn(self._sciptr, value, val, 0) + elif feature["Param2Type"] in ["string"]: + return self._scifn(self._sciptr, value, 0, val) + raise AttributeError(name) + raise AttributeError(name) + def getvalue(self, name): + if name in self.face.features: + feature = self.face.features[name] + if feature["FeatureType"] != "evt": + try: + return int(feature["Value"], 0) + except ValueError: + return -1 + return -1 + + + def ByteRange(self, start, end): + tr = TEXTRANGE() + tr.cpMin = start + tr.cpMax = end + length = end - start + tr.lpstrText = ctypes.create_string_buffer(length + 1) + self.GetTextRange(0, ctypes.byref(tr)) + text = tr.lpstrText[:length] + text += b"\0" * (length - len(text)) + return text + def StyledTextRange(self, start, end): + tr = TEXTRANGE() + tr.cpMin = start + tr.cpMax = end + length = 2 * (end - start) + tr.lpstrText = ctypes.create_string_buffer(length + 2) + self.GetStyledText(0, ctypes.byref(tr)) + styledText = tr.lpstrText[:length] + styledText += b"\0" * (length - len(styledText)) + return styledText + def FindBytes(self, start, end, s, flags): + ft = FINDTEXT() + ft.cpMin = start + ft.cpMax = end + ft.lpstrText = s + ft.cpMinText = 0 + ft.cpMaxText = 0 + pos = self.FindText(flags, ctypes.byref(ft)) + #~ print(start, end, ft.cpMinText, ft.cpMaxText) + return pos + + def Contents(self): + return self.ByteRange(0, self.Length) + def SizeTo(self, width, height): + user32.SetWindowPos(self._hwnd, 0, 0, 0, width, height, 0) + def FocusOn(self): + user32.SetFocus(self._hwnd) + +class XiteWin(): + def __init__(self, test=""): + self.face = Face.Face() + self.face.ReadFromFile(os.path.join(scintillaIncludeDirectory, "Scintilla.iface")) + + self.titleDirty = True + self.fullPath = "" + self.test = test + + self.appName = "xite" + + self.cmds = {} + self.windowName = "XiteWindow" + self.wfunc = WFUNC(self.WndProc) + RegisterClass(self.windowName, self.wfunc) + user32.CreateWindowExW(0, self.windowName, self.appName, \ + WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, \ + 0, 0, 500, 700, 0, 0, hinst, 0) + + args = sys.argv[1:] + self.SetMenus() + if args: + self.GrabFile(args[0]) + self.ed.FocusOn() + self.ed.GotoPos(self.ed.Length) + + print(self.test) + if self.test: + for k in self.cmds: + if self.cmds[k] == "Test": + user32.PostMessageW(self.win, msgs["WM_COMMAND"], k, 0) + + def OnSize(self): + width, height = WindowSize(self.win) + self.ed.SizeTo(width, height) + user32.InvalidateRect(self.win, 0, 0) + + def OnCreate(self, hwnd): + self.win = hwnd + self.ed = Scintilla(self.face, hwnd, hinst) + self.ed.FocusOn() + + + def Invalidate(self): + user32.InvalidateRect(self.win, 0, 0) + + def WndProc(self, h, m, w, l): + ms = sgsm.get(m, "XXX") + if trace: + print("%s %s %s %s" % (hex(h)[2:],ms,w,l)) + if ms == "WM_CLOSE": + user32.PostQuitMessage(0) + elif ms == "WM_CREATE": + self.OnCreate(h) + return 0 + elif ms == "WM_SIZE": + # Work out size + if w != 1: + self.OnSize() + return 0 + elif ms == "WM_COMMAND": + cmdCode = w & 0xffff + if cmdCode in self.cmds: + self.Command(self.cmds[cmdCode]) + return 0 + elif ms == "WM_ACTIVATE": + if w != WA_INACTIVE: + self.ed.FocusOn() + return 0 + else: + return user32.DefWindowProcW(h, m, w, l) + return 0 + + def Command(self, name): + name = name.replace(" ", "") + method = "Cmd" + name + cmd = None + try: + cmd = getattr(self, method) + except AttributeError: + return + if cmd: + cmd() + + def KeyDown(self, w, prefix = ""): + keyName = prefix + if IsKeyDown(VK_CONTROL): + keyName += "<control>" + if IsKeyDown(VK_SHIFT): + keyName += "<shift>" + keyName += KeyTranslate(w) + if trace: + print("Key:", keyName) + if keyName in self.keys: + method = "Cmd" + self.keys[keyName] + getattr(self, method)() + return True + #~ print("UKey:", keyName) + return False + + def Accelerator(self, msg): + ms = sgsm.get(msg.message, "XXX") + if ms == "WM_KEYDOWN": + return self.KeyDown(msg.wParam) + elif ms == "WM_SYSKEYDOWN": + return self.KeyDown(msg.wParam, "<alt>") + return False + + def AppLoop(self): + msg = ctypes.wintypes.MSG() + lpmsg = ctypes.byref(msg) + while user32.GetMessageW(lpmsg, 0, 0, 0): + if trace and msg.message != msgs["WM_TIMER"]: + print('mm', hex(msg.hWnd)[2:],sgsm.get(msg.message, "XXX")) + if not self.Accelerator(msg): + user32.TranslateMessage(lpmsg) + user32.DispatchMessageW(lpmsg) + + def DoEvents(self): + msg = ctypes.wintypes.MSG() + lpmsg = ctypes.byref(msg) + cont = True + while cont: + cont = user32.PeekMessageW(lpmsg, 0, 0, 0, PM_REMOVE) + if cont: + if not self.Accelerator(msg): + user32.TranslateMessage(lpmsg) + user32.DispatchMessageW(lpmsg) + + def SetTitle(self, changePath): + if changePath or self.titleDirty != self.ed.Modify: + self.titleDirty = self.ed.Modify + self.title = self.fullPath + if self.titleDirty: + self.title += " * " + else: + self.title += " - " + self.title += self.appName + if self.win: + user32.SetWindowTextW(self.win, self.title) + + def Open(self): + ofx = OPENFILENAME(self.win, "Open File") + opath = "\0" * 1024 + ofx.lpstrFile = opath + filters = ["Python (.py;.pyw)|*.py;*.pyw|All|*.*"] + filterText = "\0".join([f.replace("|", "\0") for f in filters])+"\0\0" + ofx.lpstrFilter = filterText + if ctypes.windll.comdlg32.GetOpenFileNameW(ctypes.byref(ofx)): + absPath = opath.replace("\0", "") + self.GrabFile(absPath) + self.ed.FocusOn() + self.ed.LexerLanguage = "python" + self.ed.Lexer = self.ed.SCLEX_PYTHON + self.ed.SetKeyWords(0, "class def else for if import print return while") + for style in [k for k in self.ed.k if k.startswith("SCE_P_")]: + self.ed.StyleSetFont(self.ed.k[style], "Verdana") + if "COMMENT" in style: + self.ed.StyleSetFore(self.ed.k[style], 127 * 256) + self.ed.StyleSetFont(self.ed.k[style], "Comic Sans MS") + elif "OPERATOR" in style: + print(style, self.ed.k[style]) + self.ed.StyleSetBold(self.ed.k[style], 1) + self.ed.StyleSetFore(self.ed.k[style], 127 * 256 * 256) + elif "WORD" in style: + print(style, self.ed.k[style]) + self.ed.StyleSetItalic(self.ed.k[style], 255) + self.ed.StyleSetFore(self.ed.k[style], 255 * 256 * 256) + else: + self.ed.StyleSetFore(self.ed.k[style], 0) + + def SaveAs(self): + ofx = OPENFILENAME(self.win, "Save File") + opath = "\0" * 1024 + ofx.lpstrFile = opath + if ctypes.windll.comdlg32.GetSaveFileNameW(ctypes.byref(ofx)): + self.fullPath = opath.replace("\0", "") + self.Save() + self.SetTitle(1) + self.ed.FocusOn() + + def SetMenus(self): + ui = XiteMenu.MenuStructure + self.cmds = {} + self.keys = {} + + cmdId = 0 + self.menuBar = user32.CreateMenu() + for name, contents in ui: + cmdId += 1 + menu = user32.CreateMenu() + for item in contents: + text, key = item + cmdText = text.replace("&", "") + cmdText = cmdText.replace("...", "") + cmdText = cmdText.replace(" ", "") + cmdId += 1 + if key: + keyText = key.replace("<control>", "Ctrl+") + keyText = keyText.replace("<shift>", "Shift+") + text += "\t" + keyText + if text == "-": + user32.AppendMenuW(menu, MF_SEPARATOR, cmdId, text) + else: + user32.AppendMenuW(menu, 0, cmdId, text) + self.cmds[cmdId] = cmdText + self.keys[key] = cmdText + #~ print(cmdId, item) + user32.AppendMenuW(self.menuBar, MF_POPUP, menu, name) + user32.SetMenu(self.win, self.menuBar) + self.CheckMenuItem("Wrap", True) + user32.ShowWindow(self.win, SW_SHOW) + + def CheckMenuItem(self, name, val): + #~ print(name, val) + if self.cmds: + for k,v in self.cmds.items(): + if v == name: + #~ print(name, k) + user32.CheckMenuItem(user32.GetMenu(self.win), \ + k, [MF_UNCHECKED, MF_CHECKED][val]) + + def Exit(self): + sys.exit(0) + + def DisplayMessage(self, msg, ask): + return IDYES == user32.MessageBoxW(self.win, \ + msg, self.appName, [MB_OK, MB_YESNOCANCEL][ask]) + + def NewDocument(self): + self.ed.ClearAll() + self.ed.EmptyUndoBuffer() + self.ed.SetSavePoint() + + def SaveIfUnsure(self): + if self.ed.Modify: + msg = "Save changes to \"" + self.fullPath + "\"?" + print(msg) + decision = self.DisplayMessage(msg, True) + if decision: + self.CmdSave() + return decision + return True + + def New(self): + if self.SaveIfUnsure(): + self.fullPath = "" + self.overrideMode = None + self.NewDocument() + self.SetTitle(1) + self.Invalidate() + + def CheckMenus(self): + pass + + def MoveSelection(self, caret, anchor=-1): + if anchor == -1: + anchor = caret + self.ed.SetSelectionStart(caret) + self.ed.SetSelectionEnd(anchor) + self.ed.ScrollCaret() + self.Invalidate() + + def GrabFile(self, name): + self.fullPath = name + self.overrideMode = None + self.NewDocument() + fsr = open(name, "rb") + data = fsr.read() + fsr.close() + self.ed.AddText(len(data), data) + self.ed.EmptyUndoBuffer() + self.MoveSelection(0) + self.SetTitle(1) + + def Save(self): + fos = open(self.fullPath, "wb") + blockSize = 1024 + length = self.ed.Length + i = 0 + while i < length: + grabSize = length - i + if grabSize > blockSize: + grabSize = blockSize + #~ print(i, grabSize, length) + data = self.ed.ByteRange(i, i + grabSize) + fos.write(data) + i += grabSize + fos.close() + self.ed.SetSavePoint() + self.SetTitle(0) + + # Command handlers are called by menu actions + + def CmdNew(self): + self.New() + + def CmdOpen(self): + self.Open() + + def CmdSave(self): + if (self.fullPath == None) or (len(self.fullPath) == 0): + self.SaveAs() + else: + self.Save() + + def CmdSaveAs(self): + self.SaveAs() + + def CmdTest(self): + runner = unittest.TextTestRunner() + if self.test: + tests = unittest.defaultTestLoader.loadTestsFromName(self.test) + else: + tests = unittest.defaultTestLoader.loadTestsFromName("simpleTests") + results = runner.run(tests) + #~ print(results) + if self.test: + user32.PostQuitMessage(0) + + def CmdExercised(self): + print() + unused = sorted(self.ed.all.difference(self.ed.used)) + print("Unused", len(unused)) + print() + print("\n".join(unused)) + print() + print("Used", len(self.ed.used)) + print() + print("\n".join(sorted(self.ed.used))) + + def CmdUncalled(self): + print() + unused = sorted(self.ed.all.difference(self.ed.used)) + uu = {} + for u in unused: + v = self.ed.getvalue(u) + if v > 2000: + uu[v] = u + for x in sorted(uu.keys())[150:]: + print(x, uu[x]) + print() + + def CmdExit(self): + self.Exit() + + def CmdUndo(self): + self.ed.Undo() + + def CmdRedo(self): + self.ed.Redo() + + def CmdCut(self): + self.ed.Cut() + + def CmdCopy(self): + self.ed.Copy() + + def CmdPaste(self): + self.ed.Paste() + + def CmdDelete(self): + self.ed.Clear() + +xiteFrame = None + +def main(test): + global xiteFrame + xiteFrame = XiteWin(test) + xiteFrame.AppLoop() |