# -*- coding: utf-8 -*- from __future__ import with_statement from __future__ import unicode_literals 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 import ctypes.util print >> sys.stderr, "SciLexer path: ",ctypes.util.find_library("scilexer") 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 FindBytes2(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, ft.cpMinText, ft.cpMaxText) 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) if self.test: print(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 += "" if IsKeyDown(VK_SHIFT): keyName += "" 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, "") 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, b"class def else for from 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], b"Verdana") if "COMMENT" in style: self.ed.StyleSetFore(self.ed.k[style], 127 * 256) self.ed.StyleSetFont(self.ed.k[style], b"Comic Sans MS") elif "OPERATOR" in style: self.ed.StyleSetBold(self.ed.k[style], 1) self.ed.StyleSetFore(self.ed.k[style], 127 * 256 * 256) elif "WORD" in style: self.ed.StyleSetItalic(self.ed.k[style], 255) self.ed.StyleSetFore(self.ed.k[style], 255 * 256 * 256) elif "TRIPLE" in style: self.ed.StyleSetFore(self.ed.k[style], 0xA0A0) elif "STRING" in style or "CHARACTER" in style: self.ed.StyleSetFore(self.ed.k[style], 0xA000A0) 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("", "Ctrl+") keyText = keyText.replace("", "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 Uncalled(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:]: return uu 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() #~ xiteFrame.CmdExercised() return xiteFrame.Uncalled()