# -*- coding: utf-8 -*- # # This file is part of EventGhost. # Copyright © 2005-2020 EventGhost Project <http://www.eventghost.net/> # # EventGhost is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free # Software Foundation, either version 2 of the License, or (at your option) # any later version. # # EventGhost is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along # with EventGhost. If not, see <http://www.gnu.org/licenses/>. import inspect import os import sys import threading import time import warnings import wx import traceback from locale import windows_locale from CommonMark import commonmark from ctypes import c_ulonglong, windll from datetime import datetime as dt, timedelta as td from docutils.core import publish_parts as ReSTPublishParts from docutils.writers.html4css1 import Writer from functools import update_wrapper from os.path import abspath, dirname, exists, join from types import ClassType # Local imports import eg # Make sure our deprecation warnings will be shown warnings.filterwarnings( action="always", category=DeprecationWarning, module='^eg\..*' ) __all__ = [ "Bunch", "NotificationHandler", "LogIt", "LogItWithReturn", "TimeIt", "AssertInMainThread", "AssertInActionThread", "ParseString", "SetDefault", "EnsureVisible", "VBoxSizer", "HBoxSizer", "EqualizeWidths", "AsTasklet", "ExecFile", "GetTopLevelWindow", "GetClosestLanguage" ] USER_CLASSES = (type, ClassType) class Bunch(object): """ Universal collection of a bunch of named stuff. Often we want to just collect a bunch of stuff together, naming each item of the bunch. A dictionary is OK for that; however, when names are constants and to be used just like variables, the dictionary-access syntax ("if bunch['squared'] > threshold", etc) is not maximally clear. It takes very little effort to build a little class, as in this 'Bunch', that will both ease the initialisation task and provide elegant attribute-access syntax ("if bunch.squared > threshold", etc). Usage is simple:: point = eg.Bunch(x=100, y=200) # and of course you can read/write the named # attributes you just created, add others, del # some of them, etc, etc: point.squared = point.x * point.y if point.squared > threshold: point.isok = True """ def __init__(self, **kwargs): self.__dict__.update(kwargs) class HBoxSizer(wx.BoxSizer): #IGNORE:R0904 def __init__(self, *items): wx.BoxSizer.__init__(self, wx.HORIZONTAL) self.AddMany(items) class MyHtmlDocWriter(Writer): def apply_template(self): return """\ %(head_prefix)s %(head)s %(stylesheet)s %(body_prefix)s %(body_pre_docinfo)s %(docinfo)s %(body)s %(body_suffix)s """ % self.interpolation_dict() HTML_DOC_WRITER = MyHtmlDocWriter() class NotificationHandler(object): __slots__ = ["listeners"] def __init__(self): self.listeners = [] class VBoxSizer(wx.BoxSizer): #IGNORE:R0904 def __init__(self, *items): wx.BoxSizer.__init__(self, wx.VERTICAL) self.AddMany(items) def AppUrl(description, url): if url: txt = '<p><div align=right><i><font color="#999999" size=-1>%s <a href="%s">%s</a>.</font></i></div></p>' % ( eg.text.General.supportSentence, url, eg.text.General.supportLink ) else: return description if description.startswith("<md>"): description = description[4:] description = DecodeMarkdown(description) elif description.startswith("<rst>"): description = description[5:] description = DecodeReST(description) return description + txt def AssertInActionThread(func): if not eg.debugLevel: return func def AssertWrapper(*args, **kwargs): if eg.actionThread._ThreadWorker__thread != threading.currentThread(): try: raise AssertionError( "Called outside ActionThread: %s() in %s" % (func.__name__, func.__module__) ) except AssertionError: eg.PrintWarningNotice(traceback.format_exc()) return func(*args, **kwargs) return update_wrapper(AssertWrapper, func) def AssertInMainThread(func): if not eg.debugLevel: return func def AssertWrapper(*args, **kwargs): if eg.mainThread != threading.currentThread(): try: raise AssertionError( "Called outside MainThread: %s in %s" % (func.__name__, func.__module__) ) except AssertionError: eg.PrintWarningNotice(traceback.format_exc()) return func(*args, **kwargs) return update_wrapper(AssertWrapper, func) def AsTasklet(func): def Wrapper(*args, **kwargs): eg.Tasklet(func)(*args, **kwargs).run() return update_wrapper(Wrapper, func) def CollectGarbage(): import gc #gc.set_debug(gc.DEBUG_SAVEALL) #gc.set_debug(gc.DEBUG_UNCOLLECTABLE) from pprint import pprint print "threshold:", gc.get_threshold() print "unreachable object count:", gc.collect() garbageList = gc.garbage[:] for i, obj in enumerate(garbageList): print "Object Num %d:" % i pprint(obj) #print "Referrers:" #print(gc.get_referrers(o)) #print "Referents:" #print(gc.get_referents(o)) print "Done." #print "unreachable object count:", gc.collect() #from pprint import pprint #pprint(gc.garbage) def DecodeMarkdown(source): return commonmark(source) def DecodeReST(source): #print repr(source) res = ReSTPublishParts( source=PrepareDocstring(source), writer=HTML_DOC_WRITER, settings_overrides={"stylesheet_path": ""} ) #print repr(res) return res['body'] def EnsureVisible(window): """ Ensures the given wx.TopLevelWindow is visible on the screen. Moves and resizes it if necessary. """ from eg.WinApi.Dynamic import ( sizeof, byref, GetMonitorInfo, MonitorFromWindow, GetWindowRect, MONITORINFO, RECT, MONITOR_DEFAULTTONEAREST, # MonitorFromRect, MONITOR_DEFAULTTONULL, ) hwnd = window.GetHandle() windowRect = RECT() GetWindowRect(hwnd, byref(windowRect)) #hMonitor = MonitorFromRect(byref(windowRect), MONITOR_DEFAULTTONULL) #if hMonitor: # return parent = window.GetParent() if parent: hwnd = parent.GetHandle() hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) monInfo = MONITORINFO() monInfo.cbSize = sizeof(MONITORINFO) GetMonitorInfo(hMonitor, byref(monInfo)) displayRect = monInfo.rcWork left = windowRect.left right = windowRect.right top = windowRect.top bottom = windowRect.bottom # shift the window horizontally into the display area if left < displayRect.left: right += (displayRect.left - left) left = displayRect.left if right > displayRect.right: right = displayRect.right elif right > displayRect.right: left += (displayRect.right - right) right = displayRect.right if left < displayRect.left: left = displayRect.left # shift the window vertically into the display area if top < displayRect.top: bottom += (displayRect.top - top) top = displayRect.top if bottom > displayRect.bottom: bottom = displayRect.bottom elif bottom > displayRect.bottom: top += (displayRect.bottom - bottom) bottom = displayRect.bottom if top < displayRect.top: top = displayRect.top # set the new position and size window.SetRect((left, top, right - left, bottom - top)) def EqualizeWidths(ctrls): maxWidth = max((ctrl.GetBestSize()[0] for ctrl in ctrls)) for ctrl in ctrls: ctrl.SetMinSize((maxWidth, -1)) def ExecFile(filename, globals=None, locals=None): """ Replacement for the Python built-in execfile() function, but handles unicode filenames right. """ FSE = sys.getfilesystemencoding() flnm = filename.encode(FSE) if isinstance(filename, unicode) else filename return execfile(flnm, globals, locals) def GetBootTimestamp(unix_timestamp = True): """ Returns the time of the last system boot. If unix_timestamp == True, result is a unix temestamp. Otherwise it is in human readable form. """ now = time.time() GetTickCount64 = windll.kernel32.GetTickCount64 GetTickCount64.restype = c_ulonglong up = GetTickCount64() / 1000.0 if not unix_timestamp: st = str(dt.fromtimestamp(now - up)) return st if "." not in st else st[:st.index(".")] return now - up def GetClosestLanguage(): """ Returns the language file closest to system locale. """ langDir = join(dirname(abspath(sys.executable)), "languages") if exists(langDir): uiLang = windows_locale[windll.kernel32.GetUserDefaultUILanguage()] langFiles = tuple( f[:-3] for f in os.listdir(langDir) if f.endswith(".py") and ( f.startswith(uiLang) or f.startswith(uiLang[:3]) ) ) if uiLang in langFiles: return uiLang if langFiles: return langFiles[0] return "en_US" def GetFirstParagraph(text): """ Return the first paragraph of a description string. The string can be encoded in HTML or reStructuredText. The paragraph is returned as HTML. """ text = text.lstrip() if text.startswith("<md>"): text = text[4:] text = DecodeMarkdown(text) start = text.find("<p>") end = text.find("</p>") return text[start + 3:end].replace("\n", " ") elif text.startswith("<rst>"): text = text[5:] text = DecodeReST(text) start = text.find("<p>") end = text.find("</p>") return text[start + 3:end].replace("\n", " ") else: result = "" for line in text.splitlines(): if line == "": break result += " " + line return ' '.join(result.split()) def GetFuncArgString(func, args, kwargs): classname = "" argnames = inspect.getargspec(func)[0] start = 0 if argnames: if argnames[0] == "self": classname = args[0].__class__.__name__ + "." start = 1 res = [] append = res.append for key, value in zip(argnames, args)[start:]: append(str(key) + GetMyRepresentation(value)) for key, value in kwargs.items(): append(str(key) + GetMyRepresentation(value)) fname = classname + func.__name__ return fname, "(" + ", ".join(res) + ")" def GetMyRepresentation(value): """ Give a shorter representation of some wx-objects. Returns normal repr() for everything else. Also adds a "=" sign at the beginning to make it useful as a "formatvalue" function for inspect.formatargvalues(). """ typeString = repr(type(value)) if typeString.startswith("<class 'wx._core."): return "=<wx.%s>" % typeString[len("<class 'wx._core."): -2] if typeString.startswith("<class 'wx._controls."): return "=<wx.%s>" % typeString[len("<class 'wx._controls."): -2] return "=" + repr(value) def GetTopLevelWindow(window): """ Returns the top level parent window of a wx.Window. This is in most cases a wx.Dialog or wx.Frame. """ result = window while True: parent = result.GetParent() if parent is None: return result elif isinstance(parent, wx.TopLevelWindow): return parent result = parent def GetUpTime(seconds = True): """ Returns a runtime of system in seconds. If seconds == False, returns the number of days, hours, minutes and seconds. """ GetTickCount64 = windll.kernel32.GetTickCount64 GetTickCount64.restype = c_ulonglong ticks = GetTickCount64() / 1000.0 if not seconds: delta = str(td(seconds = ticks)) return delta if "." not in delta else delta[:delta.index(".")] return ticks def IsVista(): """ Determine if we're running Vista or higher. """ warnings.warn( "eg.Utils.IsVista() is deprecated. " "Use eg.WindowsVersion >= 'Vista' instead", DeprecationWarning, stacklevel=2 ) return eg.WindowsVersion >= 'Vista' def IsXP(): """ Determine if we're running XP or higher. """ warnings.warn( "eg.Utils.IsXP() is deprecated. " "Use eg.WindowsVersion >= 'XP' instead", DeprecationWarning, stacklevel=2 ) return eg.WindowsVersion >= 'XP' def LogIt(func): """ Logs the function call, if eg.debugLevel is set. """ if not eg.debugLevel: return func if func.func_code.co_flags & 0x20: raise TypeError("Can't wrap generator function") def LogItWrapper(*args, **kwargs): funcName, argString = GetFuncArgString(func, args, kwargs) eg.PrintDebugNotice(funcName + argString) return func(*args, **kwargs) return update_wrapper(LogItWrapper, func) def LogItWithReturn(func): """ Logs the function call and return, if eg.debugLevel is set. """ if not eg.debugLevel: return func def LogItWithReturnWrapper(*args, **kwargs): funcName, argString = GetFuncArgString(func, args, kwargs) eg.PrintDebugNotice(funcName + argString) result = func(*args, **kwargs) eg.PrintDebugNotice(funcName + " => " + repr(result)) return result return update_wrapper(LogItWithReturnWrapper, func) def ParseString(text, filterFunc=None): start = 0 chunks = [] last = len(text) - 1 while 1: pos = text.find('{', start) if pos < 0: break if pos == last: break chunks.append(text[start:pos]) if text[pos + 1] == '{': chunks.append('{') start = pos + 2 else: start = pos + 1 end = text.find('}', start) if end == -1: raise SyntaxError("unmatched bracket") word = text[start:end] res = None if filterFunc: res = filterFunc(word) if res is None: res = eval(word, {}, eg.globals.__dict__) chunks.append(unicode(res)) start = end + 1 chunks.append(text[start:]) return "".join(chunks) def PrepareDocstring(docstring): """ Convert a docstring into lines of parseable reST. Return it as a list of lines usable for inserting into a docutils ViewList (used as argument of nested_parse()). An empty line is added to act as a separator between this docstring and following content. """ lines = docstring.expandtabs().splitlines() # Find minimum indentation of any non-blank lines after first line. margin = sys.maxint for line in lines[1:]: content = len(line.lstrip()) if content: indent = len(line) - content margin = min(margin, indent) # Remove indentation. if lines: lines[0] = lines[0].lstrip() if margin < sys.maxint: for i in range(1, len(lines)): lines[i] = lines[i][margin:] # Remove any leading blank lines. while lines and not lines[0]: lines.pop(0) # make sure there is an empty line at the end if lines and lines[-1]: lines.append('') return "\n".join(lines) def Reset(): eg.stopExecutionFlag = True eg.programCounter = None del eg.programReturnStack[:] eg.eventThread.ClearPendingEvents() eg.actionThread.ClearPendingEvents() eg.PrintError("Execution stopped by user") def SetDefault(targetCls, defaultCls): targetDict = targetCls.__dict__ for defaultKey, defaultValue in defaultCls.__dict__.iteritems(): if defaultKey not in targetDict: setattr(targetCls, defaultKey, defaultValue) elif type(defaultValue) in USER_CLASSES: SetDefault(targetDict[defaultKey], defaultValue) def SplitFirstParagraph(text): """ Split the first paragraph of a description string. The string can be encoded in HTML or reStructuredText. The paragraph is returned as HTML. """ text = text.lstrip() if text.startswith("<md>"): text = text[4:] text = DecodeMarkdown(text) start = text.find("<p>") end = text.find("</p>") return ( text[start + 3:end].replace("\n", " "), text[end + 4:].replace("\n", " ") ) elif text.startswith("<rst>"): text = text[5:] text = DecodeReST(text) start = text.find("<p>") end = text.find("</p>") return ( text[start + 3:end].replace("\n", " "), text[end + 4:].replace("\n", " ") ) else: result = "" remaining = "" lines = text.splitlines() for i, line in enumerate(lines): if line.strip() == "": remaining = " ".join(lines[i:]) break result += " " + line return ' '.join(result.split()), remaining def TimeIt(func): """ Decorator to measure the execution time of a function. Will print the time to the log. """ if not eg.debugLevel: return func def TimeItWrapper(*args, **kwargs): startTime = time.clock() funcName, _ = GetFuncArgString(func, args, kwargs) res = func(*args, **kwargs) eg.PrintDebugNotice(funcName + " :" + repr(time.clock() - startTime)) return res return update_wrapper(TimeItWrapper, func) def UpdateStartupShortcut(create): from eg import Shortcut path = os.path.join( eg.folderPath.Startup, eg.APP_NAME + ".lnk" ) if os.path.exists(path): os.remove(path) if create: if not os.path.exists(eg.folderPath.Startup): os.makedirs(eg.folderPath.Startup) Shortcut.Create( path=path, target=os.path.abspath(sys.executable), arguments="-h -e OnInitAfterBoot", startIn=os.path.dirname(os.path.abspath(sys.executable)), )