def image_grab(bbox=None, childprocess=None, backend=None): if platform_is(WINDOWS) or platform_is(MAC): return ImageGrab.grab(bbox) else: # only import pyscreenshot if not on windows import pyscreenshot # noqa return pyscreenshot.grab(bbox, childprocess, backend)
def all_supported_cursors() -> tuple: """ Get all cursors from the database that are supported in the current operating system :return: Tuple of strings """ if platform_is(MAC): return BUILTIN_CURSORS + BUILTIN_CURSORS_MAC elif platform_is(WINDOWS): return BUILTIN_CURSORS + BUILTIN_CURSORS_WINDOWS
def resize_cursor() -> tuple: r""" Returns a tuple of the cursors to be used based on platform :return: tuple ("nw_se", "ne_sw") cursors roughly equal to \ and / respectively """ if platform_is(WINDOWS): # Windows provides corner resize cursors so use those return "size_nw_se", "size_ne_sw" if platform_is(LINUX): return "bottom_right_corner", "bottom_left_corner" # Use circles for other platforms return ("circle", ) * 2
class Symbol(Key): if platform_is(LINUX): _keycodes = { '\'': 48, '[': 34, '-': 20, '\\': 51, ',': 59, ']': 35, '.': 60, '`': 49, '/': 61, '=': 21, ';': 47, } else: _keycodes = { '\'': 222, '[': 219, '-': 189, '\\': 220, ',': 188, ']': 221, '.': 190, '`': 192, '/': 192, '=': 187, ';': 186, } def __init__(self, symbol: str): if symbol: symbol = symbol[:1] if symbol in self._keycodes: super().__init__(symbol, self._keycodes[symbol]) else: raise ValueError('symbol {} not found'.format(symbol)) else: raise ValueError('symbol must not be empty')
def __init__(self, alphanumeric: str): alphanumeric = alphanumeric.upper() if platform_is(LINUX): super().__init__(alphanumeric, 10 + self.qwerty.index(alphanumeric)) else: super().__init__(alphanumeric, ord(alphanumeric))
def is_admin(): """ Returns ``True`` if current process is running in elevated mode otherwise ``False`` is returned """ if platform_is(WINDOWS): return windll.shell32.IsUserAnAdmin() return os.getuid() == 0
def upgrade(args): # elevate process to run in admin mode command = f"\"{sys.executable}\" -m pip install --upgrade formation-studio" if platform_is(WINDOWS): # run command directly in elevated mode elevate(command) else: elevate() sys.exit(os.system(command))
def _restore_position(self): pos = pref.get("studio::pos") if pos.get("state") == 'zoomed': if platform_is(WINDOWS): self.state('zoomed') else: self.wm_attributes('-zoomed', True) return self.state('normal') self.geometry('{width}x{height}+{x}+{y}'.format(**pos))
def __init__(self, master, **cnf): super().__init__(master, **cnf) if master: self.style = master.window.style self.window = self self.pos = (0, 0) self.overrideredirect(True) self.attributes("-alpha", 0.6) # Default transparency if platform_is(MAC): # needed for macos to make window visible self.lift()
def show_loading(self): if platform_is(LINUX) or self._is_loading: # render transitions in linux are very fast and glitch free # for other platforms or at least for windows we need to hide the glitching return self.remove_empty() self._empty_frame.place(x=0, y=0, relheight=1, relwidth=1) Label(self._empty_frame, text="Loading...", **self.style.text_passive).place(x=0, y=0, relheight=1, relwidth=1) self._is_loading = True
class KeyPad(Key): if platform_is(LINUX): _offset = 79 _keys = '789-456+1230.' else: _offset = 96 _keys = '0123456789*+ -./' def __init__(self, key): key = str(key)[:1] key = key.replace(' ', '') if key != '' and key in self._keys: super().__init__(key, self._offset + self._keys.index(key)) else: raise ValueError('Invalid keypad key')
def __init__(self, master, render_routine=None, **kw): super().__init__(master) self.configure(**self.style.surface) # ensure the dialog is above the parent window at all times self.transient(master) # prevent resizing by default self.resizable(False, False) self.bar = None # Common dialogs routines = { "OKAY_CANCEL": self._ask_okay_cancel, "YES_NO": self._ask_yes_no, "RETRY_CANCEL": self._ask_retry_cancel, "SHOW_ERROR": self._show_error, "SHOW_WARNING": self._show_warning, "SHOW_INFO": self._show_info, "SHOW_PROGRESS": self._show_progress, "BUILDER": self._builder # Allows building custom dialogs } if render_routine in routines: # Completely custom dialogs routines[render_routine](**kw) # noqa elif render_routine is not None: render_routine(self) self.enable_centering() self.value = None # quirk that prevents explicit window centering on linux for best results add = "+" if platform_is(WINDOWS, MAC) else None self.bind('<Visibility>', lambda _: self._get_focus(), add=add) if platform_is(WINDOWS): # This special case fixes a windows specific issue which blocks # application from retuning from a withdrawn state self.bind('<FocusOut>', lambda _: self.grab_release()) self.bind('<FocusIn>', lambda _: self._get_focus()) self._reaction = -1 self.bind('<Button>', self._on_event)
def _elevate_posix(): if os.getuid() == 0: return working_dir = os.getcwd() args = [sys.executable] + sys.argv commands = [] def quote_shell(a): return " ".join(quote(arg) for arg in a) def quote_applescript(string): charmap = { "\n": "\\n", "\r": "\\r", "\t": "\\t", "\"": "\\\"", "\\": "\\\\", } return '"%s"' % "".join(charmap.get(char, char) for char in string) if platform_is(MAC): commands.append([ "osascript", "-e", "do shell script %s " "with administrator privileges " "without altering line endings" % quote_applescript(quote_shell(args)) ]) elif os.environ.get("DISPLAY"): commands.append(["pkexec"] + args) commands.append(["gksudo"] + args) commands.append(["kdesudo"] + args) commands.append(["sudo"] + args) for args in commands: try: os.execlp(args[0], *args) # restore working directory which may be changed in certain systems if working_dir != os.getcwd(): os.chdir(working_dir) # we are confident process has been elevated break except OSError as e: if e.errno != errno.ENOENT or args[0] == "sudo": sys.exit(1)
def elevate(args=None): """ Runs the current process with root privileges. In posix systems, the current process is swapped while in Windows UAC creates a new process and the return code of the spawned process is chained back to the process that initiated the elevation. In case of elevation failures the process will terminate with a non zero exit code :param args: A list of commandline arguments to be used in creating the command to be run directly in elevated mode. If not provided current process is elevated instead. Only available in windows. """ if platform_is(WINDOWS): _elevate_win(args) else: _elevate_posix()
def popup(cls, event, menu): """ Display context menu based on a right click event :param event: tk event object :param menu: tk menu to be displayed :return: None """ try: menu.post(event.x_root, event.y_root) if platform_is(LINUX): menu.focus_set() menu.bind("<FocusOut>", lambda e: menu.unpost()) except tk.TclError: pass finally: menu.grab_release()
def make_dynamic(cls, templates, parent=None, style: StyleDelegator = None, dynamic=True, **cnf): """ Create a dynamic menu object under a tkinter widget parent :param dynamic: suppress dynamic behaviour, useful for toplevel menubar. Default is set to true :param style: hoverset StyleDelegator object to allow retrieval of necessary menu theme styles :param templates: a tuple that may contain the following 1. a tuples of the format (type, label, icon, command, additional_config) where type is either ``command, cascade, radiobutton, checkbutton`` and additional_config is a dict containing menu item configuration 2. a tuple of he format ('separator', additional_config) to declare a separator. The additional_config is optional 3. a :class:`Hoverset.ui.menu.Manipulator` object. :param parent: The parent of the menu. You will never need to set this attribute directly as it only exists for the purposes of recursion :param cnf: configuration for created menu :return: dynamic menu """ if style and not platform_is(MAC): # styles are not applied consistently on macOS so just suppress them cnf.update(style.context_menu) menu = tk.Menu(parent, **cnf) def on_post(): # clear former contents of menu to allow _make_menu to populate it afresh cls.clear_menu(menu) cls._make_menu(templates, menu, style) if dynamic: # set postcommand only if dynamic behaviour is not suppressed menu.config(postcommand=on_post) else: # otherwise just populate menu on creation cls._make_menu(templates, menu, style) return menu
def __init__(self, master=None, **cnf): super().__init__(master, **cnf) # Load icon asynchronously to prevent issues which have been known to occur when loading it synchronously icon_image = load_tk_image(self.ICON_PATH) self.load_styles(self.THEME_PATH) self.iconphoto(True, icon_image) self.pref = pref self._restore_position() self.title('Formation Studio') self.protocol('WM_DELETE_WINDOW', self._on_close) self.shortcuts = ShortcutManager(self, pref) self.shortcuts.bind_all() self._register_actions() self._toolbar = Frame(self, **self.style.surface, height=30) self._toolbar.pack(side="top", fill="x") self._toolbar.pack_propagate(0) self._statusbar = Frame(self, **self.style.surface, height=20) self._statusbar.pack(side="bottom", fill="x") self._statusbar.pack_propagate(0) body = Frame(self, **self.style.surface) body.pack(fill="both", expand=True, side="top") self._right_bar = SideBar(body) self._right_bar.pack(side="right", fill="y") self._left_bar = SideBar(body) self._left_bar.pack(side="left", fill="y") self._pane = PanedWindow(body, **self.style.pane_horizontal) self._pane.pack(side="left", fill="both", expand=True) self._left = FeaturePane(self._pane, **self.style.pane_vertical) self._center = PanedWindow(self._pane, **self.style.pane_vertical) self._right = FeaturePane(self._pane, **self.style.pane_vertical) self._bin = [] self._clipboard = None self.current_preview = None self._pane.add(self._left, minsize=320, sticky='nswe', width=320) self._pane.add(self._center, minsize=400, width=16000, sticky='nswe') self._pane.add(self._right, minsize=320, sticky='nswe', width=320) self._panes = { "left": (self._left, self._left_bar), "right": (self._right, self._right_bar), "center": (self._center, None) } icon = get_icon_image self.actions = ( ("Delete", icon("delete", 20, 20), lambda e: self.delete(), "Delete selected widget"), ("Undo", icon("undo", 20, 20), lambda e: self.undo(), "Undo action"), ("Redo", icon("redo", 20, 20), lambda e: self.redo(), "Redo action"), ("Cut", icon("cut", 20, 20), lambda e: self.cut(), "Cut selected widget"), ("separator",), ("Fullscreen", icon("image_editor", 20, 20), lambda e: self.close_all(), "Design mode"), ("Separate", icon("separate", 20, 20), lambda e: self.features_as_windows(), "Open features in window mode"), ("Dock", icon("flip_horizontal", 15, 15), lambda e: self.features_as_docked(), "Dock all features"), ("separator",), ("New", icon("add", 20, 20), lambda e: self.open_new(), "New design"), ("Save", icon("save", 20, 20), lambda e: self.save(), "Save design"), ("Preview", icon("play", 20, 20), lambda e: self.preview(), "Preview design"), ) self.init_toolbar() self.selected = None # set the image option to blank if there is no image for the menu option self.blank_img = blank_img = icon("blank", 14, 14) self.tool_manager = ToolManager(self) # -------------------------------------------- menu definition ------------------------------------------------ self.menu_template = (EnableIf( lambda: self.selected, ("separator",), ("command", "copy", icon("copy", 14, 14), actions.get('STUDIO_COPY'), {}), ("command", "duplicate", icon("copy", 14, 14), actions.get('STUDIO_DUPLICATE'), {}), EnableIf( lambda: self._clipboard is not None, ("command", "paste", icon("clipboard", 14, 14), actions.get('STUDIO_PASTE'), {}) ), ("command", "cut", icon("cut", 14, 14), actions.get('STUDIO_CUT'), {}), ("separator",), ("command", "delete", icon("delete", 14, 14), actions.get('STUDIO_DELETE'), {}), ),) self.menu_bar = MenuUtils.make_dynamic( (( ("cascade", "formation", None, None, {"menu": ( ("command", "Restart", None, actions.get('STUDIO_RESTART'), {}), ("separator", ), ("command", "About Formation", icon("formation", 14, 14), lambda: about_window(self), {}), ), "name": "apple"}), ) if platform_is(MAC) else ()) + ( ("cascade", "File", None, None, {"menu": ( ("command", "New", icon("add", 14, 14), actions.get('STUDIO_NEW'), {}), ("command", "Open", icon("folder", 14, 14), actions.get('STUDIO_OPEN'), {}), ("cascade", "Recent", icon("clock", 14, 14), None, {"menu": self._create_recent_menu()}), ("separator",), EnableIf( lambda: self.designer, ("command", "Save", icon("save", 14, 14), actions.get('STUDIO_SAVE'), {}), ("command", "Save As", icon("blank", 14, 14), actions.get('STUDIO_SAVE_AS'), {}) ), EnableIf( # more than one design contexts open lambda: len([i for i in self.contexts if isinstance(i, DesignContext)]) > 1, ("command", "Save All", icon("blank", 14, 14), actions.get('STUDIO_SAVE_ALL'), {}) ), ("separator",), ("command", "Settings", icon("settings", 14, 14), actions.get('STUDIO_SETTINGS'), {}), ("command", "Restart", icon("blank", 14, 14), actions.get('STUDIO_RESTART'), {}), ("command", "Exit", icon("close", 14, 14), actions.get('STUDIO_EXIT'), {}), )}), ("cascade", "Edit", None, None, {"menu": ( EnableIf(lambda: self.context and self.context.has_undo(), ("command", "undo", icon("undo", 14, 14), actions.get('STUDIO_UNDO'), {})), EnableIf(lambda: self.context and self.context.has_redo(), ("command", "redo", icon("redo", 14, 14), actions.get('STUDIO_REDO'), {})), *self.menu_template, )}), ("cascade", "Code", None, None, {"menu": ( EnableIf( lambda: self.designer and self.designer.root_obj, ("command", "Preview design", icon("play", 14, 14), actions.get('STUDIO_PREVIEW'), {}), ("command", "close preview", icon("close", 14, 14), actions.get('STUDIO_PREVIEW_CLOSE'), {}), ("separator", ), EnableIf( lambda: self.designer and self.designer.design_path, ("command", "Reload design file", icon("rotate_clockwise", 14, 14), actions.get('STUDIO_RELOAD'), {}), ), ) )}), ("cascade", "View", None, None, {"menu": ( ("command", "show all panes", blank_img, actions.get('FEATURE_SHOW_ALL'), {}), ("command", "close all panes", icon("close", 14, 14), actions.get('FEATURE_CLOSE_ALL'), {}), ("command", "close all panes on the right", blank_img, actions.get('FEATURE_CLOSE_RIGHT'), {}), ("command", "close all panes on the left", blank_img, actions.get('FEATURE_CLOSE_LEFT'), {}), ("separator",), ("command", "Undock all windows", blank_img, actions.get('FEATURE_UNDOCK_ALL'), {}), ("command", "Dock all windows", blank_img, actions.get('FEATURE_DOCK_ALL'), {}), ("separator",), LoadLater(self.get_features_as_menu), ("separator",), EnableIf( lambda: self.context, ("command", "close tab", icon("close", 14, 14), actions.get('CONTEXT_CLOSE'), {}), ("command", "close all tabs", blank_img, actions.get('CONTEXT_CLOSE_ALL'), {}), EnableIf( lambda: self.context and len(self.tab_view.tabs()) > 1, ("command", "close other tabs", blank_img, actions.get('CONTEXT_CLOSE_OTHER'), {}) ), EnableIf( lambda: self.context and self.context._contexts_right(), ("command", "close all tabs on the right", blank_img, actions.get('CONTEXT_CLOSE_OTHER_RIGHT'), {}) ) ), ("separator",), ("command", "Save window positions", blank_img, actions.get('FEATURE_SAVE_POS'), {}) )}), ("cascade", "Tools", None, None, {"menu": (LoadLater(self.tool_manager.get_tools_as_menu), )}), ("cascade", "Help", None, None, {"menu": ( ("command", "Help", icon('dialog_info', 14, 14), actions.get('STUDIO_HELP'), {}), ("command", "Check for updates", icon("cloud", 14, 14), self._check_updates, {}), ("separator",), ("command", "About Formation", icon("formation", 14, 14), lambda: about_window(self), {}), )}) ), self, self.style, False) self.config(menu=self.menu_bar) if platform_is(MAC): self.createcommand("tk::mac::ShowPreferences", lambda: actions.get('STUDIO_SETTINGS').invoke()) self.createcommand("tk::mac::ShowHelp", lambda: actions.get('STUDIO_HELP').invoke()) self.createcommand("tk::mac::Quit", lambda: actions.get('STUDIO_EXIT').invoke()) self.features = [] self.context = None self.contexts = [] self.tab_view = TabView(self._center) self.tab_view.malleable(True) self.tab_view.bind("<<TabSelectionChanged>>", self.on_context_switch) self.tab_view.bind("<<TabClosed>>", self.on_context_close) self.tab_view.bind("<<TabAdded>>", self.on_context_add) self.tab_view.bind("<<TabOrderChanged>>", lambda _: self.save_tab_status()) self._center.add(self.tab_view, sticky='nswe') self._tab_view_empty = Label( self.tab_view, **self.style.text_passive, compound='top', image=get_icon_image("paint", 60, 60) ) self._tab_view_empty.config(**self.style.bright) # install features for feature in FEATURES: self.install(feature) # common feature references self.style_pane = self.get_feature(StylePane) # initialize tools with everything ready self.tool_manager.initialize() self._ignore_tab_status = False self._startup() self._exit_failures = 0 self._is_shutting_down = False
'gray50', 'gray25', 'gray12', 'hourglass', 'info', 'questhead', 'question', 'warning', ) BUILTIN_BITMAPS_MAC = ('document', 'stationery', 'edition', 'application', 'accessory', 'forder', 'pfolder', 'trash', 'floppy', 'ramdisk', 'cdrom', 'preferences', 'querydoc', 'stop', 'note', 'caution') if platform_is(MAC): BUILTIN_BITMAPS = BUILTIN_BITMAPS + BUILTIN_BITMAPS_MAC def all_cursors() -> tuple: """ Get all cursors in the cursor database regardless of the platform they belong to :return: Tuple of strings """ return BUILTIN_CURSORS + BUILTIN_CURSORS_WINDOWS + BUILTIN_CURSORS_MAC def all_supported_cursors() -> tuple: """ Get all cursors from the database that are supported in the current operating system :return: Tuple of strings
# ======================================================================= # # Copyright (C) 2020 Hoverset Group. # # ======================================================================= # import threading import time import functools import errno import os import sys import importlib.util import pathlib from hoverset.platform import platform_is, WINDOWS, MAC if platform_is(WINDOWS): import ctypes from ctypes import POINTER, c_ulong, c_char_p, c_int, c_void_p from ctypes.wintypes import HANDLE, BOOL, DWORD, HWND, HINSTANCE, HKEY from ctypes import windll import subprocess else: try: from shlex import quote except ImportError: from pipes import quote def timed(func): """ Time the execution of a wrapped function and print the output
def function_key(number): if number > 12 or number < 1: raise ValueError("Function keys should be between 1 and 12 inclusive") if platform_is(LINUX): return Key('F{}'.format(number), 66 + number) return Key('F{}'.format(number), 111 + number)
def platform(cls, label, windows, linux): if platform_is(LINUX): return Key(label, linux) return Key(label, windows)