diff --git a/FreeSimpleGUIQt/FreeSimpleGUIQt/FreeSimpleGUIQt.py b/FreeSimpleGUIQt/FreeSimpleGUIQt/FreeSimpleGUIQt.py deleted file mode 100644 index 3ec86102..00000000 --- a/FreeSimpleGUIQt/FreeSimpleGUIQt/FreeSimpleGUIQt.py +++ /dev/null @@ -1,14293 +0,0 @@ -#!/usr/bin/python3 -version = __version__ = ( - '0.35.0.18.1 Unreleased\nMassive update of docstrings (thanks nngogol), default for slider tick interval set automatically now, margins added to Window but not yet hooked up, VSeparator added (spelling error), added Radio.reset_group and removed clearing all when one of them is cleared (recent change), added default key for one_line_progress_meter, auto-add keys to tables & trees, InputText element gets new disabled-readonly foreground and background color settings and also a readonly parameter, InputText gets border_width parameter, fixed up some docstrings, popup gets new image and any_key_closes parms, input type popups also get image parameter, error checks for trying to manipulate a window prior to finalize, added a dummy Element.expand method, added theme_add_new, added Window.set_title, updated to the latest themes from tktiner port, big styles update (thanks nngogol!), more Styles work, changed popup text layout to match tkinter port, fixed vertical alignment in row, added margin to some elements, renamed styles related variables, window margin support but be careful. Added back the truncated portion' -) - -__version__ = version.split()[0] # For PEP 396 and PEP 345 - -# The shortened version of version -try: - ver = version.split(' ')[0] -except: - ver = '' - - -port = 'PySimpleGUIQt' - -import sys, datetime, textwrap, pickle, random, warnings, time - -try: # Because Raspberry Pi is still on 3.4....it's not critical if this module isn't imported on the Pi - from typing import ( - List, - Any, - Union, - Tuple, - Dict, - ) # because this code has to run on 2.7 can't use real type hints. Must do typing only in comments -except: - print('*** Skipping import of Typing module. "pip3 install typing" to remove this warning ***') - -# Copyright 2024 FreeSimpleGui authors -# Copyright 2020 PySimpleGUI.org - - -from PySide6.QtWidgets import ( - QApplication, - QLabel, - QWidget, - QLineEdit, - QComboBox, - QFormLayout, - QVBoxLayout, - QHBoxLayout, - QListWidget, - QDial, - QTableWidget, -) -from PySide6.QtWidgets import ( - QSlider, - QCheckBox, - QRadioButton, - QSpinBox, - QPushButton, - QTextEdit, - QMainWindow, - QDialog, - QAbstractItemView, -) -from PySide6.QtWidgets import ( - QSpacerItem, - QFrame, - QGroupBox, - QTextBrowser, - QPlainTextEdit, - QButtonGroup, - QFileDialog, - QTableWidget, - QTabWidget, - QTabBar, - QTreeWidget, - QTreeWidgetItem, - QLayout, - QTreeWidgetItemIterator, - QProgressBar, -) -from PySide6.QtWidgets import ( - QTableWidgetItem, - QGraphicsView, - QGraphicsScene, - QGraphicsItemGroup, - QMenu, - QMenuBar, - QSystemTrayIcon, - QColorDialog, -) -from PySide6.QtGui import QPainter, QPixmap, QPen, QColor, QBrush, QPainterPath, QFont, QImage, QIcon, QAction - -from PySide6.QtCore import Qt, QEvent, QSize -import PySide6.QtGui as QtGui -import PySide6.QtCore as QtCore -import PySide6.QtWidgets as QtWidgets - -using_pyqt5 = False - - -DEFAULT_BASE64_ICON = b'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' - - -FACE_PALM = b'iVBORw0KGgoAAAANSUhEUgAAAEkAAAA8CAMAAAAdQmecAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAAB8SDx4UEiAUDyAUEiMZFSUbGSsUEy8aFiwbGTsUFTkdICwiHTIjHDQrHzsiGysjIjIlIDMqIzMsKjsgIzotID0uKT01IzszMUkYHE0ZIFkbKEQhHkUqH0sgHUUgI0MtIkUoKEsmIEwqIkwqL00rM0I1I0s1JEswKE04J0w7LUc9OVYpJ1omMVM0JVQ1K1I4JVU7KlszK1k6J1s7KVw4NGEdLmQeMWghNmE5LWQ4NXIiPWs2RHkkQnAzRVRENVNDOF1ENFxEOVpKOWNDM2NHPWNMPGxFOWxKPG9QPnJKPHVQP05HRFJMSltUUWpLQ2lUQ2NcWndNQndIU3JUQ3JUSHRZTHxSRHxVSHxZSnlaVH1YYmZgX39gVmZjYnZran14doFURoNWSYNbTYNeUoleUItbaYRgT4VhU4JiWIVoXIpjVIxlWo1oVo1pWpFiVpFlWZJsXZRxX4JmZYVsY4trY4pzaoN9e5NtYpRtaJltY5ZxYpRyaZpzZZx1ap14a5N4eJx1cJp3eJ15cZp6eqB2a6J6baN8cqJ7eax9cah+epx7hImBf5uBeqWAdKOCe6qCdKqDequJfbKJfY2EgpmIiZqVk52Zk6SBhKaFiKyDgqqFiayJgqyNi6aLkayNlKGQi6uRja+QlqySm6qZlLKMhLKOi7SRi76Th7qTi7KTk7KVm7KYk7SZm7iWk7yZk7ucm7Sco7ScqbqeorOfsK2knrmhnKukoqyop7GrqrqhpLqlq7mup7urqrSosbKuubqjsrymubyps7qqub6xrbSxsLizvbe1wr68ycKYi8OcmsCmo8KmqcGup8Ooq8qqqcersMGvv8yutMKxu8uwtM2zusy+v9G3usCwwsS0yca8xsW7y82/x8m9ysm90Ne8wMjAvMPCxsLAyc3Ax8vAy8/IzMbD0c3F08zK2NPLx9DJztnBydLO09LL2tPS19TS29rU29bU4dnT5NzV6NvZ4+Hf5uDd6+Pi7eXj8ejl9Ozp9/Lw+wAAAAAAAAAAAAAAAErQjXwAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAHU0lEQVRYR5WWf1gbZx3A8bw2y7nJVq00ZM9Eq63t0o3QFNKkrLbjoRjXsWFHCI+T0gNNe2nWm4Ndm9xFTLmj1UFONsUU1s2aZRRxdP7otNZN1Omc1inTdbad06danzlkVoR2/hG/7w8gNG/Y9iE/uPf9vp/7vt/37r0UZPLx34nxiYmJNybp4VuSxzQ9MXn5zempqelLlybfpotteu31ifFzfzp7/sL45PT09MS/aPOCME1jZ8+fGxs9Njw0fGLswuT01D/+SjsWgmU6fnJs7IUTx7410N8/MDx64eKlqV++DRXD9M2BkZOnfjicTvV0d/f0Hx09f/HNf//iL7QzP7mmn0XN9BMjR9PpfhOpkkdHz0z97+9PT9DuvOSa7gvHus2vYEzT6E4MDP10/PKlM0/T7rzkmoLBeyORWMwwzf6UaZowvydPX7z8nzPP0v585JhO7pIkWZbvVXUzlUomzYNmcui7L49fvPDs6zQiDzmmr9UHg7vDu8MRVTcOHESq5KEjz7z8yitnfkwj8pBr2njHNjEo7ZbliGFAQsk+eB155vTpc6f/RkPY5JiOr7i1uh5UYUVROw4ehEolk1193zj2k9+NLpxUbsWXrt5YfdfnpDAUS9UgLaCrq2/g6JETT9IINrmmJStWb9wUCKK6K0h1wDCMDs1IHjr8Tk23F63aeOsdooSXUNU6O7QOTdM7937x0NAbNIRJrumrS4tWb64OiFKrJEshWdnXqWr7QqGO/fuHztIQJrmmzKKiFauc9aLYIkp7xHvkfXtCLSG5Y+TU1w8foRFMGKat7y1asbLaH/CLkNeO5h2trZJk/Oq3I/uHh/9IQ1gwTD+4qnBpkbPK5xf9zTua/c1+fyT1WLLnvi+8MPwoDWHBMEWDjuuFpc5P+Hz3iM3Ndb5mPZVOKfU93xnqj/2GxjDINb3q8PicJYUly2vkutZWURR1PdErtwz8+vloQPk+DWLAyMnjWb58ZUnhrsf3SJ9s8fl1TVWlwODzI/HaxrYFpscw1fscxY7Slc7Yc5LkatDhTpZE3YzH133+1EsP0RgGDNOPqm74cJWrtHRbond9Q0LVdSmsmWa4rf3Ffw4/QmMYMEyZ9rIy502u0hpDlRK6HtNN40BCjI68+IeBtndWp0xmU5VrpWttFdqiensff27QjIuxwf6+/Y13v0YjGDBNr25yrV3rqoL7t7fX1A/0Girs6Eqw7fcPfJlGMGCaMt+rdt7k3Natqwkjpmnm4BNpQ27o+fNLD7yL4xYt+/RxGjYPtimTKSxxqYluEOm63psaTBtB89tDfe2LeY7neW7xnT+ncXPkMT109aoaJaYaugY7CmxSj6UHU12haNPV4EFw3I00cpY8psWLVlcHIxFdVTUtFod9Lm0aO+/adSMVAZy1icZSmKb738fzSzY1yJCRYcBGF48qWpdW11j/AapBQMW203gMw7SVRG4ORBKaqsXjWjSqaRGldkvV5utIF8JqFazWB+kQRI5pK4eLyvPVElQbyqRF92rxDll0rK+pXQRd8LK+/6MbNtzidrvXHKOjgCtMTVTD8++uDet6J1RJaVOie5XglrK6xlrUYf3QOo8HLG6v1+uuOEwHXmHajs45w05Fhx3c0JRQOBRUAi7P+pprIJuPrHHY7cXwZ3eAraJ8Nqss01NLqANzTbumxLSIrijhnTvDQWepq+x6oVAQBAuCh7dgs9sryj10dJbpttmJYT7eDlWOaJEwiO5uuRlJLNDMcSgK3lBOjkeyE3Q8NW1fNk8D1WiD9YJnZ1gOB1sCH0MK2oO/LYIF0gO3RWgkBmJ6cG51yRk5vkbR1IgcDisxWWwoIX0ILBAEm2AjCPx7sIeYbpvLx3odmgLPCaFIREY/WGDnrYOM5sAqUNgJNkEgImRaTGN44YPF0AFYhJsDIXicS/I+EC2n3QBHc7LZYO0q0KXgtttmTdfijDhLMQAiGxxYINK3Ax6ccqcq3jCzVrBaWAKdOKNyckk56N5esJ3UBqLQrAGYHg531CGV5ENtuAEDuSDWVKCMvN5Kb6X7s9R0O8poZvZ4FB0n2MrqGhr8vjLSij5JnYnMDSpIqbLS66amZbOiOSB9PNzmqdtSVVhL2rAcXtCHTeVIVQl4yfQKrs0WoWuNDKLp2dZ5eCvVzkGSmlWRpMCEhtORxIqj8TEBH88ja4IoKfxrqAC2wbmxUG0O7id6NAM+no+9GMuo6hZs+gxZKQQaRTPMFjFMUCuaFlo/Mr2CJkgJReNPfIFfmRM+ZIBVFfhS+BLOCZWb3C7kPofLEkM0CNqQy4zKuwGZ7iTRWIURIPW3mt0MMyo0vYL7s64luCt4Hu6ZeR4EHcgAuaBYcJ0XZPATAC0ampyAJ0fHz0IGZTPXiFTgQiYyJwAnh+pNd4QsZrcjgDYB5Bir7BvAhB5vKCvUZ+F4vMQ4cB54EP0/C2ik9/TDBZmnyG6BGqHuWJR9XvqdD2yCXcq+Dna6JtpIYJ15QWB+5W6v+1P/B0gPXHqaGwimAAAAAElFTkSuQmCC' - - -g_time_start = 0 -g_time_end = 0 -g_time_delta = 0 - - -def TimerStart(): - global g_time_start - - g_time_start = time.time() - - -def TimerStop(): - global g_time_delta, g_time_end - - g_time_end = time.time() - g_time_delta = g_time_end - g_time_start - print(int(g_time_delta * 1000)) - - -""" - Welcome to the "core" PySimpleGUI code.... - - It's a mess.... really... it's a mess internally... it's the external-facing interfaces that - are not a mess. The Elements and the methods for them are well-designed. - PEP8 - this code is far far from PEP8 compliant. - It was written PRIOR to learning that PEP8 existed. - - The variable and function naming in particular are not compliant. There is - liberal use of CamelVariableAndFunctionNames. If you've got a serious enough problem with this - that you'll pass on this package, then that's your right and I invite you to do so. However, if - perhaps you're a practical thinker where it's the results that matter, then you'll have no - trouble with this code base. There is consisency however. - - I truly hope you get a lot of enjoyment out of using PySimpleGUI. It came from good intentions. -""" - -# ----====----====----==== Constants the user CAN safely change ====----====----====----# -DEFAULT_WINDOW_ICON = DEFAULT_BASE64_ICON -DEFAULT_ELEMENT_SIZE = (250, 22) # In PIXELS -DEFAULT_BUTTON_ELEMENT_SIZE = (80, 25) # In PIXELS -DEFAULT_MARGINS = (0, 0) # For Qt, use a Column element with padding to get same effect as tkinter port -DEFAULT_ELEMENT_PADDING = (4, 2) # Padding between elements (row, col) in pixels -# DEFAULT_ELEMENT_PADDING = (0, 0) # Padding between elements (row, col) in pixels -DEFAULT_PIXELS_TO_CHARS_SCALING = (10, 35) # 1 character represents x by y pixels -DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT = (10, 20) # 1 character represents x by y pixels -DEFAULT_PIXEL_TO_CHARS_CUTOFF = 15 # number of chars that triggers using pixels instead of chars -DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE = 70 # number of chars that triggers using pixels instead of chars -DEFAULT_AUTOSIZE_TEXT = True -DEFAULT_AUTOSIZE_BUTTONS = True -DEFAULT_FONT = ('Helvetica', 10) -DEFAULT_TEXT_JUSTIFICATION = 'left' -DEFAULT_BORDER_WIDTH = 1 -DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form -DEFAULT_DEBUG_WINDOW_SIZE = (800, 400) -DEFAULT_WINDOW_LOCATION = (None, None) -MAX_SCROLLED_TEXT_BOX_HEIGHT = 50 -DEFAULT_TOOLTIP_TIME = 400 -DEFAULT_TOOLTIP_OFFSET = (20, -20) -#################### COLOR STUFF #################### -BLUES = ('#082567', '#0A37A3', '#00345B') -PURPLES = ('#480656', '#4F2398', '#380474') -GREENS = ('#01826B', '#40A860', '#96D2AB', '#00A949', '#003532') -YELLOWS = ('#F3FB62', '#F0F595') -TANS = ('#FFF9D5', '#F4EFCF', '#DDD8BA') -NICE_BUTTON_COLORS = ( - (GREENS[3], TANS[0]), - ('#000000', '#FFFFFF'), - ('#FFFFFF', '#000000'), - (YELLOWS[0], PURPLES[1]), - (YELLOWS[0], GREENS[3]), - (YELLOWS[0], BLUES[2]), -) - -COLOR_SYSTEM_DEFAULT = '1234567890' # Colors should never be this long - -DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default -OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = ('white', BLUES[0]) # Colors should never be this long - - -CURRENT_LOOK_AND_FEEL = 'DarkBlue3' -# CURRENT_LOOK_AND_FEEL = 'Dark Red' - -DEFAULT_ERROR_BUTTON_COLOR = ('#FFFFFF', '#FF0000') -DEFAULT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_ELEMENTS_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_SCROLLBAR_COLOR = None - -# A transparent button is simply one that matches the background -TRANSPARENT_BUTTON = 'This constant has been depricated. You must set your button background = background it is on for it to be transparent appearing' -# -------------------------------------------------------------------------------- -# Progress Bar Relief Choices -RELIEF_RAISED = 'raised' -RELIEF_SUNKEN = 'sunken' -RELIEF_FLAT = 'flat' -RELIEF_RIDGE = 'ridge' -RELIEF_GROOVE = 'groove' -RELIEF_SOLID = 'solid' - -RELIEF_TICK_POSITION_NO_TICKS = 'none' -RELIEF_TICK_POSITION_BOTH_SIDES = 'both' -RELIEF_TICK_POSITION_ABOVE = 'above' -RELIEF_TICK_POSITION_BELOW = 'below' -RELIEF_TICK_POSITION_LEFT = 'left' -RELIEF_TICK_POSITION_RIGHT = 'right' - -DEFAULT_PROGRESS_BAR_COMPUTE = ( - '#000000', - '#000000', -) # Means that the progress bar colors should be computed from other colors -DEFAULT_PROGRESS_BAR_COLOR = (GREENS[0], '#D0D0D0') # a nice green progress bar -DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL = (GREENS[0], '#D0D0D0') # a nice green progress bar -DEFAULT_PROGRESS_BAR_SIZE = (200, 20) # Size of Progress Bar (characters for length, pixels for width) -DEFAULT_PROGRESS_BAR_BORDER_WIDTH = 1 -DEFAULT_PROGRESS_BAR_RELIEF = RELIEF_GROOVE -PROGRESS_BAR_STYLES = ('default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative') -DEFAULT_PROGRESS_BAR_STYLE = 'default' -DEFAULT_METER_ORIENTATION = 'Horizontal' -DEFAULT_SLIDER_ORIENTATION = 'vertical' -DEFAULT_SLIDER_BORDER_WIDTH = 1 - -DEFAULT_SLIDER_RELIEF = RELIEF_TICK_POSITION_BOTH_SIDES -DEFAULT_FRAME_RELIEF = 'groove' - -DEFAULT_LISTBOX_SELECT_MODE = 'extended' -SELECT_MODE_MULTIPLE = 'multiple' -LISTBOX_SELECT_MODE_MULTIPLE = 'multiple' -SELECT_MODE_BROWSE = 'browse' -LISTBOX_SELECT_MODE_BROWSE = 'browse' -SELECT_MODE_EXTENDED = 'extended' -LISTBOX_SELECT_MODE_EXTENDED = 'extended' -SELECT_MODE_SINGLE = 'single' -LISTBOX_SELECT_MODE_SINGLE = 'single' -SELECT_MODE_CONTIGUOUS = 'contiguous' -LISTBOX_SELECT_MODE_CONTIGUOUS = 'contiguous' - -TABLE_SELECT_MODE_NONE = 'NONE' -TABLE_SELECT_MODE_BROWSE = 'BROWSE' -TABLE_SELECT_MODE_EXTENDED = 'EXTENDED' -DEFAULT_TABLE_SECECT_MODE = TABLE_SELECT_MODE_EXTENDED - -TITLE_LOCATION_TOP = 'N' -TITLE_LOCATION_BOTTOM = 'S' -TITLE_LOCATION_LEFT = 'W' -TITLE_LOCATION_RIGHT = 'E' -TITLE_LOCATION_TOP_LEFT = 'NW' -TITLE_LOCATION_TOP_RIGHT = 'NE' -TITLE_LOCATION_BOTTOM_LEFT = 'SW' -TITLE_LOCATION_BOTTOM_RIGHT = 'SE' - -THEME_DEFAULT = 'default' -THEME_WINNATIVE = 'winnative' -THEME_CLAM = 'clam' -THEME_ALT = 'alt' -THEME_CLASSIC = 'classic' -THEME_VISTA = 'vista' -THEME_XPNATIVE = 'xpnative' - -# DEFAULT_METER_ORIENTATION = 'Vertical' -# ----====----====----==== Constants the user should NOT f-with ====----====----====----# -ThisRow = 555666777 # magic number - -# DEFAULT_WINDOW_ICON = '' -MESSAGE_BOX_LINE_WIDTH = 60 - -# Icons for displaying system tray messages -SYSTEM_TRAY_MESSAGE_ICON_INFORMATION = QSystemTrayIcon.Information -SYSTEM_TRAY_MESSAGE_ICON_WARNING = QSystemTrayIcon.Warning -SYSTEM_TRAY_MESSAGE_ICON_CRITICAL = QSystemTrayIcon.Critical -SYSTEM_TRAY_MESSAGE_ICON_NOICON = QSystemTrayIcon.NoIcon - -# "Special" Key Values.. reserved -# Key representing a Read timeout -EVENT_TIMEOUT = TIMEOUT_EVENT = TIMEOUT_KEY = '__TIMEOUT__' -# Window closed event (user closed with X or destroyed using OS) -WIN_CLOSED = WINDOW_CLOSED = None - -# Key indicating should not create any return values for element -WRITE_ONLY_KEY = '__WRITE ONLY__' -EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED = '__DOUBLE_CLICKED__' -EVENT_SYSTEM_TRAY_ICON_ACTIVATED = '__ACTIVATED__' -EVENT_SYSTEM_TRAY_MESSAGE_CLICKED = '__MESSAGE_CLICKED__' - -# Meny key indicator character / string -MENU_KEY_SEPARATOR = '::' -MENU_DISABLED_CHARACTER = '!' - -SUPPRESS_ERROR_POPUPS = False - - -# ====================================================================== # -# One-liner functions that are handy as f_ck # -# ====================================================================== # -def RGB(red, green, blue): - return '#%02x%02x%02x' % (red, green, blue) - - -# ====================================================================== # -# Enums for types # -# ====================================================================== # -# ------------------------- Button types ------------------------- # -# todo Consider removing the Submit, Cancel types... they are just 'RETURN' type in reality -# uncomment this line and indent to go back to using Enums -# was an Enum previously ButtonType(Enum): -BUTTON_TYPE_BROWSE_FOLDER = 1 -BUTTON_TYPE_BROWSE_FILE = 2 -BUTTON_TYPE_BROWSE_FILES = 21 -BUTTON_TYPE_SAVEAS_FILE = 3 -BUTTON_TYPE_CLOSES_WIN = 5 -BUTTON_TYPE_CLOSES_WIN_ONLY = 6 -BUTTON_TYPE_READ_FORM = 7 -BUTTON_TYPE_REALTIME = 9 -BUTTON_TYPE_CALENDAR_CHOOSER = 30 -BUTTON_TYPE_COLOR_CHOOSER = 40 - -BROWSE_FILES_DELIMITER = ';' # the delimeter to be used between each file in the returned string - -# ------------------------- Element types ------------------------- # -# Used in Element - Was an enum once ElementType(Enum): -ELEM_TYPE_TEXT = 'text' -ELEM_TYPE_INPUT_TEXT = 'input' -ELEM_TYPE_INPUT_COMBO = 'combo' -ELEM_TYPE_INPUT_OPTION_MENU = 'option menu' -ELEM_TYPE_INPUT_RADIO = 'radio' -ELEM_TYPE_INPUT_MULTILINE = 'multiline' -ELEM_TYPE_MULTILINE_OUTPUT = 'multioutput' -ELEM_TYPE_INPUT_CHECKBOX = 'checkbox' -ELEM_TYPE_INPUT_SPIN = 'spind' -ELEM_TYPE_BUTTON = 'button' -ELEM_TYPE_IMAGE = 'image' -ELEM_TYPE_CANVAS = 'canvas' -ELEM_TYPE_FRAME = 'frame' -ELEM_TYPE_GRAPH = 'graph' -ELEM_TYPE_TAB = 'tab' -ELEM_TYPE_TAB_GROUP = 'tabgroup' -ELEM_TYPE_INPUT_SLIDER = 'slider' -ELEM_TYPE_INPUT_DIAL = 'dial' -ELEM_TYPE_INPUT_LISTBOX = 'listbox' -ELEM_TYPE_OUTPUT = 'output' -ELEM_TYPE_COLUMN = 'column' -ELEM_TYPE_MENUBAR = 'menubar' -ELEM_TYPE_PROGRESS_BAR = 'progressbar' -ELEM_TYPE_BLANK = 'blank' -ELEM_TYPE_TABLE = 'table' -ELEM_TYPE_TREE = 'tree' -ELEM_TYPE_ERROR = 'error' -ELEM_TYPE_SEPARATOR = 'separator' -ELEM_TYPE_STRETCH = 'stretch' -ELEM_TYPE_BUTTONMENU = 'buttonmenu' - -# ------------------------- Popup Buttons Types ------------------------- # -POPUP_BUTTONS_YES_NO = 1 -POPUP_BUTTONS_CANCELLED = 2 -POPUP_BUTTONS_ERROR = 3 -POPUP_BUTTONS_OK_CANCEL = 4 -POPUP_BUTTONS_OK = 0 -POPUP_BUTTONS_NO_BUTTONS = 5 - -# def apply_new_font(css_dict, font_string): -# return css_dict.update({k.strip().replace('_', '-') : v.strip() for k,v in font_string.replace('\n', '').split(';')}) - - -# ---------------------------------------------------------------------- # -# Cascading structure.... Objects get larger # -# Button # -# Element # -# Row # -# Form # -# ---------------------------------------------------------------------- # -# ------------------------------------------------------------------------- # -# Element CLASS # -# ------------------------------------------------------------------------- # -class Element: - def __init__( - self, - elem_type, - size=(None, None), - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - key=None, - k=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param elem_type: ??? - :type elem_type: ??? - :param size: w=characters-wide, h=rows-high - :type size: Tuple[int, int] (width, height) - :param auto_size_text: True if the Widget should be shrunk to exactly fit the number of chars to show - :type auto_size_text: bool - :param font: specifies the font family, size, etc (see docs for exact formats) - :type font: Union[str, Tuple[str, int]] - :param background_color: color of background. Can be in #RRGGBB format or a color name "black" - :type background_color: (str) - :param text_color: element's text color. Can be in #RRGGBB format or a color name "black" - :type text_color: (str) - :param key: Identifies an Element. Should be UNIQUE to this window. - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element (Default = True) - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - - if size_px != (None, None): - self.Size = size_px - else: - self.Size = _convert_tkinter_size_to_Qt(size) - - self.Type = elem_type - self.AutoSizeText = auto_size_text - # self.Pad = DEFAULT_ELEMENT_PADDING if pad is None else pad - self.Pad = pad - if font is not None and type(font) is not str: - self.Font = font - elif font is not None: - self.Font = font.split(' ') - else: - self.Font = font - - self.TKStringVar = None - self.TKIntVar = None - self.TKText = None - self.TKEntry = None - self.TKImage = None - - self.ParentForm = None - self.ParentContainer = None # will be a Form, Column, or Frame element - self.TextInputDefault = None - self.Position = (0, 0) # Default position Row 0, Col 0 - self.BackgroundColor = background_color if background_color is not None else DEFAULT_ELEMENT_BACKGROUND_COLOR - self.TextColor = text_color if text_color is not None else DEFAULT_ELEMENT_TEXT_COLOR - self.Key = key # dictionary key for return values - self.Tooltip = tooltip - self.TooltipObject = None - self.Visible = visible - self.metadata = metadata # type: Any - self.row_frame = None # type: QHBoxLayout - self.qt_styles = [] # type: List[QtStyle] - self.Widget = None # type: QWidget - - def _FindReturnKeyBoundButton(self, form): - for row in form.Rows: - for element in row: - if element.Type == ELEM_TYPE_BUTTON: - if element.BindReturnKey: - return element - if element.Type == ELEM_TYPE_COLUMN: - rc = self._FindReturnKeyBoundButton(element) - if rc is not None: - return rc - if element.Type == ELEM_TYPE_FRAME: - rc = self._FindReturnKeyBoundButton(element) - if rc is not None: - return rc - if element.Type == ELEM_TYPE_TAB_GROUP: - rc = self._FindReturnKeyBoundButton(element) - if rc is not None: - return rc - if element.Type == ELEM_TYPE_TAB: - rc = self._FindReturnKeyBoundButton(element) - if rc is not None: - return rc - return None - - def _ReturnKeyHandler(self, event): - MyForm = self.ParentForm - button_element = self._FindReturnKeyBoundButton(MyForm) - if button_element is not None: - button_element._ButtonCallBack() - - def _widget_was_created(self): - """ - Determines if a Widget was created for this element. - - :return: True if a Widget has been created previously (Widget is not None) - :rtype: (bool) - """ - if self.Widget is not None: - return True - else: - warnings.warn( - 'You cannot Update element with key = {} until the window has been Read or Finalized'.format(self.Key), - UserWarning, - ) - if not SUPPRESS_ERROR_POPUPS: - popup_error( - 'Unable to complete operation on element with key {}'.format(self.Key), - 'You cannot perform operations (such as calling update) on an Element until Window is read or finalized.', - 'Adding a "finalize=True" parameter to your Window creation will likely fix this.', - image=_random_error_icon(), - ) - return False - - def Update(self, widget, background_color=None, text_color=None, font=None, visible=None): - if not self._widget_was_created(): - return - - a_style = self.qt_styles[0] - # print(f'a_style = {a_style}') - if font is not None: # = apply_new_font(css_props_dict, create_style_from_font(font)) - a_style['font'] = create_style_from_font(font) - if text_color is not None: - a_style['color'] = text_color - self.TextColor = text_color - if background_color is not None: - a_style['background-color'] = background_color - self.BackgroundColor = background_color - - # print(f'a_style = {a_style}') - widget.setStyleSheet(a_style.build_css_string()) - set_widget_visiblity(widget, visible) - - def set_stylesheet(self, stylesheet): - """ - Sets the stylesheet for a Qt Widget - :param stylesheet: Stylesheet (string) to set stylesheet to - :type stylesheet: (str) - """ - try: - self.Widget.setStyleSheet(stylesheet) - except Exception as e: - print('** Error Setting Stylesheet **', e) - - def get_stylesheet(self): - """ - Returns the stylesheet for element's associated Qt Widget - :return: stylesheet - :rtype: (str) - """ - stylesheet = '' - try: - stylesheet = self.Widget.styleSheet() - except Exception as e: - print('** Error Setting Stylesheet **', e) - - return stylesheet - - update = Update - - # ---------------------------------- DUMMY METHODS - They don't do anything! ---------------------------------- - - # These methods are here for porting purposes only. They are meant to allow you to change your import statement - # from PySimpleGUI to PySimpleGUIQt and still be able to run your program. - - def expand(self, expand_x=False, expand_y=False, expand_row=True): - """ - WARNING - NOT USED IN PySimpleGUIQt port. Provided as dummy method - - :param expand_x: If True Element will expand in the Horizontal directions - :type expand_x: (bool) - :param expand_y: If True Element will expand in the Vertical directions - :type expand_y: (bool) - :param expand_row: If True the row containing the element will also expand. Without this your element is "trapped" within the row - :type expand_row: (bool) - :return: None - :rtype: None - """ - - return - - def __call__(self, *args, **kwargs): - """ - Makes it possible to "call" an already existing element. When you do make the "call", it actually calls - the Update method for the element. - Example: If this text element was in yoiur layout: - sg.Text('foo', key='T') - Then you can call the Update method for that element by writing: - window.FindElement('T')('new text value') - - - :param kwargs: - :return: - """ - return self.Update(*args, **kwargs) - - update = Update - - -# ---------------------------------------------------------------------- # -# Input Class # -# ---------------------------------------------------------------------- # -class InputText(Element): - def __init__( - self, - default_text='', - size=(None, None), - disabled=False, - password_char='', - justification=None, - background_color=None, - text_color=None, - font=None, - tooltip=None, - disabled_readonly_background_color=None, - disabled_readonly_text_color=None, - change_submits=False, - enable_events=False, - readonly=False, - border_width=None, - do_not_clear=True, - key=None, - k=None, - focus=False, - pad=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - Input a line of text Element - :param default_text: Text initially shown in the input box as a default value(Default value = '') - :type default_text: (str) - :param size: w=characters-wide, h=rows-high - :type size: Tuple[int, int] (width, height) - :param disabled: set disable state for element (Default = False) - :type disabled: (bool) - :param password_char: Password character if this is a password field (Default value = '') - :type password_char: (char) - :param justification: justification for data display. Valid choices - left, right, center - :type justification: (str) - :param background_color: color of background in one of the color formats - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param disabled_readonly_background_color: If state is set to readonly or disabled, the color to use for the background - :type disabled_readonly_background_color: (str) - :param disabled_readonly_text_color: If state is set to readonly or disabled, the color to use for the text - :type disabled_readonly_text_color: (str) - :param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead - :type change_submits: (bool) - :param enable_events: If True then changes to this element are immediately reported as an event. Use this instead of change_submits (Default = False) - :type enable_events: (bool) - :param do_not_clear: If False then the field will be set to blank after ANY event (button, any event) (Default = True) - :type do_not_clear: (bool) - :param readonly: If True then the user cannot modify the field (Default = False) - :type readonly: (bool) - :param border_width: width of border around element in pixels - :type border_width: (int) - :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param focus: Determines if initial focus should go to this element. - :type focus: (bool) - :param pad: Amount of padding to put around element. Normally (horizontal pixels, vertical pixels) but can be split apart further into ((horizontal left, horizontal right), (vertical above, vertical below)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param visible: set visibility state of the element (Default = True) - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.DefaultText = default_text - self.PasswordCharacter = password_char - bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - self.Justification = justification or 'left' - self.Disabled = disabled - self.ReadOnly = readonly - self.disabled_readonly_background_color = disabled_readonly_background_color - self.disabled_readonly_text_color = disabled_readonly_text_color - self.ChangeSubmits = change_submits or enable_events - self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH - self.Widget = self.QT_QLineEdit = None # type: QLineEdit - self.ValueWasChanged = False - super().__init__( - ELEM_TYPE_INPUT_TEXT, - size=size, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - font=font, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - - def _dragEnterEvent(self, e): - if e.mimeData().hasText(): - e.accept() - else: - e.ignore() - - def _dropEvent(self, e): - self.QT_QLineEdit.setText(e.mimeData().text()) - - class InputTextWidget(QWidget): - def __init__(self, qt_qlineedit, element): - self.QT_QLineEdit = qt_qlineedit - self.Element = element - super().__init__() - - def eventFilter(self, widget, event): - # print(f'Got input text event {event}') - if event.type() == QEvent.FocusIn and widget is self.QT_QLineEdit: - self.Element.ParentForm.FocusElement = self.Element - return QWidget.eventFilter(self, widget, event) - - def _QtCallbackFocusInEvent(self, value): - return - - def _QtCallbackFocusInEvent(self, value): - if not self.ChangeSubmits: - return - # if was changed using an "update" call, then skip the next changed callback - if self.ValueWasChanged: - self.ValueWasChanged = False - print('skipping update') - return - _element_callback_quit_mainloop(self) - - def _QtCallbackReturnPressed(self): - self._ReturnKeyHandler(None) - return - - def Update(self, value=None, disabled=None, select=None, background_color=None, text_color=None, font=None, visible=None): - if disabled is True: - self.QT_QLineEdit.setDisabled(True) - elif disabled is False: - self.QT_QLineEdit.setDisabled(False) - if value is not None: - self.QT_QLineEdit.setText(str(value)) - self.DefaultText = value - # was getting into an infinite loop when the update was triggering a text changed callback, but unable - # to dupliate this - # self.ValueWasChanged = True - if select: - self.QT_QLineEdit.setSelection(0, QtGui.QTextCursor.End) - super().Update(self.QT_QLineEdit, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def Get(self): - return self.QT_QLineEdit.text() - # return self.TKStringVar.get() - - def SetFocus(self): - self.QT_QLineEdit.setFocus() - - get = Get - set_focus = SetFocus - update = Update - - -# ------------------------- INPUT TEXT lazy functions ------------------------- # -In = InputText -Input = InputText -I = InputText - - -# ---------------------------------------------------------------------- # -# Combo # -# ---------------------------------------------------------------------- # -class Combo(Element): - def __init__( - self, - values, - default_value=None, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - disabled=False, - key=None, - k=None, - pad=None, - tooltip=None, - readonly=False, - visible_items=10, - font=None, - auto_complete=True, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - Input Combo Box Element (also called Dropdown box) - - :param values: values to choose. While displayed as text, the items returned are what the caller supplied, not text - :type values: List[Any] or Tuple[Any] - :param default_value: Choice to be displayed as initial value. Must match one of values variable contents - :type default_value: (Any) - :param size: width = characters-wide, height = rows-high - :type size: Tuple[int, int] (width, height) - :param auto_size_text: True if element should be the same size as the contents - :type auto_size_text: (bool) - :param background_color: Color for Element. Text or RGB Hex - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param change_submits: DEPRICATED DO NOT USE. Use `enable_events` instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. Combo event is when a choice is made - :type enable_events: (bool) - :param disabled: set disable state for element - :type disabled: (bool) - :param key: Used with window.FindElement and with return values to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text that will appear when mouse hovers over this element - :type tooltip: (str) - :param readonly: make element readonly (user can't change). True means user cannot change - :type readonly: (bool) - :param visible_items: ??? - :type visible_items: ??? - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param auto_complete: ??? - :type auto_complete: ??? - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.Values = values - self.DefaultValue = default_value - self.ChangeSubmits = change_submits or enable_events - self.TKCombo = None - # self.InitializeAsDisabled = disabled - self.Disabled = disabled - self.Readonly = readonly - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.VisibleItems = visible_items - self.AutoComplete = auto_complete - self.Widget = self.QT_ComboBox = None # type: QComboBox - super().__init__( - ELEM_TYPE_INPUT_COMBO, - size=size, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - font=font or DEFAULT_FONT, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - - def _QtCurrentItemChanged(self, state): - if self.ChangeSubmits: - _element_callback_quit_mainloop(self) - - def Update( - self, - value=None, - values=None, - set_to_index=None, - disabled=None, - readonly=None, - background_color=None, - text_color=None, - font=None, - visible=None, - ): - if values is not None: - self.Values = values - for i in range(self.QT_ComboBox.count()): - self.QT_ComboBox.removeItem(0) - self.QT_ComboBox.addItems(values) - if value is not None: - for index, v in enumerate(self.Values): - if v == value: - self.QT_ComboBox.setCurrentIndex(index) - break - if set_to_index is not None: - self.QT_ComboBox.setCurrentIndex(set_to_index) - if disabled == True: - self.QT_ComboBox.setDisabled(True) - elif disabled == False: - self.QT_ComboBox.setDisabled(False) - if readonly is not None: - self.Readonly = readonly - - super().Update(self.QT_ComboBox, background_color=background_color, text_color=text_color, font=font, visible=visible) - - update = Update - - -# ------------------------- INPUT COMBO Element lazy functions ------------------------- # -InputCombo = Combo -DropDown = InputCombo -Drop = InputCombo - - -# ---------------------------------------------------------------------- # -# Option Menu # -# ---------------------------------------------------------------------- # -class OptionMenu(Element): - def __init__( - self, - values, - default_value=None, - size=(None, None), - disabled=False, - auto_size_text=None, - background_color=None, - text_color=None, - key=None, - k=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - InputOptionMenu - NOT USED IN QT - :param values: Values to be displayed - :type values: List[Any] or Tuple[Any] - :param default_value: the value to choose by default - :type default_value: (Any) - :param size: size in characters (wide) and rows (high) - :type size: Tuple[int, int] (width, height) - :param disabled: control enabled / disabled - :type disabled: (bool) - :param auto_size_text: True if size of Element should match the contents of the items - :type auto_size_text: (bool) - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param key: Used with window.FindElement and with return values to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text that will appear when mouse hovers over this element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.Values = values - self.DefaultValue = default_value - self.TKOptionMenu = None - self.Disabled = disabled - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - super().__init__( - ELEM_TYPE_INPUT_OPTION_MENU, - size=size, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - - def Update(self, value=None, values=None, disabled=None): - return - - update = Update - - -# ------------------------- OPTION MENU Element lazy functions ------------------------- # -InputOptionMenu = OptionMenu - - -# ---------------------------------------------------------------------- # -# Listbox # -# ---------------------------------------------------------------------- # -class Listbox(Element): - def __init__( - self, - values, - default_values=None, - select_mode=None, - change_submits=False, - enable_events=False, - bind_return_key=False, - size=(None, None), - disabled=False, - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - key=None, - k=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param values: list of values to display. Can be any type including mixed types as long as they have __str__ method - :type values: List[Any] or Tuple[Any] - :param default_values: which values should be initially selected - :type default_values: List[Any] - :param select_mode: Select modes are used to determine if only 1 item can be selected or multiple and how they can be selected. Valid choices begin with "LISTBOX_SELECT_MODE_" and include: LISTBOX_SELECT_MODE_SINGLE LISTBOX_SELECT_MODE_MULTIPLE LISTBOX_SELECT_MODE_BROWSE LISTBOX_SELECT_MODE_EXTENDED - :type select_mode: [enum] - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. Listbox generates events when an item is clicked - :type enable_events: (bool) - :param bind_return_key: If True, then the return key will cause a the Listbox to generate an event - :type bind_return_key: (bool) - :param size: width = characters-wide, height = rows-high - :type size: Tuple(int, int) (width, height) - :param disabled: set disable state for element - :type disabled: (bool) - :param auto_size_text: True if element should be the same size as the contents - :type auto_size_text: (bool) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param key: Used with window.FindElement and with return values to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.Values = values - self.DefaultValues = default_values - self.TKListbox = None - self.ChangeSubmits = change_submits or enable_events - self.BindReturnKey = bind_return_key - self.Disabled = disabled - if select_mode == LISTBOX_SELECT_MODE_BROWSE: - self.SelectMode = SELECT_MODE_BROWSE - elif select_mode == LISTBOX_SELECT_MODE_EXTENDED: - self.SelectMode = SELECT_MODE_EXTENDED - elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE: - self.SelectMode = SELECT_MODE_MULTIPLE - elif select_mode == LISTBOX_SELECT_MODE_SINGLE: - self.SelectMode = SELECT_MODE_SINGLE - elif select_mode == LISTBOX_SELECT_MODE_CONTIGUOUS: - self.SelectMode = SELECT_MODE_CONTIGUOUS - else: - self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Widget = self.QT_ListWidget = None # type: QListWidget - tsize = size # convert tkinter size to pixels - if size[0] is not None and size[0] < 100: - tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] - - super().__init__( - ELEM_TYPE_INPUT_LISTBOX, - size=tsize, - auto_size_text=auto_size_text, - font=font, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - - def _QtCurrentRowChanged(self, state): - if self.ChangeSubmits: - _element_callback_quit_mainloop(self) - - def Update( - self, - values=None, - disabled=None, - set_to_index=None, - background_color=None, - text_color=None, - font=None, - visible=None, - ): - if values is not None: - self.Values = values - for i in range(self.QT_ListWidget.count()): - self.QT_ListWidget.takeItem(0) - items = [str(v) for v in self.Values] - self.QT_ListWidget.addItems(items) - if disabled == True: - self.QT_ListWidget.setDisabled(True) - elif disabled == False: - self.QT_ListWidget.setDisabled(False) - if set_to_index is not None: - self.QT_ListWidget.setCurrentRow(set_to_index) - super().Update(self.QT_ListWidget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - return - - def SetValue(self, values): - # for index, item in enumerate(self.Values): - for index, value in enumerate(self.Values): - item = self.QT_ListWidget.item(index) - if value in values: - self.QT_ListWidget.setItemSelected(item, True) - - def GetListValues(self): - return self.Values - - def get(self): - """ - Gets the current value of the Element as it would be represented in the results dictionary. - Normally you would NOT be using this method, but instead using the return values dictionary - that is returned from reading your window - - :return: (List[Any]) The currently selected items in the listbox - """ - value = [] - selected_items = [item.text() for item in self.QT_ListWidget.selectedItems()] - for v in self.Values: - if str(v) in selected_items: - value.append(v) - return value - - get_list_values = GetListValues - set_value = SetValue - update = Update - - -LBox = Listbox -LB = Listbox - - -# ---------------------------------------------------------------------- # -# Radio # -# ---------------------------------------------------------------------- # -class Radio(Element): - def __init__( - self, - text, - group_id, - default=False, - disabled=False, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - font=None, - key=None, - k=None, - pad=None, - tooltip=None, - change_submits=False, - enable_events=False, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param text: Text to display next to button - :type text: (str) - :param group_id: Groups together multiple Radio Buttons. Any type works - :type group_id: (Any) - :param default: Set to True for the one element of the group you want initially selected - :type default: (bool) - :param disabled: set disable state - :type disabled: (bool) - :param size: (width, height) width = characters-wide, height = rows-high - :type size: Tuple[int, int] - :param auto_size_text: if True will size the element to match the length of the text - :type auto_size_text: (bool) - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param key: Used with window.FindElement and with return values to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. Radio Button events happen when an item is selected - :type enable_events: (bool) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.InitialState = default - self.Text = text - self.GroupID = group_id - self.Value = None - self.Disabled = disabled - self.TextColor = text_color or DEFAULT_TEXT_COLOR - self.ChangeSubmits = change_submits or enable_events - self.Widget = self.QT_Radio_Button = None # type: QRadioButton - self.QT_RadioButtonGroup = None # type: QButtonGroup - - super().__init__( - ELEM_TYPE_INPUT_RADIO, - size=size, - auto_size_text=auto_size_text, - font=font, - background_color=background_color, - text_color=self.TextColor, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - - def Update(self, value=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): - if value is not None: - self.InitialState = value - if disabled: - self.QT_Radio_Button.setDisabled(True) - else: - self.QT_Radio_Button.setDisabled(False) - if value is True: - self.QT_Radio_Button.setChecked(True) - if value is False: - self.QT_RadioButtonGroup.setExclusive(False) - self.QT_Radio_Button.setChecked(False) - self.QT_RadioButtonGroup.setExclusive(True) - super().Update(self.QT_Radio_Button, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def reset_group(self): - self.QT_Radio_Button.setChecked(True) - self.QT_RadioButtonGroup.setExclusive(False) - self.QT_Radio_Button.setChecked(False) - self.QT_RadioButtonGroup.setExclusive(True) - - def _QtCallbackValueChanged(self, value): - if not self.ChangeSubmits: - return - _element_callback_quit_mainloop(self) - - update = Update - - -R = Radio -Rad = Radio - - -# ---------------------------------------------------------------------- # -# Checkbox # -# ---------------------------------------------------------------------- # -class Checkbox(Element): - def __init__( - self, - text, - default=False, - size=(None, None), - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - disabled=False, - key=None, - k=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param text: Text to display next to checkbox - :type text: (str) - :param default: Set to True if you want this checkbox initially checked - :type default: (bool) - :param size: (width, height) width = characters-wide, height = rows-high - :type size: Tuple[int, int] - :param auto_size_text: if True will size the element to match the length of the text - :type auto_size_text: (bool) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. Checkbox events happen when an item changes - :type enable_events: (bool) - :param disabled: set disable state - :type disabled: (bool) - :param key: Used with window.FindElement and with return values to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.Text = text - self.InitialState = default - self.Value = None - self.TKCheckbutton = None - self.Disabled = disabled - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.ChangeSubmits = change_submits or enable_events - self.Widget = self.QT_Checkbox = None # type: QCheckBox - - super().__init__( - ELEM_TYPE_INPUT_CHECKBOX, - size=size, - auto_size_text=auto_size_text, - font=font, - background_color=background_color, - text_color=self.TextColor, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - - def QtCallbackStateChanged(self, state): - if self.ChangeSubmits: - _element_callback_quit_mainloop(self) - - def Get(self): - return self.QT_Checkbox.isChecked() - - def Update(self, value=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): - self.QT_Checkbox.setChecked(value or False) - if disabled == True: - self.QT_Checkbox.setDisabled(True) - elif disabled == False: - self.QT_Checkbox.setDisabled(False) - super().Update(self.QT_Checkbox, background_color=background_color, text_color=text_color, font=font, visible=visible) - - get = Get - update = Update - - -# ------------------------- CHECKBOX Element lazy functions ------------------------- # -CB = Checkbox -CBox = Checkbox -Check = Checkbox - - -# ---------------------------------------------------------------------- # -# Spin # -# ---------------------------------------------------------------------- # - - -class Spin(Element): - # Values = None - # TKSpinBox = None - def __init__( - self, - values, - initial_value=None, - disabled=False, - change_submits=False, - enable_events=False, - size=(None, None), - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - key=None, - k=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - Spinner Element - :param values: List of valid values - :type values: Tuple[Any] or List[Any] - :param initial_value: Initial item to show in window. Choose from list of values supplied - :type initial_value: (Any) - :param disabled: set disable state - :type disabled: (bool) - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. Spin events happen when an item changes - :type enable_events: (bool) - :param size: (width, height) width = characters-wide, height = rows-high - :type size: Tuple[int, int] - :param auto_size_text: if True will size the element to match the length of the text - :type auto_size_text: (bool) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param key: Used with window.FindElement and with return values to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.Values = values - self.DefaultValue = initial_value - self.ChangeSubmits = change_submits or enable_events - self.Disabled = disabled - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Widget = self.QT_Spinner = None # type: StringBox - - super().__init__( - ELEM_TYPE_INPUT_SPIN, - size, - auto_size_text, - font=font, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - return - - class StringBox(QSpinBox): - def __init__(self, strings, parent=None): - super(Spin.StringBox, self).__init__(parent) - self.setStrings(strings) - - def strings(self): - return self._strings - - def setStrings(self, strings): - self._strings = tuple(strings) - self._values = dict(zip(strings, range(len(strings)))) - self.setRange(0, len(strings) - 1) - - def textFromValue(self, value): - return str(self._strings[value]) - - def valueFromText(self, text): - return self._values[text] - - def _QtCallbackValueChanged(self, value): - if not self.ChangeSubmits: - return - _element_callback_quit_mainloop(self) - - def Update(self, value=None, values=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): - if values != None: - self.Values = values - self.QT_Spinner.setStrings(values) - # self.QT_Spinner.setRange(self.Values[0], self.Values[1]) - if value is not None: - # self.QT_Spinner.setValue(value) - try: - self.QT_Spinner.setValue(self.QT_Spinner.valueFromText(value)) - self.DefaultValue = value - except: - pass - if disabled == True: - self.QT_Spinner.setDisabled(True) - elif disabled == False: - self.QT_Spinner.setDisabled(False) - super().Update(self.QT_Spinner, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def Get(self): - return self.QT_Spinner.value() - - get = Get - update = Update - - -# ---------------------------------------------------------------------- # -# Multiline # -# ---------------------------------------------------------------------- # -class Multiline(Element): - def __init__( - self, - default_text='', - enter_submits=False, - disabled=False, - autoscroll=False, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - do_not_clear=True, - key=None, - k=None, - write_only=False, - focus=False, - font=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param default_text: Initial text to show - :type default_text: (str) - :param enter_submits: if True, the Window.Read call will return is enter key is pressed in this element - :type enter_submits: (bool) - :param disabled: set disable state - :type disabled: (bool) - :param autoscroll: If True the contents of the element will automatically scroll as more data added to the end - :type autoscroll: (bool) - :param size: (width, height) width = characters-wide, height = rows-high - :type size: Tuple[int, int] - :param auto_size_text: if True will size the element to match the length of the text - :type auto_size_text: (bool) - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. Spin events happen when an item changes - :type enable_events: (bool) - :param do_not_clear: if False the element will be cleared any time the Window.Read call returns - :type do_not_clear: bool - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param write_only: If True then no entry will be added to the values dictionary when the window is read - :type write_only: bool - :param focus: if True initial focus will go to this element - :type focus: (bool) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.DefaultText = default_text - self.EnterSubmits = enter_submits - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Autoscroll = autoscroll - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.WriteOnly = write_only - tsize = ( - _convert_tkinter_size_to_Qt( - size, - scaling=DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT, - height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE, - ) - if size[0] is not None - else size_px - ) - - self.Widget = self.QT_TextEdit = None # type: QTextEdit - - super().__init__( - ELEM_TYPE_INPUT_MULTILINE, - size=(None, None), - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - font=font or DEFAULT_FONT, - visible=visible, - size_px=tsize, - metadata=metadata, - ) - return - - class MultiQWidget(QWidget): - def __init__(self, qt_textedit, element): - self.QT_TextEdit = qt_textedit - self.Element = element - super().__init__() - - def eventFilter(self, widget, event): - if self.Element.EnterSubmits and event.type() == QEvent.KeyPress and widget is self.QT_TextEdit: - key = event.key() - if key in (Qt.Key_Return, Qt.Key_Enter): - self.Element._ReturnKeyHandler(0) - if event.type() == QEvent.FocusIn and widget is self.QT_TextEdit: - self.Element.ParentForm.FocusElement = self.Element - return QWidget.eventFilter(self, widget, event) - - def _QtCallbackFocusInEvent(self): - if not self.ChangeSubmits: - return - _element_callback_quit_mainloop(self) - - def _dragEnterEvent(self, e): - if e.mimeData().hasText(): - e.accept() - else: - e.ignore() - - def _dropEvent(self, e): - self.Widget.setText(e.mimeData().text()) - - def Update( - self, - value=None, - disabled=None, - append=False, - autoscroll=False, - background_color=None, - text_color=None, - font=None, - text_color_for_value=None, - background_color_for_value=None, - visible=None, - readonly=None, - ): - """ - Changes some of the settings for the Multiline Element. Must call `Window.read` or `Window.finalize` or "finalize" the window using finalize parameter prior - - :param value: (str) new text to display - :param disabled: (bool) disable or enable state of the element - :param append: (bool) if True then new value will be added onto the end of the current value. if False then contents will be replaced. - :param autoscroll: (bool) if True cursor will be moved to end of element after updating - :param background_color: (str) color of background - :param text_color: (str) color of the text - :param font: Union[str, Tuple[str, int]] specifies the font family, size, etc - :param text_color_for_value: (str) color of the new text being added - :param visible: (bool) set visibility state of the element - :param autoscroll: (bool) if True then contents of element are scrolled down when new text is added to the end - """ - - if value is not None and not append: - self.DefaultText = value - self.QT_TextEdit.setText(str(value)) - elif value is not None and append: - self.DefaultText = value - # self.QT_TextEdit.setText(self.QT_TextEdit.toPlainText() + str(value)) # original code - # self.QT_TextEdit.append(str(value)) # can't use because adds a newline - if text_color_for_value is not None: - self.QT_TextEdit.setTextColor(text_color_for_value) - if background_color_for_value is not None: - self.QT_TextEdit.setTextBackgroundColor(background_color_for_value) - self.QT_TextEdit.insertPlainText(str(value)) - if self.Autoscroll or autoscroll and autoscroll is not False: - self.QT_TextEdit.moveCursor(QtGui.QTextCursor.End) - if text_color_for_value is not None: - self.QT_TextEdit.setTextColor(self.TextColor) - if background_color_for_value is not None: - self.QT_TextEdit.setTextBackgroundColor(self.BackgroundColor) - if disabled is True: - self.QT_TextEdit.setDisabled(True) - elif disabled is False: - self.QT_TextEdit.setDisabled(False) - if readonly is True: - self.QT_TextEdit.setReadOnly(True) - elif readonly is False: - self.QT_TextEdit.setReadOnly(False) - super().Update(self.QT_TextEdit, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def Get(self): - return self.QT_TextEdit.toPlainText() - - def SetFocus(self): - self.QT_TextEdit.setFocus() - - def print(self, *args, end=None, sep=None, text_color=None, background_color=None, autoscroll=True): - """ - Print like Python normally prints except route the output to a multline element and also add colors if desired - - :param args: The arguments to print - :type args: List[Any] - :param end: (str) The end char to use just like print uses - :type end: (str) - :param sep: (str) The separation character like print uses - :type sep: (str) - :param text_color: The color of the text - :type text_color: (str) - :param background_color: The background color of the line - :type background_color: (str) - :param autoscroll: (bool) If True cursor is moved to end after print - :type autoscroll: (bool) - """ - _print_to_element( - self, - *args, - end=end, - sep=sep, - text_color=text_color, - background_color=background_color, - autoscroll=autoscroll, - ) - - get = Get - set_focus = SetFocus - update = Update - - -ML = Multiline -MLine = Multiline - - -# ---------------------------------------------------------------------- # -# ScrolledOutput # -# ---------------------------------------------------------------------- # -class MultilineOutput(Element): - def __init__( - self, - default_text='', - enter_submits=False, - disabled=False, - autoscroll=False, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - do_not_clear=True, - key=None, - k=None, - focus=False, - font=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param default_text: default value to put into input area - :type default_text: (str) - :param enter_submits: if True, the Window.Read call will return is enter key is pressed in this element - :type enter_submits: (bool) - :param disabled: set disable state - :type disabled: (bool) - :param autoscroll: If True cursor is moved to end after print - :typep autoscroll: (bool) - :param size: (width, height) width = characters-wide, height = rows-high - :type size: Tuple[int, int] - :param auto_size_text: if True size of the Text Element will be sized to fit the string provided in 'text' parm - :type auto_size_text: (bool) - :param background_color: color of background in one of the color formats - :type background_color: (str) - ::param text_color: color of the text - :type text_color: (str) - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. If this button is a target, should it generate an event when filled in - :type enable_events: (bool) - :param do_not_clear: if False the element will be cleared any time the Window.Read call returns - :type do_not_clear: (bool) - :param focus: if True, initial focus will be put on this button - :type focus: (bool) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.DefaultText = default_text - self.EnterSubmits = enter_submits - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Autoscroll = autoscroll - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.Widget = self.QT_TextBrowser = None # type: QTextBrowser - tsize = ( - _convert_tkinter_size_to_Qt( - size, - scaling=DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT, - height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE, - ) - if size[0] is not None - else size_px - ) - super().__init__( - ELEM_TYPE_MULTILINE_OUTPUT, - size=(None, None), - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - font=font or DEFAULT_FONT, - visible=visible, - size_px=tsize, - metadata=metadata, - ) - return - - def Update( - self, - value=None, - disabled=None, - append=False, - autoscroll=None, - background_color=None, - text_color=None, - font=None, - text_color_for_value=None, - background_color_for_value=None, - visible=None, - ): - if value is not None and not append: - self.QT_TextBrowser.setText(str(value)) - elif value is not None and append: - self.QT_TextBrowser.insertPlainText(str(value)) - # self.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) - if disabled == True: - self.QT_TextBrowser.setDisabled(True) - elif disabled == False: - self.QT_TextBrowser.setDisabled(False) - if self.Autoscroll or autoscroll and autoscroll is not False: - self.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) - super().Update(self.QT_TextBrowser, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def Get(self): - return self.QT_TextBrowser.toPlainText() - - def print(self, *args, end=None, sep=None, text_color=None, background_color=None, autoscroll=True): - """ - Print like Python normally prints except route the output to a multline element and also add colors if desired - - :param args: The arguments to print - :type args: List[Any] - :param end: The end char to use just like print uses - :type end: (str) - :param sep: The separation character like print uses - :type sep: (str) - :param text_color: The color of the text - :type text_color: (str) - :param background_color: The background color of the line - :type background_color: (str) - :param autoscroll: If True cursor is moved to end after print - :typep autoscroll: (bool) - """ - _print_to_element( - self, - *args, - end=end, - sep=sep, - text_color=text_color, - background_color=background_color, - autoscroll=autoscroll, - ) - - get = Get - update = Update - - -MLineOut = Multiline - - -# ---------------------------------------------------------------------- # -# Text # -# ---------------------------------------------------------------------- # -class Text(Element): - def __init__( - self, - text='', - size=(None, None), - auto_size_text=None, - click_submits=None, - enable_events=False, - relief=None, - font=None, - text_color=None, - background_color=None, - justification=None, - pad=None, - margins=None, - key=None, - k=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param text: The text to display. Can include /n to achieve multiple lines. Will convert (optional) parameter into a string - :type text: (Any) - :param size: (width, height) width = characters-wide, height = rows-high - :type size: Tuple[int, int] - :param auto_size_text: if True size of the Text Element will be sized to fit the string provided in 'text' parm - :type auto_size_text: (bool) - :param click_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type click_submits: (bool) - :param enable_events: Turns on the element specific events. Text events happen when the text is clicked - :type enable_events: (bool) - :param relief: relief style around the text. Values are same as progress meter relief values. Should be a constant that is defined at starting with "RELIEF_" - `RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID` - :type relief: (str/enum) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param text_color: color of the text - :type text_color: (str) - :param background_color: color of background - :type background_color: (str) - :param justification: how string should be aligned within space provided by size. Valid choices = `left`, `right`, `center` - :type justification: (str) - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param margins: ??? - :type margins: ??? - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.DisplayText = str(text) - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.Justification = justification or 'left' - self.Relief = relief - self.ClickSubmits = click_submits or enable_events - self.Margins = margins - if background_color is None: - bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - else: - bg = background_color - self.Widget = self.QT_Label = None # type: QLabel - - super().__init__( - ELEM_TYPE_TEXT, - size, - auto_size_text, - background_color=bg, - font=font if font else DEFAULT_FONT, - text_color=self.TextColor, - visible=visible, - pad=pad, - key=key, - tooltip=tooltip, - size_px=size_px, - metadata=metadata, - ) - return - - def _QtCallbackTextClicked(self, event): - if not self.ClickSubmits: - return - _element_callback_quit_mainloop(self) - - def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): - """ - - :param value: - :param background_color: - :param text_color: - :param font: - :param visible: - :return: - """ - if value is not None: - self.DisplayText = str(value) - self.QT_Label.setText(str(value)) - super().Update(self.QT_Label, background_color=background_color, text_color=text_color, font=font, visible=visible) - - update = Update - - -# ------------------------- Text Element lazy functions ------------------------- # -Txt = Text -T = Text - - -# ---------------------------------------------------------------------- # -# Output # -# Routes stdout, stderr to a scrolled window # -# ---------------------------------------------------------------------- # -class Output(Element): - def __init__( - self, - size=(None, None), - background_color=None, - text_color=None, - pad=None, - font=None, - tooltip=None, - key=None, - k=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param size: (width, height) w=characters-wide, h=rows-high - :type size: Tuple[int, int] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self._TKOut = None - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Widget = self.QT_TextBrowser = None # type: QTextBrowser - tsize = ( - size_px - if size_px != (None, None) - else ( - _convert_tkinter_size_to_Qt( - size, - scaling=DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT, - height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE, - ) - if size[0] is not None - else size - ) - ) - - super().__init__( - ELEM_TYPE_OUTPUT, - size=(None, None), - background_color=bg, - text_color=fg, - pad=pad, - font=font, - tooltip=tooltip, - key=key, - visible=visible, - size_px=tsize, - metadata=metadata, - ) - - def _reroute_stdout(self): - self.my_stdout = sys.stdout - self.my_stderr = sys.stderr - sys.stdout = self - sys.stderr = self - - def write(self, m): - """ - MUST be called write. Don't mess with. It's called by Python itself because of reroute - :param m: - :return: - """ - self.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) - self.QT_TextBrowser.insertPlainText(str(m)) - - # if self.my_stdout: - # self.my_stdout.write(str(m)) - - def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): - if value is not None: - self.QT_TextBrowser.setText(value) - super().Update(self.QT_TextBrowser, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def __del__(self): - sys.stdout = self.my_stdout - sys.stderr = self.my_stderr - - update = Update - write = write - - -# ---------------------------------------------------------------------- # -# Button Class # -# ---------------------------------------------------------------------- # -class Button(Element): - def __init__( - self, - button_text='', - button_type=BUTTON_TYPE_READ_FORM, - target=(None, None), - tooltip=None, - file_types=(('ALL Files', '*'),), - initial_folder=None, - disabled=False, - change_submits=False, - enable_events=False, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param button_text: Text to be displayed on the button - :type button_text: (str) - :param button_type: You should NOT be setting this directly. ONLY the shortcut functions set this - :type button_type: (int) - :param target: key or (row,col) target for the button. Note that -1 for column means 1 element to the left of this one. The constant ThisRow is used to indicate the current row. The Button itself is a valid target for some types of button - :type target: Union[str, Tuple[int, int]] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param file_types: the filetypes that will be used to match files. To indicate all files: (("ALL Files", "*.*"),). Note - NOT SUPPORTED ON MAC - :type file_types: Tuple[Tuple[str, str], ...] - :param initial_folder: starting path for folders and files - :type initial_folder: (str) - :param disabled: If True button will be created disabled - :type disabled: (bool) - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. If this button is a target, should it generate an event when filled in - :type enable_events: (bool) - :param image_filename: image filename if there is a button image. GIFs and PNGs only. - :type image_filename: (str) - :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data - :type image_data: Union[bytes, str] - :param image_size: Size of the image in pixels (width, height) - :type image_size: Tuple[int, int] - :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc - :type image_subsample: (int) - :param border_width: width of border around button in pixels - :type border_width: (int) - :param size: (width, height) of the button in characters wide, rows high - :type size: Tuple[int, int] - :param auto_size_button: if True the button size is sized to fit the text - :type auto_size_button: (bool) - :param button_color: of button. Easy to remember which is which if you say "ON" between colors. "red" on "green". - :type button_color: Tuple[str, str] == (text color, background color) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param bind_return_key: If True the return key will cause this button to be pressed - :type bind_return_key: (bool) - :param focus: if True, initial focus will be put on this button - :type focus: (bool) - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.AutoSizeButton = auto_size_button - self.BType = button_type - self.FileTypes = file_types - self.TKButton = None - self.Target = target - self.ButtonText = str(button_text) - - # Button colors can be a tuple (text, background) or a string with format "text on background" - if button_color is None: - button_color = DEFAULT_BUTTON_COLOR - else: - try: - if isinstance(button_color, str): - button_color = button_color.split(' on ') - except Exception as e: - print('* cprint warning * you messed up with color formatting', e) - - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.TextColor = self.ButtonColor[0] - self.BackgroundColor = self.ButtonColor[1] - self.ImageFilename = image_filename - self.ImageData = image_data - self.ImageSize = image_size - self.ImageSubsample = image_subsample - self.UserData = None - self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH - self.BindReturnKey = bind_return_key - self.Focus = focus - self.TKCal = None - self.CalendarCloseWhenChosen = None - self.DefaultDate_M_D_Y = (None, None, None) - self.InitialFolder = initial_folder - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.Widget = self.QT_QPushButton = None # type: QPushButton - self.ColorChosen = None - # self.temp_size = size if size != (NONE, NONE) else - - super().__init__( - ELEM_TYPE_BUTTON, - size=size, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - text_color=self.TextColor, - background_color=self.BackgroundColor, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - return - - # Realtime button release callback - def _ButtonReleaseCallBack(self, parm): - self.LastButtonClickedWasRealtime = False - self.ParentForm.LastButtonClicked = None - - # Realtime button callback - def _ButtonPressCallBack(self, parm): - self.ParentForm.LastButtonClickedWasRealtime = True - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - if self.ParentForm.CurrentlyRunningMainloop: - pass # kick out of loop if read was called - - # ------- Button Callback ------- # - def _ButtonCallBack(self): - - # print('Button callback') - - # print(f'Parent = {self.ParentForm} Position = {self.Position}') - # Buttons modify targets or return from the form - # If modifying target, get the element object at the target and modify its StrVar - target = self.Target - target_element = None - if target[0] == ThisRow: - target = [self.Position[0], target[1]] - if target[1] < 0: - target[1] = self.Position[1] + target[1] - strvar = None - should_submit_window = False - if target == (None, None): - strvar = self.TKStringVar - else: - if not isinstance(target, str): - if target[0] < 0: - target = [self.Position[0] + target[0], target[1]] - target_element = self.ParentContainer._GetElementAtLocation(target) - else: - target_element = self.ParentForm.FindElement(target) - try: - strvar = target_element.TKStringVar - except: - pass - try: - if target_element.ChangeSubmits: - should_submit_window = True - except: - pass - filetypes = (('ALL Files', '*'),) if self.FileTypes is None else self.FileTypes - if self.BType == BUTTON_TYPE_BROWSE_FOLDER: - folder_name = QFileDialog.getExistingDirectory(dir=self.InitialFolder) - if folder_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = folder_name - else: - target_element.Update(folder_name) - elif self.BType == BUTTON_TYPE_BROWSE_FILE: - qt_types = convert_tkinter_filetypes_to_qt(self.FileTypes) - file_name = QFileDialog.getOpenFileName(dir=self.InitialFolder, filter=qt_types) - if file_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_name - else: - target_element.Update(file_name[0]) - elif self.BType == BUTTON_TYPE_COLOR_CHOOSER: - qcolor = QColorDialog.getColor() - rgb_color = qcolor.getRgb() - color = '#' + ''.join('%02x' % i for i in rgb_color[:3]) - if self.Target == (None, None): - self.FileOrFolderName = color - else: - target_element.Update(color) - elif self.BType == BUTTON_TYPE_BROWSE_FILES: - qt_types = convert_tkinter_filetypes_to_qt(self.FileTypes) - file_name = QFileDialog.getOpenFileNames(dir=self.InitialFolder, filter=qt_types) - if file_name != '': - file_name = BROWSE_FILES_DELIMITER.join(file_name[0]) - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_name - else: - target_element.Update(file_name) - elif self.BType == BUTTON_TYPE_SAVEAS_FILE: - qt_types = convert_tkinter_filetypes_to_qt(self.FileTypes) - file_name = QFileDialog.getSaveFileName(dir=self.InitialFolder, filter=qt_types) - if file_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_name - else: - target_element.Update(file_name[0]) - elif self.BType == BUTTON_TYPE_CLOSES_WIN: # this is a return type button so GET RESULTS and destroy window - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = False - self.ParentForm._Close() - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.QTApplication.exit() # Exit the mainloop - self.ParentForm.QT_QMainWindow.close() - if self.ParentForm.NonBlocking: - # TODO DESTROY WIN - Window.DecrementOpenCount() - elif self.BType == BUTTON_TYPE_READ_FORM: # LEAVE THE WINDOW OPEN!! DO NOT CLOSE - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out - self.ParentForm.QTApplication.exit() - elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop - self.ParentForm._Close() - self.ParentForm.QT_QMainWindow.close() - if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out - self.ParentForm.QTApplication.exit() - Window.DecrementOpenCount() - elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window - should_submit_window = False - - if should_submit_window: - self.ParentForm.LastButtonClicked = target_element.Key - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.QTApplication.exit() - pass # TODO # kick the users out of the mainloop - return - - def Update( - self, - text=None, - button_color=(None, None), - disabled=None, - image_data=None, - image_filename=None, - font=None, - visible=None, - ): - if text is not None: - self.QT_QPushButton.setText(str(text)) - self.ButtonText = text - if self.ParentForm.Font and (self.Font == DEFAULT_FONT or not self.Font): - font = self.ParentForm.Font - elif self.Font is not None: - font = self.Font - else: - font = DEFAULT_FONT - - fg = bg = None - if button_color != (None, None): - self.ButtonColor = button_color - fg, bg = button_color - if self.Disabled != disabled and disabled is not None: - if not disabled: # if enabling buttons, set the color - fg, bg = self.ButtonColor - self.Disabled = disabled - if disabled: - self.QT_QPushButton.setDisabled(True) - else: - self.QT_QPushButton.setDisabled(False) - # fg, bg = self.ButtonColor - # print(f'Button update fg, bg {fg}, {bg}') - super().Update(self.QT_QPushButton, background_color=bg, text_color=fg, font=font, visible=visible) - - def GetText(self): - return self.ButtonText - - def SetFocus(self): - self.QT_QPushButton.setFocus() - - def Click(self): - if self.Widget is None: - return - try: - self.Widget.click() - except Exception as e: - print('Exception {} \nclicking button {}'.format(e, self.ButtonText)) - - click = Click - get_text = GetText - set_focus = SetFocus - update = Update - - -# ------------------------- Button lazy functions ------------------------- # -B = Button -Btn = Button - - -# ---------------------------------------------------------------------- # -# ButtonMenu Class # -# ---------------------------------------------------------------------- # -class ButtonMenu(Element): - def __init__( - self, - button_text, - menu_def, - tooltip=None, - disabled=False, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - pad=None, - key=None, - k=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param button_text: Text to be displayed on the button - :type button_text: (str) - :param menu_def: A list of lists of Menu items to show when this element is clicked. See docs for format as they are the same for all menu types - :type menu_def: List[List[str]] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param disabled: If True button will be created disabled - :type disabled: (bool) - :param image_filename: image filename if there is a button image. GIFs and PNGs only. - :type image_filename: (str) - :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data - :type image_data: Union[bytes, str] - :param image_size: Size of the image in pixels (width, height) - :type image_size: Tuple[int, int] - :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc - :type image_subsample: (int) - :param border_width: width of border around button in pixels - :type border_width: (int) - :param size: (width, height) of the button in characters wide, rows high - :type size: Tuple[int, int] - :param auto_size_button: if True the button size is sized to fit the text - :type auto_size_button: (bool) - :param button_color: of button. Easy to remember which is which if you say "ON" between colors. "red" on "green" - :type button_color: Tuple[str, str] == (text color, background color) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.MenuDefinition = menu_def - self.AutoSizeButton = auto_size_button - self.ButtonText = button_text - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.TextColor = self.ButtonColor[0] - self.BackgroundColor = self.ButtonColor[1] - self.BorderWidth = border_width - self.ImageFilename = image_filename - self.ImageData = image_data - self.ImageSize = image_size - self.ImageSubsample = image_subsample - self.Disabled = disabled - self.Widget = self.QT_QPushButton = None # type: QPushButton - self.IsButtonMenu = True - self.MenuItemChosen = None - - # self.temp_size = size if size != (NONE, NONE) else - - super().__init__( - ELEM_TYPE_BUTTONMENU, - size=size, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - text_color=self.TextColor, - background_color=self.BackgroundColor, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - return - - def _QT_MenuItemChosenCallback(self, item_chosen): - # print('IN BUTTON MENU ITEM CALLBACK', item_chosen) - self.Key = item_chosen.replace('&', '') # fool the quit function into thinking this was a key - _element_callback_quit_mainloop(self) - - def Update(self, menu_definition=None, text=None, button_color=(None, None), font=None, visible=None): - if menu_definition is not None: - menu_def = menu_definition - qmenu = QMenu(self.QT_QPushButton) - qmenu.setTitle(menu_def[0]) - AddMenuItem(qmenu, menu_def[1], self) - self.QT_QPushButton.setMenu(qmenu) - super().Update( - self.QT_QPushButton, - background_color=button_color[1], - text_color=button_color[0], - font=font, - visible=visible, - ) - - def Click(self): - """ """ - try: - self.QT_QPushButton.click() - except Exception as e: - print('Exception {} clicking button. Has your Window been Finalized() or Read()?'.format(e)) - - click = Click - update = Update - - -BMenu = ButtonMenu - - -# ---------------------------------------------------------------------- # -# ProgreessBar # -# ---------------------------------------------------------------------- # -class ProgressBar(Element): - def __init__( - self, - max_value, - orientation=None, - size=(None, None), - start_value=0, - auto_size_text=None, - bar_color=(None, None), - style=None, - border_width=None, - relief=None, - key=None, - k=None, - pad=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param max_value: max value of progressbar - :type max_value: (int) - :param orientation: 'horizontal' or 'vertical' - :type orientation: (str) - :param size: Size of the bar. If horizontal (chars wide, pixels high), vert (pixels wide, rows high) - :type size: Tuple[int, int] - :param start_value: ??? - :type start_value: ??? - :param auto_size_text: Not sure why this is here - :type auto_size_text: (bool) - :param bar_color: The 2 colors that make up a progress bar. One is the background, the other is the bar - :type bar_color: Tuple[str, str] - :param style: Progress bar style defined as one of these 'default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative' - :type style: (str) - :param border_width: The amount of pixels that go around the outside of the bar - :type border_width: (int) - :param relief: relief style. Values are same as progress meter relief values. Can be a constant or a string: `RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID` (Default value = DEFAULT_PROGRESS_BAR_RELIEF) - :type relief: (str) - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.MaxValue = max_value - self.TKProgressBar = None - self.Cancelled = False - self.NotRunning = True - self.Orientation = orientation if orientation else DEFAULT_METER_ORIENTATION - self.BarColor = bar_color if bar_color != (None, None) else DEFAULT_PROGRESS_BAR_COLOR - self.BarStyle = style if style else DEFAULT_PROGRESS_BAR_STYLE - self.BorderWidth = border_width if border_width is not None else DEFAULT_PROGRESS_BAR_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF - self.BarExpired = False - self.StartValue = start_value - tsize = size - if size[0] is not None and size[0] < 100: - # tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] - tsize = size[0] * 10, size[1] - self.Widget = self.QT_QProgressBar = None # type: QProgressBar - - super().__init__( - ELEM_TYPE_PROGRESS_BAR, - size=tsize, - auto_size_text=auto_size_text, - key=key, - pad=pad, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - - # returns False if update failed - def UpdateBar(self, current_count, max=None): - if max is not None: - self.QT_QProgressBar.setMaximum(max) - self.QT_QProgressBar.setValue(current_count) - self.ParentForm.QTApplication.processEvents() # refresh the window - return True - - def Update(self, visible=None): - super().Update(self.QT_QProgressBar, visible=visible) - - update = Update - update_bar = UpdateBar - - -PBar = ProgressBar -Prog = ProgressBar - - -# ---------------------------------------------------------------------- # -# Image # -# ---------------------------------------------------------------------- # -class Image(Element): - def __init__( - self, - filename=None, - data=None, - data_base64=None, - background_color=None, - size=(None, None), - pad=None, - key=None, - k=None, - tooltip=None, - click_submits=False, - enable_events=False, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param filename: image filename if there is a button image. GIFs and PNGs only. - :type filename: (str) - :param data: Raw or Base64 representation of the image to put on button. Choose either filename or data - :type data: Union[bytes, str] - :param data_base64: ??? - :type data_base64: ??? - :param background_color: color of background - :type background_color: - :param size: (width, height) size of image in pixels - :type size: Tuple[int, int] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param click_submits: ??? - :type click_submits: (bool) - :param enable_events: Turns on the element specific events. For an Image element, the event is "image clicked" - :type enable_events: (bool) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.Filename = filename - self.Data = data - self.DataBase64 = data_base64 - self.tktext_label = None - self.BackgroundColor = background_color - self.ClickSubmits = click_submits or enable_events - if data is None and filename is None and data_base64 is None: - print('* Warning... no image specified in Image Element! *') - self.Widget = self.QT_QLabel = None # type: QLabel - - super().__init__( - ELEM_TYPE_IMAGE, - size=size, - background_color=background_color, - pad=pad, - key=key, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - return - - def QtCallbackImageClicked(self, event): - if not self.ClickSubmits: - return - _element_callback_quit_mainloop(self) - - def Update(self, filename=None, data=None, data_base64=None, size=(None, None), visible=None): - if filename is not None: - qlabel = self.QT_QLabel - qlabel.setText('') - w = QtGui.QPixmap(filename).width() - h = QtGui.QPixmap(filename).height() - qlabel.setGeometry(QtCore.QRect(0, 0, w, h)) - qlabel.setPixmap(QtGui.QPixmap(filename)) - elif data is not None: - qlabel = self.QT_QLabel - qlabel.setText('') - ba = QtCore.QByteArray.fromRawData(data) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qlabel.setPixmap(pixmap) - elif data_base64 is not None: - qlabel = self.QT_QLabel - qlabel.setText('') - ba = QtCore.QByteArray.fromBase64(data_base64) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qlabel.setPixmap(pixmap) - super().Update(self.QT_QLabel, visible=visible) - - update = Update - - -# ---------------------------------------------------------------------- # -# Canvas # -# ---------------------------------------------------------------------- # -class Canvas(Element): - def __init__( - self, - canvas=None, - background_color=None, - size=(None, None), - pad=None, - key=None, - k=None, - tooltip=None, - metadata=None, - ): - """ - Canvas Element - NOT USED IN QT PORT ? - :param canvas: Your own tk.Canvas if you already created it. Leave blank to create a Canvas - :type canvas: (tk.Canvas) - :param background_color: color of background - :type background_color: (str) - :param size: (width in char, height in rows) size in pixels to make canvas - :type size: Tuple[int,int] - :param pad: Amount of padding to put around element - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: Used with window.FindElement and with return values to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self._TKCanvas = canvas - - super().__init__( - ELEM_TYPE_CANVAS, - background_color=background_color, - size=size, - pad=pad, - key=key, - tooltip=tooltip, - metadata=metadata, - ) - return - - @property - def TKCanvas(self): - if self._TKCanvas is None: - print('*** Did you forget to call Finalize()? Your code should look something like: ***') - print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') - return self._TKCanvas - - -# ---------------------------------------------------------------------- # -# Graph # -# ---------------------------------------------------------------------- # -class Graph(Element): - def __init__( - self, - canvas_size, - graph_bottom_left, - graph_top_right, - background_color=None, - pad=None, - key=None, - k=None, - tooltip=None, - visible=True, - change_submits=False, - enable_events=False, - drag_submits=False, - metadata=None, - ): - """ - :param canvas_size: size of the canvas area in pixels - :type canvas_size: Tuple[int, int] - :param graph_bottom_left: (x,y) The bottoms left corner of your coordinate system - :type graph_bottom_left: Tuple[int, int] - :param graph_top_right: (x,y) The top right corner of your coordinate system - :type graph_top_right: Tuple[int, int] - :param background_color: background color of the drawing area - :type background_color: (str) - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element (Default = True) - :type visible: (bool) - :param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead - :type change_submits: (bool) - :param enable_events: If True then clicks on the Graph are immediately reported as an event. Use this instead of change_submits - :type enable_events: (bool) - :param drag_submits: if True and Events are enabled for the Graph, will report Events any time the mouse moves while button down - :type drag_submits: (bool) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.CanvasSize = canvas_size - self.BottomLeft = graph_bottom_left - self.TopRight = graph_top_right - self.x = self.y = 0 - self.Widget = self.QT_QGraphicsScene = None # type: QGraphicsScene - - super().__init__( - ELEM_TYPE_GRAPH, - background_color=background_color, - size=(None, None), - pad=pad, - key=key, - tooltip=tooltip, - visible=visible, - size_px=canvas_size, - metadata=metadata, - ) - return - - def _convert_xy_to_canvas_xy(self, x_in, y_in): - scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) - scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) - new_x = 0 + scale_x * (x_in - self.BottomLeft[0]) - new_y = self.CanvasSize[1] + scale_y * (y_in - self.BottomLeft[1]) - return new_x, new_y - - def DrawLine(self, point_from, point_to, color='black', width=1): - converted_point_from = self._convert_xy_to_canvas_xy(point_from[0], point_from[1]) - converted_point_to = self._convert_xy_to_canvas_xy(point_to[0], point_to[1]) - - qcolor = QColor(color) - pen = QPen(qcolor, width) - line = self.QT_QGraphicsScene.addLine( - self.x + converted_point_from[0], - self.y + converted_point_from[1], - self.x + converted_point_to[0], - self.y + converted_point_to[1], - pen=pen, - ) - # self.QT_QGraphicsItemGroup.addToGroup(line) - return line - - def DrawRectangle(self, top_left, bottom_right, fill_color=None, line_color=None): - converted_point_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_point_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - - qcolor = QColor(line_color) - pen = QPen(qcolor, 1) - qcolor = QColor(fill_color) - brush = QBrush(qcolor) - line = self.QT_QGraphicsScene.addRect( - converted_point_top_left[0], - converted_point_top_left[1], - converted_point_bottom_right[0] - converted_point_top_left[0], - converted_point_bottom_right[1] - converted_point_top_left[1], - pen, - brush, - ) - # self.QT_QGraphicsItemGroup.addToGroup(line) - - def DrawCircle(self, center_location, radius, fill_color=None, line_color='black'): - converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1]) - qcolor = QColor(line_color) - pen = QPen(qcolor) - qcolor = QColor(fill_color) - brush = QBrush(qcolor) - circle_id = self.QT_QGraphicsScene.addEllipse( - self.x + converted_point[0] - radius, - self.y + converted_point[1] - radius, - radius * 2, - radius * 2, - pen=pen, - brush=brush, - ) - return circle_id # type: QGraphicsEllipseItem - - def RelocateFigure(self, id, x, y): - id = id # type: QtWidgets.QGraphicsEllipseItem - converted_point = self._convert_xy_to_canvas_xy(x, y) - id.setX(converted_point[0]) - id.setY(converted_point[1]) - - def DrawText(self, text, location, color='black', font=None, angle=0): - converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) - - qcolor = QColor(color) - qpath = QPainterPath() - _font = font or ('courier', 12) - qfont = QFont(_font[0], _font[1]) - # qfont.setWeight(.5) - - text_id = qpath.addText(self.x + converted_point[0], self.y + converted_point[1], qfont, str(text)) - self.QT_QGraphicsScene.addPath(qpath, qcolor) - return text_id - - def Move(self, x_direction, y_direction): - x_direction = -x_direction - y_direction = -y_direction - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - rect = self.QT_QGraphicsScene.sceneRect() - rect.translate(shift_amount[0], shift_amount[1]) - self.x += shift_amount[0] - self.y += shift_amount[1] - self.QT_QGraphicsScene.setSceneRect(rect) - # items = self.QT_QGraphicsScene.items() - # print(len(items)) - # for item in items: - # if not item.isActive(): - # print('*', end='') - # item.removeFromIndex() - - # print(rect) - - def DrawOval(self, top_left, bottom_right, fill_color=None, line_color=None): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_oval( - converted_top_left[0], - converted_top_left[1], - converted_bottom_right[0], - converted_bottom_right[1], - fill=fill_color, - outline=line_color, - ) - - def DrawPoint(self, point, size=2, color='black'): - converted_point = self._convert_xy_to_canvas_xy(point[0], point[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_oval( - converted_point[0] - size, - converted_point[1] - size, - converted_point[0] + size, - converted_point[1] + size, - fill=color, - outline=color, - ) - - def DrawArc(self, top_left, bottom_right, extent, start_angle, style=None, arc_color='black'): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_arc( - converted_top_left[0], - converted_top_left[1], - converted_bottom_right[0], - converted_bottom_right[1], - extent=extent, - start=start_angle, - style='tkstyle', - outline=arc_color, - ) - - def DrawRectangleOld(self, top_left, bottom_right, fill_color=None, line_color=None): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_rectangle( - converted_top_left[0], - converted_top_left[1], - converted_bottom_right[0], - converted_bottom_right[1], - fill=fill_color, - outline=line_color, - ) - - def Erase(self): - if self.QT_QGraphicsScene is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - self.QT_QGraphicsScene.clear() - - def Update(self, background_color, visible=None): - # TODO - # if self._TKCanvas2 is None: - # print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - # print('Call Window.Finalize() prior to this operation') - # return None - # self._TKCanvas2.configure(background=background_color) - super().Update(self.QT_QGraphicsScene, visible=visible) - - def MoveFigure(self, figure, x_direction, y_direction): - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - if figure is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - self._TKCanvas2.move(figure, shift_amount[0], shift_amount[1]) - - def change_coordinates(self, graph_bottom_left, graph_top_right): - """ - Changes the corrdinate system to a new one. The same 2 points in space are used to define the coorinate - system - the bottom left and the top right values of your graph. - - :param graph_bottom_left: Tuple[int, int] (x,y) The bottoms left corner of your coordinate system - :param graph_top_right: Tuple[int, int] (x,y) The top right corner of your coordinate system - """ - self.BottomLeft = graph_bottom_left - self.TopRight = graph_top_right - - @property - def TKCanvas(self): - if self._TKCanvas2 is None: - print('*** Did you forget to call Finalize()? Your code should look something like: ***') - print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') - return self._TKCanvas2 - - draw_arc = DrawArc - draw_circle = DrawCircle - draw_line = DrawLine - draw_oval = DrawOval - draw_point = DrawPoint - draw_rectangle = DrawRectangle - draw_rectangle_old = DrawRectangleOld - draw_text = DrawText - erase = Erase - move = Move - move_figure = MoveFigure - relocate_figure = RelocateFigure - update = Update - - -# ---------------------------------------------------------------------- # -# Frame # -# ---------------------------------------------------------------------- # -class Frame(Element): - def __init__( - self, - title, - layout, - title_color=None, - background_color=None, - title_location=None, - frame_color=None, - relief=DEFAULT_FRAME_RELIEF, - element_justification='float', - size=(None, None), - font=None, - pad=None, - border_width=None, - key=None, - k=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param title: text that is displayed as the Frame's "label" or title - :type title: (str) - :param layout: The layout to put inside the Frame - :type layout: List[List[Elements]] - :param title_color: color of the title text - :type title_color: (str) - :param background_color: background color of the Frame - :type background_color: (str) - :param title_location: location to place the text title. Choices include: TITLE_LOCATION_TOP TITLE_LOCATION_BOTTOM TITLE_LOCATION_LEFT TITLE_LOCATION_RIGHT TITLE_LOCATION_TOP_LEFT TITLE_LOCATION_TOP_RIGHT TITLE_LOCATION_BOTTOM_LEFT TITLE_LOCATION_BOTTOM_RIGHT - :type title_location: (enum) - :param frame_color: color of the frame lines - :type frame_color: (str) - :param relief: relief style. Values are same as other elements with reliefs. Choices include RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID - :type relief: (enum) - :param element_justification: All elements inside the Frame will have this justification 'left', 'right', 'center' are valid values - :type element_justification: (str) - :param size: (width, height) (note this parameter may not always work) - :type size: Tuple[int, int] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param border_width: width of border around element in pixels - :type border_width: (int) - :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - # self.ParentForm = None - self.TKFrame = None - self.Title = title - self.Relief = relief - self.TitleLocation = title_location - self.BorderWidth = border_width - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.ElementJustification = element_justification - self.FrameColor = frame_color - self.Widget = self.QT_QGroupBox = None # type: QGroupBox - self.Layout(layout) - - super().__init__( - ELEM_TYPE_FRAME, - background_color=background_color, - text_color=title_color, - size=size, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - return - - def AddRow(self, *args): - """Parms are a variable number of Elements""" - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def Update(self, visible=None): - super().Update(self.QT_QGroupBox, visible=visible) - - add_row = AddRow - layout = Layout - update = Update - - -# ---------------------------------------------------------------------- # -# Separator # -# ---------------------------------------------------------------------- # -class VerticalSeparator(Element): - def __init__(self, pad=None): - """ - VerticalSeperator - A separator that spans only 1 row in a vertical fashion - :param pad: - """ - self.Orientation = 'vertical' # for now only vertical works - - super().__init__(ELEM_TYPE_SEPARATOR, pad=pad) - - -VSeperator = VerticalSeparator -VSeparator = VerticalSeparator -VSep = VerticalSeparator - - -# ---------------------------------------------------------------------- # -# Separator # -# ---------------------------------------------------------------------- # -class HorizontalSeparator(Element): - def __init__(self, pad=None, size_px=(None, None)): - """ - VerticalSeperator - A separator that spans only 1 row in a vertical fashion - :param pad: - """ - self.Orientation = 'horizontal' # for now only vertical works - - super().__init__(ELEM_TYPE_SEPARATOR, pad=pad) - - -HSeperator = HorizontalSeparator -HSep = HorizontalSeparator - - -# ---------------------------------------------------------------------- # -# Tab # -# ---------------------------------------------------------------------- # -class Tab(Element): - def __init__( - self, - title, - layout, - title_color=None, - element_justification='float', - background_color=None, - font=None, - pad=None, - disabled=False, - border_width=None, - key=None, - k=None, - tooltip=None, - visible=True, - metadata=None, - ): - """ - :param title: text to show on the tab - :type title: (str) - :param layout: The element layout that will be shown in the tab - :type layout: List[List[Element]] - :param title_color: color of the tab text (note not currently working on tkinter) - :type title_color: (str) - :param element_justification: All elements inside the Tab will have this justification 'left', 'right', 'center' are valid values - :type element_justification: (str) - :param background_color: color of background of the entire layout - :type background_color: (str) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param disabled: If True button will be created disabled - :type disabled: (bool) - :param border_width: width of border around element in pixels - :type border_width: (int) - :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.TKFrame = None - self.Title = title - self.BorderWidth = border_width - self.Disabled = disabled - self.ParentTabGroup = None # type: TabGroup - self.TabID = None - self.ElementJustification = element_justification - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.Widget = self.QT_QWidget = None # type: QWidget - - self.Layout(layout) - - super().__init__( - ELEM_TYPE_TAB, - background_color=background_color, - text_color=title_color, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - visible=visible, - metadata=metadata, - ) - return - - def AddRow(self, *args): - """Parms are a variable number of Elements""" - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - return self - - def Update(self, disabled=None, visible=None): # TODO Disable / enable of tabs is not complete - if disabled is None: - return - self.Disabled = disabled - # state = 'disabled' if disabled is True else 'normal' - # self.ParentNotebook.tab(self.TabID, state=state) - super().Update(self.QT_QWidget, visible=visible) - - return self - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def Select(self): - """ - Selects this tab. Mimics user clicking on this tab. Must have called window.Finalize / Read first! - """ - try: - index = self.ParentTabGroup.TabList.index(self) - self.ParentTabGroup.QT_QTabWidget.setCurrentIndex(index) - except: - print('** EXCEPTION while trying to Select tab with key =', self.Key) - - add_row = AddRow - layout = Layout - select = Select - update = Update - - -# ---------------------------------------------------------------------- # -# TabGroup # -# ---------------------------------------------------------------------- # -class TabGroup(Element): - def __init__( - self, - layout, - tab_location=None, - title_color=None, - selected_title_color=None, - background_color=None, - font=None, - change_submits=False, - enable_events=False, - pad=None, - border_width=None, - theme=None, - key=None, - k=None, - tooltip=None, - visible=True, - metadata=None, - ): - """ - :param layout: Layout of Tabs. Different than normal layouts. ALL Tabs should be on first row - :type layout: List[List[Tab]] - :param tab_location: location that tabs will be displayed. Choices are left, right, top, bottom, lefttop, leftbottom, righttop, rightbottom, bottomleft, bottomright, topleft, topright - :type tab_location: (str) - :param title_color: color of text on tabs - :type title_color: (str) - :param selected_title_color: color of tab text when it is selected - :type selected_title_color: (str) - :param background_color: color of background area that tabs are located on - :type background_color: (str) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead - :type change_submits: (bool) - :param enable_events: If True then switching tabs will generate an Event - :type enable_events: (bool) - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param border_width: width of border around element in pixels - :type border_width: (int) - :param theme: DEPRICATED - You can only specify themes using set options or when window is created. It's not possible to do it on an element basis - :type theme: (enum) - :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.SelectedTitleColor = selected_title_color - self.Rows = [] - self.TKNotebook = None - self.TabCount = 0 - self.BorderWidth = border_width - self.Theme = theme - self.BackgroundColor = background_color if background_color is not None else COLOR_SYSTEM_DEFAULT - self.ChangeSubmits = change_submits or enable_events - self.TabLocation = tab_location - self.TabList = [] # type: List[Tab] - self.Widget = self.QT_QTabWidget = None # type: QTabWidget - self.ElementJustification = 'float' # not actually used, but needed for packer to work - self.Layout(layout) - - super().__init__( - ELEM_TYPE_TAB_GROUP, - background_color=self.BackgroundColor, - text_color=title_color, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - visible=visible, - metadata=metadata, - ) - return - - def AddRow(self, *args): - """Parms are a variable number of Elements""" - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - element.ParentTabGroup = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - self.TabList.append(element) - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def FindKeyFromTabName(self, tab_name): - for row in self.Rows: - for element in row: - if element.Title == tab_name: - return element.Key - return None - - def Update(self, visible=None): - super().Update(self.QT_QTabWidget, visible=visible) - return self - - def QtCallbackStateChanged(self, state): - if self.ChangeSubmits: - _element_callback_quit_mainloop(self) - - def Get(self): - """ - Returns the current value for the Tab Group, which will be the currently selected tab's KEY or the text on - the tab if no key is defined. Returns None if an error occurs. - Note that this is exactly the same data that would be returned from a call to Window.Read. Are you sure you - are using this method correctly? - - :return: Union[Any, None] The key of the currently selected tab or the tab's text if it has no key - """ - value = None - try: - cur_index = self.QT_QTabWidget.currentIndex() - tab_element = self.TabList[cur_index] - value = tab_element.Key - except: - value = None - return value - - add_row = AddRow - find_key_from_tab_name = FindKeyFromTabName - get = Get - update = Update - - -# ---------------------------------------------------------------------- # -# Slider # -# ---------------------------------------------------------------------- # -class Slider(Element): - def __init__( - self, - range=(None, None), - default_value=None, - resolution=None, - tick_interval=None, - orientation=None, - border_width=None, - relief=None, - change_submits=False, - enable_events=False, - disabled=False, - size=(None, None), - font=None, - background_color=None, - text_color=None, - key=None, - k=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param range: slider's range (min value, max value) - :type range: Union[Tuple[int, int], Tuple[float, float]] - :param default_value: starting value for the slider - :type default_value: Union[int, float] - :param resolution: the smallest amount the slider can be moved - :type resolution: Union[int, float] - :param tick_interval: how often a visible tick should be shown next to slider - :type tick_interval: Union[int, float] - :param orientation: 'horizontal' or 'vertical' ('h' or 'v' also work) - :type orientation: (str) - :param border_width: width of border around element in pixels - :type border_width: (int) - :param relief: relief style. RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID - :type relief: (enum) - :param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead - :type change_submits: (bool) - :param enable_events: If True then moving the slider will generate an Event - :type enable_events: (bool) - :param disabled: set disable state for element - :type disabled: (bool) - :param size: (w=characters-wide, h=rows-high) - :type size: Tuple[int, int] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param background_color: color of slider's background - :type background_color: (str) - :param text_color: color of the slider's text - :type text_color: (str) - :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.TKScale = None - self.Range = (1, 10) if range == (None, None) else range - self.DefaultValue = self.Range[0] if default_value is None else default_value - self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION - self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF - self.Resolution = 1 if resolution is None else resolution - self.ChangeSubmits = change_submits or enable_events - self.Disabled = disabled - self.TickInterval = tick_interval if tick_interval is not None else self.Range[1] // 10 - temp_size = size - if temp_size == (None, None): - temp_size = (150, 30) if self.Orientation.startswith('h') else (30, 150) - elif size[0] is not None and size[0] < 100: - temp_size = size[0] * 10, size[1] * 3 - self.Widget = self.QT_Slider = None # type:QSlider - - super().__init__( - ELEM_TYPE_INPUT_SLIDER, - size=temp_size, - font=font, - background_color=background_color, - text_color=text_color, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - return - - def _QtCallbackValueChanged(self, value): - if not self.ChangeSubmits: - return - _element_callback_quit_mainloop(self) - - def Update(self, value=None, range=(None, None), disabled=None, visible=None): - if value is not None: - self.QT_Slider.setValue(int(value)) - self.DefaultValue = value - if disabled == True: - self.QT_Slider.setDisabled(True) - elif disabled == False: - self.QT_Slider.setDisabled(False) - if range != (None, None): - self.Range = range - self.QT_Slider.setMinimum(range[0]) - self.QT_Slider.setMaximum(range[1]) - super().Update(self.QT_Slider, visible=visible) - - update = Update - - -# ---------------------------------------------------------------------- # -# Dial # -# ---------------------------------------------------------------------- # -class Dial(Element): - def __init__( - self, - range=(None, None), - default_value=None, - resolution=None, - tick_interval=None, - orientation=None, - border_width=None, - relief=None, - change_submits=False, - enable_events=False, - disabled=False, - size=(None, None), - font=None, - background_color=None, - text_color=None, - key=None, - k=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param range: slider's range (min value, max value) - :type range: Union[Tuple[int, int], Tuple[float, float]] - :param default_value: Choice to be displayed as initial value. Must match one of values variable contents - :type default_value: (Any) - :param resolution: the smallest amount the slider can be moved - :type resolution: Union[int, float] - :param tick_interval: how often a visible tick should be shown next to slider - :type tick_interval: Union[int, float] - :param orientation: 'horizontal' or 'vertical' ('h' or 'v' also work) - :type orientation: (str) - :param border_width: width of border around button in pixels - :type border_width: (int) - :param relief: relief style. RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID - :type relief: (enum) - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. If this button is a target, should it generate an event when filled in - :type enable_events: (bool) - :param disabled: set disable state for element - :type disabled: (bool) - :param size: (width, height) of the button in characters wide, rows high - :type size: Tuple[int, int] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param background_color: color of slider's background - :type background_color: (str) - :param text_color: color of the element's text - :type text_color: (str) - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.TKScale = None - self.Range = (1, 10) if range == (None, None) else range - self.DefaultValue = self.Range[0] if default_value is None else default_value - self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION - self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF - self.Resolution = 1 if resolution is None else resolution - self.ChangeSubmits = change_submits or enable_events - self.Disabled = disabled - self.TickInterval = tick_interval - temp_size = size - if temp_size == (None, None): - temp_size = (20, 20) if self.Orientation.startswith('h') else (8, 20) - self.Widget = self.QT_Dial = None # type: QDial - - super().__init__( - ELEM_TYPE_INPUT_DIAL, - size=temp_size, - font=font, - background_color=background_color, - text_color=text_color, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - return - - def Update(self, value=None, range=(None, None), disabled=None, visible=None): - if value is not None: # TODO clearly not done! - pass - self.DefaultValue = value - if disabled == True: - pass - elif disabled == False: - pass - super().Update(self.QT_Dial, visible=visible) - - def _QtCallbackValueChanged(self, value): - if not self.ChangeSubmits: - return - _element_callback_quit_mainloop(self) - - update = Update - - -# ---------------------------------------------------------------------- # -# Stretch # -# ---------------------------------------------------------------------- # -class Stretch(Element): - def __init__( - self, - size=(None, None), - font=None, - background_color=None, - text_color=None, - key=None, - k=None, - pad=None, - tooltip=None, - ): - """ - :param size: (width, height) of the button in characters wide, rows high - :type size: Tuple[int, int] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param background_color: color of slider's background - :type background_color: (str) - :param text_color: color of the element's text - :type text_color: (str) - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - """ - key = key if key is not None else k - self.Widget = None # type: Stretch - super().__init__( - ELEM_TYPE_STRETCH, - size=size, - font=font, - background_color=background_color, - text_color=text_color, - key=key, - pad=pad, - tooltip=tooltip, - ) - return - - -# ---------------------------------------------------------------------- # -# Column # -# ---------------------------------------------------------------------- # -class Column(Element): - def __init__( - self, - layout, - background_color=None, - element_justification='float', - size=(None, None), - pad=None, - scrollable=False, - key=None, - k=None, - visible=True, - metadata=None, - ): - """ - :param layout: Layout that will be shown in the Column container - :type layout: List[List[Element]] - :param background_color: color of background of entire Column - :type background_color: (str) - :param element_justification: All elements inside the Column will have this justification 'left', 'right', 'center' are valid values - :type element_justification: (str) - :param size: (width, height) size in pixels (doesn't work quite right, sometimes only 1 dimension is set by tkinter - :type size: Tuple[int, int] - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param scrollable: if True then scrollbars will be added to the column - :type scrollable: (bool) - :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param visible: set visibility state of the element - :type visible: (bool) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.TKFrame = None - self.Scrollable = scrollable - # self.ImageFilename = image_filename - # self.ImageData = image_data - # self.ImageSize = image_size - # self.ImageSubsample = image_subsample - bg = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.ElementJustification = element_justification - self.Widget = self.QT_QGroupBox = None # type: QGroupBox - self.vbox_layout = None # type: QVBoxLayout - self.Layout(layout) - - super().__init__(ELEM_TYPE_COLUMN, background_color=bg, size=size, pad=pad, key=key, visible=visible, metadata=metadata) - return - - def AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def Update(self, visible=None): - - super().Update(self.QT_QGroupBox, visible=visible) - - add_row = AddRow - layout = Layout - update = Update - - -Col = Column - - -# ---------------------------------------------------------------------- # -# Menu # -# ---------------------------------------------------------------------- # -class Menu(Element): - def __init__( - self, - menu_definition, - background_color=None, - size=(None, None), - tearoff=False, - pad=None, - key=None, - k=None, - visible=True, - metadata=None, - ): - """ - :param menu_definition: a menu definition (in menu definition format) - :type menu_definition: List[List[Tuple[str, List[str]]] - :param background_color: color of the background - :type background_color: (str) - :param size: Not used in the tkinter port - :type size: Tuple[int, int] - :param tearoff: if True, then can tear the menu off from the window ans use as a floating window. Very cool effect - :type tearoff: (bool) - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param visible: set visibility state of the element - :type visible: (bool) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.MenuItemTextColor = theme_text_color() - self.MenuItemBackgroundColor = theme_background_color() - self.MenuDefinition = menu_definition - self.TKMenu = None - self.Tearoff = tearoff - self.IsButtonMenu = False - self.MenuItemChosen = None - self.Widget = self.QT_QMenuBar = None # type: QMenuBar - - super().__init__( - ELEM_TYPE_MENUBAR, - background_color=background_color, - size=size, - pad=pad, - key=key, - visible=visible, - metadata=metadata, - ) - - def _QT_MenuItemChosenCallback(self, item_chosen): - # print('IN MENU ITEM CALLBACK', item_chosen) - self.MenuItemChosen = item_chosen.replace('&', '') - _element_callback_quit_mainloop(self) - # self.ParentForm.LastButtonClicked = item_chosen - # self.ParentForm.FormRemainedOpen = True - # if self.ParentForm.CurrentlyRunningMainloop: - # pass # TODO # kick the users out of the mainloop - - def Update(self, menu_definition=None, visible=None): - if menu_definition is not None: - menu_def = menu_definition - self.MenuDefinition = menu_def - self.QT_QMenuBar = QMenuBar(self.ParentForm.QT_QMainWindow) - - for menu_entry in menu_def: - # print(f'Adding a Menubar ENTRY {menu_entry}') - baritem = QMenu(self.QT_QMenuBar) - if menu_entry[0][0] == MENU_DISABLED_CHARACTER: - baritem.setDisabled(True) - baritem.setTitle(menu_entry[0][1:]) - else: - baritem.setTitle(menu_entry[0]) - self.QT_QMenuBar.addAction(baritem.menuAction()) - AddMenuItem(baritem, menu_entry[1], self) - - self.ParentForm.QT_QMainWindow.setMenuBar(self.QT_QMenuBar) - super().Update(self.QT_QMenuBar, visible=visible) - - update = Update - - -# ---------------------------------------------------------------------- # -# Table # -# ---------------------------------------------------------------------- # -class Table(Element): - def __init__( - self, - values, - headings=None, - visible_column_map=None, - col_widths=None, - def_col_width=10, - auto_size_columns=True, - max_col_width=20, - select_mode=None, - display_row_numbers=False, - num_rows=None, - font=None, - justification='right', - header_text_color=None, - header_background_color=None, - header_font=None, - text_color=None, - background_color=None, - alternating_row_color=None, - size=(None, None), - change_submits=False, - enable_events=False, - bind_return_key=False, - pad=None, - key=None, - k=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param values: ??? - :type values: List[List[Union[str, int, float]]] - :param headings: The headings to show on the top line - :type headings: Union[List[str], Tuple[str]] - :param visible_column_map: One entry for each column. False indicates the column is not shown - :type visible_column_map: List[bool] - :param col_widths: Number of characters that each column will occupy - :type col_widths: List[int] - :param def_col_width: Default column width in characters - :type def_col_width: (int) - :param auto_size_columns: if True columns will be sized automatically - :type auto_size_columns: (bool) - :param max_col_width: Maximum width for all columns in characters - :type max_col_width: (int) - :param select_mode: Select Mode. Valid values start with "TABLE_SELECT_MODE_". Valid values are: TABLE_SELECT_MODE_NONE TABLE_SELECT_MODE_BROWSE TABLE_SELECT_MODE_EXTENDED - :type select_mode: (enum) - :param display_row_numbers: if True, the first column of the table will be the row # - :type display_row_numbers: (bool) - :param num_rows: The number of rows of the table to display at a time - :type num_rows: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param justification: 'left', 'right', 'center' are valid choices - :type justification: (str) - :param header_text_color: sets the text color for the header - :type header_text_color: (str) - :param header_background_color: sets the background color for the header - :type header_background_color: (str) - :param header_font: specifies the font family, size, etc - :type header_font: Union[str, Tuple[str, int]] - :param text_color: color of the text - :type text_color: (str) - :param background_color: color of background - :type background_color: (str) - :param alternating_row_color: if set then every other row will have this color in the background. - :type alternating_row_color: (str) - :param size: DO NOT USE! Use num_rows instead - :type size: Tuple[int, int] - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. Table events happen when row is clicked - :type enable_events: (bool) - :param bind_return_key: if True, pressing return key will cause event coming from Table, ALSO a left button double click will generate an event if this parameter is True - :type bind_return_key: (bool) - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.Values = values - self.ColumnHeadings = headings - self.ColumnsToDisplay = visible_column_map - self.ColumnWidths = col_widths - self.MaxColumnWidth = max_col_width - self.DefaultColumnWidth = def_col_width - self.AutoSizeColumns = auto_size_columns - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.TextColor = text_color - self.HeaderTextColor = header_text_color if header_text_color is not None else theme_input_text_color() - self.HeaderBackgroundColor = header_background_color if header_background_color is not None else theme_input_background_color() - self.HeaderFont = header_font - self.Justification = justification - self.InitialState = None - self.SelectMode = select_mode - self.DisplayRowNumbers = display_row_numbers - self.NumRows = num_rows if num_rows is not None else size[1] - self.TKTreeview = None - self.AlternatingRowColor = alternating_row_color - self.SelectedRows = [] - self.ChangeSubmits = change_submits or enable_events - self.BindReturnKey = bind_return_key - self.Widget = self.QT_TableWidget = None # type: QTableWidget - - super().__init__( - ELEM_TYPE_TABLE, - text_color=text_color, - background_color=background_color, - font=font, - size=size, - pad=pad, - key=key, - tooltip=tooltip, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - return - - def _QtCallbackCellActivated(self, value=None): - # print('CELL ACTIVATED ', value) - # first, get the results table built - # modify the Results table in the parent FlexForm object - if not self.ChangeSubmits: - return - _element_callback_quit_mainloop(self) - - def _QtCallbackVerticalHeader(self, value): - print('Vertical Header value ', value) - - def Update(self, values=None, num_rows=None, visible=None): - if values is not None: - self.Values = values - self.SelectedRows = [] - self.QT_TableWidget.clearContents() - if len(values) != 0: - self.QT_TableWidget.setRowCount(len(self.Values)) - self.QT_TableWidget.setColumnCount(len(self.Values[0])) - for rownum, rows in enumerate(self.Values): - # self.QT_TableWidget.insertRow(rownum) - for colnum, columns in enumerate(rows): - self.QT_TableWidget.setItem(rownum, colnum, QTableWidgetItem(self.Values[rownum][colnum])) - if num_rows is not None: - self.QT_TableWidget.setFixedHeight(num_rows * 35 + 25) # convert num rows into pixels...crude but effective - - super().Update(self.QT_TableWidget, visible=visible) - - def Get(self): - num_rows = self.QT_TableWidget.rowCount() - num_cols = self.QT_TableWidget.columnCount() - table = [] - for row in range(num_rows): - row_list = [] - for col in range(num_cols): - item = self.QT_TableWidget.item(row, col).text() - row_list.append(item) - table.append(row_list) - - return table - - def _treeview_selected(self, event): - if self.ChangeSubmits: - MyForm = self.ParentForm - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - pass # TODO Quit mainloop - - def treeview_double_click(self, event): - if self.BindReturnKey: - MyForm = self.ParentForm - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - pass # TODO Quit mainloop - - class QTTableWidget(QTableWidget): - def __init__(self, enable_key_events, window): - self.KeyEventsEnabled = enable_key_events - self.Window = window - super().__init__() - - def eventFilter(self, widget, event): - # print(event.type()) - if event.type() == QEvent.MouseButtonPress and self.Window.GrabAnywhere: - self.mouse_offset = event.pos() - if event.type() == QEvent.MouseMove and self.Window.GrabAnywhere: - x = event.globalX() - y = event.globalY() - x_w = self.mouse_offset.x() - y_w = self.mouse_offset.y() - self.move(x - x_w, y - y_w) - - if event.type() == QEvent.KeyRelease and self.KeyEventsEnabled: - # print("got key event") - key = event.key() - try: - self.Window.LastButtonClicked = chr(key).lower() - except: - self.Window.LastButtonClicked = 'special %s' % key - self.Window.FormRemainedOpen = True - if self.Window.CurrentlyRunningMainloop: - self.Window.QTApplication.exit() - return QWidget.eventFilter(self, widget, event) - - get = Get - update = Update - - -# ---------------------------------------------------------------------- # -# Tree # -# ---------------------------------------------------------------------- # -class Tree(Element): - def __init__( - self, - data=None, - headings=None, - visible_column_map=None, - col_widths=None, - col0_width=10, - def_col_width=10, - auto_size_columns=True, - max_col_width=20, - select_mode=None, - show_expanded=False, - change_submits=False, - enable_events=False, - font=None, - size=(200, 600), - justification='right', - header_text_color=None, - header_background_color=None, - header_font=None, - text_color=None, - background_color=None, - num_rows=None, - pad=None, - key=None, - k=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - :param data: The data represented using a PySimpleGUI provided TreeData class - :type data: (TreeData) - :param headings: List of individual headings for each column - :type headings: List[str] - :param visible_column_map: Determines if a column should be visible. If left empty, all columns will be shown - :type visible_column_map: List[bool] - :param col_widths: List of column widths so that individual column widths can be controlled - :type col_widths: List[int] - :param col0_width: Size of Column 0 which is where the row numbers will be optionally shown - :type col0_width: (int) - :param def_col_width: default column width - :type def_col_width: (int) - :param auto_size_columns: if True, the size of a column is determined using the contents of the column - :type auto_size_columns: (bool) - :param max_col_width: the maximum size a column can be - :type max_col_width: (int) - :param select_mode: Use same values as found on Table Element. Valid values include: TABLE_SELECT_MODE_NONE TABLE_SELECT_MODE_BROWSE TABLE_SELECT_MODE_EXTENDED - :type select_mode: (enum) - :param show_expanded: if True then the tree will be initially shown with all nodes completely expanded - :type show_expanded: (bool) - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. Tree events happen when row is clicked - :type enable_events: (bool) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param size: ??? - :type size: ??? - :param justification: 'left', 'right', 'center' are valid choices - :type justification: (str) - :param header_text_color: sets the text color for the header - :type header_text_color: (str) - :param header_background_color: sets the background color for the header - :type header_background_color: (str) - :param header_font: specifies the font family, size, etc - :type header_font: Union[str, Tuple[str, int]] - :param text_color: color of the text - :type text_color: (str) - :param background_color: color of background - :type background_color: (str) - :param num_rows: The number of rows of the table to display at a time - :type num_rows: (int) - :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param visible: set visibility state of the element - :type visible: (bool) - :param size_px: size in pixels (width, height). Will override the size parameter - :type size_px: Tupple[int, int] (width, height) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - key = key if key is not None else k - self.TreeData = data - self.ColumnHeadings = headings - self.ColumnsToDisplay = visible_column_map - self.ColumnWidths = col_widths - self.MaxColumnWidth = max_col_width - self.DefaultColumnWidth = def_col_width - self.AutoSizeColumns = auto_size_columns - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.TextColor = text_color - self.HeaderTextColor = header_text_color if header_text_color is not None else theme_input_text_color() - self.HeaderBackgroundColor = header_background_color if header_background_color is not None else theme_input_background_color() - self.HeaderFont = header_font - self.Justification = justification - self.InitialState = None - self.SelectMode = select_mode - self.ShowExpanded = show_expanded - self.NumRows = num_rows - self.Col0Width = col0_width - self.TKTreeview = None - self.SelectedRows = [] - self.ChangeSubmits = change_submits or enable_events - self.Size = size - self.Widget = self.QT_QTreeWidget = None # type: QTreeWidget - super().__init__( - ELEM_TYPE_TREE, - text_color=text_color, - background_color=background_color, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - size=size, - visible=visible, - size_px=size_px, - metadata=metadata, - ) - return - - def _treeview_selected(self, event): - selections = 000000 - self.SelectedRows = [x for x in selections] - # print('Got selection') - if self.ChangeSubmits: - _element_callback_quit_mainloop(self) - - def _QtCallbackCellActivated(self, value=None): - # print('CELL ACTIVATED ', value) - # first, get the results table built - # modify the Results table in the parent FlexForm object - if not self.ChangeSubmits: - return - _element_callback_quit_mainloop(self) - - # if self.ChangeSubmits: - # MyForm = self.ParentForm - # if self.Key is not None: - # self.ParentForm.LastButtonClicked = self.Key - # else: - # self.ParentForm.LastButtonClicked = '' - # self.ParentForm.FormRemainedOpen = True - # if self.ParentForm.CurrentlyRunningMainloop: - # self.ParentForm.TKroot.quit() - - def Update(self, values=None, key=None, value=None, text=None, visible=None): - if values is not None: - self.TreeData = values - self.SelectedRows = [] - # self.QT_QTreeWidget = QTreeWidget() - TreeWidgetItems = QTreeWidgetItemIterator(self.QT_QTreeWidget) - - for item in TreeWidgetItems: - item2 = item.value() - self.QT_QTreeWidget.removeItemWidget(item2, 0) - - # def add_treeview_data(node, widget): - # # print(f'Inserting {node.key} under parent {node.parent}') - # child = QTreeWidgetItem(widget) - # if node.key != '': - # child.setText(0, str(node.text)) - # # child.setData(0,0,node.values) - # if node.icon is not None: - # qicon = QIcon(node.icon) - # child.setIcon(0, qicon) - # for node in node.children: - # add_treeview_data(node, child) - - def add_treeview_data(node, widget): - # print(f'Inserting {node.key} under parent {node.parent}') - if node != self.TreeData.root_node: - child = QTreeWidgetItem(widget) - child.setText(0, str(node.text)) - else: - child = widget - # if node.key != '': - # child.setData(0,0,node.values) - if type(node.icon) is bytes: - ba = QtCore.QByteArray.fromBase64(node.icon) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qicon = QIcon(pixmap) - child.setIcon(0, qicon) - elif node.icon is not None: - qicon = QIcon(node.icon) - child.setIcon(0, qicon) - for node in node.children: - add_treeview_data(node, child) - return - - add_treeview_data(self.TreeData.root_node, self.QT_QTreeWidget) - - if key is not None: - pass - super().Update(self.QT_QTreeWidget, visible=visible) - - return self - - update = Update - - -class TreeData(object): - class Node(object): - def __init__(self, parent, key, text, values, icon=None): - self.parent = parent - self.children = [] - self.key = key - self.text = text - self.values = values - self.icon = icon - - def _Add(self, node): - self.children.append(node) - - def __init__(self): - self.tree_dict = {} - self.root_node = self.Node('', '', 'root', [], None) - self.tree_dict[''] = self.root_node - - def _AddNode(self, key, node): - self.tree_dict[key] = node - - def Insert(self, parent, key, text, values, icon=None): - node = self.Node(parent, key, text, values, icon) - self.tree_dict[key] = node - parent_node = self.tree_dict[parent] - parent_node._Add(node) - - def __repr__(self): - return self._NodeStr(self.root_node, 1) - - def _NodeStr(self, node, level): - return '\n'.join([str(node.key) + ' : ' + str(node.text)] + [' ' * 4 * level + self._NodeStr(child, level + 1) for child in node.children]) - - insert = Insert - - -# ---------------------------------------------------------------------- # -# Error Element # -# ---------------------------------------------------------------------- # -class ErrorElement(Element): - def __init__(self, key=None, k=None): - """ - Error Element - :param key: key for uniquely identify this element (for window.FindElement) - :type key: (Any) - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - """ - self.Key = key - self.Widget = None - - super().__init__(ELEM_TYPE_ERROR, key=key) - return - - def Update(self, *args, **kwargs): - PopupError( - 'Keyword error in Update', - 'You need to stop this madness and check your spelling', - 'Bad key = {}'.format(self.Key), - 'Your bad line of code may resemble this:', - 'window.FindElement("{}")'.format(self.Key), - ) - return self - - def Get(self): - return 'This is NOT a valid Element!\nSTOP trying to do things with it or I will have to crash at some point!' - - get = Get - update = Update - - -# ---------------------------------------------------------------------- # -# Pane Element # -# ---------------------------------------------------------------------- # - -# This is for source code compatibility with tkinter version. No Qt equivalent -Pane = ErrorElement - - -# ------------------------------------------------------------------------- # -# Tray CLASS # -# ------------------------------------------------------------------------- # -class SystemTray: - def __init__(self, menu=None, filename=None, data=None, data_base64=None, tooltip=None, metadata=None): - """ - SystemTray - create an icon in the system tray - :param menu: Menu definition. Example - ['UNUSED', ['My', 'Simple', '---', 'Menu', 'Exit']] - :type menu: List[List[List[str] or str]] - :param filename: filename for icon - :type filename: (str) - :param data: in-ram image for icon (same as data_base64 parm) - :type data: (bytes) - :param data_base64: base-64 data for icon - :type data_base64: (bytes) - :param tooltip: tooltip string - :type tooltip: (str) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - self.Menu = menu - self.TrayIcon = None - self.Shown = False - self.MenuItemChosen = TIMEOUT_KEY - self.LastMessage = None - self.LastTitle = None - self.metadata = metadata - - if Window.QTApplication is None: - Window.QTApplication = QApplication(sys.argv) - self.App = Window.QTApplication - self.Widget = self.QWidget = QWidget() # type: QWidget - - if filename is None and data is None and data_base64 is None: - data_base64 = DEFAULT_BASE64_ICON - qicon = None - if filename is not None: - qicon = QIcon(filename) - elif data is not None: - ba = QtCore.QByteArray.fromRawData(data) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qicon = QIcon(pixmap) - elif data_base64 is not None: - ba = QtCore.QByteArray.fromBase64(data_base64) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qicon = QIcon(pixmap) - if qicon is None: - PopupError('ERROR - Tray must have one form of Icon specified') - return - self.TrayIcon = QSystemTrayIcon(qicon) - - if self.Menu is not None: - qmenu = QMenu() - qmenu.setTitle(self.Menu[0]) - AddTrayMenuItem(qmenu, self.Menu[1], self) - self.TrayIcon.setContextMenu(qmenu) - - if tooltip is not None: - self.TrayIcon.setToolTip(str(tooltip)) - - self.TrayIcon.messageClicked.connect(self._message_clicked) - self.TrayIcon.activated.connect(self._double_clicked) - - self.TrayIcon.show() - - def _QT_MenuItemChosenCallback(self, item_chosen): - self.MenuItemChosen = item_chosen.replace('&', '') - self.App.exit() # kick the users out of the mainloop - - # callback function when message is clicked - def _message_clicked(self): - self.MenuItemChosen = EVENT_SYSTEM_TRAY_MESSAGE_CLICKED - self.App.exit() - - def _double_clicked(self, reason): - # print(reason) - if reason == QSystemTrayIcon.DoubleClick: - self.MenuItemChosen = EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED - self.App.exit() - if reason == QSystemTrayIcon.Trigger: - self.MenuItemChosen = EVENT_SYSTEM_TRAY_ICON_ACTIVATED - self.App.exit() - - def Read(self, timeout=None): - """ - Reads the context menu - :param timeout: Optional. Any value other than None indicates a non-blocking read - :return: - """ - if not self.Shown: - self.Shown = True - self.TrayIcon.show() - if timeout is None: - self.App.exec_() - elif timeout == 0: - self.App.processEvents() - else: - self.timer = start_systray_read_timer(self, timeout) - self.App.exec_() - - if self.timer: - stop_timer(self.timer) - - item = self.MenuItemChosen - self.MenuItemChosen = TIMEOUT_KEY - return item - - def _timer_timeout(self): - self.App.exit() # kick the users out of the mainloop - - def Hide(self): - self.TrayIcon.hide() - - def UnHide(self): - self.TrayIcon.show() - - def ShowMessage(self, title, message, filename=None, data=None, data_base64=None, messageicon=None, time=10000): - """ - Shows a balloon above icon in system tray - :param title: Title shown in balloon - :param message: Message to be displayed - :param filename: Optional icon filename - :param data: Optional in-ram icon - :param data_base64: Optional base64 icon - :param time: How long to display message in milliseconds - :return: - """ - qicon = None - if filename is not None: - qicon = QIcon(filename) - elif data is not None: - ba = QtCore.QByteArray.fromRawData(data) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qicon = QIcon(pixmap) - elif data_base64 is not None: - ba = QtCore.QByteArray.fromBase64(data_base64) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qicon = QIcon(pixmap) - - if qicon is not None: - self.TrayIcon.showMessage(title, message, qicon, time) - elif messageicon is not None: - self.TrayIcon.showMessage(title, message, messageicon, time) - else: - self.TrayIcon.showMessage(title, message, QIcon(), time) - - self.LastMessage = message - self.LastTitle = title - return self - - def Close(self): - """ - - :return: - """ - self.Hide() - # Don't close app because windows could be depending on it - # self.App.quit() - - def Update( - self, - menu=None, - tooltip=None, - filename=None, - data=None, - data_base64=None, - ): - """ - Updates the menu, tooltip or icon - :param menu: menu defintion - :param tooltip: string representing tooltip - :param filename: icon filename - :param data: icon raw image - :param data_base64: icon base 64 image - :return: - """ - # Menu - if menu is not None: - self.Menu = menu - qmenu = QMenu() - qmenu.setTitle(self.Menu[0]) - AddTrayMenuItem(qmenu, self.Menu[1], self) - self.TrayIcon.setContextMenu(qmenu) - # Tooltip - if tooltip is not None: - self.TrayIcon.setToolTip(str(tooltip)) - # Icon - qicon = None - if filename is not None: - qicon = QIcon(filename) - elif data is not None: - ba = QtCore.QByteArray.fromRawData(data) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qicon = QIcon(pixmap) - elif data_base64 is not None: - ba = QtCore.QByteArray.fromBase64(data_base64) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qicon = QIcon(pixmap) - if qicon is not None: - self.TrayIcon.setIcon(qicon) - - close = Close - hide = Hide - read = Read - show_message = ShowMessage - un_hide = UnHide - update = Update - - -# ------------------------------------------------------------------------- # -# Window CLASS # -# ------------------------------------------------------------------------- # -class Window: - - NumOpenWindows = 0 - user_defined_icon = None - hidden_master_root = None - QTApplication = None - active_popups = {} - - def __init__( - self, - title, - layout=None, - default_element_size=DEFAULT_ELEMENT_SIZE, - default_button_element_size=(None, None), - auto_size_text=None, - auto_size_buttons=None, - location=(None, None), - size=(None, None), - element_padding=None, - margins=(None, None), - button_color=None, - font=None, - progress_bar_color=(None, None), - background_color=None, - border_depth=None, - auto_close=False, - auto_close_duration=DEFAULT_AUTOCLOSE_TIME, - icon=DEFAULT_WINDOW_ICON, - force_toplevel=False, - alpha_channel=1, - return_keyboard_events=False, - use_default_focus=True, - text_justification=None, - element_justification='float', - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - resizable=True, - disable_close=False, - disable_minimize=False, - background_image=None, - finalize=False, - metadata=None, - ): - """ - :param title: The title that will be displayed in the Titlebar and on the Taskbar - :type title: (str) - :param layout: The layout for the window. Can also be specified in the Layout method - :type layout: List[List[Elements]] - :param default_element_size: size in characters (wide) and rows (high) for all elements in this window - :type default_element_size: Tuple[int, int] - (width, height) - :param default_button_element_size: (width, height) size in characters (wide) and rows (high) for all Button elements in this window - :type default_button_element_size: Tuple[int, int] - :param auto_size_text: True if Elements in Window should be sized to exactly fir the length of text - :type auto_size_text: (bool) - :param auto_size_buttons: True if Buttons in this Window should be sized to exactly fit the text on this. - :type auto_size_buttons: (bool) - :param location: (x,y) location, in pixels, to locate the upper left corner of the window on the screen. Default is to center on screen. - :type location: Tuple[int, int] - :param size: (width, height) size in pixels for this window. Normally the window is autosized to fit contents, not set to an absolute size by the user - :type size: Tuple[int, int] - :param element_padding: Default amount of padding to put around elements in window (left/right, top/bottom) or ((left, right), (top, bottom)) - :type element_padding: Tuple[int, int] or ((int, int),(int,int)) - :param margins: (left/right, top/bottom) Not yet implemented! Parameter here for potability purposes. - :type margins: Tuple[int, int] - :param button_color: Default button colors for all buttons in the window - :type button_color: Tuple[str, str] == (text color, button color) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param progress_bar_color: (bar color, background color) Sets the default colors for all progress bars in the window - :type progress_bar_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param border_depth: Default border depth (width) for all elements in the window - :type border_depth: (int) - :param auto_close: If True, the window will automatically close itself - :type auto_close: (bool) - :param auto_close_duration: Number of seconds to wait before closing the window - :type auto_close_duration: (int) - :param icon: Can be either a filename or Base64 value. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO - :type icon: Union[str, str] - :param force_toplevel: If True will cause this window to skip the normal use of a hidden master window - :type force_toplevel: (bool) - :param alpha_channel: Sets the opacity of the window. 0 = invisible 1 = completely visible. Values bewteen 0 & 1 will produce semi-transparent windows in SOME environments (The Raspberry Pi always has this value at 1 and cannot change. - :type alpha_channel: (float) - :param return_keyboard_events: if True key presses on the keyboard will be returned as Events from Read calls - :type return_keyboard_events: (bool) - :param use_default_focus: If True will use the default focus algorithm to set the focus to the "Correct" element - :type use_default_focus: (bool) - :param text_justification: Default text justification for all Text Elements in window - :type text_justification: Union['left', 'right', 'center'] - :param element_justification: All elements in the Window itself will have this justification 'left', 'right', 'center' are valid values - :type element_justification: (str) - :param no_titlebar: If true, no titlebar nor frame will be shown on window. This means you cannot minimize the window and it will not show up on the taskbar - :type no_titlebar: (bool) - :param grab_anywhere: If True can use mouse to click and drag to move the window. Almost every location of the window will work except input fields on some systems - :type grab_anywhere: (bool) - :param keep_on_top: If True, window will be created on top of all other windows on screen. It can be bumped down if another window created with this parm - :type keep_on_top: (bool) - :param resizable: If True, allows the user to resize the window. Note the not all Elements will change size or location when resizing. - :type resizable: (bool) - :param disable_close: If True, the X button in the top right corner of the window will no work. Use with caution and always give a way out toyour users - :type disable_close: (bool) - :param disable_minimize: if True the user won't be able to minimize window. Good for taking over entire screen and staying that way. - :type disable_minimize: (bool) - :param background_image: ??? - :type background_image: ??? - :param finalize: If True then the Finalize method will be called. Use this rather than chaining .Finalize for cleaner code - :type finalize: (bool) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) - """ - self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT - self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS - self.Title = title - self.Rows = [] # a list of ELEMENTS for this row - self.DefaultElementSize = _convert_tkinter_size_to_Qt(default_element_size) - self.DefaultButtonElementSize = _convert_tkinter_size_to_Qt(default_button_element_size) if default_button_element_size != (None, None) else DEFAULT_BUTTON_ELEMENT_SIZE - self.Location = location - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.BackgroundColor = background_color if background_color else DEFAULT_BACKGROUND_COLOR - self.ParentWindow = None - self.Font = font if font else DEFAULT_FONT - self.RadioDict = {} - self.BorderDepth = border_depth - self.WindowIcon = icon if icon is not None else Window.user_defined_icon - self.AutoClose = auto_close - self.NonBlocking = False - self.TKroot = None - self.TKrootDestroyed = False - self.CurrentlyRunningMainloop = False - self.FormRemainedOpen = False - self.TKAfterID = None - self.ProgressBarColor = progress_bar_color - self.AutoCloseDuration = auto_close_duration - self.RootNeedsDestroying = False - self.Shown = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.LastButtonClicked = None - self.LastButtonClickedWasRealtime = False - self.UseDictionary = False - self.UseDefaultFocus = use_default_focus - self.ReturnKeyboardEvents = return_keyboard_events - self.LastKeyboardEvent = None - self.TextJustification = text_justification - self.NoTitleBar = no_titlebar - self.GrabAnywhere = grab_anywhere - self.KeepOnTop = keep_on_top - self.ForcefTopLevel = force_toplevel - self.Resizable = resizable - self._AlphaChannel = alpha_channel - self.Timeout = None - self.TimeoutKey = '_timeout_' - self.TimerCancelled = False - self.DisableClose = disable_close - self._Hidden = False - self.QTApplication = None - self.QT_QMainWindow = None - self.QTWindow = None # type Window.QTMainWindow - self._Size = size - self.ElementPadding = element_padding or DEFAULT_ELEMENT_PADDING - self.FocusElement = None - self.BackgroundImage = background_image - self.XFound = False - self.DisableMinimize = disable_minimize - self.UniqueKeyCounter = 0 - self.metadata = metadata - self.ElementJustification = element_justification - self.AllKeysDict = {} - self.margins = margins - - if layout is not None: - self.Layout(layout) - if finalize: - self.Finalize() - - @classmethod - def IncrementOpenCount(self): - self.NumOpenWindows += 1 - # print('+++++ INCREMENTING Num Open Windows = {} ---'.format(Window.NumOpenWindows)) - - @classmethod - def DecrementOpenCount(self): - self.NumOpenWindows -= 1 * (self.NumOpenWindows != 0) # decrement if not 0 - # print('----- DECREMENTING Num Open Windows = {} ---'.format(Window.NumOpenWindows)) - - # ------------------------- Add ONE Row to Form ------------------------- # - def AddRow(self, *args): - """Parms are a variable number of Elements""" - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - if type(element) is list: - PopupError( - 'Error creating layout', - 'Layout has a LIST instead of an ELEMENT', - 'This means you have a badly placed ]', - 'The offensive list is:', - element, - 'This list will be stripped from your layout', - ) - continue - elif callable(element) and not isinstance(element, Element): - PopupError( - 'Error creating layout', - 'Layout has a FUNCTION instead of an ELEMENT', - 'This means you are missing () from your layout', - 'The offensive list is:', - element, - 'This item will be stripped from your layout', - ) - continue - if element.ParentContainer is not None: - warnings.warn( - '*** YOU ARE ATTEMPTING TO RESUSE A LAYOUT! You must not attempt this kind of re-use ***', - UserWarning, - ) - PopupError( - 'Error creating layout', - 'The layout specified has already been used', - 'You MUST start witha "clean", unused layout every time you create a window', - 'The offensive Element = ', - element, - 'and has a key = ', - element.Key, - 'This item will be stripped from your layout', - ) - continue - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - # ------------------------- Add Multiple Rows to Form ------------------------- # - def AddRows(self, rows): - for row in rows: - try: - iter(row) - except TypeError: - PopupError( - 'Error creating layout', - 'Your row is not an iterable (e.g. a list)', - 'The offensive row = ', - row, - 'This item will be stripped from your layout', - ) - continue - self.AddRow(*row) - - def Layout(self, rows): - self.AddRows(rows) - self._BuildKeyDict() - return self - - def LayoutAndRead(self, rows, non_blocking=False): - raise DeprecationWarning('LayoutAndRead is no longer supported... change your call to window.Layout(layout).Read()') - # self.AddRows(rows) - # self.Show(non_blocking=non_blocking) - # return self.ReturnValues - - def LayoutAndShow(self, rows): - raise DeprecationWarning('LayoutAndShow is no longer supported... change your call to LayoutAndRead') - - # ------------------------- ShowForm THIS IS IT! ------------------------- # - def Show(self, non_blocking=False): - self.Shown = True - # Compute num rows & num cols (it'll come in handy debugging) - self.NumRows = len(self.Rows) - self.NumCols = max(len(row) for row in self.Rows) - self.NonBlocking = non_blocking - - # Search through entire form to see if any elements set the focus - # if not, then will set the focus to the first input element - found_focus = False - for row in self.Rows: - for element in row: - try: - if element.Focus: - found_focus = True - except: - pass - try: - if element.Key is not None: - self.UseDictionary = True - except: - pass - - if not found_focus and self.UseDefaultFocus: - self.UseDefaultFocus = True - else: - self.UseDefaultFocus = False - # -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ## - StartupTK(self) - # If a button or keyboard event happened but no results have been built, build the results - if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: - return BuildResults(self, False, self) - return self.ReturnValues - - # ------------------------- SetIcon - set the window's fav icon ------------------------- # - def SetIcon(self, icon=None, pngbase64=None): - pass - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def _GetDefaultElementSize(self): - return self.DefaultElementSize - - def _AutoCloseAlarmCallback(self): - try: - window = self - if window: - if window.NonBlocking: - self.CloseNonBlockingForm() - else: - window._Close() - if self.CurrentlyRunningMainloop: - self.QTApplication.exit() # kick the users out of the mainloop - self.RootNeedsDestroying = True - self.QT_QMainWindow.close() - - except: - pass - - def _timer_timeout(self): - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.TimerCancelled: - return - self.LastButtonClicked = self.TimeoutKey - self.FormRemainedOpen = True - if self.CurrentlyRunningMainloop: - self.QTApplication.exit() # kick the users out of the mainloop - - def _autoclose_timer_callback(self): - # print('*** TIMEOUT CALLBACK ***') - self.autoclose_timer.stop() - self.QT_QMainWindow.close() - if self.CurrentlyRunningMainloop: - # print("quitting window") - self.QTApplication.exit() # kick the users out of the mainloop - - def Read(self, timeout=None, timeout_key=TIMEOUT_KEY, close=False): - """ - THE biggest deal method in the Window class! This is how you get all of your data from your Window. - Pass in a timeout (in milliseconds) to wait for a maximum of timeout milliseconds. Will return timeout_key - if no other GUI events happen first. - Use the close parameter to close the window after reading - - :param timeout: (int) Milliseconds to wait until the Read will return IF no other GUI events happen first - :param timeout_key: (Any) The value that will be returned from the call if the timer expired - :param close: (bool) if True the window will be closed prior to returning - :return: Tuple[(Any), Union[Dict[Any:Any]], List[Any], None] (event, values) - """ - results = self._read(timeout=timeout, timeout_key=timeout_key) - if close: - self.close() - - return results - - def _read(self, timeout=None, timeout_key=TIMEOUT_KEY): - if timeout == 0: # timeout of zero runs the old readnonblocking - event, values = self._ReadNonBlocking() - if event is None: - event = timeout_key - if values is None: - event = None - return event, values # make event None if values was None and return - # Read with a timeout - self.Timeout = timeout - self.TimeoutKey = timeout_key - self.NonBlocking = False - if not self.Shown: - self.Show() - else: - # if already have a button waiting, the return previously built results - if self.LastButtonClicked is not None and not self.LastButtonClickedWasRealtime: - # print(f'*** Found previous clicked saved {self.LastButtonClicked}') - results = BuildResults(self, False, self) - self.LastButtonClicked = None - return results - InitializeResults(self) - # if the last button clicked was realtime, emulate a read non-blocking - # the idea is to quickly return realtime buttons without any blocks until released - if self.LastButtonClickedWasRealtime: - # print(f'RTime down {self.LastButtonClicked}' ) - try: - rc = self.TKroot.update() - except: - self.TKrootDestroyed = True - Window.DecrementOpenCount() - results = BuildResults(self, False, self) - if results[0] != None and results[0] != timeout_key: - return results - else: - pass - - # else: - # print("** REALTIME PROBLEM FOUND **", results) - - # normal read blocking code.... - if timeout != None: - self.TimerCancelled = False - timer = start_window_read_timer(self, timeout) - else: - timer = None - self.CurrentlyRunningMainloop = True - # print(f'In main {self.Title}') - ################################# CALL GUI MAINLOOP ############################ - - self.QTApplication.exec_() - # self.LastButtonClicked = 'TEST' - self.CurrentlyRunningMainloop = False - self.TimerCancelled = True - if timer: - stop_timer(timer) - if self.RootNeedsDestroying: - self.LastButtonClicked = None - self.QTApplication.exit() - Window.DecrementOpenCount() - # if form was closed with X - if self.LastButtonClicked is None and self.LastKeyboardEvent is None and self.ReturnValues[0] is None: - Window.DecrementOpenCount() - # Determine return values - if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: - results = BuildResults(self, False, self) - if not self.LastButtonClickedWasRealtime: - self.LastButtonClicked = None - return results - else: - if not self.XFound and self.Timeout != 0 and self.Timeout is not None and self.ReturnValues[0] is None: # Special Qt case because returning for no reason so fake timeout - self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout - elif not self.XFound and self.ReturnValues[0] is None: # TODO HIGHLY EXPERIMENTAL... added due to tray icon interaction - # print("*** Faking timeout ***") - self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout - return self.ReturnValues - - def _ReadNonBlocking(self): - if self.TKrootDestroyed: - return None, None - if not self.Shown: - self.Show(non_blocking=True) - else: - self.QTApplication.processEvents() # refresh the window - if 0: # TODO add window closed with X logic - self.TKrootDestroyed = True - _my_windows.Decrement() - # print("read failed") - # return None, None - return BuildResults(self, False, self) - - def Finalize(self): - if self.TKrootDestroyed: - return self - if not self.Shown: - self.Show(non_blocking=True) - else: - try: - self.QTApplication.processEvents() # refresh the window - except: - print('* ERROR FINALIZING *') - self.TKrootDestroyed = True - Window.DecrementOpenCount() - return self - - def Refresh(self): - self.QTApplication.processEvents() # refresh the window - return self - - def VisibilityChanged(self): - self.Refresh() - self.Size = self.Size - self.Refresh() - self.Size = self.Size - self.Refresh() - return self - - def Fill(self, values_dict): - FillFormWithValues(self, values_dict) - return self - - def FindElement(self, key, silent_on_error=False): - try: - element = self.AllKeysDict[key] - except KeyError: - element = None - # element = _FindElementFromKeyInSubForm(self, key) - if element is None: - if not silent_on_error: - print('*** WARNING = FindElement did not find the key. Please check your key\'s spelling ***') - PopupError( - 'Keyword error in FindElement Call', - 'Bad key = {}'.format(key), - 'Your bad line of code may resemble this:', - 'window.FindElement("{}")'.format(key), - ) - return ErrorElement(key=key) - else: - return False - return element - - Element = FindElement # Shortcut function - - def _BuildKeyDict(self): - dict = {} - self.AllKeysDict = self._BuildKeyDictForWindow(self, self, dict) - - def _BuildKeyDictForWindow(self, top_window, window, key_dict): - for row_num, row in enumerate(window.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Type == ELEM_TYPE_FRAME: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Type == ELEM_TYPE_TAB_GROUP: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Type == ELEM_TYPE_TAB: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Key is None: # if no key has been assigned.... create one for input elements - if element.Type == ELEM_TYPE_BUTTON: - element.Key = element.ButtonText - elif element.Type == ELEM_TYPE_TAB: - element.Key = element.Title - if element.Type in ( - ELEM_TYPE_MENUBAR, - ELEM_TYPE_BUTTONMENU, - ELEM_TYPE_CANVAS, - ELEM_TYPE_INPUT_SLIDER, - ELEM_TYPE_GRAPH, - ELEM_TYPE_IMAGE, - ELEM_TYPE_INPUT_CHECKBOX, - ELEM_TYPE_INPUT_LISTBOX, - ELEM_TYPE_INPUT_COMBO, - ELEM_TYPE_INPUT_MULTILINE, - ELEM_TYPE_INPUT_OPTION_MENU, - ELEM_TYPE_INPUT_SPIN, - ELEM_TYPE_TABLE, - ELEM_TYPE_TREE, - ELEM_TYPE_INPUT_TEXT, - ): - element.Key = top_window.DictionaryKeyCounter - top_window.DictionaryKeyCounter += 1 - if element.Key is not None: - if element.Key in key_dict.keys(): - (print('*** Duplicate key found in your layout {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) - element.Key = str(element.Key) + str(self.UniqueKeyCounter) - self.UniqueKeyCounter += 1 - (print('*** Replaced new key with {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) - key_dict[element.Key] = element - return key_dict - - def FindElementWithFocus(self): - return _FindElementWithFocusInSubForm(self) - # return self.FocusElement - - def SaveToDisk(self, filename): - try: - results = BuildResults(self, False, self) - with open(filename, 'wb') as sf: - pickle.dump(results[1], sf) - except: - print('*** Error saving form to disk ***') - - def LoadFromDisk(self, filename): - try: - with open(filename, 'rb') as df: - self.Fill(pickle.load(df)) - except: - print('*** Error loading form to disk ***') - - def GetScreenDimensions(self): - if Window.QTApplication is None: - Window.QTApplication = QApplication(sys.argv) - try: - screen = Window.QTApplication.primaryScreen() - except: - return None, None - size = screen.size() - screen_width = size.width() - screen_height = size.height() - return screen_width, screen_height - - def Move(self, x, y): - if not self._is_window_created(): - return - self.QT_QMainWindow.move(x, y) - - def Minimize(self): - if not self._is_window_created(): - return - self.QT_QMainWindow.setWindowState(Qt.WindowMinimized) - - def Maximize(self): - if not self._is_window_created(): - return - self.QT_QMainWindow.setWindowState(Qt.WindowMaximized) - - def StartMove(self, event): - try: - self.TKroot.x = event.x - self.TKroot.y = event.y - except: - pass - - def StopMove(self, event): - try: - self.TKroot.x = None - self.TKroot.y = None - except: - pass - - def OnMotion(self, event): - try: - deltax = event.x - self.TKroot.x - deltay = event.y - self.TKroot.y - x = self.TKroot.winfo_x() + deltax - y = self.TKroot.winfo_y() + deltay - self.TKroot.geometry('+%s+%s' % (x, y)) - except: - pass - - def _KeyboardCallback(self, event): - self.LastButtonClicked = None - self.FormRemainedOpen = True - if event.char != '': - self.LastKeyboardEvent = event.char - else: - self.LastKeyboardEvent = str(event.keysym) + ':' + str(event.keycode) - if not self.NonBlocking: - BuildResults(self, False, self) - if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! - self.TKroot.quit() - - def _MouseWheelCallback(self, event): - self.LastButtonClicked = None - self.FormRemainedOpen = True - self.LastKeyboardEvent = 'MouseWheel:Down' if event.delta < 0 else 'MouseWheel:Up' - if not self.NonBlocking: - BuildResults(self, False, self) - if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! - self.TKroot.quit() - - def _Close(self): - try: - self.TKroot.update() - except: - pass - if not self.NonBlocking: - BuildResults(self, False, self) - if self.TKrootDestroyed: - return None - self.TKrootDestroyed = True - self.RootNeedsDestroying = True - return None - - def Close(self): - if self.TKrootDestroyed: - return - try: - self.QT_QMainWindow.close() - except: - print('error closing window') - - CloseNonBlockingForm = Close - CloseNonBlocking = Close - - def Disable(self): - if not self._is_window_created(): - return - self.QT_QMainWindow.setEnabled(False) - - def Enable(self): - if not self._is_window_created(): - return - self.QT_QMainWindow.setEnabled(True) - - def Hide(self): - if not self._is_window_created(): - return - self._Hidden = True - self.QT_QMainWindow.hide() - return - - def UnHide(self): - if not self._is_window_created(): - return - if self._Hidden: - self.QT_QMainWindow.show() - self._Hidden = False - - def Disappear(self): - self.AlphaChannel = 0 - - def Reappear(self): - self.AlphaChannel = 255 - - def SetAlpha(self, alpha): - """ - Change the window's transparency - :param alpha: From 0 to 1 with 0 being completely transparent - :return: - """ - if not self._is_window_created(): - return - self._AlphaChannel = alpha - if self._AlphaChannel is not None: - self.QT_QMainWindow.setWindowOpacity(self._AlphaChannel) - - @property - def AlphaChannel(self): - return self._AlphaChannel - - @AlphaChannel.setter - def AlphaChannel(self, alpha): - if not self._is_window_created(): - return - self._AlphaChannel = alpha - if self._AlphaChannel is not None: - self.QT_QMainWindow.setWindowOpacity(self._AlphaChannel) - - def BringToFront(self): - if not self._is_window_created(): - return - self.QTMainWindow.activateWindow(self.QT_QMainWindow) - self.QTMainWindow.raise_(self.QT_QMainWindow) - - def CurrentLocation(self): - if not self._is_window_created(): - return - location = self.QT_QMainWindow.geometry() - return location.left(), location.top() - - def set_title(self, title): - """ - Change the title of the window - - :param title: The string to set the title to - :type title: (str) - """ - if not self._is_window_created(): - return - self.Title = str(title) - self.QT_QMainWindow.setWindowTitle(self.Title) - - class QTMainWindow(QWidget): - def __init__(self, enable_key_events, window): - self.KeyEventsEnabled = enable_key_events - self.Window = window - super().__init__(window.QT_QMainWindow) - - def eventFilter(self, widget, event): - # print(event.type()) - if event.type() == QEvent.MouseButtonPress and self.Window.GrabAnywhere: - self.mouse_offset = event.pos() - if event.type() == QEvent.MouseMove and self.Window.GrabAnywhere: - x = event.globalX() - y = event.globalY() - x_w = self.mouse_offset.x() - y_w = self.mouse_offset.y() - self.move(x - x_w, y - y_w) - - if event.type() == QEvent.KeyRelease and self.KeyEventsEnabled: - # print("got key event") - key = event.key() - try: - self.Window.LastButtonClicked = chr(key).lower() - except: - self.Window.LastButtonClicked = 'special %s' % key - self.Window.FormRemainedOpen = True - if self.Window.CurrentlyRunningMainloop: - self.Window.QTApplication.exit() - return QWidget.eventFilter(self, widget, event) - - class QT_QMainWindowClass(QMainWindow): - def __init__(self, enable_key_events, window): - self.KeyEventsEnabled = enable_key_events - self.Window = window - super().__init__() - - def eventFilter(self, widget, event): - # print(event.type()) - if event.type() == QEvent.MouseButtonPress and self.Window.GrabAnywhere: - self.mouse_offset = event.pos() - if event.type() == QEvent.MouseMove and self.Window.GrabAnywhere: - x = event.globalX() - y = event.globalY() - x_w = self.mouse_offset.x() - y_w = self.mouse_offset.y() - self.move(x - x_w, y - y_w) - - if event.type() == QEvent.KeyRelease and self.KeyEventsEnabled: - # print("got key event") - key = event.key() - try: - self.Window.LastButtonClicked = chr(key).lower() - except: - self.Window.LastButtonClicked = 'special %s' % key - self.Window.FormRemainedOpen = True - if self.Window.CurrentlyRunningMainloop: - self.Window.QTApplication.exit() - return QWidget.eventFilter(self, widget, event) - - def closeEvent(self, event): - if self.Window.DisableClose: - event.ignore() - return - # print('GOT A CLOSE EVENT!', event, self.Window.Title) - self.Window.LastButtonClicked = None - self.Window.XFound = True - if not self.Window.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! - self.Window.RootNeedsDestroying = True - else: - self.Window.RootNeedsDestroying = True - self.Window.QTApplication.exit() # kick the users out of the mainloop - self.Window.QT_QMainWindow.close() - self.Window.TKrootDestroyed = True - self.Window.RootNeedsDestroying = True - - # if self.CurrentlyRunningMainloop: - # print("quitting window") - # self.QTApplication.exit() # kick the users out of the mainloop - - @property - def Size(self): - if not self._is_window_created(): - return - size = self.QT_QMainWindow.sizeHint() - return [size.width(), size.height()] - - @Size.setter - def Size(self, size): - if not self._is_window_created(): - return - self.QT_QMainWindow.resize(QSize(size[0], size[1])) - - def _is_window_created(self): - if self.QT_QMainWindow is None: - warnings.warn( - 'You cannot perform operations on a Window until it is read or finalized. Adding a "finalize=True" parameter to your Window creation will fix this', - UserWarning, - ) - popup_error( - 'You cannot perform operations on a Window until it is read or finalized.', - 'Yea, I know, it\'s a weird thing, but easy to fix.... ', - 'Adding a "finalize=True" parameter to your Window creation will likely fix this', - image=FACE_PALM, - ) - return False - return True - - def __getitem__(self, key): - """ - Returns Element that matches the passed in key. - This is "called" by writing code as thus: - window['element key'].Update - - :param key: The key to find - :type key: Union[str, int, tuple, object] - :return: Union[Element, None] The element found or None if no element was found - :rtype: Element - """ - try: - return self.Element(key) - except Exception as e: - print('The key you passed in is no good. Key = {}*'.format(key)) - return None - - def __call__(self, *args, **kwargs): - """ - Call window.Read but without having to type it out. - window() == window.Read() - window(timeout=50) == window.Read(timeout=50) - - :param args: * - :type args: (any) - :param kwargs: ** - :type kaargs: (any) - :return: Tuple[Any, Dict[Any:Any]] The famous event, values that Read returns. - """ - return self.Read(*args, **kwargs) - - add_row = AddRow - add_rows = AddRows - alpha_channel = AlphaChannel - bring_to_front = BringToFront - close = Close - current_location = CurrentLocation - disable = Disable - disappear = Disappear - element = Element - enable = Enable - fill = Fill - finalize = Finalize - find_element = FindElement - find_element_with_focus = FindElementWithFocus - get_screen_dimensions = GetScreenDimensions - hide = Hide - layout = Layout - load_from_disk = LoadFromDisk - maximize = Maximize - minimize = Minimize - move = Move - read = Read - reappear = Reappear - refresh = Refresh - save_to_disk = SaveToDisk - set_alpha = SetAlpha - set_icon = SetIcon - size = Size - un_hide = UnHide - visibility_changed = VisibilityChanged - - -FlexForm = Window - - -class QtStyle(object): - ''' - API - - # step 1 - make a style - ss = QtStyle(QLabel) - - # step 2 - add fields - ss['font'] = create_style_from_font() - ss['background_color'] = (color, color_default) - ss['color'] = (color, color_default) - # step 2.1 - add additions - ss.append_css_to_end.append(" QScrollBar:vertical { ... some css here ... } ") - # step 2.2 - add anchor - ss.my_anchor = '::chunk' - - # step 3 - build result - css_str = ss.build_css_string() - qt_widget.setStyleSheet(css_str) - - ====== Special fields - - font - - margin - Why they are special? Because of the formatting. - - === === === - made by nngogol - ''' - - def __init__(self, widget_name=''): - self.widget_name = widget_name - self.css_props = {} - self.my_anchor = None - - self.logging = True - self.logging = not True - self.append_css_to_end = [] - - self.make_secure_check = True # Check if "css property is valid, i.e. it's present in default names". - # Make makes development safer, if you have a erro in spelling css-property - # In production: it can be disabled, I guess. - - def __setitem__(self, css_prop_name, css_prop_value): - - css_prop_name = css_prop_name.replace('_', '-') - # validation - if not isinstance(css_prop_value, (tuple, list, str)): - raise Exception('Bad value fro css property -> %s.' % css_prop_value) - if self.make_secure_check: - if not is_valid_css_prop(css_prop_name): - raise Exception('Bad css property name: ' % css_prop_name) - - self.css_props[css_prop_name] = css_prop_value - - def build_css_string(self): - # no css props added -> return empty string - if not self.css_props: # empty case - print(f' final_str # {self.widget_name} = ""') - return '' - - css_props_str_list = [] - for key, value in self.css_props.items(): - # special cases: - if key == 'margin' or key == 'padding': - # validation - if not isinstance(value, (tuple, list)): - raise Exception('Cant handle this TYPE for margin property : %s ' % str(type(value))) - - result_css_string = '' - - if len(value) == 4: - # skip all zeros - if value[0] == value[1] == value[2] == value[3] == 0: - result_css_string = '' - - result_css_string = '{} : {}px {}px {}px {}px;'.format(key, *value) - elif len(value) == 1: - # skip all zeros - if value[0] == 0: - continue - - result_css_string = '{} : {}px;'.format(key, value[0]) - else: - raise Exception('Bad value for margin/padding property: ' % str(value)) - - # # Fix for this case: - # # Wrong: margin: 0px; - # # Right: margin: 0; - # result_css_string = result_css_string.replace(': 0px', ': 0') - - css_props_str_list.append(result_css_string) - - continue - # if key == 'border': ... - - # it's a css string! Format: 'propery: value;'' - if isinstance(value, str): - # is css prop name + css prop value - if ':' in value: - css_props_str_list.append(value) - # is css value - else: - css_props_str_list.append(_to_css_prop(key, value)) - # we continue, because it was 'string type parsing' - continue - - isnot = None - # it's a pair! Format: (val, default_val) - if isinstance(value, (tuple, list)): - user_css_prop_value, isnot = value - - if user_css_prop_value is not None and user_css_prop_value != isnot: - css_props_str_list.append(_to_css_prop(key, user_css_prop_value)) - - # join all props - css_all = ''.join(css_props_str_list) - final_str = css_all - if self.widget_name: - my_anchor = '' if self.my_anchor is None else self.my_anchor - final_str = '%s%s { %s }' % (self.widget_name, my_anchor, css_all) - - # if needed: append some css from self.append_css_to_end - final_str += ' '.join(self.append_css_to_end) - - if self.logging: - print(f'final css string (self.widget_name): {final_str}') - return final_str - - def __repr__(self): - return self.build_css_string() - - -# =========================================================================== # -# Stops the mainloop and sets the event information # -# =========================================================================== # - - -def _element_callback_quit_mainloop(element): - if element.Key is not None: - element.ParentForm.LastButtonClicked = element.Key - else: - element.ParentForm.LastButtonClicked = '' - element.ParentForm.FormRemainedOpen = True - if element.ParentForm.CurrentlyRunningMainloop: - element.ParentForm.QTApplication.exit() # kick the users out of the mainloop - - -# =========================================================================== # -# Convert from characters to pixels # -# =========================================================================== # -def _convert_tkinter_size_to_Qt(size, scaling=DEFAULT_PIXELS_TO_CHARS_SCALING, height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF): - """ - Converts size in characters to size in pixels - :param size: size in characters, rows - :return: size in pixels, pixels - """ - qtsize = size - if size[1] is not None and size[1] < height_cutoff: # change from character based size to pixels (roughly) - qtsize = size[0] * scaling[0], size[1] * scaling[1] - return qtsize - - -# =========================================================================== # -# Stops the mainloop and sets the event information # -# =========================================================================== # -def convert_tkinter_filetypes_to_qt(filetypes): - qt_filetypes = '' - for i, item in enumerate(filetypes): - filetype = item[0] + ' (' + item[1] + ')' + (';;' if i != len(filetypes) - 1 else '') - qt_filetypes += filetype - return qt_filetypes - - -# =========================================================================== # -# Converts a "Font" string or tuple into Qt Style Sheet Entries # -# =========================================================================== # -def create_style_from_font(font): - """ - Convert from font string/tuple into a Qt style sheet string - :param font: "Arial 10 Bold" or ('Arial', 10, 'Bold') - :return: style string that can be combined with other style strings - """ - - if font is None: - return '' - _font = font.split(' ') if type(font) is str else font - - # parsing name + size - font_name, font_size = _font[:2] - # parsing options: - is_bold, is_underline = False, False - if len(_font) > 2: - options = _font[2:] - for some_option in options: - if some_option == 'underline': - is_underline = True - else: - is_bold = True - - # build - is_bold_text = 'font-weight : bold;' if is_bold else '' - is_underline_text = 'text-decoration: underline;' if is_underline else '' - - return textwrap.dedent( - f''' - {is_underline_text} - {is_bold_text} - font-family: "{font_name}"; - font-size: {font_size}pt; - '''.strip() - ).replace('\n', '') - - -def set_widget_visiblity(widget, visible): - if visible is False: - widget.setVisible(False) - elif visible is True: - widget.setVisible(True) - - -# ################################################################################ -# ################################################################################ -# END OF ELEMENT DEFINITIONS -# ################################################################################ -# ################################################################################ - - -# =========================================================================== # -# Button Lazy Functions so the caller doesn't have to define a bunch of stuff # -# =========================================================================== # - - -# ------------------------- FOLDER BROWSE Element lazy function ------------------------- # -def FolderBrowse( - button_text='Browse', - target=(ThisRow, -1), - initial_folder=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - change_submits=False, - enable_events=False, - font=None, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_BROWSE_FOLDER, - target=target, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - disabled=disabled, - button_color=button_color, - change_submits=change_submits, - enable_events=enable_events, - font=font, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- FILE BROWSE Element lazy function ------------------------- # -def FileBrowse( - button_text='Browse', - target=(ThisRow, -1), - file_types=(('ALL Files', '*'),), - initial_folder=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - enable_events=False, - font=None, - disabled=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_BROWSE_FILE, - target=target, - file_types=file_types, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - change_submits=change_submits, - enable_events=enable_events, - disabled=disabled, - button_color=button_color, - font=font, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- FILES BROWSE Element (Multiple file selection) lazy function ------------------------- # -def FilesBrowse( - button_text='Browse', - target=(ThisRow, -1), - file_types=(('ALL Files', '*'),), - disabled=False, - initial_folder=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - enable_events=False, - font=None, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_BROWSE_FILES, - target=target, - file_types=file_types, - initial_folder=initial_folder, - change_submits=change_submits, - enable_events=enable_events, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - disabled=disabled, - button_color=button_color, - font=font, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- FILE BROWSE Element lazy function ------------------------- # -def FileSaveAs( - button_text='Save As...', - target=(ThisRow, -1), - file_types=(('ALL Files', '*'),), - initial_folder=None, - disabled=False, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - enable_events=False, - font=None, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_SAVEAS_FILE, - target=target, - file_types=file_types, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - disabled=disabled, - auto_size_button=auto_size_button, - button_color=button_color, - change_submits=change_submits, - enable_events=enable_events, - font=font, - pad=pad, - key=key, - metadata=metadata, - ) - - -# ------------------------- SAVE AS Element lazy function ------------------------- # -def SaveAs( - button_text='Save As...', - target=(ThisRow, -1), - file_types=(('ALL Files', '*'),), - initial_folder=None, - disabled=False, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - enable_events=False, - font=None, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_SAVEAS_FILE, - target=target, - file_types=file_types, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - disabled=disabled, - auto_size_button=auto_size_button, - button_color=button_color, - change_submits=change_submits, - enable_events=enable_events, - font=font, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- SAVE BUTTON Element lazy function ------------------------- # -def Save( - button_text='Save', - size=(None, None), - auto_size_button=None, - button_color=None, - bind_return_key=True, - disabled=False, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- SUBMIT BUTTON Element lazy function ------------------------- # -def Submit( - button_text='Submit', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- OPEN BUTTON Element lazy function ------------------------- # -# ------------------------- OPEN BUTTON Element lazy function ------------------------- # -def Open( - button_text='Open', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- OK BUTTON Element lazy function ------------------------- # -def OK( - button_text='OK', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- YES BUTTON Element lazy function ------------------------- # -def Ok( - button_text='Ok', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- CANCEL BUTTON Element lazy function ------------------------- # -def Cancel( - button_text='Cancel', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- QUIT BUTTON Element lazy function ------------------------- # -def Quit( - button_text='Quit', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- Exit BUTTON Element lazy function ------------------------- # -def Exit( - button_text='Exit', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- YES BUTTON Element lazy function ------------------------- # -def Yes( - button_text='Yes', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=True, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- NO BUTTON Element lazy function ------------------------- # -def No( - button_text='No', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- NO BUTTON Element lazy function ------------------------- # -def Help( - button_text='Help', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - font=None, - tooltip=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # -def SimpleButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - disabled=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_CLOSES_WIN, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - disabled=disabled, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- CLOSE BUTTON Element lazy function ------------------------- # -def CloseButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - disabled=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_CLOSES_WIN, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - disabled=disabled, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -CButton = CloseButton - - -# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # -def ReadButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - disabled=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - """ - :param button_text: text in the button - :type button_text: (str) - :param image_filename: image filename if there is a button image. GIFs and PNGs only. - :type image_filename: (str) - :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data - :type image_data: Union[bytes, str] - :param image_size: Size of the image in pixels (width, height) - :type image_size: Tuple[int, int] - :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc - :type image_subsample: (int) - :param border_width: width of border around element - :type border_width: (int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param size: (w,h) w=characters-wide, h=rows-high - :type size: Tuple[int, int] - :param auto_size_button: True if button size is determined by button text - :type auto_size_button: (bool) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event - :type bind_return_key: (bool) - :param disabled: set disable state for element (Default = False) - :type disabled: (bool) - :param focus: if focus should be set to this - :type focus: idk_yetReally - :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: key for uniquely identify this element (for window.FindElement) - :type key: Union[str, int, tuple, object] - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param metadata: Anything you want to store along with this button - :type metadata: (Any) - """ - - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - disabled=disabled, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -ReadFormButton = ReadButton -RButton = ReadFormButton - - -# ------------------------- Realtime BUTTON Element lazy function ------------------------- # -def RealtimeButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - disabled=False, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - """ - :param button_text: text in the button - :type button_text: (str) - :param image_filename: image filename if there is a button image. GIFs and PNGs only. - :type image_filename: (str) - :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data - :type image_data: Union[bytes, str] - :param image_size: Size of the image in pixels (width, height) - :type image_size: Tuple[int, int] - :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc - :type image_subsample: (int) - :param border_width: width of border around element - :type border_width: (int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param size: (w,h) w=characters-wide, h=rows-high - :type size: Tuple[int, int] - :param auto_size_button: True if button size is determined by button text - :type auto_size_button: (bool) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param disabled: set disable state for element (Default = False) - :type disabled: (bool) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event - :type bind_return_key: (bool) - :param focus: if focus should be set to this - :type focus: (bool) - :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: key for uniquely identify this element (for window.FindElement) - :type key: Union[str, int, tuple, object] - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param metadata: Anything you want to store along with this button - :type metadata: (Any) - """ - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_REALTIME, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - disabled=disabled, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- Dummy BUTTON Element lazy function ------------------------- # -def DummyButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - disabled=False, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - """ - :param button_text: text in the button - :type button_text: (str) - :param image_filename: image filename if there is a button image. GIFs and PNGs only. - :type image_filename: (str) - :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data - :type image_data: Union[bytes, str] - :param image_size: Size of the image in pixels (width, height) - :type image_size: Tuple[int, int] - :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc - :type image_subsample: (int) - :param border_width: width of border around element - :type border_width: (int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param size: (w,h) w=characters-wide, h=rows-high - :type size: Tuple[int, int] - :param auto_size_button: True if button size is determined by button text - :type auto_size_button: (bool) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param disabled: set disable state for element (Default = False) - :type disabled: (bool) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event - :type bind_return_key: (bool) - :param focus: if focus should be set to this - :type focus: (bool) - :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param key: key for uniquely identify this element (for window.FindElement) - :type key: Union[str, int, tuple, object] - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: Union[str, int, tuple, object] - :param metadata: Anything you want to store along with this button - :type metadata: (Any) - """ - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_CLOSES_WIN_ONLY, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -# ------------------------- Calendar Chooser Button lazy function ------------------------- # -def CalendarButton( - button_text, - target=(None, None), - close_when_date_chosen=True, - default_date_m_d_y=(None, None, None), - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - tooltip=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - button = Button( - button_text=button_text, - button_type=BUTTON_TYPE_CALENDAR_CHOOSER, - target=target, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - button.CalendarCloseWhenChosen = close_when_date_chosen - button.DefaultDate_M_D_Y = default_date_m_d_y - return button - - -# ------------------------- Calendar Chooser Button lazy function ------------------------- # -def ColorChooserButton( - button_text, - target=(None, None), - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - tooltip=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - k=None, - metadata=None, -): - """ - :param button_text: text in the button - :type button_text: (str) - :param target: key or (row,col) target for the button. Note that -1 for column means 1 element to the left of this one. The constant ThisRow is used to indicate the current row. The Button itself is a valid target for some types of button - :type target: Union[str, Tuple[int, int]] - :param image_filename: image filename if there is a button image. GIFs and PNGs only. - :type image_filename: (str) - :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data - :type image_data: Union[bytes, str] - :param image_size: Size of the image in pixels (width, height) - :type image_size: Tuple[int, int] - :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc - :type image_subsample: (int) - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param border_width: width of border around element - :type border_width: (int) - :param size: (w,h) w=characters-wide, h=rows-high - :type size: Tuple[int, int] - :param auto_size_button: True if button size is determined by button text - :type auto_size_button: (bool) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param disabled: set disable state for element (Default = False) - :type disabled: (bool) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event - :type bind_return_key: (bool) - :param focus: if focus should be set to this - :type focus: (bool) - :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) - :param metadata: Anything you want to store along with this button - :type metadata: (Any) - """ - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_COLOR_CHOOSER, - target=target, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - k=k, - metadata=metadata, - ) - - -##################################### ----- RESULTS ------ ################################################## - - -def AddToReturnDictionary(form, element, value): - form.ReturnValuesDictionary[element.Key] = value - return - if element.Key is None: - form.ReturnValuesDictionary[form.DictionaryKeyCounter] = value - element.Key = form.DictionaryKeyCounter - form.DictionaryKeyCounter += 1 - else: - form.ReturnValuesDictionary[element.Key] = value - - -def AddToReturnList(form, value): - form.ReturnValuesList.append(value) - - -# ----------------------------------------------------------------------------# -# ------- FUNCTION InitializeResults. Sets up form results matrix --------# -def InitializeResults(form): - BuildResults(form, True, form) - return - - -# ===== Radio Button RadVar encoding and decoding =====# -# ===== The value is simply the row * 1000 + col =====# -def DecodeRadioRowCol(RadValue): - row = RadValue // 1000 - col = RadValue % 1000 - return row, col - - -def EncodeRadioRowCol(row, col): - RadValue = row * 1000 + col - return RadValue - - -# ------- FUNCTION BuildResults. Form exiting so build the results to pass back ------- # -# format of return values is -# (Button Pressed, input_values) -def BuildResults(form, initialize_only, top_level_form): - # Results for elements are: - # TEXT - Nothing - # INPUT - Read value from TK - # Button - Button Text and position as a Tuple - - # Get the initialized results so we don't have to rebuild - form.DictionaryKeyCounter = 0 - form.ReturnValuesDictionary = {} - form.ReturnValuesList = [] - BuildResultsForSubform(form, initialize_only, top_level_form) - if not top_level_form.LastButtonClickedWasRealtime: - top_level_form.LastButtonClicked = None - return form.ReturnValues - - -def BuildResultsForSubform(form, initialize_only, top_level_form): - button_pressed_text = top_level_form.LastButtonClicked - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Key is not None and WRITE_ONLY_KEY in str(element.Key): - continue - value = None - if element.Type == ELEM_TYPE_COLUMN: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_FRAME: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_TAB_GROUP: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_TAB: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if not initialize_only: - if element.Type == ELEM_TYPE_INPUT_TEXT: - value = element.QT_QLineEdit.text() - if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: - element.QT_QLineEdit.setText('') - - elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: - value = element.QT_Checkbox.isChecked() - elif element.Type == ELEM_TYPE_INPUT_RADIO: - this_rowcol = EncodeRadioRowCol(row_num, col_num) - value = element.QT_Radio_Button.isChecked() - elif element.Type == ELEM_TYPE_BUTTON: - if top_level_form.LastButtonClicked == element.ButtonText: - button_pressed_text = top_level_form.LastButtonClicked - if element.BType != BUTTON_TYPE_REALTIME: # Do not clear realtime buttons - top_level_form.LastButtonClicked = None - if element.BType == BUTTON_TYPE_CALENDAR_CHOOSER: - try: - value = element.TKCal.selection - except: - value = None - else: - try: - value = element.FileOrFolderName - except: - value = None - elif element.Type == ELEM_TYPE_INPUT_COMBO: - element = element # type: Combo - index = element.QT_ComboBox.currentIndex() # index into the list of values, but can be larger if manual entry - if index < len(element.Values): - value = element.Values[index] - else: # if not a valid index, then get what was typed in - value = element.QT_ComboBox.currentText() - elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: - value = 0 - elif element.Type == ELEM_TYPE_INPUT_LISTBOX: - element = element # type: Listbox - # print(f'selected indexes = {element.QT_ListWidget.selectedIndexes()}') - value = [] - # value = [element.Values[int(i)] for i in element.QT_ListWidget.selectedIndexes()] - # value= [ for i, item in enumerate(element.QT_ListWidget.selectedItems()] - selected_items = [item.text() for item in element.QT_ListWidget.selectedItems()] - for v in element.Values: - if str(v) in selected_items: - value.append(v) - # try: - # value= [item.index() for item in element.QT_ListWidget.selectedItems()] - # # value= [item.text() for item in element.QT_ListWidget.selectedItems()] - # except: - # value = [] - elif element.Type == ELEM_TYPE_INPUT_SPIN: - # value = str(element.QT_Spinner.value()) - # value = str(element.QT_Spinner.textFromValue(element.QT_Spinner.value())) - value = element.Values[element.QT_Spinner.value()] - elif element.Type == ELEM_TYPE_INPUT_DIAL: - value = str(element.QT_Dial.value()) - elif element.Type == ELEM_TYPE_INPUT_SLIDER: - value = element.QT_Slider.value() - elif element.Type == ELEM_TYPE_INPUT_MULTILINE: - if element.WriteOnly: - continue - value = element.QT_TextEdit.toPlainText() - if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: - element.QT_TextEdit.setText('') - elif element.Type == ELEM_TYPE_TAB_GROUP: - element = element # type: TabGroup - cur_index = element.QT_QTabWidget.currentIndex() - tab_element = element.TabList[cur_index] - value = tab_element.Key - elif element.Type == ELEM_TYPE_TABLE: - value = [] - indexes = element.QT_TableWidget.selectionModel().selectedRows() - for index in sorted(indexes): - value.append(index.row()) - elif element.Type == ELEM_TYPE_TREE: - value = [] - indexes = element.QT_QTreeWidget.selectionModel().selectedRows() - for index in sorted(indexes): - value.append(index.row()) - elif element.Type == ELEM_TYPE_BUTTONMENU: - value = element.MenuItemChosen - element.MenuItemChosen = None - elif element.Type == ELEM_TYPE_MENUBAR: - if element.MenuItemChosen is not None: - top_level_form.LastButtonClicked = element.MenuItemChosen - button_pressed_text = top_level_form.LastButtonClicked - value = element.MenuItemChosen - element.MenuItemChosen = None - else: - value = None - - # if an input type element, update the results - if element.Type != ELEM_TYPE_BUTTON and element.Type != ELEM_TYPE_TEXT and element.Type != ELEM_TYPE_IMAGE and element.Type != ELEM_TYPE_OUTPUT and element.Type != ELEM_TYPE_PROGRESS_BAR and element.Type != ELEM_TYPE_COLUMN and element.Type != ELEM_TYPE_FRAME and element.Type != ELEM_TYPE_TAB: - AddToReturnList(form, value) - AddToReturnDictionary(top_level_form, element, value) - elif ( - (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_CALENDAR_CHOOSER and element.Target == (None, None)) - or (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_COLOR_CHOOSER and element.Target == (None, None)) - or ( - element.Type == ELEM_TYPE_BUTTON - and element.Key is not None - and ( - element.BType - in ( - BUTTON_TYPE_SAVEAS_FILE, - BUTTON_TYPE_BROWSE_FILE, - BUTTON_TYPE_BROWSE_FILES, - BUTTON_TYPE_BROWSE_FOLDER, - ) - ) - ) - ): - AddToReturnList(form, value) - AddToReturnDictionary(top_level_form, element, value) - - # if this is a column, then will fail so need to wrap with tr - try: - if form.ReturnKeyboardEvents and form.LastKeyboardEvent is not None: - button_pressed_text = form.LastKeyboardEvent - form.LastKeyboardEvent = None - except: - pass - - try: - form.ReturnValuesDictionary.pop(None, None) # clean up dictionary include None was included - except: - pass - - if not form.UseDictionary: - form.ReturnValues = button_pressed_text, form.ReturnValuesList - else: - form.ReturnValues = button_pressed_text, form.ReturnValuesDictionary - - return form.ReturnValues - - -def FillFormWithValues(form, values_dict): - FillSubformWithValues(form, values_dict) - - -def FillSubformWithValues(form, values_dict): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - value = None - if element.Type == ELEM_TYPE_COLUMN: - FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_FRAME: - FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_TAB_GROUP: - FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_TAB: - FillSubformWithValues(element, values_dict) - try: - value = values_dict[element.Key] - except: - continue - if element.Type == ELEM_TYPE_INPUT_TEXT: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_RADIO: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_COMBO: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_LISTBOX: - element.SetValue(value) - elif element.Type == ELEM_TYPE_INPUT_SLIDER: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_MULTILINE: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_SPIN: - element.Update(value) - elif element.Type == ELEM_TYPE_BUTTON: - element.Update(value) - - -def _FindElementFromKeyInSubForm(form, key): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_FRAME: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB_GROUP: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Key == key: - return element - - -def _FindElementWithFocusInSubForm(form): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_FRAME: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB_GROUP: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - try: - if element.Widget.hasFocus(): - return element - except: - continue - # if element.Type == ELEM_TYPE_INPUT_TEXT: - # if element.QT_QLineEdit is not None: - # if element.QT_QLineEdit is element.TKEntry.focus_get(): - # return element - - -def AddTrayMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): - if type(sub_menu_info) is str: - if not is_sub_menu and not skip: - # print(f'Adding command {sub_menu_info}') - action = QAction(top_menu) - if sub_menu_info == '---': - action.setSeparator(True) - else: - try: - item_without_key = sub_menu_info[: sub_menu_info.index(MENU_KEY_SEPARATOR)] - except: - item_without_key = sub_menu_info - if item_without_key[0] == MENU_DISABLED_CHARACTER: - action.setText(item_without_key[len(MENU_DISABLED_CHARACTER) :]) - action.setDisabled(True) - else: - action.setText(item_without_key) - action.triggered.connect(lambda: SystemTray._QT_MenuItemChosenCallback(element, sub_menu_info)) - top_menu.addAction(action) - else: - i = 0 - while i < (len(sub_menu_info)): - item = sub_menu_info[i] - if i != len(sub_menu_info) - 1: - if type(sub_menu_info[i + 1]) is list: - new_menu = QMenu(top_menu) - item = sub_menu_info[i] - try: - item_without_key = item[: item.index(MENU_KEY_SEPARATOR)] - except: - item_without_key = item - if item_without_key[0] == MENU_DISABLED_CHARACTER: - new_menu.setTitle(item_without_key[len(MENU_DISABLED_CHARACTER) :]) - new_menu.setDisabled(True) - else: - new_menu.setTitle(item_without_key) - top_menu.addAction(new_menu.menuAction()) - # print(f'Adding submenu {sub_menu_info[i]}') - AddTrayMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) - i += 1 # skip the next one - else: - AddTrayMenuItem(top_menu, item, element) - else: - AddTrayMenuItem(top_menu, item, element) - i += 1 - - -def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): - if type(sub_menu_info) is str: - if not is_sub_menu and not skip: - # print(f'Adding command {sub_menu_info}') - action = QAction(top_menu) - - if sub_menu_info == '---': - action.setSeparator(True) - else: - - # Key handling.... strip off key before setting text - try: - item_without_key = sub_menu_info[: sub_menu_info.index(MENU_KEY_SEPARATOR)] - except: - item_without_key = sub_menu_info - if item_without_key[0] == MENU_DISABLED_CHARACTER: - action.setText(item_without_key[len(MENU_DISABLED_CHARACTER) :]) - action.setDisabled(True) - else: - action.setText(item_without_key) - action.triggered.connect(lambda: Menu._QT_MenuItemChosenCallback(element, sub_menu_info)) - top_menu.addAction(action) - else: - i = 0 - while i < (len(sub_menu_info)): - item = sub_menu_info[i] - if i != len(sub_menu_info) - 1: - if type(sub_menu_info[i + 1]) is list: - new_menu = QMenu(top_menu) - # - # # === style === - # menu_style = _Style('QMenu') - # menu_style['font'] = create_style_from_font(element.Font) - # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - # menu_style['color'] = element.TextColor - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # menu_style['background-color'] = element.BackgroundColor - # # style['margin'] = full_element_pad - # new_menu.setStyleSheet(menu_style.build_css_string()) - # print(menu_style) - # # element.qt_styles = (style,) - # # === style === end - # - # Key handling.... strip off key before setting text - item = sub_menu_info[i] - try: - item_without_key = item[: item.index(MENU_KEY_SEPARATOR)] - except: - item_without_key = item - if item_without_key[0] == MENU_DISABLED_CHARACTER: - new_menu.setTitle(item_without_key[len(MENU_DISABLED_CHARACTER) :]) - new_menu.setDisabled(True) - else: - new_menu.setTitle(item_without_key) - top_menu.addAction(new_menu.menuAction()) - # print(f'Adding submenu {sub_menu_info[i]}') - AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) - i += 1 # skip the next one - else: - AddMenuItem(top_menu, item, element) - else: - AddMenuItem(top_menu, item, element) - i += 1 - - -""" - QQQQQQQQQ tttt - QQ:::::::::QQ ttt:::t - QQ:::::::::::::QQ t:::::t -Q:::::::QQQ:::::::Q t:::::t -Q::::::O Q::::::Qttttttt:::::ttttttt -Q:::::O Q:::::Qt:::::::::::::::::t -Q:::::O Q:::::Qt:::::::::::::::::t -Q:::::O Q:::::Qtttttt:::::::tttttt -Q:::::O Q:::::Q t:::::t -Q:::::O Q:::::Q t:::::t -Q:::::O QQQQ:::::Q t:::::t -Q::::::O Q::::::::Q t:::::t tttttt -Q:::::::QQ::::::::Q t::::::tttt:::::t - QQ::::::::::::::Q tt::::::::::::::t - QQ:::::::::::Q tt:::::::::::tt - QQQQQQQQ::::QQ ttttttttttt - Q:::::Q - QQQQQQ -""" - -# My crappy Qt code starts here - -# ░░░░░░░░░░░█▀▀░░█░░░░░░ -# ░░░░░░▄▀▀▀▀░░░░░█▄▄░░░░ -# ░░░░░░█░█░░░░░░░░░░▐░░░ -# ░░░░░░▐▐░░░░░░░░░▄░▐░░░ -# ░░░░░░█░░░░░░░░▄▀▀░▐░░░ -# ░░░░▄▀░░░░░░░░▐░▄▄▀░░░░ -# ░░▄▀░░░▐░░░░░█▄▀░▐░░░░░ -# ░░█░░░▐░░░░░░░░▄░█░░░░░ -# ░░░█▄░░▀▄░░░░▄▀▐░█░░░░░ -# ░░░█▐▀▀▀░▀▀▀▀░░▐░█░░░░░ -# ░░▐█▐▄░░▀░░░░░░▐░█▄▄░░░ -# ░░░▀▀▄░░░░░░░░▄▐▄▄▄▀░░░ -# ░░░░░░░░░░░░░░░░░░░░░░░ - - -# ------------------------------------------------------------------------------------------------------------------ # -# ------------------------------------------------------------------------------------------------------------------ # -# ===================================== Qt CODE STARTS HERE ====================================================== # -# ------------------------------------------------------------------------------------------------------------------ # -# ------------------------------------------------------------------------------------------------------------------ # -# to_css_prop(css_prop.replace('_','-'), value) -def _to_css_prop(key_, val_): - return '{}:{}; '.format(key_.replace('_', '-'), val_) - - -# def style_generate(qt_element_type, css_props_str): -# return '%s {\n %s \n}' % (qt_element_type, css_props_str) -_valid_css_fields = [ - 'align-content', - 'align-items', - 'align-self', - 'background', - 'background-attachment', - 'background-color', - 'background-image', - 'background-position', - 'background-size', - 'border', - 'border-collapse', - 'border-image', - 'border-radius', - 'border-spacing', - 'bottom', - 'box-decoration-break', - 'caret-color', - 'clear', - 'clip-path', - 'color', - 'color-adjust', - 'column-count', - 'column-fill', - 'column-gap', - 'column-rule', - 'column-rule-color', - 'column-rule-style', - 'column-rule-width', - 'column-span', - 'column-width', - 'columns', - 'contain', - 'content', - 'counter-increment', - 'counter-reset', - 'counter-set', - 'cursor', - 'direction', - 'display', - 'empty-cells', - 'fill', - 'filter', - 'flex', - 'flex-basis', - 'flex-direction', - 'flex-flow', - 'flex-grow', - 'flex-shrink', - 'flex-wrap', - 'float', - 'font', - 'font-display', - 'font-family', - 'font-feature-settings', - 'font-kerning', - 'font-optical-sizing', - 'font-size', - 'font-size-adjust', - 'font-stretch', - 'font-style', - 'font-synthesis', - 'font-variant', - 'font-variant-numeric', - 'font-weight', - 'gap', - 'grid-column', - 'grid-row', - 'grid-template-columns', - 'grid-template-rows', - 'hanging-punctuation', - 'height', - 'hyphens', - 'image-rendering', - 'initial-letter', - 'inline-size', - 'inset', - 'inset-block', - 'inset-block-end', - 'inset-block-start', - 'inset-inline', - 'inset-inline-end', - 'inset-inline-start', - 'isolation', - 'justify-content', - 'left', - 'letter-spacing', - 'line-clamp', - 'line-height', - 'list-style', - 'margin', - 'mask-image', - 'mask-position', - 'mask-repeat', - 'mask-size', - 'max-height', - 'max-width', - 'min-height', - 'min-width', - 'mix-blend-mode', - 'object-fit', - 'object-position', - 'offset-anchor', - 'offset-distance', - 'offset-path', - 'offset-rotate', - 'opacity', - 'order', - 'orphans', - 'outline', - 'outline-offset', - 'overflow', - 'overflow-anchor', - 'overflow-wrap', - 'overscroll-behavior', - 'padding', - 'page-break', - 'paint-order', - 'perspective', - 'perspective-origin', - 'place-items', - 'pointer-events', - 'position', - 'quotes', - 'resize', - 'right', - 'row-gap', - 'scroll-behavior', - 'scroll-margin', - 'scroll-padding', - 'scroll-snap-align', - 'scroll-snap-stop', - 'scroll-snap-type', - 'scrollbar', - 'scrollbar-color', - 'scrollbar-gutter', - 'scrollbar-width', - 'shape-image-threshold', - 'shape-margin', - 'shape-outside', - 'speak', - 'stroke', - 'stroke-dasharray', - 'stroke-dashoffset', - 'stroke-linecap', - 'stroke-linejoin', - 'stroke-width', - 'tab-size', - 'table-layout', - 'text-align', - 'text-align-last', - 'text-decoration', - 'text-decoration-color', - 'text-decoration-line', - 'text-decoration-skip', - 'text-decoration-skip-ink', - 'text-decoration-style', - 'text-decoration-thickness', - 'text-indent', - 'text-justify', - 'text-overflow', - 'text-rendering', - 'text-shadow', - 'text-stroke', - 'text-transform', - 'text-underline-offset', - 'text-underline-position', - 'top', - 'touch-action', - 'transform', - 'transform-origin', - 'transform-style', - 'transition', - 'transition-delay', - 'transition-duration', - 'transition-property', - 'transition-timing-function', - 'unicode-bidi', - 'unicode-range', - 'user-select', - 'vertical-align', - 'visibility', - 'white-space', - 'widows', - 'width', - 'will-change', - 'word-break', - 'word-spacing', - 'writing-mode', - 'z-index', - 'zoom', -] - - -def is_valid_css_prop(a_css_prop): - '''Check if a given property EXISTS in qt Spec''' - return True - global _valid_css_fields - norm_ = a_css_prop.replace('_', '-') - return norm_ in _valid_css_fields - - -def PackFormIntoFrame(container_elem, containing_frame, toplevel_win): - """ - :param form: a window class - :type form: (Window) - :param containing_frame: ??? - :type containing_frame: ??? - :param toplevel_form: ??? - :type toplevel_form: (Window) - """ - # align2qt_align - align2qt_align = {'c': Qt.AlignCenter, 'l': Qt.AlignLeft, 'r': Qt.AlignRight} - - border_depth = toplevel_win.BorderDepth if toplevel_win.BorderDepth is not None else DEFAULT_BORDER_WIDTH - # --------------------------------------------------------------------------- # - # **************** Use FlexForm to build the tkinter window ********** ----- # - # Building is done row by row. # - # --------------------------------------------------------------------------- # - focus_set = False - ######################### LOOP THROUGH ROWS ######################### - # *********** ------- Loop through ROWS ------- ***********# - for row_num, flex_row in enumerate(container_elem.Rows): - ######################### LOOP THROUGH ELEMENTS ON ROW ######################### - # *********** ------- Loop through ELEMENTS ------- ***********# - # *********** Make TK Row ***********# - qt_row_layout = QHBoxLayout() - elem_align = container_elem.ElementJustification[0] - if elem_align in align2qt_align: - qt_row_layout.setAlignment(align2qt_align[elem_align]) - for col_num, element in enumerate(flex_row): - element.ParentForm = toplevel_win # save the button's parent form object - element.row_frame = qt_row_layout - if toplevel_win.Font and (element.Font == DEFAULT_FONT or not element.Font): - font = toplevel_win.Font - element.Font = font - elif element.Font is not None: - font = element.Font - else: - font = DEFAULT_FONT - # ------- Determine Auto-Size setting on a cascading basis ------- # - if element.AutoSizeText is not None: # if element overide - auto_size_text = element.AutoSizeText - elif toplevel_win.AutoSizeText is not None: # if form override - auto_size_text = toplevel_win.AutoSizeText - else: - auto_size_text = DEFAULT_AUTOSIZE_TEXT - element_type = element.Type - # Set foreground color - text_color = element.TextColor - # Determine Element size - element_size = element.Size - if element_size == (None, None) and element_type not in ( - ELEM_TYPE_BUTTON, - ELEM_TYPE_BUTTONMENU, - ): # user did not specify a size - element_size = toplevel_win.DefaultElementSize - elif element_size == (None, None) and element_type in (ELEM_TYPE_BUTTON, ELEM_TYPE_BUTTONMENU): - element_size = toplevel_win.DefaultButtonElementSize - else: - auto_size_text = False # if user has specified a size then it shouldn't autosize - full_element_pad = [0, 0, 0, 0] # Top, Right, Bottom, Left - elementpad = element.Pad if element.Pad is not None else toplevel_win.ElementPadding - if type(elementpad[0]) is not tuple: # left and right - full_element_pad[1] = full_element_pad[3] = elementpad[0] - else: - full_element_pad[3], full_element_pad[1] = elementpad[0] - - if type(elementpad[1]) is not tuple: # top and bottom - full_element_pad[0] = full_element_pad[2] = elementpad[1] - else: - full_element_pad[0], full_element_pad[2] = elementpad[1] - - border_depth = toplevel_win.BorderDepth if toplevel_win.BorderDepth is not None else DEFAULT_BORDER_WIDTH - try: - if element.BorderWidth is not None: - border_depth = element.BorderWidth - except: - pass - - # ------------------------- COLUMN placement element ------------------------- # - if element_type == ELEM_TYPE_COLUMN: - element = element # type: Column - # column_widget = QWidget() - column_widget = QGroupBox() - element.Widget = element.QT_QGroupBox = column_widget - # column_widget.setFrameShape(QtWidgets.QFrame.NoFrame) - - # === style === - style = QtStyle('QGroupBox') - style['font'] = create_style_from_font(font) - if element.BackgroundColor is not None: - style['background_color'] = element.BackgroundColor - style['border'] = '0px solid gray' # FIXv2 - column_widget.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - column_layout = QFormLayout() - element.vbox_layout = column_vbox = QVBoxLayout() - PackFormIntoFrame(element, column_layout, toplevel_win) - - scroll = None - if element.Scrollable and (element_size[0] is not None or element_size[1] is not None): - scroll = QtWidgets.QScrollArea() - scroll.setWidget(column_widget) - if element_size[0] is not None: - scroll.setFixedWidth(element_size[0]) - if element_size[1] is not None: - scroll.setFixedHeight(element_size[1]) - scroll.setWidgetResizable(True) - - column_vbox.addLayout(column_layout) - column_widget.setLayout(column_vbox) - - # column_widget.setStyleSheet(style) - if not element.Visible: - column_widget.setVisible(False) - - qt_row_layout.addWidget(scroll if scroll else column_widget, alignment=Qt.AlignVCenter) - # ------------------------- TEXT placement element ------------------------- # - elif element_type == ELEM_TYPE_TEXT: - element.Widget = element.QT_Label = qlabel = QLabel(element.DisplayText, toplevel_win.QTWindow) - if element.Justification is not None: - justification = element.Justification - elif toplevel_win.TextJustification is not None: - justification = toplevel_win.TextJustification - else: - justification = DEFAULT_TEXT_JUSTIFICATION - - if justification[0] in align2qt_align: - element.QT_Label.setAlignment(align2qt_align[justification[0]]) - if not auto_size_text: - if element_size[0] is not None: - element.QT_Label.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_Label.setFixedHeight(element_size[1]) - # element.QT_Label.setWordWrap(True) - - # === style === - style = QtStyle('QLabel') - style['font'] = create_style_from_font(font) - style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) - style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) - style['margin'] = full_element_pad - element.QT_Label.setStyleSheet(str(style)) - element.qt_styles = (style,) - # === style === end - - if element.Relief is not None: - if element.Relief in (RELIEF_RIDGE, RELIEF_RAISED): - qlabel.setFrameStyle(QFrame.Panel | QFrame.Raised) - elif element.Relief in (RELIEF_SUNKEN, RELIEF_GROOVE): - qlabel.setFrameStyle(QFrame.Panel | QFrame.Sunken) - elif element.Relief == RELIEF_FLAT: - qlabel.setFrameStyle(QFrame.Panel | QFrame.NoFrame) - - if element.Margins is not None: - m = element.Margins - qlabel.setContentsMargins(m[0], m[2], m[1], m[3]) # L T B R - if element.Tooltip: - element.QT_Label.setToolTip(element.Tooltip) - if element.ClickSubmits: - element.QT_Label.mousePressEvent = element._QtCallbackTextClicked - if not element.Visible: - element.QT_Label.setVisible(False) - qt_row_layout.addWidget(element.QT_Label, alignment=Qt.AlignVCenter) - # ------------------------- BUTTON placement element ------------------------- # - elif element_type == ELEM_TYPE_BUTTON: - element = element # type: Button - btext = element.ButtonText - btype = element.BType - element.Widget = element.QT_QPushButton = QPushButton(btext) - # === style === - style = QtStyle('QPushButton') - style['font'] = create_style_from_font(font) - style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) - style['background_color'] = element.BackgroundColor - if element.BorderWidth == 0: - style['border'] = 'none' - style['margin'] = full_element_pad - # style['border'] = '{}px solid gray '.format(border_depth) - element.QT_QPushButton.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - # element.QT_QPushButton.setFlat(False) - if (element.AutoSizeButton is False or toplevel_win.AutoSizeButtons is False or element.Size[0] is not None) and element.ImageData is None: - if element_size[0] is not None: - element.QT_QPushButton.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_QPushButton.setFixedHeight(element_size[1]) - - if element.ImageFilename is not None: - element.QT_QPushButton.setIcon(QtGui.QPixmap(element.ImageFilename)) - element.QT_QPushButton.setIconSize(QtGui.QPixmap(element.ImageFilename).rect().size()) - if element.ImageData: - ba = QtCore.QByteArray.fromBase64(element.ImageData) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - element.QT_QPushButton.setIcon(pixmap) - element.QT_QPushButton.setIconSize(pixmap.rect().size()) - - if element.Disabled: - element.QT_QPushButton.setDisabled(True) - - if element.Tooltip: - element.QT_QPushButton.setToolTip(element.Tooltip) - element.QT_QPushButton.clicked.connect(element._ButtonCallBack) - if not element.Visible: - element.QT_QPushButton.setVisible(False) - - qt_row_layout.addWidget(element.QT_QPushButton, alignment=Qt.AlignVCenter) - # ------------------------- INPUT placement element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_TEXT: - element = element # type: InputText - default_text = element.DefaultText - element.Widget = element.QT_QLineEdit = qlineedit = QLineEdit() - - qlineedit.setAcceptDrops(True) - qlineedit.dragEnterEvent = element._dragEnterEvent - qlineedit.dropEvent = element._dropEvent - - if element.Justification[0] in align2qt_align: - element.QT_QLineEdit.setAlignment(align2qt_align[element.Justification[0]]) - element.QT_QLineEdit.setText(str(default_text)) - - # === style === - style = QtStyle('QLineEdit') - style['font'] = create_style_from_font(font) - if element.Disabled or element.ReadOnly: - if element.disabled_readonly_background_color: - style['background_color'] = (element.disabled_readonly_background_color, COLOR_SYSTEM_DEFAULT) - else: - style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) - if element.disabled_readonly_text_color: - style['color'] = (element.disabled_readonly_text_color, COLOR_SYSTEM_DEFAULT) - else: - style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) - else: - style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) - style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) - style['margin'] = full_element_pad - style['border'] = '{}px solid gray '.format(border_depth) - element.QT_QLineEdit.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if element.AutoSizeText is False or toplevel_win.AutoSizeText is False or element.Size[0] is not None: - if element_size[0] is not None: - element.QT_QLineEdit.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_QLineEdit.setFixedHeight(element_size[1]) - - if (element.Focus or toplevel_win.UseDefaultFocus) and not focus_set: - focus_set = True - toplevel_win.FocusElement = element.QT_QLineEdit - - if element.Disabled: - element.QT_QLineEdit.setDisabled(True) - - if element.ReadOnly: - element.QT_QLineEdit.setReadOnly(True) - - if element.ChangeSubmits: - element.QT_QLineEdit.textChanged.connect(element._QtCallbackFocusInEvent) - - element.QT_QLineEdit.returnPressed.connect(element._QtCallbackReturnPressed) - - if element.PasswordCharacter != '': - qlineedit.setEchoMode(QLineEdit.Password) - if element.Tooltip: - element.QT_QLineEdit.setToolTip(element.Tooltip) - - element.InputTextWidget = Input.InputTextWidget(element.QT_QLineEdit, element) - element.QT_QLineEdit.installEventFilter(element.InputTextWidget) - if not element.Visible: - element.QT_QLineEdit.setVisible(False) - qt_row_layout.addWidget(element.QT_QLineEdit, alignment=Qt.AlignVCenter) - # ------------------------- COMBO placement BOX (Drop Down) element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_COMBO: - element = element # type: Combo - element.Widget = element.QT_ComboBox = QComboBox() - - items_as_strings = [str(v) for v in element.Values] - # element.QT_ComboBox.addItems(element.Values) - element.QT_ComboBox.addItems(items_as_strings) - - # === style === - style = QtStyle('QComboBox') - style['font'] = create_style_from_font(font) - style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) - style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) - style['border'] = '{}px solid gray '.format(border_depth) - style['margin'] = full_element_pad - - style2 = QtStyle('QListView') - style2['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) - style2['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) - - element.QT_ComboBox.setStyleSheet(str(style) + str(style2)) - element.qt_styles = (style, style2) - # === style === end - - if element_size[0] is not None: - element.QT_ComboBox.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_ComboBox.setFixedHeight(element_size[1]) - - if element.Disabled: - element.QT_ComboBox.setDisabled(True) - - element.QT_ComboBox.setMaxVisibleItems(element.VisibleItems) - if element.DefaultValue is not None: - for index, v in enumerate(element.Values): - if v == element.DefaultValue: - element.QT_ComboBox.setCurrentIndex(index) - break - - if element.ChangeSubmits: - element.QT_ComboBox.currentIndexChanged.connect(element._QtCurrentItemChanged) - if element.Tooltip: - element.QT_ComboBox.setToolTip(element.Tooltip) - if not element.Readonly: - element.QT_ComboBox.setEditable(True) - if not element.AutoComplete: - element.QT_ComboBox.setAutoCompletion(True) - if not element.Visible: - element.QT_ComboBox.setVisible(False) - qt_row_layout.addWidget(element.QT_ComboBox, alignment=Qt.AlignVCenter) - # ------------------------- OPTION MENU (Like ComboBox but different) element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_OPTION_MENU: - pass - # ------------------------- LISTBOX placement element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_LISTBOX: - element = element # type: Listbox - element.Widget = element.QT_ListWidget = QListWidget() - - # === style === - style = QtStyle('QListWidget') - style['font'] = create_style_from_font(font) - if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - style['color'] = element.TextColor - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - style['background-color'] = element.BackgroundColor # example for mike here - style['margin'] = full_element_pad - style['border'] = '{}px solid gray; '.format(border_depth) - element.QT_ListWidget.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if not auto_size_text: - if element_size[0] is not None: - element.QT_ListWidget.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_ListWidget.setFixedHeight(element_size[1]) - - if element.SelectMode == SELECT_MODE_MULTIPLE: - element.QT_ListWidget.setSelectionMode(QAbstractItemView.MultiSelection) - elif element.SelectMode == SELECT_MODE_EXTENDED: - element.QT_ListWidget.setSelectionMode(QAbstractItemView.ExtendedSelection) - elif element.SelectMode == SELECT_MODE_CONTIGUOUS: - element.QT_ListWidget.setSelectionMode(QAbstractItemView.ContiguousSelection) - elif element.SelectMode == SELECT_MODE_SINGLE: - element.QT_ListWidget.setSelectionMode(QAbstractItemView.SingleSelection) - if element.Disabled: - element.QT_ListWidget.setDisabled(True) - if element.ChangeSubmits: - element.QT_ListWidget.currentRowChanged.connect(element._QtCurrentRowChanged) - - # add all Values to the ListWidget - element.QT_ListWidget.addItems([str(v) for v in element.Values]) - # select the default items - for index, value in enumerate(element.Values): - item = element.QT_ListWidget.item(index) - if element.DefaultValues is not None and value in element.DefaultValues: - element.QT_ListWidget.setItemSelected(item, True) - - if element.Tooltip: - element.QT_ListWidget.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_ListWidget.setVisible(False) - qt_row_layout.addWidget(element.QT_ListWidget, alignment=Qt.AlignVCenter) - # ------------------------- INPUT MULTILINE placement element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_MULTILINE: - element = element # type: Multiline - default_text = element.DefaultText - width, height = element_size - element.Widget = element.QT_TextEdit = QTextEdit() - - element.QT_TextEdit.setAcceptDrops(True) - element.QT_TextEdit.dragEnterEvent = element._dragEnterEvent - element.QT_TextEdit.dropEvent = element._dropEvent - - # === style === - style = QtStyle('QTextEdit') - style['font'] = create_style_from_font(font) - if element.TextColor is not None: - style['color'] = element.TextColor - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - style['border'] = '{}px solid gray; '.format(border_depth) - element.QT_TextEdit.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if element.AutoSizeText is False or element.Size[0] is not None: - if element_size[0] is not None: - element.QT_TextEdit.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_TextEdit.setFixedHeight(element_size[1]) - - if element.Disabled: - element.QT_TextEdit.setDisabled(True) - - element.MultiQWidget = Multiline.MultiQWidget(element.QT_TextEdit, element) - element.QT_TextEdit.installEventFilter(element.MultiQWidget) - - if element.ChangeSubmits: - element.QT_TextEdit.textChanged.connect(element._QtCallbackFocusInEvent) - - if (element.Focus or toplevel_win.UseDefaultFocus) and not focus_set: - focus_set = True - toplevel_win.FocusElement = element.QT_TextEdit - - element.QT_TextEdit.setText(str(default_text)) - element.QT_TextEdit.moveCursor(QtGui.QTextCursor.End) - if element.Tooltip: - element.QT_TextEdit.setToolTip(element.Tooltip) - # qt_row_layout.setContentsMargins(*full_element_pad) - if not element.Visible: - element.QT_TextEdit.setVisible(False) - qt_row_layout.addWidget(element.QT_TextEdit, alignment=Qt.AlignVCenter) - # ------------------------- OUTPUT MULTILINE placement element ------------------------- # - elif element_type == ELEM_TYPE_MULTILINE_OUTPUT: - element = element # type: MultilineOutput - default_text = element.DefaultText - element.Widget = element.QT_TextBrowser = QTextBrowser() - element.QT_TextBrowser.setDisabled(False) - - # === style === - style = QtStyle('QTextBrowser') - style['font'] = create_style_from_font(font) - if element.TextColor is not None: - style['color'] = element.TextColor - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - style['border'] = '{}px solid gray'.format(border_depth) - element.QT_TextBrowser.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if element.AutoSizeText is False or element.Size[0] is not None: - if element_size[0] is not None: - element.QT_TextBrowser.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_TextBrowser.setFixedHeight(element_size[1]) - - element.QT_TextBrowser.insertPlainText(default_text) - element.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) - if element.Tooltip: - element.QT_TextBrowser.setToolTip(element.Tooltip) - # qt_row_layout.setContentsMargins(*full_element_pad) - if not element.Visible: - element.QT_TextBrowser.setVisible(False) - qt_row_layout.addWidget(element.QT_TextBrowser, alignment=Qt.AlignVCenter) - # ------------------------- INPUT CHECKBOX placement element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_CHECKBOX: - element = element # type: Checkbox - element.QT_Checkbox = QCheckBox(element.Text) - element.QT_Checkbox.setChecked(element.InitialState) - if element.Disabled: - element.QT_Checkbox.setDisabled(True) - - # === style === - style = QtStyle('QCheckBox') - style['font'] = create_style_from_font(font) - if element.TextColor is not None: - style['color'] = element.TextColor - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - element.QT_Checkbox.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if element.AutoSizeText is False or element.Size[0] is not None: - if element_size[0] is not None: - element.QT_Checkbox.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_Checkbox.setFixedHeight(element_size[1]) - if element.ChangeSubmits: - element.QT_Checkbox.stateChanged.connect(element.QtCallbackStateChanged) - # qt_row_layout.setContentsMargins(*full_element_pad) - if element.Tooltip: - element.QT_Checkbox.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_Checkbox.setVisible(False) - qt_row_layout.addWidget(element.QT_Checkbox, alignment=Qt.AlignVCenter) - # ------------------------- PROGRESSBAR placement element ------------------------- # - elif element_type == ELEM_TYPE_PROGRESS_BAR: - element.Widget = element.QT_QProgressBar = QProgressBar() - orientation = element.Orientation.lower()[0] - if element.Size[0] is not None: - if element_size[0] is not None: - element.QT_QProgressBar.setFixedWidth(element_size[orientation != 'h']) - if element_size[1] is not None: - element.QT_QProgressBar.setFixedHeight(element_size[orientation == 'h']) - - element.QT_QProgressBar.setMaximum(element.MaxValue) - element.QT_QProgressBar.setValue(element.StartValue) - if element.Orientation.lower().startswith('v'): - element.QT_QProgressBar.setOrientation(QtCore.Qt.Vertical) - - # === style === - style = QtStyle('QProgressBar') - style_chunk = QtStyle('QProgressBar::chunk') - style['margin'] = full_element_pad - # style += 'margin: {}px {}px {}px {}px;'.format(*full_element_pad) - # style += 'border: {}px solid gray; '.format(border_depth) - if element.BarColor != (None, None): - if element.BarColor[0] is not None: - style_chunk['background-color'] = element.BarColor[0] - - style['border'] = '%spx solid grey' % border_depth - style['border-radius'] = '0px' - style['background-color'] = str(element.BarColor[1] if element.BarColor[1] is not None else DEFAULT_PROGRESS_BAR_COLOR[1]) - - element.QT_QProgressBar.setStyleSheet(style.build_css_string() + style_chunk.build_css_string()) - element.qt_styles = (style, style_chunk) - # === style === end - - element.QT_QProgressBar.setTextVisible(False) - if element.Tooltip: - element.QT_QProgressBar.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_QProgressBar.setVisible(False) - - qt_row_layout.addWidget(element.QT_QProgressBar, alignment=Qt.AlignVCenter) - # ------------------------- INPUT RADIO placement element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_RADIO: - element = element # type: Radio - default_value = element.InitialState - element.Widget = qradio = QRadioButton(element.Text) - element.QT_Radio_Button = qradio - if element.Disabled: - element.QT_Radio_Button.setDisabled(True) - if default_value: - qradio.setChecked(True) - - # === style === - style = QtStyle('QRadioButton') - style['font'] = create_style_from_font(font) - if element.TextColor is not None: - style['color'] = element.TextColor - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - element.QT_Radio_Button.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if element.AutoSizeText is False or element.Size[0] is not None: - if element_size[0] is not None: - element.QT_Radio_Button.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_Radio_Button.setFixedHeight(element_size[1]) - - if element.GroupID in toplevel_win.RadioDict: - element.QT_RadioButtonGroup = toplevel_win.RadioDict[element.GroupID] - else: - element.QT_RadioButtonGroup = QButtonGroup(toplevel_win.QTApplication) - toplevel_win.RadioDict[element.GroupID] = element.QT_RadioButtonGroup - element.QT_RadioButtonGroup.setExclusive(True) - - element.QT_RadioButtonGroup.addButton(element.QT_Radio_Button) - - if element.ChangeSubmits: - element.QT_Radio_Button.toggled.connect(element._QtCallbackValueChanged) - - # qt_row_layout.setContentsMargins(*full_element_pad) - if element.Tooltip: - element.QT_Radio_Button.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_Radio_Button.setVisible(False) - qt_row_layout.addWidget(element.QT_Radio_Button, alignment=Qt.AlignVCenter) - # ------------------------- INPUT SPIN placement element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_SPIN: - # element.QT_Spinner = QSpinBox() - element = element # type: Spin - element.Widget = element.QT_Spinner = Spin.StringBox(element.Values) - if element.DefaultValue is not None: # try to set the default value without crashing on error - try: - element.QT_Spinner.setValue(element.QT_Spinner.valueFromText(element.DefaultValue)) - except: - pass - # === style === - style = QtStyle('QSpinBox') - style['font'] = create_style_from_font(font) - if element.TextColor is not None: - style['color'] = element.TextColor - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - style['border'] = '{}px solid gray'.format(border_depth) - element.QT_Spinner.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - # element.QT_Spinner.setRange(element.Values[0], element.Values[1]) - if not auto_size_text: - if element_size[0] is not None: - element.QT_Spinner.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_Spinner.setFixedHeight(element_size[1]) - - if element.Disabled: - element.QT_Spinner.setDisabled(True) - if element.ChangeSubmits: - element.QT_Spinner.valueChanged.connect(element._QtCallbackValueChanged) - if element.Tooltip: - element.QT_Spinner.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_Spinner.setVisible(False) - qt_row_layout.addWidget(element.QT_Spinner, alignment=Qt.AlignVCenter) - # ------------------------- OUTPUT placement element ------------------------- # - elif element_type == ELEM_TYPE_OUTPUT: - element = element # type: Output - element.Widget = element.QT_TextBrowser = QTextBrowser() - element.QT_TextBrowser.setDisabled(False) - - # === style === - style = QtStyle('QTextBrowser') - style['font'] = create_style_from_font(font) - if element.TextColor is not None: - style['color'] = element.TextColor - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - style['border'] = '{}px solid gray'.format(border_depth) - # style += "QScrollBar:vertical {border: none; background:lightgray; width:12px; margin: 0px 0px 0px 0px; } " - element.QT_TextBrowser.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if element.AutoSizeText is False or element.Size[0] is not None: - if element_size[0] is not None: - element.QT_TextBrowser.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_TextBrowser.setFixedHeight(element_size[1]) - - element.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) - element._reroute_stdout() - if element.Tooltip: - element.QT_TextBrowser.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_TextBrowser.setVisible(False) - qt_row_layout.addWidget(element.QT_TextBrowser, alignment=Qt.AlignVCenter) - # ------------------------- IMAGE placement element ------------------------- # - elif element_type == ELEM_TYPE_IMAGE: - element = element # type: Image - element.Widget = element.QT_QLabel = qlabel = QLabel() - if element.Filename is not None: - qlabel.setText('') - w = QtGui.QPixmap(element.Filename).width() - h = QtGui.QPixmap(element.Filename).height() - qlabel.setGeometry(QtCore.QRect(0, 0, w, h)) - qlabel.setPixmap(QtGui.QPixmap(element.Filename)) - elif element.Data is not None: - qlabel.setText('') - ba = QtCore.QByteArray.fromRawData(element.Data) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qlabel.setPixmap(pixmap) - elif element.DataBase64: - qlabel.setText('') - ba = QtCore.QByteArray.fromBase64(element.DataBase64) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qlabel.setPixmap(pixmap) - - # === style === - style = QtStyle('QLabel') - style['margin'] = full_element_pad - element.QT_QLabel.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if element.Tooltip: - element.QT_QLabel.setToolTip(element.Tooltip) - if element.ClickSubmits: - element.QT_QLabel.mousePressEvent = element.QtCallbackImageClicked - if not element.Visible: - element.QT_QLabel.setVisible(False) - qt_row_layout.addWidget(element.QT_QLabel, alignment=Qt.AlignVCenter) - # ------------------------- Canvas placement element ------------------------- # - elif element_type == ELEM_TYPE_CANVAS: - width, height = element_size - # ------------------------- Graph placement element ------------------------- # - elif element_type == ELEM_TYPE_GRAPH: - element = element # type: Graph - width, height = element_size - # print(f'Graph element size = {element_size}') - element.Widget = element.QT_QGraphicsView = qgraphicsview = QGraphicsView() - # element.QT_QGraphicsView.setGeometry(0,0,element.CanvasSize[0],element.CanvasSize[1]) - # print(f'Graph Canvas size = {element.CanvasSize}') - - element.QT_QGraphicsScene = QGraphicsScene() - element.QT_QGraphicsScene.setSceneRect(0, 0, element.CanvasSize[0], element.CanvasSize[1]) - element.QT_QGraphicsView.setScene(element.QT_QGraphicsScene) - - # === style === - style = QtStyle('QGraphicsView') - style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) - style['margin'] = full_element_pad - style['border'] = '{}px solid gray '.format(border_depth) - # print(f'style content = {style.content}') - element.QT_QGraphicsView.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - qgraphicsview.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - qgraphicsview.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - # qt_row_layout.setContentsMargins(*full_element_pad) - if element.Tooltip: - element.QT_QGraphicsView.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_QGraphicsView.setVisible(False) - qt_row_layout.addWidget(element.QT_QGraphicsView, alignment=Qt.AlignVCenter) - # ------------------------- MENUBAR placement element ------------------------- # - elif element_type == ELEM_TYPE_MENUBAR: - element = element # type: Menu - menu_def = element.MenuDefinition - element.Widget = element.QT_QMenuBar = QMenuBar(toplevel_win.QT_QMainWindow) - - for menu_entry in menu_def: - # print(f'Adding a Menubar ENTRY {menu_entry}') - baritem = QMenu(element.QT_QMenuBar) - if menu_entry[0][0] == MENU_DISABLED_CHARACTER: - baritem.setDisabled(True) - baritem.setTitle(menu_entry[0][1:]) - else: - baritem.setTitle(menu_entry[0]) - element.QT_QMenuBar.addAction(baritem.menuAction()) - AddMenuItem(baritem, menu_entry[1], element) - # === style === - menu_style = QtStyle('QMenu') - menu_style['font'] = create_style_from_font(font) - if element.MenuItemTextColor is not None and element.MenuItemTextColor != COLOR_SYSTEM_DEFAULT: - menu_style['color'] = element.MenuItemTextColor - if element.MenuItemBackgroundColor is not None and element.MenuItemBackgroundColor != COLOR_SYSTEM_DEFAULT: - menu_style['background-color'] = element.MenuItemBackgroundColor - # style['margin'] = full_element_pad - baritem.setStyleSheet(menu_style.build_css_string()) - # === style === end - - if element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # === style === - style = QtStyle('QMenuBar') - style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) - element.QT_QMenuBar.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if not element.Visible: - element.QT_QMenuBar.setVisible(False) - toplevel_win.QT_QMainWindow.setMenuBar(element.QT_QMenuBar) - # ------------------------- BUTTONMENU placement element ------------------------- # - elif element_type == ELEM_TYPE_BUTTONMENU: - btext = element.ButtonText - element.Widget = element.QT_QPushButton = QPushButton(btext) - - # === style === - style = QtStyle('QPushButton') - style['font'] = create_style_from_font(font) - if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - style['color'] = element.TextColor - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - style['background-color'] = element.BackgroundColor - if element.BorderWidth == 0: - style['border'] = 'none' - style['margin'] = full_element_pad - element.QT_QPushButton.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if (element.AutoSizeButton is False or toplevel_win.AutoSizeButtons is False or element.Size[0] is not None) and element.ImageData is None: - if element_size[0] is not None: - element.QT_QPushButton.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_QPushButton.setFixedHeight(element_size[1]) - - if element.ImageData: - ba = QtCore.QByteArray.fromBase64(element.ImageData) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - element.QT_QPushButton.setIcon(pixmap) - element.QT_QPushButton.setIconSize(pixmap.rect().size()) - - if element.Disabled: - element.QT_QPushButton.setDisabled(True) - - if element.Tooltip: - element.QT_QPushButton.setToolTip(element.Tooltip) - # element.QT_QPushButton.clicked.connect(element._ButtonCallBack) - - menu_def = element.MenuDefinition - - qmenu = QMenu(element.QT_QPushButton) - qmenu.setTitle(menu_def[0]) - AddMenuItem(qmenu, menu_def[1], element) - - # === style === - menu_style = QtStyle('QMenu') - menu_style['font'] = create_style_from_font(font) - if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - menu_style['color'] = element.TextColor - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - menu_style['background-color'] = element.BackgroundColor - # style['margin'] = full_element_pad - qmenu.setStyleSheet(menu_style.build_css_string()) - # element.qt_styles = (style,) - # === style === end - - element.QT_QPushButton.setMenu(qmenu) - if element.Tooltip: - element.QT_QPushButton.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_QPushButton.setVisible(False) - qt_row_layout.addWidget(element.QT_QPushButton, alignment=Qt.AlignVCenter) - # ------------------------- Frame placement element ------------------------- # - elif element_type == ELEM_TYPE_FRAME: - element = element # type: Frame - element.Widget = column_widget = QGroupBox() - element.QT_QGroupBox = column_widget - - # === style === - style = QtStyle('QGroupBox') - # style['font'] = create_style_from_font(font) - if element.TextColor is not None: - style['color'] = element.TextColor - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - # style['origin'] = 'margin' - style['font'] = create_style_from_font(font) - if element.FrameColor is not None: - style['border'] = '{}px solid {} '.format(border_depth, element.FrameColor) - else: - style['border'] = '{}px solid {} '.format(border_depth, 'gainsboro') # default to a light gray - - # style['padding'] = (10,10,10,10) - # style['margin'] = full_element_pad - # style['padding'] = (0,15,15,15) - # style['padding'] = (10,10,10,10) - style['margin-top'] = '10px' - # style['top'] = '60px' - # style['margin'] = (0,0,0,0) - style['origin'] = 'margin' - - style_title = QtStyle('QGroupBox::title') - # style_title['padding'] = (0,5,0,5) - style_title['margin'] = (0, 0, 0, 0) - - style_title['left'] = '15px' - # style_title['margin-top'] = '-20px' - # style_title['top'] = '20px' - style_title['subcontrol-origin'] = 'margin' - # style_title['subcontrol-origin'] = 'border' - - # style_title['subcontrol-origin'] = 'padding' - style_title['subcontrol-position'] = 'top left' - - column_widget.setStyleSheet(str(style) + str(style_title)) - # column_widget.setStyleSheet(str(style)) - # print(element.Widget.styleSheet()) - element.qt_styles = (style,) - # === style === end - - column_widget.setTitle(element.Title) - - column_layout, column_vbox = QFormLayout(), QVBoxLayout() - PackFormIntoFrame(element, column_layout, toplevel_win) - column_vbox.addLayout(column_layout) - column_widget.setLayout(column_vbox) - - # Add a padding groupbox - pad_layout, pad_vbox = QFormLayout(), QVBoxLayout() - pad_groupbox = QGroupBox() - - pad_vbox.addLayout(pad_layout) - pad_groupbox.setLayout(pad_vbox) - pad_vbox.addWidget(column_widget) - - pad_layout.setSpacing(0) - pad_vbox.setSpacing(0) - # === style === - style = QtStyle('QGroupBox') - # style['font'] = create_style_from_font(font) - style['border'] = '0px' - style['margin'] = (0, 0, 0, 0) - # style['margin'] = full_element_pad - - style['padding'] = (0, 0, 0, 0) - style['margin-top'] = '0px' - style['origin'] = 'content' - style_title = QtStyle('QGroupBox::title') - style_title['subcontrol-origin'] = 'content' - - style_title['padding'] = (0, 0, 0, 0) - style_title['margin'] = (0, 0, 0, 0) - pad_groupbox.setStyleSheet(str(style) + str(style_title)) - # === style === end - - if element.Tooltip: - column_widget.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_QGroupBox.setVisible(False) - - qt_row_layout.addWidget(pad_groupbox) - # qt_row_layout.addWidget(column_widget) - # ------------------------- Tab placement element ------------------------- # - elif element_type == ELEM_TYPE_TAB: - element.Widget = tab_widget = QWidget() - element.QT_QWidget = tab_widget - # tab_widget.setFrameShape(QtWidgets.QFrame.NoFrame) - - # === style === - style = QtStyle('QTabWidget') - style['font'] = create_style_from_font(font) - if element.BackgroundColor is not None: - # style += 'background-color: %s;' % element.BackgroundColor - # style += 'QTabWidget > QWidget > QWidget {background: %s;}'% element.BackgroundColor - style['background-color'] = element.BackgroundColor - style.my_anchor = '::pane' - # style += 'background-color: %s;' % element.BackgroundColor - tab_widget.setAutoFillBackground(True) - palette = tab_widget.palette() - palette.setColor(tab_widget.backgroundRole(), element.BackgroundColor) - tab_widget.setPalette(palette) - - # style += 'border: {}px solid gray; '.format(border_depth) - style['margin'] = full_element_pad - tab_widget.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - column_layout, column_vbox = QFormLayout(), QVBoxLayout() - - PackFormIntoFrame(element, column_layout, toplevel_win) - - column_vbox.addLayout(column_layout) - tab_widget.setLayout(column_vbox) - if element.Tooltip: - tab_widget.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_QWidget.setVisible(False) - container_elem.QT_QTabWidget.addTab(tab_widget, element.Title) - # ------------------------- TabGroup placement element ------------------------- # - elif element_type == ELEM_TYPE_TAB_GROUP: - element = element # type:TabGroup - element.Widget = element.QT_QTabWidget = qtab = QTabWidget() - - # === style === - style = QtStyle('QTabWidget') - # print(f'qtab.styleSheet() -> {qtab.styleSheet()}') - # style = qtab.styleSheet() # FIXv2 - if element.SelectedTitleColor not in (None, COLOR_SYSTEM_DEFAULT): - style.my_anchor = '::tab:selected' - style['background'] = element.SelectedTitleColor - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - style.my_anchor = '::tab' - style['background'] = element.BackgroundColor - if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): - style.my_anchor = '::tab' - style['color'] = element.TextColor - style['margin'] = full_element_pad - qtab.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if element.TabLocation is not None: - position_dict = { - 'left': QtWidgets.QTabWidget.TabPosition.West, - 'right': QtWidgets.QTabWidget.TabPosition.East, - 'top': QtWidgets.QTabWidget.TabPosition.North, - 'bottom': QtWidgets.QTabWidget.TabPosition.South, - 'lefttop': QtWidgets.QTabWidget.TabPosition.North, - 'leftbottom': QtWidgets.QTabWidget.TabPosition.South, - 'righttop': QtWidgets.QTabWidget.TabPosition.North, - 'rightbottom': QtWidgets.QTabWidget.TabPosition.South, - 'bottomleft': QtWidgets.QTabWidget.TabPosition.South, - 'bottomright': QtWidgets.QTabWidget.TabPosition.South, - 'topleft': QtWidgets.QTabWidget.TabPosition.North, - 'topright': QtWidgets.QTabWidget.TabPosition.North, - } - try: - element.Widget.setTabPosition(position_dict[element.TabLocation]) - except: - print('Bad tab position specified {}', element.TabLocation) - PackFormIntoFrame(element, element.ParentForm.QFormLayout, toplevel_win) - - qt_row_layout.addWidget(element.QT_QTabWidget, alignment=Qt.AlignVCenter) - if not element.Visible: - element.QT_QTabWidget.setVisible(False) - - if element.ChangeSubmits: - element.QT_QTabWidget.currentChanged.connect(element.QtCallbackStateChanged) - # ------------------------- SLIDER placement element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_SLIDER: - element = element # type: Slider - element.Widget = element.QT_Slider = QSlider() - element.QT_Slider.setOrientation(Qt.Horizontal if element.Orientation.startswith('h') else Qt.Vertical) - if element.Disabled: - element.QT_Slider.setDisabled(True) - - # === style === - style = QtStyle('QSlider') - style['font'] = create_style_from_font(font) - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - style['border'] = '{}px solid gray'.format(border_depth) - element.QT_Slider.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - element.QT_Slider.setMinimum(element.Range[0]) - element.QT_Slider.setMaximum(element.Range[1]) - position = QSlider.TicksBothSides - if element.Relief == RELIEF_TICK_POSITION_NO_TICKS: - position = QSlider.NoTicks - elif element.Relief == RELIEF_TICK_POSITION_BOTH_SIDES: - position = QSlider.TicksBothSides - elif element.Relief == RELIEF_TICK_POSITION_ABOVE: - position = QSlider.TicksAbove - elif element.Relief == RELIEF_TICK_POSITION_BELOW: - position = QSlider.TicksBelow - elif element.Relief == RELIEF_TICK_POSITION_LEFT: - position = QSlider.TicksLeft - elif element.Relief == RELIEF_TICK_POSITION_RIGHT: - position = QSlider.TicksRight - element.QT_Slider.setTickPosition(position) - - if element.TickInterval is not None: - element.QT_Slider.setTickInterval(element.TickInterval) - if element_size[0] is not None: - element.QT_Slider.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_Slider.setFixedHeight(element_size[1]) - if element.Resolution is not None: - element.QT_Slider.setSingleStep(element.Resolution) - element.QT_Slider.setPageStep(element.Resolution) - element.QT_Slider.setValue(element.DefaultValue) - - if element.ChangeSubmits: - element.QT_Slider.valueChanged.connect(element._QtCallbackValueChanged) - if element.Tooltip: - element.QT_Slider.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_Slider.setVisible(False) - qt_row_layout.addWidget(element.QT_Slider, alignment=Qt.AlignVCenter) - # ------------------------- DIAL placement element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_DIAL: - element.Widget = element.QT_Dial = qdial = QDial() - - # === style === - style = QtStyle('QDial') - style['font'] = create_style_from_font(font) - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - style['border'] = '{}px solid gray'.format(border_depth) - element.QT_Dial.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - # === style === end - - if element.Disabled: - element.QT_Dial.setDisabled(True) - element.QT_Dial.setMinimum(element.Range[0]) - element.QT_Dial.setMaximum(element.Range[1]) - element.QT_Dial.setValue(element.DefaultValue) - qdial.setNotchesVisible(True) - if element.TickInterval is not None: - qdial.setNotchTarget(element.TickInterval) - if element.Resolution is not None: - element.QT_Dial.setSingleStep(element.Resolution) - if element_size[0] is not None: - element.QT_Dial.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_Dial.setFixedHeight(element_size[1]) - if element.ChangeSubmits: - element.QT_Dial.valueChanged.connect(element._QtCallbackValueChanged) - if element.Tooltip: - element.QT_Dial.setToolTip(element.Tooltip) - # qt_row_layout.setContentsMargins(*full_element_pad) - if not element.Visible: - element.QT_Dial.setVisible(False) - qt_row_layout.addWidget(element.QT_Dial, alignment=Qt.AlignVCenter) - # ------------------------- Stretch placement element ------------------------- # - elif element_type == ELEM_TYPE_STRETCH: - element = element # type: Stretch - element.Widget = qt_row_layout.addStretch(1) - # ------------------------- TABLE placement element ------------------------- # - elif element_type == ELEM_TYPE_TABLE: - element = element # type: Table - element.Widget = element.QT_TableWidget = Table.QTTableWidget(toplevel_win.ReturnKeyboardEvents, toplevel_win) - if element.NumRows is not None: - element.QT_TableWidget.setFixedHeight(element.NumRows * 35 + 25) - # element.QT_TableWidget = QTableWidget() - # === style === - - style = QtStyle('QTableWidget') - style['font'] = create_style_from_font(font) - if element.TextColor is not None: - style['color'] = element.TextColor - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - style['border'] = '{}px solid gray'.format(border_depth) - - # more css: ScrollBar - style.append_css_to_end.append(' QScrollBar:vertical {border: none; background:lightgray; width:12px; margin: 0px 0px 0px 0px; } ') - # more css: QHeaderView - header_style = QtStyle('QHeaderView::section') - header_style['font'] = create_style_from_font(element.HeaderFont if element.HeaderFont is not None else font) - header_style['background-color'] = element.HeaderBackgroundColor - header_style['color'] = element.HeaderTextColor - - element.QT_TableWidget.setStyleSheet(style.build_css_string() + header_style.build_css_string()) - element.qt_styles = (style, header_style) - # === style === end - - if element.ChangeSubmits: - element.QT_TableWidget.itemSelectionChanged.connect(element._QtCallbackCellActivated) - element.QT_TableWidget.setRowCount(len(element.Values)) - element.QT_TableWidget.setColumnCount(len(element.Values[0])) - for rownum, rows in enumerate(element.Values): - # element.QT_TableWidget.insertRow(rownum) - for colnum, columns in enumerate(rows): - element.QT_TableWidget.setItem(rownum, colnum, QTableWidgetItem(element.Values[rownum][colnum])) - - if element.ColumnHeadings is not None: - element.QT_TableWidget.setHorizontalHeaderLabels(element.ColumnHeadings) - - element.QT_TableWidget.installEventFilter(element.QT_TableWidget) - element.QT_TableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) - if element.Tooltip: - element.QT_TableWidget.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_TableWidget.setVisible(False) - - qt_row_layout.addWidget(element.QT_TableWidget, alignment=Qt.AlignVCenter) - # ------------------------- Tree placement element ------------------------- # - elif element_type == ELEM_TYPE_TREE: - element = element # type: Tree - element.Widget = element.QT_QTreeWidget = QTreeWidget() - if element_size != (None, None): - element.QT_QTreeWidget.setFixedWidth(element_size[0]) - element.QT_QTreeWidget.setFixedHeight(element_size[1]) - height = element.NumRows - element.QT_QTreeWidget.setFixedHeight(height * 25) # convert num rows into pixels...crude but effective - - if element.ColumnsToDisplay is None: # Which cols to display - displaycolumns = element.ColumnHeadings - else: - displaycolumns = [] - for i, should_display in enumerate(element.ColumnsToDisplay): - if should_display: - displaycolumns.append(element.ColumnHeadings[i]) - column_headings = element.ColumnHeadings - # ------------- GET THE TREEVIEW WIDGET ------------- - for i, heading in enumerate(element.ColumnHeadings): # Configure cols + headings - # QTree.heading(heading, text=heading) - if element.AutoSizeColumns: - width = min(element.MaxColumnWidth, len(heading) + 1) - else: - try: - width = element.ColumnWidths[i] - except: - width = element.DefaultColumnWidth - # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) - - def add_treeview_data(node, widget): - # print(f'Inserting {node.key} under parent {node.parent}') - child = widget - if node != element.TreeData.root_node: - child = QTreeWidgetItem(widget) - child.setText(0, str(node.text)) - # if node.key != '': - # child.setData(0,0,node.values) - if type(node.icon) is bytes: - ba = QtCore.QByteArray.fromBase64(node.icon) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qicon = QIcon(pixmap) - child.setIcon(0, qicon) - elif node.icon is not None: - qicon = QIcon(node.icon) - child.setIcon(0, qicon) - - for node in node.children: - add_treeview_data(node, child) - - # for node in element.TreeData.root_node.children: - # add_treeview_data(node, element.QT_QTreeWidget) - - add_treeview_data(element.TreeData.root_node, element.QT_QTreeWidget) - - # === style === - style = QtStyle('QTreeWidget') - style['font'] = create_style_from_font(font) - if element.TextColor is not None: - style['color'] = element.TextColor - if element.BackgroundColor is not None: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - style['border'] = '{}px solid gray'.format(border_depth) - style.append_css_to_end.append(' QScrollBar:vertical {border: none; background:lightgray; width:12px; margin: 0px 0px 0px 0px; } ') - - header_style = QtStyle('QHeaderView::section') - header_style['font'] = create_style_from_font(element.HeaderFont if element.HeaderFont is not None else font) - header_style['background-color'] = element.HeaderBackgroundColor - header_style['color'] = element.HeaderTextColor - - element.QT_QTreeWidget.setStyleSheet(style.build_css_string() + header_style.build_css_string()) - element.qt_styles = (style, header_style) - # === style === end - - if element.ChangeSubmits: - element.QT_QTreeWidget.itemSelectionChanged.connect(element._QtCallbackCellActivated) - - if element.ShowExpanded: - element.QT_QTreeWidget.expandAll() - element.QT_QTreeWidget.show() - if element.Tooltip: - element.QT_QTreeWidget.setToolTip(element.Tooltip) - if not element.Visible: - element.QT_QTreeWidget.setVisible(False) - qt_row_layout.addWidget(element.QT_QTreeWidget, alignment=Qt.AlignVCenter) - # ------------------------- Separator placement element ------------------------- # - elif element_type == ELEM_TYPE_SEPARATOR: - element = element # type: HorizontalSeparator - element.Widget = element.QT_Label = qlabel = QLabel('', toplevel_win.QTWindow) - if not auto_size_text: - if element_size[0] is not None: - element.QT_Label.setFixedWidth(element_size[0]) - if element_size[1] is not None: - element.QT_Label.setFixedHeight(element_size[1]) - style = QtStyle('QLabel') - if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - style['color'] = element.TextColor - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - style['background-color'] = element.BackgroundColor - style['margin'] = full_element_pad - element.QT_Label.setStyleSheet(style.build_css_string()) - element.qt_styles = (style,) - - qlabel.setFrameStyle(QFrame.VLine if element.Orientation[0] == 'v' else QFrame.HLine) - - qt_row_layout.addWidget(element.QT_Label, alignment=Qt.AlignVCenter) - - # Align the Element on center in the row - # try: - # element.Widget.setAlignment(element.Widget.alignment() | Qt.AlignVCenter) - # except Exception as e: - # print(f'* Alignment error {e}') - - # ............................DONE WITH ROW pack the row of widgets ..........................# - qt_row_layout.setSpacing(0) - containing_frame.setSpacing(0) - containing_frame.addRow('', qt_row_layout) - - # done with row, pack the row of widgets - # tk_row_frame.grid(row=row_num+2, sticky=tk.NW, padx=DEFAULT_MARGINS[0]) - return - - -def ConvertFlexToTK(window): - InitializeResults(window) - master = 000000 - PackFormIntoFrame(window, window.QFormLayout, window) - # ....................................... DONE creating and laying out window ..........................# - screen_width = 000000 # get window info to move to middle of screen - screen_height = 000000 - if window.Location != (None, None): - window.QT_QMainWindow.move(window.Location[0], window.Location[1]) - x, y = window.Location - elif DEFAULT_WINDOW_LOCATION != (None, None): - x, y = DEFAULT_WINDOW_LOCATION - else: - win_width = 0000000 - win_height = 000000 - x = screen_width / 2 - win_width / 2 - y = screen_height / 2 - win_height / 2 - if y + win_height > screen_height: - y = screen_height - win_height - if x + win_width > screen_width: - x = screen_width - win_width - - return - - -# ----====----====----====----====----==== Start timer ====----====----====----====----====----# - - -def start_window_read_timer(window, amount): - timer = QtCore.QTimer() - timer.timeout.connect(window._timer_timeout) - timer.start(amount) - return timer - - -def start_systray_read_timer(tray, amount): - timer = QtCore.QTimer() - timer.timeout.connect(tray._timer_timeout) - timer.start(amount) - return timer - - -def start_window_autoclose_timer(window, amount): - timer = QtCore.QTimer() - window.autoclose_timer = timer - timer.timeout.connect(window._autoclose_timer_callback) - timer.start(amount) - return timer - - -def stop_timer(timer): - timer.stop() - - -# ----====----====----====----====----==== STARTUP TK ====----====----====----====----====----# -def StartupTK(window): - """ - Does the building of the window with all the widgets - :param window: you window object - :type window: (Window) - """ - - global using_pyqt5 - - ow = Window.NumOpenWindows - - if Window.QTApplication is None: - Window.QTApplication = QApplication(sys.argv) - - window.QTApplication = Window.QTApplication - - Window.IncrementOpenCount() - - # window.QTWindow = QWidget() - - window.QT_QMainWindow = Window.QT_QMainWindowClass(window.ReturnKeyboardEvents, window) - window.QTWindow = Window.QTMainWindow(window.ReturnKeyboardEvents, window) - window.QT_QMainWindow.setCentralWidget(window.QTWindow) - - window.QT_QMainWindow.installEventFilter(window.QT_QMainWindow) - - window.QTApplication.setActiveWindow(window.QT_QMainWindow) - - flags = QtCore.Qt.WindowFlags() - if window.NoTitleBar: - flags |= Qt.FramelessWindowHint - flags |= QtCore.Qt.Tool - if window.KeepOnTop: - flags |= Qt.WindowStaysOnTopHint - - if not using_pyqt5 and flags is not None: - window.QT_QMainWindow.setWindowFlags(flags) - if window.AlphaChannel: - window.QT_QMainWindow.setWindowOpacity(window.AlphaChannel) - if window.WindowIcon is not None: - if type(window.WindowIcon) is bytes: - ba = QtCore.QByteArray.fromBase64(window.WindowIcon) - pixmap = QtGui.QPixmap() - pixmap.loadFromData(ba) - qicon = QIcon(pixmap) - window.QT_QMainWindow.setWindowIcon(qicon) - else: - window.QT_QMainWindow.setWindowIcon(QtGui.QIcon(window.WindowIcon)) - if window.DisableMinimize: - window.QT_QMainWindow.setWindowFlags(window.QT_QMainWindow.windowFlags() & ~Qt.WindowMinimizeButtonHint) - window.QT_QMainWindow.setWindowFlags(window.QT_QMainWindow.windowFlags() & ~Qt.WindowMaximizeButtonHint) - if window.DisableClose: - window.QT_QMainWindow.setWindowFlags(window.QT_QMainWindow.windowFlags() & ~Qt.WindowCloseButtonHint) - - # window.QTWindow.setAttribute(Qt.WA_TranslucentBackground) - # shadow = QtWidgets.QGraphicsDropShadowEffect() - # shadow.setBlurRadius(9.0) - # shadow.setBlurRadius(50) - # window.QTWindow.setGraphicsEffect(shadow) - - # if window.KeepOnTop: - # window.QTWindow.setWindowFlags(Qt.WindowStaysOnTopHint) - - style = QtStyle('QMainWindow') - if window.BackgroundColor is not None and window.BackgroundColor != COLOR_SYSTEM_DEFAULT: - style['background-color'] = window.BackgroundColor - window.QT_QMainWindow.setStyleSheet(str(style)) - - if window.margins != (None, None): - margin_left = margin_right = margin_top = margin_bottom = 0 - if isinstance(window.margins[0], tuple): - margin_left = window.margins[0][0] - margin_right = window.margins[0][1] - elif isinstance(window.margins[0], int): - margin_left = window.margins[0] - margin_right = window.margins[0] - if isinstance(window.margins[1], tuple): - margin_top = window.margins[1][0] - margin_bottom = window.margins[1][1] - elif isinstance(window.margins[1], int): - margin_top = window.margins[1] - margin_bottom = window.margins[1] - window.QT_QMainWindow.setContentsMargins(margin_left, margin_top, margin_right, margin_bottom) - - if window.BackgroundImage is not None: - qlabel = QLabel(window.QTWindow) - qlabel.setText('') - w = QtGui.QPixmap(window.BackgroundImage).width() - h = QtGui.QPixmap(window.BackgroundImage).height() - qlabel.setGeometry(QtCore.QRect(0, 0, w, h)) - # qlabel.setGeometry(window.QTWindow.geometry()) - qlabel.setPixmap(QtGui.QPixmap(window.BackgroundImage)) - # style += 'background-image: url(%s);' % window.BackgroundImage - - window.QT_QMainWindow.setWindowTitle(window.Title) - - if window.GrabAnywhere is not False and not (window.NonBlocking and window.GrabAnywhere is not True): - pass - - window.QFormLayout = QFormLayout() - window.QT_Box_Layout = QVBoxLayout() - ConvertFlexToTK(window) - window.QT_Box_Layout.addLayout(window.QFormLayout) - - # shadow = QtWidgets.QGraphicsDropShadowEffect( window.QFormLayout) - # window.QTWindow.setGraphicsEffect(shadow) - - # Make window visible again - pass - - if window.ReturnKeyboardEvents and not window.NonBlocking: - pass - elif window.ReturnKeyboardEvents: - pass - - # print('..... CALLING MainLoop') - window.CurrentlyRunningMainloop = True - window.QTWindow.setLayout(window.QT_Box_Layout) - - if window.FocusElement is not None: - window.FocusElement.setFocus() - - # Resize the window to the size it should be at... dunno why I need to do this but I do... - # add 5 pixels onto it because stuff was getting cut off - qsize = window.QT_QMainWindow.sizeHint() - size = [qsize.width(), qsize.height()] - size[0] += 10 - window.QT_QMainWindow.resize(*size) - - if window._Size != (None, None): - window.QT_QMainWindow.resize(window._Size[0], window._Size[1]) - - if not window.Resizable: - window.QT_QMainWindow.setFixedSize(*size) - - timer = None - if window.AutoClose: - timer = start_window_autoclose_timer(window, window.AutoCloseDuration * 1000) - - if not window.NonBlocking: - if window.Timeout: - timer = start_window_read_timer(window, window.Timeout) - window.QT_QMainWindow.show() ####### The thing that causes the window to be visible ###### - #### ------------------------------ RUN MAIN LOOP HERE ------------------------------ ##### - window.QTApplication.exec_() - if timer: - stop_timer(timer) - else: # Non-blocking window - window.QT_QMainWindow.show() ####### The thing that causes the window to be visible ###### - window.QTApplication.processEvents() - - window.CurrentlyRunningMainloop = False - window.TimerCancelled = True - # print('..... BACK from MainLoop') - if not window.FormRemainedOpen: - Window.DecrementOpenCount() - if window.RootNeedsDestroying: - # print('** Destroying window **') - window.QT_QMainWindow.close() # destroy the window - window.RootNeedsDestroying = False - return - - -# ==============================_GetNumLinesNeeded ==# -# Helper function for determining how to wrap text # -# ===================================================# -def _GetNumLinesNeeded(text, max_line_width): - if max_line_width == 0: - return 1 - lines = text.split('\n') - num_lines = len(lines) # number of original lines of text - max_line_len = max([len(l) for l in lines]) # longest line - lines_used = [] - for L in lines: - lines_used.append(len(L) // max_line_width + (len(L) % max_line_width > 0)) # fancy math to round up - total_lines_needed = sum(lines_used) - return total_lines_needed - - -# ============================== PROGRESS METER ========================================== # - - -def ConvertArgsToSingleString(*args): - ( - max_line_total, - width_used, - total_lines, - ) = ( - 0, - 0, - 0, - ) - single_line_message = '' - # loop through args and built a SINGLE string from them - for message in args: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = max(longest_line_len, width_used) - max_line_total = max(max_line_total, width_used) - lines_needed = _GetNumLinesNeeded(message, width_used) - total_lines += lines_needed - single_line_message += message + '\n' - return single_line_message, width_used, total_lines - - -METER_REASON_CANCELLED = 'cancelled' -METER_REASON_CLOSED = 'closed' -METER_REASON_REACHED_MAX = 'finished' -METER_OK = True -METER_STOPPED = False - - -class QuickMeter(object): - active_meters = {} - exit_reasons = {} - - def __init__( - self, - title, - current_value, - max_value, - key, - *args, - orientation='v', - bar_color=(None, None), - button_color=(None, None), - size=DEFAULT_PROGRESS_BAR_SIZE, - border_width=None, - grab_anywhere=False, - ): - self.start_time = datetime.datetime.utcnow() - self.key = key - self.orientation = orientation - self.bar_color = bar_color - self.size = size - self.grab_anywhere = grab_anywhere - self.button_color = button_color - self.border_width = border_width - self.title = title - self.current_value = current_value - self.max_value = max_value - self.close_reason = None - self.window = self.BuildWindow(*args) - - def BuildWindow(self, *args): - layout = [] - if self.orientation.lower().startswith('h'): - col = [[T(''.join(map(lambda x: str(x) + '\n', args)), key='_OPTMSG_')]] ### convert all *args into one string that can be updated - col += [ - [T('', size=(25, 5), key='_STATS_')], - [ - ProgressBar( - max_value=self.max_value, - orientation='h', - key='_PROG_', - size=self.size, - bar_color=self.bar_color, - ) - ], - [Cancel(button_color=self.button_color), Stretch()], - ] - layout += [Column(col)] - else: - col = [ - [ - ProgressBar( - max_value=self.max_value, - orientation='v', - key='_PROG_', - size=self.size, - bar_color=self.bar_color, - ) - ] - ] - col2 = [[T(''.join(map(lambda x: str(x) + '\n', args)), key='_OPTMSG_')]] ### convert all *args into one string that can be updated - col2 += [[T('', size=(25, 5), key='_STATS_')], [Cancel(button_color=self.button_color), Stretch()]] - layout += [Column(col), Column(col2)] - self.window = Window(self.title, grab_anywhere=self.grab_anywhere, border_depth=self.border_width) - self.window.Layout([layout]).Finalize() - - return self.window - - def UpdateMeter(self, current_value, max_value, *args): - self.current_value = current_value - self.max_value = max_value - self.window.Element('_PROG_').UpdateBar(self.current_value, self.max_value) - self.window.Element('_STATS_').Update('\n'.join(self.ComputeProgressStats())) - self.window.Element('_OPTMSG_').Update(value=''.join(map(lambda x: str(x) + '\n', args))) ### update the string with the args - event, values = self.window.Read(timeout=0) - if event in ('Cancel', None) or current_value >= max_value: - self.window.Close() - del QuickMeter.active_meters[self.key] - QuickMeter.exit_reasons[self.key] = METER_REASON_CANCELLED if event == 'Cancel' else METER_REASON_CLOSED if event is None else METER_REASON_REACHED_MAX - return QuickMeter.exit_reasons[self.key] - return METER_OK - - def ComputeProgressStats(self): - utc = datetime.datetime.utcnow() - time_delta = utc - self.start_time - total_seconds = time_delta.total_seconds() - if not total_seconds: - total_seconds = 1 - try: - time_per_item = total_seconds / self.current_value - except: - time_per_item = 1 - seconds_remaining = (self.max_value - self.current_value) * time_per_item - time_remaining = str(datetime.timedelta(seconds=seconds_remaining)) - time_remaining_short = (time_remaining).split('.')[0] - time_delta_short = str(time_delta).split('.')[0] - total_time = time_delta + datetime.timedelta(seconds=seconds_remaining) - total_time_short = str(total_time).split('.')[0] - self.stat_messages = [ - '{} of {}'.format(self.current_value, self.max_value), - '{} %'.format(100 * self.current_value // self.max_value), - '', - ' {:6.2f} Iterations per Second'.format(self.current_value / total_seconds), - ' {:6.2f} Seconds per Iteration'.format(total_seconds / (self.current_value if self.current_value else 1)), - '', - '{} Elapsed Time'.format(time_delta_short), - '{} Time Remaining'.format(time_remaining_short), - '{} Estimated Total Time'.format(total_time_short), - ] - return self.stat_messages - - -def OneLineProgressMeter( - title, - current_value, - max_value, - key='OK for 1 meter', - *args, - orientation='v', - bar_color=(None, None), - button_color=None, - size=DEFAULT_PROGRESS_BAR_SIZE, - border_width=None, - grab_anywhere=False, -): - """ - :param orientation: 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical' / 'v') - :type orientation: (str) - :param bar_color: color of a bar line - :type bar_color: Tuple(str, str) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param size: (w,h) w=characters-wide, h=rows-high (Default value = DEFAULT_PROGRESS_BAR_SIZE) - :type size: Tuple[int, int] - :param border_width: width of border around element - :type border_width: (int) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - """ - if key not in QuickMeter.active_meters: - meter = QuickMeter( - title, - current_value, - max_value, - key, - *args, - orientation=orientation, - bar_color=bar_color, - button_color=button_color, - size=size, - border_width=border_width, - grab_anywhere=grab_anywhere, - ) - QuickMeter.active_meters[key] = meter - else: - meter = QuickMeter.active_meters[key] - - rc = meter.UpdateMeter(current_value, max_value, *args) - OneLineProgressMeter.exit_reasons = getattr(OneLineProgressMeter, 'exit_reasons', QuickMeter.exit_reasons) - return rc == METER_OK - - -def OneLineProgressMeterCancel(key='OK for 1 meter'): - try: - meter = QuickMeter.active_meters[key] - meter.window.Close() - del QuickMeter.active_meters[key] - QuickMeter.exit_reasons[key] = METER_REASON_CANCELLED - except: # meter is already deleted - return - - -# input is #RRGGBB -# output is #RRGGBB -def GetComplimentaryHex(color): - """ - :param color: color string, like "#RRGGBB" - :type color: (str) - :return color: color string, like "#RRGGBB" - :type color: (str) - """ - # strip the # from the beginning - color = color[1:] - # convert the string into hex - color = int(color, 16) - # invert the three bytes - # as good as substracting each of RGB component by 255(FF) - comp_color = 0xFFFFFF ^ color - # convert the color back to hex by prefixing a # - comp_color = '#%06X' % comp_color - return comp_color - - -# ======================== EasyPrint =====# -# ===================================================# - - -class DebugWin: - debug_window = None - - def __init__( - self, - size=(None, None), - location=(None, None), - font=None, - no_titlebar=False, - no_button=False, - grab_anywhere=False, - keep_on_top=False, - title=None, - do_not_reroute_stdout=False, - ): - # Show a form that's a running counter - self.size = size - self.location = location - self.font = font - self.no_titlebar = no_titlebar - self.no_button = no_button - self.grab_anywhere = grab_anywhere - self.keep_on_top = keep_on_top - self.do_not_reroute_stdout = do_not_reroute_stdout - - win_size = size if size != (None, None) else DEFAULT_DEBUG_WINDOW_SIZE - self.window = Window( - title=title or 'Debug Window', - no_titlebar=no_titlebar, - auto_size_text=True, - location=location, - font=font or ('Courier New', 10), - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - ) - self.output_element = MultilineOutput(size=win_size, key='_MULTILINE_') if do_not_reroute_stdout else Output(size=win_size) - - if no_button: - self.layout = [[self.output_element]] - else: - self.layout = [[self.output_element], [DummyButton('Quit'), Stretch()]] - self.window.AddRows(self.layout) - self.window.Read(timeout=0) # Show a non-blocking form, returns immediately - Window.active_popups[self.window] = 'debug window' - return - - def Print(self, *args, end=None, sep=None): - sepchar = sep if sep is not None else ' ' - endchar = end if end is not None else '\n' - - if self.window is None: # if window was destroyed already, just print - self.__init__( - size=self.size, - location=self.location, - font=self.font, - no_titlebar=self.no_titlebar, - no_button=self.no_button, - grab_anywhere=self.grab_anywhere, - keep_on_top=self.keep_on_top, - do_not_reroute_stdout=self.do_not_reroute_stdout, - ) - event, values = self.window.Read(timeout=0) - if event == 'Quit' or event is None: - self.Close() - self.__init__( - size=self.size, - location=self.location, - font=self.font, - no_titlebar=self.no_titlebar, - no_button=self.no_button, - grab_anywhere=self.grab_anywhere, - keep_on_top=self.keep_on_top, - do_not_reroute_stdout=self.do_not_reroute_stdout, - ) - if self.do_not_reroute_stdout: - end_str = str(end) if end is not None else '\n' - sep_str = str(sep) if sep is not None else ' ' - - outstring = '' - num_args = len(args) - for i, arg in enumerate(args): - outstring += str(arg) - if i != num_args - 1: - outstring += sep_str - outstring += end_str - self.output_element.Update(outstring, append=True) - else: - print(*args, sep=sepchar, end=endchar) - - def Close(self): - self.window.Close() - self.window = None - - -def PrintClose(): - EasyPrintClose() - - -def EasyPrint( - *args, - size=(None, None), - end=None, - sep=None, - location=(None, None), - font=None, - no_titlebar=False, - no_button=False, - grab_anywhere=False, - keep_on_top=False, - do_not_reroute_stdout=True, -): - """ - :param args: The arguments to display - :type args: List[Any] - :param size: (w,h) w=characters-wide, h=rows-high - :type size: Tuple[int, int] - :param end: The end char to use just like print uses - :type end: (str) - :param sep: end character - :type end: (str) - :param sep: separator character - :type sep: (str) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param no_button: don't show button - :type no_button: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param do_not_reroute_stdout: do not reroute stdout - :type do_not_reroute_stdout: (bool) - :param text_color: color of the text - :type text_color: (str) - :param background_color: color of background - :type background_color: (str) - """ - - if DebugWin.debug_window is None: - DebugWin.debug_window = DebugWin( - size=size, - location=location, - font=font, - no_titlebar=no_titlebar, - no_button=no_button, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - do_not_reroute_stdout=do_not_reroute_stdout, - ) - DebugWin.debug_window.Print(*args, end=end, sep=sep) - - -Print = EasyPrint -eprint = EasyPrint - - -def EasyPrintClose(): - if DebugWin.debug_window is not None: - DebugWin.debug_window.Close() - DebugWin.debug_window = None - - -# d8b 888 -# Y8P 888 -# 888 -# .d8888b 88888b. 888d888 888 88888b. 888888 -# d88P" 888 "88b 888P" 888 888 "88b 888 -# 888 888 888 888 888 888 888 888 -# Y88b. 888 d88P 888 888 888 888 Y88b. -# "Y8888P 88888P" 888 888 888 888 "Y888 -# 888 -# 888 -# 888 - - -CPRINT_DESTINATION_WINDOW = None -CPRINT_DESTINATION_MULTILINE_ELMENT_KEY = None - - -def cprint_set_output_destination(window, multiline_key): - """ - Sets up the color print (cprint) output destination - :param window: The window that the cprint call will route the output to - :type window: (Window) - :param multiline_key: Key for the Multiline Element where output will be sent - :type multiline_key: (Any) - :return: None - :rtype: None - """ - - global CPRINT_DESTINATION_WINDOW, CPRINT_DESTINATION_MULTILINE_ELMENT_KEY - - CPRINT_DESTINATION_WINDOW = window - CPRINT_DESTINATION_MULTILINE_ELMENT_KEY = multiline_key - - -# def cprint(*args, **kwargs): -def cprint( - *args, - end=None, - sep=' ', - text_color=None, - t=None, - background_color=None, - b=None, - colors=None, - c=None, - window=None, - key=None, -): - """ - Color print to a multiline element in a window of your choice. - Must have EITHER called cprint_set_output_destination prior to making this call so that the - window and element key can be saved and used here to route the output, OR used the window - and key parameters to the cprint function to specicy these items. - - args is a variable number of things you want to print. - - end - The end char to use just like print uses - sep - The separation character like print uses - text_color - The color of the text - key - overrides the previously defined Multiline key - window - overrides the previously defined window to output to - background_color - The color of the background - colors -(str, str) or str. A combined text/background color definition in a single parameter - - There are also "aliases" for text_color, background_color and colors (t, b, c) - t - An alias for color of the text (makes for shorter calls) - b - An alias for the background_color parameter - c - Tuple[str, str] - "shorthand" way of specifying color. (foreground, backgrouned) - c - str - can also be a string of the format "foreground on background" ("white on red") - - With the aliases it's possible to write the same print but in more compact ways: - cprint('This will print white text on red background', c=('white', 'red')) - cprint('This will print white text on red background', c='white on red') - cprint('This will print white text on red background', text_color='white', background_color='red') - cprint('This will print white text on red background', t='white', b='red') - - :param *args: stuff to output - :type *args: (Any) - :param text_color: Color of the text - :type text_color: (str) - :param background_color: The background color of the line - :type background_color: (str) - :param colors: Either a tuple or a string that has both the text and background colors - :type colors: (str) or Tuple[str, str] - :param t: Color of the text - :type t: (str) - :param b: The background color of the line - :type b: (str) - :param c: Either a tuple or a string that has both the text and background colors - :type c: (str) or Tuple[str, str] - :param end: end character - :type end: (str) - :param sep: separator character - :type sep: (str) - :param key: key of multiline to output to (if you want to override the one previously set) - :type key: (Any) - :param window: Window containing the multiline to output to (if you want to override the one previously set) - :type window: (Window) - :return: None - :rtype: None - """ - - destination_key = CPRINT_DESTINATION_MULTILINE_ELMENT_KEY if key is None else key - destination_window = window or CPRINT_DESTINATION_WINDOW - - if (destination_window is None and window is None) or (destination_key is None and key is None): - print( - '** Warning ** Attempting to perform a cprint without a valid window & key', - 'Will instead print on Console', - 'You can specify window and key in this cprint call, or set ahead of time using cprint_set_output_destination', - ) - print(*args) - return - - kw_text_color = text_color or t - kw_background_color = background_color or b - dual_color = colors or c - try: - if isinstance(dual_color, tuple): - kw_text_color = dual_color[0] - kw_background_color = dual_color[1] - elif isinstance(dual_color, str): - kw_text_color = dual_color.split(' on ')[0] - kw_background_color = dual_color.split(' on ')[1] - except Exception as e: - print('* cprint warning * you messed up with color formatting', e) - - mline = destination_window.find_element(destination_key, silent_on_error=True) # type: Multiline - try: - # mline = destination_window[destination_key] # type: # Multiline - if end is None: - mline.print(*args, text_color=kw_text_color, background_color=kw_background_color, end='', sep=sep) - mline.print('') - else: - mline.print(*args, text_color=kw_text_color, background_color=kw_background_color, end=end, sep=sep) - except Exception as e: - print('** cprint error trying to print to the multiline. Printing to console instead **', e) - print(*args, end=end, sep=sep) - - -# ------------------------------------------------------------------------------------------------ # -# A print-like call that can be used to output to a multiline element as if it's an Output element # -# ------------------------------------------------------------------------------------------------ # - - -def _print_to_element(multiline_element, *args, end=None, sep=None, text_color=None, background_color=None, autoscroll=True): - """ - Print like Python normally prints except route the output to a multline element and also add colors if desired - - :param multiline_element: The multiline element to be output to - :type multiline_element: (Multiline) - :param args: The arguments to print - :type args: List[Any] - :param end: The end char to use just like print uses - :type end: (str) - :param sep: The separation character like print uses - :type sep: (str) - :param text_color: color of the text - :type text_color: (str) - :param background_color: The background color of the line - :type background_color: (str) - :param autoscroll: If True (the default), the element will scroll to bottom after updating - :type autoscroll: Bool - """ - end_str = str(end) if end is not None else '\n' - sep_str = str(sep) if sep is not None else ' ' - - outstring = '' - num_args = len(args) - for i, arg in enumerate(args): - outstring += str(arg) - if i != num_args - 1: - outstring += sep_str - outstring += end_str - - multiline_element.update( - outstring, - append=True, - text_color_for_value=text_color, - background_color_for_value=background_color, - autoscroll=autoscroll, - ) - - -# ======================== Scrolled Text Box =====# -# ===================================================# -def PopupScrolled( - *args, - button_color=None, - yes_no=False, - auto_close=False, - auto_close_duration=None, - size=(None, None), - location=(None, None), - title=None, - non_blocking=False, -): - """ - :param args: The arguments to display - :type args: List[Any] - :param title: Title to display in the window. - :type title: (str) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param yes_no: If True, displays Yes and No buttons instead of Ok - :type yes_no: (bool) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param size: (w,h) w=characters-wide, h=rows-high - :type size: Tuple[int, int] - :param location: Location on the screen to place the upper left corner of the window - :type location: Tuple[int, int] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - """ - if not args: - return - width, height = size - width = width if width else MESSAGE_BOX_LINE_WIDTH - window = Window( - title=title or args[0], - auto_size_text=True, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - location=location, - ) - max_line_total, max_line_width, total_lines, height_computed = 0, 0, 0, 0 - complete_output = '' - for message in args: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - new - message = str(message) - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = min(longest_line_len, width) - max_line_total = max(max_line_total, width_used) - max_line_width = width - lines_needed = _GetNumLinesNeeded(message, width_used) - height_computed += lines_needed - complete_output += message + '\n' - total_lines += lines_needed - height_computed = MAX_SCROLLED_TEXT_BOX_HEIGHT if height_computed > MAX_SCROLLED_TEXT_BOX_HEIGHT else height_computed - if height: - height_computed = height - computed_size = (max_line_width * 10, height_computed * 16) - window.AddRow(MultilineOutput(complete_output, size=computed_size)) - pad = max_line_total - 15 if max_line_total > 15 else 1 - # show either an OK or Yes/No depending on paramater - button = DummyButton if non_blocking else Button - if yes_no: - window.AddRow(Text('', size=(pad, 1), auto_size_text=False), button('Yes'), button('No')) - else: - window.AddRow(Text('', size=(pad, 1), auto_size_text=False), button('OK', size=(5, 1), button_color=button_color)) - - if non_blocking: - button, values = window.Read(timeout=0) - Window.active_popups[window] = title - else: - button, values = window.Read() - return button - - -ScrolledTextBox = PopupScrolled - - -# ============================== SetGlobalIcon ======# -# Sets the icon to be used by default # -# ===================================================# -def SetGlobalIcon(icon): - """ - :param icon: Either a Base64 byte string or a filename - :type icon: Union[bytes, str] - """ - - try: - with open(icon, 'r') as icon_file: - pass - except: - raise FileNotFoundError - Window.user_defined_icon = icon - return True - - -# ============================== SetOptions =========# -# Sets the icon to be used by default # -# ===================================================# -def SetOptions( - icon=None, - button_color=None, - element_size=(None, None), - button_element_size=(None, None), - margins=(None, None), - element_padding=(None, None), - auto_size_text=None, - auto_size_buttons=None, - font=None, - border_width=None, - slider_border_width=None, - slider_relief=None, - slider_orientation=None, - autoclose_time=None, - message_box_line_width=None, - progress_meter_border_depth=None, - progress_meter_style=None, - progress_meter_relief=None, - progress_meter_color=None, - progress_meter_size=None, - text_justification=None, - background_color=None, - element_background_color=None, - text_element_background_color=None, - input_elements_background_color=None, - input_text_color=None, - scrollbar_color=None, - text_color=None, - element_text_color=None, - debug_win_size=(None, None), - window_location=(None, None), - error_button_color=(None, None), - tooltip_time=None, -): - """ - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param button_color: Color of the button (text, background) - :type button_color: Tuple[str, str] - :param element_size: element size (width, height) in characters - :type element_size: Tuple[int, int] - :param button_element_size: Size of button - :type button_element_size: Tuple[int, int] - :param margins: (left/right, top/bottom) tkinter margins around outsize. Amount of pixels to leave inside the window's frame around the edges before your elements are shown. - :type margins: Tuple[int, int] - :param element_padding: Default amount of padding to put around elements in window (left/right, top/bottom) or ((left, right), (top, bottom)) - :type element_padding: Tuple[int, int] or ((int, int),(int,int)) - :param auto_size_text: True if the Widget should be shrunk to exactly fit the number of chars to show - :type auto_size_text: bool - :param auto_size_buttons: True if Buttons in this Window should be sized to exactly fit the text on this. - :type auto_size_buttons: (bool) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param border_width: width of border around element - :type border_width: (int) - :param slider_border_width: ??? - :type slider_border_width: ??? - :param slider_relief: ??? - :type slider_relief: ??? - :param slider_orientation: ??? - :type slider_orientation: ??? - :param autoclose_time: ??? - :type autoclose_time: ??? - :param message_box_line_width: ??? - :type message_box_line_width: ??? - :param progress_meter_border_depth: ??? - :type progress_meter_border_depth: ??? - :param progress_meter_style: You can no longer set a progress bar style. All ttk styles must be the same for the window - :type progress_meter_style: ??? - :param progress_meter_relief: - :type progress_meter_relief: ??? - :param progress_meter_color: ??? - :type progress_meter_color: ??? - :param progress_meter_size: ??? - :type progress_meter_size: ??? - :param text_justification: Default text justification for all Text Elements in window - :type text_justification: Union['left', 'right', 'center'] - :param background_color: color of background - :type background_color: (str) - :param element_background_color: element background color - :type element_background_color: (str) - :param text_element_background_color: text element background color - :type text_element_background_color: (str) - :param input_elements_background_color: ??? - :type input_elements_background_color: idk_yetReally - :param input_text_color: ??? - :type input_text_color: ??? - :param scrollbar_color: ??? - :type scrollbar_color: ??? - :param text_color: color of the text - :type text_color: (str) - :param element_text_color: ??? - :type element_text_color: ??? - :param debug_win_size: window size - :type debug_win_size: Tuple[int, int] - :param window_location: (Default = (None)) - :type window_location: ??? - :param error_button_color: (Default = (None)) - :type error_button_color: ??? - :param tooltip_time: time in milliseconds to wait before showing a tooltip. Default is 400ms - :type tooltip_time: (int) - :param tooltip_font: font to use for all tooltips - :type tooltip_font: str or Tuple[str, int] or Tuple[str, int, str] - :param use_ttk_buttons: if True will cause all buttons to be ttk buttons - :type use_ttk_buttons: (bool) - :param ttk_theme: Theme to use with ttk widgets. Choices (on Windows) include - 'default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative' - :type ttk_theme: (str) - """ - global DEFAULT_ELEMENT_SIZE - global DEFAULT_BUTTON_ELEMENT_SIZE - global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term - global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels - global DEFAULT_AUTOSIZE_TEXT - global DEFAULT_AUTOSIZE_BUTTONS - global DEFAULT_FONT - global DEFAULT_BORDER_WIDTH - global DEFAULT_AUTOCLOSE_TIME - global DEFAULT_BUTTON_COLOR - global MESSAGE_BOX_LINE_WIDTH - global DEFAULT_PROGRESS_BAR_BORDER_WIDTH - global DEFAULT_PROGRESS_BAR_STYLE - global DEFAULT_PROGRESS_BAR_RELIEF - global DEFAULT_PROGRESS_BAR_COLOR - global DEFAULT_PROGRESS_BAR_SIZE - global DEFAULT_TEXT_JUSTIFICATION - global DEFAULT_DEBUG_WINDOW_SIZE - global DEFAULT_SLIDER_BORDER_WIDTH - global DEFAULT_SLIDER_RELIEF - global DEFAULT_SLIDER_ORIENTATION - global DEFAULT_BACKGROUND_COLOR - global DEFAULT_INPUT_ELEMENTS_COLOR - global DEFAULT_ELEMENT_BACKGROUND_COLOR - global DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - global DEFAULT_SCROLLBAR_COLOR - global DEFAULT_TEXT_COLOR - global DEFAULT_WINDOW_LOCATION - global DEFAULT_ELEMENT_TEXT_COLOR - global DEFAULT_INPUT_TEXT_COLOR - global DEFAULT_TOOLTIP_TIME - global DEFAULT_ERROR_BUTTON_COLOR - - if icon: - Window.user_defined_icon = icon - - if button_color != None: - DEFAULT_BUTTON_COLOR = button_color - - if element_size != (None, None): - DEFAULT_ELEMENT_SIZE = _convert_tkinter_size_to_Qt(element_size) - - if button_element_size != (None, None): - DEFAULT_BUTTON_ELEMENT_SIZE = _convert_tkinter_size_to_Qt(button_element_size) - - if margins != (None, None): - DEFAULT_MARGINS = margins - - if element_padding != (None, None): - DEFAULT_ELEMENT_PADDING = element_padding - - if auto_size_text != None: - DEFAULT_AUTOSIZE_TEXT = auto_size_text - - if auto_size_buttons != None: - DEFAULT_AUTOSIZE_BUTTONS = auto_size_buttons - - if font != None: - DEFAULT_FONT = font - - if border_width != None: - DEFAULT_BORDER_WIDTH = border_width - - if autoclose_time != None: - DEFAULT_AUTOCLOSE_TIME = autoclose_time - - if message_box_line_width != None: - MESSAGE_BOX_LINE_WIDTH = message_box_line_width - - if progress_meter_border_depth != None: - DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth - - if progress_meter_style != None: - DEFAULT_PROGRESS_BAR_STYLE = progress_meter_style - - if progress_meter_relief != None: - DEFAULT_PROGRESS_BAR_RELIEF = progress_meter_relief - - if progress_meter_color != None: - DEFAULT_PROGRESS_BAR_COLOR = progress_meter_color - - if progress_meter_size != None: - DEFAULT_PROGRESS_BAR_SIZE = progress_meter_size - - if slider_border_width != None: - DEFAULT_SLIDER_BORDER_WIDTH = slider_border_width - - if slider_orientation != None: - DEFAULT_SLIDER_ORIENTATION = slider_orientation - - if slider_relief != None: - DEFAULT_SLIDER_RELIEF = slider_relief - - if text_justification != None: - DEFAULT_TEXT_JUSTIFICATION = text_justification - - if background_color != None: - DEFAULT_BACKGROUND_COLOR = background_color - - if text_element_background_color != None: - DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = text_element_background_color - - if input_elements_background_color != None: - DEFAULT_INPUT_ELEMENTS_COLOR = input_elements_background_color - - if element_background_color != None: - DEFAULT_ELEMENT_BACKGROUND_COLOR = element_background_color - - if window_location != (None, None): - DEFAULT_WINDOW_LOCATION = window_location - - if debug_win_size != (None, None): - DEFAULT_DEBUG_WINDOW_SIZE = debug_win_size - - if text_color != None: - DEFAULT_TEXT_COLOR = text_color - - if scrollbar_color != None: - DEFAULT_SCROLLBAR_COLOR = scrollbar_color - - if element_text_color != None: - DEFAULT_ELEMENT_TEXT_COLOR = element_text_color - - if input_text_color is not None: - DEFAULT_INPUT_TEXT_COLOR = input_text_color - - if tooltip_time is not None: - DEFAULT_TOOLTIP_TIME = tooltip_time - - if error_button_color != (None, None): - print('error button') - DEFAULT_ERROR_BUTTON_COLOR = error_button_color - - return True - - -# ----------------------------------------------------------------- # - -# .########.##.....##.########.##.....##.########..######. -# ....##....##.....##.##.......###...###.##.......##....## -# ....##....##.....##.##.......####.####.##.......##...... -# ....##....#########.######...##.###.##.######....######. -# ....##....##.....##.##.......##.....##.##.............## -# ....##....##.....##.##.......##.....##.##.......##....## -# ....##....##.....##.########.##.....##.########..######. - -# ----------------------------------------------------------------- # - -# The official Theme code - -#################### ChangeLookAndFeel ####################### -# Predefined settings that will change the colors and styles # -# of the elements. # -############################################################## -LOOK_AND_FEEL_TABLE = { - 'SystemDefault': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'SystemDefaultForReal': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': COLOR_SYSTEM_DEFAULT, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'SystemDefault1': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': COLOR_SYSTEM_DEFAULT, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'Material1': { - 'BACKGROUND': '#E3F2FD', - 'TEXT': '#000000', - 'INPUT': '#86A8FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#86A8FF', - 'BUTTON': ('#FFFFFF', '#5079D3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'Material2': { - 'BACKGROUND': '#FAFAFA', - 'TEXT': '#000000', - 'INPUT': '#004EA1', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#5EA7FF', - 'BUTTON': ('#FFFFFF', '#0079D3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'Reddit': { - 'BACKGROUND': '#ffffff', - 'TEXT': '#1a1a1b', - 'INPUT': '#dae0e6', - 'TEXT_INPUT': '#222222', - 'SCROLL': '#a5a4a4', - 'BUTTON': ('#FFFFFF', '#0079d3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#ff5414', - 'ACCENT2': '#33a8ff', - 'ACCENT3': '#dbf0ff', - }, - 'Topanga': { - 'BACKGROUND': '#282923', - 'TEXT': '#E7DB74', - 'INPUT': '#393a32', - 'TEXT_INPUT': '#E7C855', - 'SCROLL': '#E7C855', - 'BUTTON': ('#E7C855', '#284B5A'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#c15226', - 'ACCENT2': '#7a4d5f', - 'ACCENT3': '#889743', - }, - 'GreenTan': { - 'BACKGROUND': '#9FB8AD', - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': '#F7F3EC', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#F7F3EC', - 'BUTTON': ('#FFFFFF', '#475841'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Dark': { - 'BACKGROUND': '#404040', - 'TEXT': '#FFFFFF', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#707070', - 'BUTTON': ('#FFFFFF', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen': { - 'BACKGROUND': '#B7CECE', - 'TEXT': '#000000', - 'INPUT': '#FDFFF7', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#FDFFF7', - 'BUTTON': ('#FFFFFF', '#658268'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'ACCENT1': '#76506d', - 'ACCENT2': '#5148f1', - 'ACCENT3': '#0a1c84', - 'PROGRESS_DEPTH': 0, - }, - 'Dark2': { - 'BACKGROUND': '#404040', - 'TEXT': '#FFFFFF', - 'INPUT': '#FFFFFF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#707070', - 'BUTTON': ('#FFFFFF', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Black': { - 'BACKGROUND': '#000000', - 'TEXT': '#FFFFFF', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#707070', - 'BUTTON': ('#000000', '#FFFFFF'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Tan': { - 'BACKGROUND': '#fdf6e3', - 'TEXT': '#268bd1', - 'INPUT': '#eee8d5', - 'TEXT_INPUT': '#6c71c3', - 'SCROLL': '#eee8d5', - 'BUTTON': ('#FFFFFF', '#063542'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'TanBlue': { - 'BACKGROUND': '#e5dece', - 'TEXT': '#063289', - 'INPUT': '#f9f8f4', - 'TEXT_INPUT': '#242834', - 'SCROLL': '#eee8d5', - 'BUTTON': ('#FFFFFF', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkTanBlue': { - 'BACKGROUND': '#242834', - 'TEXT': '#dfe6f8', - 'INPUT': '#97755c', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#a9afbb', - 'BUTTON': ('#FFFFFF', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkAmber': { - 'BACKGROUND': '#2c2825', - 'TEXT': '#fdcb52', - 'INPUT': '#705e52', - 'TEXT_INPUT': '#fdcb52', - 'SCROLL': '#705e52', - 'BUTTON': ('#000000', '#fdcb52'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue': { - 'BACKGROUND': '#1a2835', - 'TEXT': '#d1ecff', - 'INPUT': '#335267', - 'TEXT_INPUT': '#acc2d0', - 'SCROLL': '#1b6497', - 'BUTTON': ('#000000', '#fafaf8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Reds': { - 'BACKGROUND': '#280001', - 'TEXT': '#FFFFFF', - 'INPUT': '#d8d584', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#763e00', - 'BUTTON': ('#000000', '#daad28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Green': { - 'BACKGROUND': '#82a459', - 'TEXT': '#000000', - 'INPUT': '#d8d584', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e3ecf3', - 'BUTTON': ('#FFFFFF', '#517239'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BluePurple': { - 'BACKGROUND': '#A5CADD', - 'TEXT': '#6E266E', - 'INPUT': '#E0F5FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#E0F5FF', - 'BUTTON': ('#FFFFFF', '#303952'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Purple': { - 'BACKGROUND': '#B0AAC2', - 'TEXT': '#000000', - 'INPUT': '#F2EFE8', - 'SCROLL': '#F2EFE8', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#C2D4D8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BlueMono': { - 'BACKGROUND': '#AAB6D3', - 'TEXT': '#000000', - 'INPUT': '#F1F4FC', - 'SCROLL': '#F1F4FC', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#7186C7'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'GreenMono': { - 'BACKGROUND': '#A8C1B4', - 'TEXT': '#000000', - 'INPUT': '#DDE0DE', - 'SCROLL': '#E3E3E3', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#6D9F85'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BrownBlue': { - 'BACKGROUND': '#64778d', - 'TEXT': '#FFFFFF', - 'INPUT': '#f0f3f7', - 'SCROLL': '#A6B2BE', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#283b5b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BrightColors': { - 'BACKGROUND': '#b4ffb4', - 'TEXT': '#000000', - 'INPUT': '#ffff64', - 'SCROLL': '#ffb482', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#ffa0dc'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'NeutralBlue': { - 'BACKGROUND': '#92aa9d', - 'TEXT': '#000000', - 'INPUT': '#fcfff6', - 'SCROLL': '#fcfff6', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#d0dbbd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Kayak': { - 'BACKGROUND': '#a7ad7f', - 'TEXT': '#000000', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#5d907d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'SandyBeach': { - 'BACKGROUND': '#efeccb', - 'TEXT': '#012f2f', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#012f2f', - 'BUTTON': ('#FFFFFF', '#046380'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'TealMono': { - 'BACKGROUND': '#a8cfdd', - 'TEXT': '#000000', - 'INPUT': '#dfedf2', - 'SCROLL': '#dfedf2', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#183440'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Default': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'Default1': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': COLOR_SYSTEM_DEFAULT, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'DefaultNoMoreNagging': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue': { - 'BACKGROUND': '#E3F2FD', - 'TEXT': '#000000', - 'INPUT': '#86A8FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#86A8FF', - 'BUTTON': ('#FFFFFF', '#5079D3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'LightGrey': { - 'BACKGROUND': '#FAFAFA', - 'TEXT': '#000000', - 'INPUT': '#004EA1', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#5EA7FF', - 'BUTTON': ('#FFFFFF', '#0079D3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'LightGrey1': { - 'BACKGROUND': '#ffffff', - 'TEXT': '#1a1a1b', - 'INPUT': '#dae0e6', - 'TEXT_INPUT': '#222222', - 'SCROLL': '#a5a4a4', - 'BUTTON': ('#FFFFFF', '#0079d3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#ff5414', - 'ACCENT2': '#33a8ff', - 'ACCENT3': '#dbf0ff', - }, - 'DarkBrown': { - 'BACKGROUND': '#282923', - 'TEXT': '#E7DB74', - 'INPUT': '#393a32', - 'TEXT_INPUT': '#E7C855', - 'SCROLL': '#E7C855', - 'BUTTON': ('#E7C855', '#284B5A'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#c15226', - 'ACCENT2': '#7a4d5f', - 'ACCENT3': '#889743', - }, - 'LightGreen1': { - 'BACKGROUND': '#9FB8AD', - 'TEXT': '#000000', - 'INPUT': '#F7F3EC', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#F7F3EC', - 'BUTTON': ('#FFFFFF', '#475841'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey': { - 'BACKGROUND': '#404040', - 'TEXT': '#FFFFFF', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#707070', - 'BUTTON': ('#FFFFFF', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen2': { - 'BACKGROUND': '#B7CECE', - 'TEXT': '#000000', - 'INPUT': '#FDFFF7', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#FDFFF7', - 'BUTTON': ('#FFFFFF', '#658268'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'ACCENT1': '#76506d', - 'ACCENT2': '#5148f1', - 'ACCENT3': '#0a1c84', - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey1': { - 'BACKGROUND': '#404040', - 'TEXT': '#FFFFFF', - 'INPUT': '#FFFFFF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#707070', - 'BUTTON': ('#FFFFFF', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlack': { - 'BACKGROUND': '#000000', - 'TEXT': '#FFFFFF', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#707070', - 'BUTTON': ('#000000', '#FFFFFF'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown': { - 'BACKGROUND': '#fdf6e3', - 'TEXT': '#268bd1', - 'INPUT': '#eee8d5', - 'TEXT_INPUT': '#6c71c3', - 'SCROLL': '#eee8d5', - 'BUTTON': ('#FFFFFF', '#063542'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown1': { - 'BACKGROUND': '#e5dece', - 'TEXT': '#063289', - 'INPUT': '#f9f8f4', - 'TEXT_INPUT': '#242834', - 'SCROLL': '#eee8d5', - 'BUTTON': ('#FFFFFF', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue1': { - 'BACKGROUND': '#242834', - 'TEXT': '#dfe6f8', - 'INPUT': '#97755c', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#a9afbb', - 'BUTTON': ('#FFFFFF', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBrown1': { - 'BACKGROUND': '#2c2825', - 'TEXT': '#fdcb52', - 'INPUT': '#705e52', - 'TEXT_INPUT': '#fdcb52', - 'SCROLL': '#705e52', - 'BUTTON': ('#000000', '#fdcb52'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue2': { - 'BACKGROUND': '#1a2835', - 'TEXT': '#d1ecff', - 'INPUT': '#335267', - 'TEXT_INPUT': '#acc2d0', - 'SCROLL': '#1b6497', - 'BUTTON': ('#000000', '#fafaf8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBrown2': { - 'BACKGROUND': '#280001', - 'TEXT': '#FFFFFF', - 'INPUT': '#d8d584', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#763e00', - 'BUTTON': ('#000000', '#daad28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGreen': { - 'BACKGROUND': '#82a459', - 'TEXT': '#000000', - 'INPUT': '#d8d584', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e3ecf3', - 'BUTTON': ('#FFFFFF', '#517239'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue1': { - 'BACKGROUND': '#A5CADD', - 'TEXT': '#6E266E', - 'INPUT': '#E0F5FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#E0F5FF', - 'BUTTON': ('#FFFFFF', '#303952'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightPurple': { - 'BACKGROUND': '#B0AAC2', - 'TEXT': '#000000', - 'INPUT': '#F2EFE8', - 'SCROLL': '#F2EFE8', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#C2D4D8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue2': { - 'BACKGROUND': '#AAB6D3', - 'TEXT': '#000000', - 'INPUT': '#F1F4FC', - 'SCROLL': '#F1F4FC', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#7186C7'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen3': { - 'BACKGROUND': '#A8C1B4', - 'TEXT': '#000000', - 'INPUT': '#DDE0DE', - 'SCROLL': '#E3E3E3', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#6D9F85'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue3': { - 'BACKGROUND': '#64778d', - 'TEXT': '#FFFFFF', - 'INPUT': '#f0f3f7', - 'SCROLL': '#A6B2BE', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#283b5b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen4': { - 'BACKGROUND': '#b4ffb4', - 'TEXT': '#000000', - 'INPUT': '#ffff64', - 'SCROLL': '#ffb482', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#ffa0dc'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen5': { - 'BACKGROUND': '#92aa9d', - 'TEXT': '#000000', - 'INPUT': '#fcfff6', - 'SCROLL': '#fcfff6', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#d0dbbd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown2': { - 'BACKGROUND': '#a7ad7f', - 'TEXT': '#000000', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#5d907d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown3': { - 'BACKGROUND': '#efeccb', - 'TEXT': '#012f2f', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#012f2f', - 'BUTTON': ('#FFFFFF', '#046380'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue3': { - 'BACKGROUND': '#a8cfdd', - 'TEXT': '#000000', - 'INPUT': '#dfedf2', - 'SCROLL': '#dfedf2', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#183440'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown4': { - 'BACKGROUND': '#d7c79e', - 'TEXT': '#a35638', - 'INPUT': '#9dab86', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#a35638', - 'BUTTON': ('#FFFFFF', '#a35638'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#a35638', '#9dab86', '#e08f62', '#d7c79e'], - }, - 'DarkTeal': { - 'BACKGROUND': '#003f5c', - 'TEXT': '#fb5b5a', - 'INPUT': '#bc4873', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#bc4873', - 'BUTTON': ('#FFFFFF', '#fb5b5a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], - }, - 'DarkPurple': { - 'BACKGROUND': '#472b62', - 'TEXT': '#fb5b5a', - 'INPUT': '#bc4873', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#bc4873', - 'BUTTON': ('#FFFFFF', '#472b62'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], - }, - 'LightGreen6': { - 'BACKGROUND': '#eafbea', - 'TEXT': '#1f6650', - 'INPUT': '#6f9a8d', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#1f6650', - 'BUTTON': ('#FFFFFF', '#1f6650'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#1f6650', '#6f9a8d', '#ea5e5e', '#eafbea'], - }, - 'DarkGrey2': { - 'BACKGROUND': '#2b2b28', - 'TEXT': '#f8f8f8', - 'INPUT': '#f1d6ab', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f1d6ab', - 'BUTTON': ('#2b2b28', '#e3b04b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#2b2b28', '#e3b04b', '#f1d6ab', '#f8f8f8'], - }, - 'LightBrown6': { - 'BACKGROUND': '#f9b282', - 'TEXT': '#8f4426', - 'INPUT': '#de6b35', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#8f4426', - 'BUTTON': ('#FFFFFF', '#8f4426'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#8f4426', '#de6b35', '#64ccda', '#f9b282'], - }, - 'DarkTeal1': { - 'BACKGROUND': '#396362', - 'TEXT': '#ffe7d1', - 'INPUT': '#f6c89f', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f6c89f', - 'BUTTON': ('#ffe7d1', '#4b8e8d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], - }, - 'LightBrown7': { - 'BACKGROUND': '#f6c89f', - 'TEXT': '#396362', - 'INPUT': '#4b8e8d', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#396362', - 'BUTTON': ('#FFFFFF', '#396362'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], - }, - 'DarkPurple1': { - 'BACKGROUND': '#0c093c', - 'TEXT': '#fad6d6', - 'INPUT': '#eea5f6', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#eea5f6', - 'BUTTON': ('#FFFFFF', '#df42d1'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#0c093c', '#df42d1', '#eea5f6', '#fad6d6'], - }, - 'DarkGrey3': { - 'BACKGROUND': '#211717', - 'TEXT': '#dfddc7', - 'INPUT': '#f58b54', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f58b54', - 'BUTTON': ('#dfddc7', '#a34a28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], - }, - 'LightBrown8': { - 'BACKGROUND': '#dfddc7', - 'TEXT': '#211717', - 'INPUT': '#a34a28', - 'TEXT_INPUT': '#dfddc7', - 'SCROLL': '#211717', - 'BUTTON': ('#dfddc7', '#a34a28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], - }, - 'DarkBlue4': { - 'BACKGROUND': '#494ca2', - 'TEXT': '#e3e7f1', - 'INPUT': '#c6cbef', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#c6cbef', - 'BUTTON': ('#FFFFFF', '#8186d5'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#494ca2', '#8186d5', '#c6cbef', '#e3e7f1'], - }, - 'LightBlue4': { - 'BACKGROUND': '#5c94bd', - 'TEXT': '#470938', - 'INPUT': '#1a3e59', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#470938', - 'BUTTON': ('#FFFFFF', '#470938'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#470938', '#1a3e59', '#5c94bd', '#f2d6eb'], - }, - 'DarkTeal2': { - 'BACKGROUND': '#394a6d', - 'TEXT': '#c0ffb3', - 'INPUT': '#52de97', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#52de97', - 'BUTTON': ('#c0ffb3', '#394a6d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], - }, - 'DarkTeal3': { - 'BACKGROUND': '#3c9d9b', - 'TEXT': '#c0ffb3', - 'INPUT': '#52de97', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#52de97', - 'BUTTON': ('#c0ffb3', '#394a6d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], - }, - 'DarkPurple5': { - 'BACKGROUND': '#730068', - 'TEXT': '#f6f078', - 'INPUT': '#01d28e', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#01d28e', - 'BUTTON': ('#f6f078', '#730068'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#730068', '#434982', '#01d28e', '#f6f078'], - }, - 'DarkPurple2': { - 'BACKGROUND': '#202060', - 'TEXT': '#b030b0', - 'INPUT': '#602080', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#602080', - 'BUTTON': ('#FFFFFF', '#202040'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#202040', '#202060', '#602080', '#b030b0'], - }, - 'DarkBlue5': { - 'BACKGROUND': '#000272', - 'TEXT': '#ff6363', - 'INPUT': '#a32f80', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#a32f80', - 'BUTTON': ('#FFFFFF', '#341677'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#000272', '#341677', '#a32f80', '#ff6363'], - }, - 'LightGrey2': { - 'BACKGROUND': '#f6f6f6', - 'TEXT': '#420000', - 'INPUT': '#d4d7dd', - 'TEXT_INPUT': '#420000', - 'SCROLL': '#420000', - 'BUTTON': ('#420000', '#d4d7dd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], - }, - 'LightGrey3': { - 'BACKGROUND': '#eae9e9', - 'TEXT': '#420000', - 'INPUT': '#d4d7dd', - 'TEXT_INPUT': '#420000', - 'SCROLL': '#420000', - 'BUTTON': ('#420000', '#d4d7dd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], - }, - 'DarkBlue6': { - 'BACKGROUND': '#01024e', - 'TEXT': '#ff6464', - 'INPUT': '#8b4367', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#8b4367', - 'BUTTON': ('#FFFFFF', '#543864'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#01024e', '#543864', '#8b4367', '#ff6464'], - }, - 'DarkBlue7': { - 'BACKGROUND': '#241663', - 'TEXT': '#eae7af', - 'INPUT': '#a72693', - 'TEXT_INPUT': '#eae7af', - 'SCROLL': '#a72693', - 'BUTTON': ('#eae7af', '#160f30'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#160f30', '#241663', '#a72693', '#eae7af'], - }, - 'LightBrown9': { - 'BACKGROUND': '#f6d365', - 'TEXT': '#3a1f5d', - 'INPUT': '#c83660', - 'TEXT_INPUT': '#f6d365', - 'SCROLL': '#3a1f5d', - 'BUTTON': ('#f6d365', '#c83660'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3a1f5d', '#c83660', '#e15249', '#f6d365'], - }, - 'DarkPurple3': { - 'BACKGROUND': '#6e2142', - 'TEXT': '#ffd692', - 'INPUT': '#e16363', - 'TEXT_INPUT': '#ffd692', - 'SCROLL': '#e16363', - 'BUTTON': ('#ffd692', '#943855'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], - }, - 'LightBrown10': { - 'BACKGROUND': '#ffd692', - 'TEXT': '#6e2142', - 'INPUT': '#943855', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#6e2142', - 'BUTTON': ('#FFFFFF', '#6e2142'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], - }, - 'DarkPurple4': { - 'BACKGROUND': '#200f21', - 'TEXT': '#f638dc', - 'INPUT': '#5a3d5c', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#5a3d5c', - 'BUTTON': ('#FFFFFF', '#382039'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#200f21', '#382039', '#5a3d5c', '#f638dc'], - }, - 'LightBlue5': { - 'BACKGROUND': '#b2fcff', - 'TEXT': '#3e64ff', - 'INPUT': '#5edfff', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#3e64ff', - 'BUTTON': ('#FFFFFF', '#3e64ff'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3e64ff', '#5edfff', '#b2fcff', '#ecfcff'], - }, - 'DarkTeal4': { - 'BACKGROUND': '#464159', - 'TEXT': '#c7f0db', - 'INPUT': '#8bbabb', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#8bbabb', - 'BUTTON': ('#FFFFFF', '#6c7b95'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], - }, - 'LightTeal': { - 'BACKGROUND': '#c7f0db', - 'TEXT': '#464159', - 'INPUT': '#6c7b95', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#464159', - 'BUTTON': ('#FFFFFF', '#464159'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], - }, - 'DarkTeal5': { - 'BACKGROUND': '#8bbabb', - 'TEXT': '#464159', - 'INPUT': '#6c7b95', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#464159', - 'BUTTON': ('#c7f0db', '#6c7b95'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], - }, - 'LightGrey4': { - 'BACKGROUND': '#faf5ef', - 'TEXT': '#672f2f', - 'INPUT': '#99b19c', - 'TEXT_INPUT': '#672f2f', - 'SCROLL': '#672f2f', - 'BUTTON': ('#672f2f', '#99b19c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], - }, - 'LightGreen7': { - 'BACKGROUND': '#99b19c', - 'TEXT': '#faf5ef', - 'INPUT': '#d7d1c9', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#d7d1c9', - 'BUTTON': ('#FFFFFF', '#99b19c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], - }, - 'LightGrey5': { - 'BACKGROUND': '#d7d1c9', - 'TEXT': '#672f2f', - 'INPUT': '#99b19c', - 'TEXT_INPUT': '#672f2f', - 'SCROLL': '#672f2f', - 'BUTTON': ('#FFFFFF', '#672f2f'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], - }, - 'DarkBrown3': { - 'BACKGROUND': '#a0855b', - 'TEXT': '#f9f6f2', - 'INPUT': '#f1d6ab', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f1d6ab', - 'BUTTON': ('#FFFFFF', '#38470b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], - }, - 'LightBrown11': { - 'BACKGROUND': '#f1d6ab', - 'TEXT': '#38470b', - 'INPUT': '#a0855b', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#38470b', - 'BUTTON': ('#f9f6f2', '#a0855b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], - }, - 'DarkRed': { - 'BACKGROUND': '#83142c', - 'TEXT': '#f9d276', - 'INPUT': '#ad1d45', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#ad1d45', - 'BUTTON': ('#f9d276', '#ad1d45'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#44000d', '#83142c', '#ad1d45', '#f9d276'], - }, - 'DarkTeal6': { - 'BACKGROUND': '#204969', - 'TEXT': '#fff7f7', - 'INPUT': '#dadada', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#dadada', - 'BUTTON': ('#000000', '#fff7f7'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#204969', '#08ffc8', '#dadada', '#fff7f7'], - }, - 'DarkBrown4': { - 'BACKGROUND': '#252525', - 'TEXT': '#ff0000', - 'INPUT': '#af0404', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#af0404', - 'BUTTON': ('#FFFFFF', '#252525'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#252525', '#414141', '#af0404', '#ff0000'], - }, - 'LightYellow': { - 'BACKGROUND': '#f4ff61', - 'TEXT': '#27aa80', - 'INPUT': '#32ff6a', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#27aa80', - 'BUTTON': ('#f4ff61', '#27aa80'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#27aa80', '#32ff6a', '#a8ff3e', '#f4ff61'], - }, - 'DarkGreen1': { - 'BACKGROUND': '#2b580c', - 'TEXT': '#fdef96', - 'INPUT': '#f7b71d', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f7b71d', - 'BUTTON': ('#fdef96', '#2b580c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#2b580c', '#afa939', '#f7b71d', '#fdef96'], - }, - 'LightGreen8': { - 'BACKGROUND': '#c8dad3', - 'TEXT': '#63707e', - 'INPUT': '#93b5b3', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#63707e', - 'BUTTON': ('#FFFFFF', '#63707e'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#63707e', '#93b5b3', '#c8dad3', '#f2f6f5'], - }, - 'DarkTeal7': { - 'BACKGROUND': '#248ea9', - 'TEXT': '#fafdcb', - 'INPUT': '#aee7e8', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#aee7e8', - 'BUTTON': ('#000000', '#fafdcb'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#248ea9', '#28c3d4', '#aee7e8', '#fafdcb'], - }, - 'DarkBlue8': { - 'BACKGROUND': '#454d66', - 'TEXT': '#d9d872', - 'INPUT': '#58b368', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#58b368', - 'BUTTON': ('#000000', '#009975'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#009975', '#454d66', '#58b368', '#d9d872'], - }, - 'DarkBlue9': { - 'BACKGROUND': '#263859', - 'TEXT': '#ff6768', - 'INPUT': '#6b778d', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#6b778d', - 'BUTTON': ('#ff6768', '#263859'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#17223b', '#263859', '#6b778d', '#ff6768'], - }, - 'DarkBlue10': { - 'BACKGROUND': '#0028ff', - 'TEXT': '#f1f4df', - 'INPUT': '#10eaf0', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#10eaf0', - 'BUTTON': ('#f1f4df', '#24009c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#24009c', '#0028ff', '#10eaf0', '#f1f4df'], - }, - 'DarkBlue11': { - 'BACKGROUND': '#6384b3', - 'TEXT': '#e6f0b6', - 'INPUT': '#b8e9c0', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#b8e9c0', - 'BUTTON': ('#e6f0b6', '#684949'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#684949', '#6384b3', '#b8e9c0', '#e6f0b6'], - }, - 'DarkTeal8': { - 'BACKGROUND': '#71a0a5', - 'TEXT': '#212121', - 'INPUT': '#665c84', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#212121', - 'BUTTON': ('#fab95b', '#665c84'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#212121', '#665c84', '#71a0a5', '#fab95b'], - }, - 'DarkRed1': { - 'BACKGROUND': '#c10000', - 'TEXT': '#eeeeee', - 'INPUT': '#dedede', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#dedede', - 'BUTTON': ('#c10000', '#eeeeee'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#c10000', '#ff4949', '#dedede', '#eeeeee'], - }, - 'LightBrown5': { - 'BACKGROUND': '#fff591', - 'TEXT': '#e41749', - 'INPUT': '#f5587b', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e41749', - 'BUTTON': ('#fff591', '#e41749'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#e41749', '#f5587b', '#ff8a5c', '#fff591'], - }, - 'LightGreen9': { - 'BACKGROUND': '#f1edb3', - 'TEXT': '#3b503d', - 'INPUT': '#4a746e', - 'TEXT_INPUT': '#f1edb3', - 'SCROLL': '#3b503d', - 'BUTTON': ('#f1edb3', '#3b503d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], - 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], - }, - 'DarkGreen2': { - 'BACKGROUND': '#3b503d', - 'TEXT': '#f1edb3', - 'INPUT': '#c8cf94', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#c8cf94', - 'BUTTON': ('#f1edb3', '#3b503d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], - 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], - }, - 'LightGray1': { - 'BACKGROUND': '#f2f2f2', - 'TEXT': '#222831', - 'INPUT': '#393e46', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#222831', - 'BUTTON': ('#f2f2f2', '#222831'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#222831', '#393e46', '#f96d00', '#f2f2f2'], - 'DESCRIPTION': ['#000000', 'Grey', 'Orange', 'Grey', 'Autumn'], - }, - 'DarkGrey4': { - 'BACKGROUND': '#52524e', - 'TEXT': '#e9e9e5', - 'INPUT': '#d4d6c8', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#d4d6c8', - 'BUTTON': ('#FFFFFF', '#9a9b94'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#52524e', '#9a9b94', '#d4d6c8', '#e9e9e5'], - 'DESCRIPTION': ['Grey', 'Pastel', 'Winter'], - }, - 'DarkBlue12': { - 'BACKGROUND': '#324e7b', - 'TEXT': '#f8f8f8', - 'INPUT': '#86a6df', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#86a6df', - 'BUTTON': ('#FFFFFF', '#5068a9'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#324e7b', '#5068a9', '#86a6df', '#f8f8f8'], - 'DESCRIPTION': ['Blue', 'Grey', 'Cold', 'Winter'], - }, - 'DarkPurple6': { - 'BACKGROUND': '#070739', - 'TEXT': '#e1e099', - 'INPUT': '#c327ab', - 'TEXT_INPUT': '#e1e099', - 'SCROLL': '#c327ab', - 'BUTTON': ('#e1e099', '#521477'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#070739', '#521477', '#c327ab', '#e1e099'], - 'DESCRIPTION': ['#000000', 'Purple', 'Yellow', 'Dark'], - }, - 'DarkPurple7': { - 'BACKGROUND': '#191930', - 'TEXT': '#B1B7C5', - 'INPUT': '#232B5C', - 'TEXT_INPUT': '#D0E3E7', - 'SCROLL': '#B1B7C5', - 'BUTTON': ('#272D38', '#B1B7C5'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue13': { - 'BACKGROUND': '#203562', - 'TEXT': '#e3e8f8', - 'INPUT': '#c0c5cd', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#c0c5cd', - 'BUTTON': ('#FFFFFF', '#3e588f'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#203562', '#3e588f', '#c0c5cd', '#e3e8f8'], - 'DESCRIPTION': ['Blue', 'Grey', 'Wedding', 'Cold'], - }, - 'DarkBrown5': { - 'BACKGROUND': '#3c1b1f', - 'TEXT': '#f6e1b5', - 'INPUT': '#e2bf81', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e2bf81', - 'BUTTON': ('#3c1b1f', '#f6e1b5'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3c1b1f', '#b21e4b', '#e2bf81', '#f6e1b5'], - 'DESCRIPTION': ['Brown', 'Red', 'Yellow', 'Warm'], - }, - 'DarkGreen3': { - 'BACKGROUND': '#062121', - 'TEXT': '#eeeeee', - 'INPUT': '#e4dcad', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e4dcad', - 'BUTTON': ('#eeeeee', '#181810'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], - 'DESCRIPTION': ['#000000', '#000000', 'Brown', 'Grey'], - }, - 'DarkBlack1': { - 'BACKGROUND': '#181810', - 'TEXT': '#eeeeee', - 'INPUT': '#e4dcad', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e4dcad', - 'BUTTON': ('#FFFFFF', '#062121'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], - 'DESCRIPTION': ['#000000', '#000000', 'Brown', 'Grey'], - }, - 'DarkGrey5': { - 'BACKGROUND': '#343434', - 'TEXT': '#f3f3f3', - 'INPUT': '#e9dcbe', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e9dcbe', - 'BUTTON': ('#FFFFFF', '#8e8b82'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], - 'DESCRIPTION': ['Grey', 'Brown'], - }, - 'LightBrown12': { - 'BACKGROUND': '#8e8b82', - 'TEXT': '#f3f3f3', - 'INPUT': '#e9dcbe', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e9dcbe', - 'BUTTON': ('#f3f3f3', '#8e8b82'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], - 'DESCRIPTION': ['Grey', 'Brown'], - }, - 'DarkTeal9': { - 'BACKGROUND': '#13445a', - 'TEXT': '#fef4e8', - 'INPUT': '#446878', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#446878', - 'BUTTON': ('#fef4e8', '#446878'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#13445a', '#970747', '#446878', '#fef4e8'], - 'DESCRIPTION': ['Red', 'Grey', 'Blue', 'Wedding', 'Retro'], - }, - 'DarkBlue14': { - 'BACKGROUND': '#21273d', - 'TEXT': '#f1f6f8', - 'INPUT': '#b9d4f1', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#b9d4f1', - 'BUTTON': ('#FFFFFF', '#6a759b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], - 'DESCRIPTION': ['Blue', '#000000', 'Grey', 'Cold', 'Winter'], - }, - 'LightBlue6': { - 'BACKGROUND': '#f1f6f8', - 'TEXT': '#21273d', - 'INPUT': '#6a759b', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#21273d', - 'BUTTON': ('#f1f6f8', '#6a759b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], - 'DESCRIPTION': ['Blue', '#000000', 'Grey', 'Cold', 'Winter'], - }, - 'DarkGreen4': { - 'BACKGROUND': '#044343', - 'TEXT': '#e4e4e4', - 'INPUT': '#045757', - 'TEXT_INPUT': '#e4e4e4', - 'SCROLL': '#045757', - 'BUTTON': ('#e4e4e4', '#045757'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#222222', '#044343', '#045757', '#e4e4e4'], - 'DESCRIPTION': ['#000000', 'Turquoise', 'Grey', 'Dark'], - }, - 'DarkGreen5': { - 'BACKGROUND': '#1b4b36', - 'TEXT': '#e0e7f1', - 'INPUT': '#aebd77', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#aebd77', - 'BUTTON': ('#FFFFFF', '#538f6a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#1b4b36', '#538f6a', '#aebd77', '#e0e7f1'], - 'DESCRIPTION': ['Green', 'Grey'], - }, - 'DarkTeal10': { - 'BACKGROUND': '#0d3446', - 'TEXT': '#d8dfe2', - 'INPUT': '#71adb5', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#71adb5', - 'BUTTON': ('#FFFFFF', '#176d81'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#0d3446', '#176d81', '#71adb5', '#d8dfe2'], - 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter', 'Cold'], - }, - 'DarkGrey6': { - 'BACKGROUND': '#3e3e3e', - 'TEXT': '#ededed', - 'INPUT': '#68868c', - 'TEXT_INPUT': '#ededed', - 'SCROLL': '#68868c', - 'BUTTON': ('#FFFFFF', '#405559'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], - 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], - }, - 'DarkTeal11': { - 'BACKGROUND': '#405559', - 'TEXT': '#ededed', - 'INPUT': '#68868c', - 'TEXT_INPUT': '#ededed', - 'SCROLL': '#68868c', - 'BUTTON': ('#ededed', '#68868c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], - 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], - }, - 'LightBlue7': { - 'BACKGROUND': '#9ed0e0', - 'TEXT': '#19483f', - 'INPUT': '#5c868e', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#19483f', - 'BUTTON': ('#FFFFFF', '#19483f'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#19483f', '#5c868e', '#ff6a38', '#9ed0e0'], - 'DESCRIPTION': ['Orange', 'Blue', 'Turquoise'], - }, - 'LightGreen10': { - 'BACKGROUND': '#d8ebb5', - 'TEXT': '#205d67', - 'INPUT': '#639a67', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#205d67', - 'BUTTON': ('#d8ebb5', '#205d67'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#205d67', '#639a67', '#d9bf77', '#d8ebb5'], - 'DESCRIPTION': ['Blue', 'Green', 'Brown', 'Vintage'], - }, - 'DarkBlue15': { - 'BACKGROUND': '#151680', - 'TEXT': '#f1fea4', - 'INPUT': '#375fc0', - 'TEXT_INPUT': '#f1fea4', - 'SCROLL': '#375fc0', - 'BUTTON': ('#f1fea4', '#1c44ac'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], - 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], - }, - 'DarkBlue16': { - 'BACKGROUND': '#1c44ac', - 'TEXT': '#f1fea4', - 'INPUT': '#375fc0', - 'TEXT_INPUT': '#f1fea4', - 'SCROLL': '#375fc0', - 'BUTTON': ('#f1fea4', '#151680'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], - 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], - }, - 'DarkTeal12': { - 'BACKGROUND': '#004a7c', - 'TEXT': '#fafafa', - 'INPUT': '#e8f1f5', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e8f1f5', - 'BUTTON': ('#fafafa', '#005691'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#004a7c', '#005691', '#e8f1f5', '#fafafa'], - 'DESCRIPTION': ['Grey', 'Blue', 'Cold', 'Winter'], - }, - 'LightBrown13': { - 'BACKGROUND': '#ebf5ee', - 'TEXT': '#921224', - 'INPUT': '#bdc6b8', - 'TEXT_INPUT': '#921224', - 'SCROLL': '#921224', - 'BUTTON': ('#FFFFFF', '#921224'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#921224', '#bdc6b8', '#bce0da', '#ebf5ee'], - 'DESCRIPTION': ['Red', 'Blue', 'Grey', 'Vintage', 'Wedding'], - }, - 'DarkBlue17': { - 'BACKGROUND': '#21294c', - 'TEXT': '#f9f2d7', - 'INPUT': '#f2dea8', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f2dea8', - 'BUTTON': ('#f9f2d7', '#141829'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#141829', '#21294c', '#f2dea8', '#f9f2d7'], - 'DESCRIPTION': ['#000000', 'Blue', 'Yellow'], - }, - 'DarkBrown6': { - 'BACKGROUND': '#785e4d', - 'TEXT': '#f2eee3', - 'INPUT': '#baaf92', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#baaf92', - 'BUTTON': ('#FFFFFF', '#785e4d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#785e4d', '#ff8426', '#baaf92', '#f2eee3'], - 'DESCRIPTION': ['Grey', 'Brown', 'Orange', 'Autumn'], - }, - 'DarkGreen6': { - 'BACKGROUND': '#5c715e', - 'TEXT': '#f2f9f1', - 'INPUT': '#ddeedf', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#ddeedf', - 'BUTTON': ('#f2f9f1', '#5c715e'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#5c715e', '#b6cdbd', '#ddeedf', '#f2f9f1'], - 'DESCRIPTION': ['Grey', 'Green', 'Vintage'], - }, - 'DarkGreen7': { - 'BACKGROUND': '#0C231E', - 'TEXT': '#efbe1c', - 'INPUT': '#153C33', - 'TEXT_INPUT': '#efbe1c', - 'SCROLL': '#153C33', - 'BUTTON': ('#efbe1c', '#153C33'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey7': { - 'BACKGROUND': '#4b586e', - 'TEXT': '#dddddd', - 'INPUT': '#574e6d', - 'TEXT_INPUT': '#dddddd', - 'SCROLL': '#574e6d', - 'BUTTON': ('#dddddd', '#43405d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#43405d', '#4b586e', '#574e6d', '#dddddd'], - 'DESCRIPTION': ['Grey', 'Winter', 'Cold'], - }, - 'DarkRed2': { - 'BACKGROUND': '#ab1212', - 'TEXT': '#f6e4b5', - 'INPUT': '#cd3131', - 'TEXT_INPUT': '#f6e4b5', - 'SCROLL': '#cd3131', - 'BUTTON': ('#f6e4b5', '#ab1212'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#ab1212', '#1fad9f', '#cd3131', '#f6e4b5'], - 'DESCRIPTION': ['Turquoise', 'Red', 'Yellow'], - }, - 'LightGrey6': { - 'BACKGROUND': '#e3e3e3', - 'TEXT': '#233142', - 'INPUT': '#455d7a', - 'TEXT_INPUT': '#e3e3e3', - 'SCROLL': '#233142', - 'BUTTON': ('#e3e3e3', '#455d7a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#233142', '#455d7a', '#f95959', '#e3e3e3'], - 'DESCRIPTION': ['#000000', 'Blue', 'Red', 'Grey'], - }, - 'HotDogStand': { - 'BACKGROUND': 'red', - 'TEXT': 'yellow', - 'INPUT': 'yellow', - 'TEXT_INPUT': '#000000', - 'SCROLL': 'yellow', - 'BUTTON': ('red', 'yellow'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey8': { - 'BACKGROUND': '#19232D', - 'TEXT': '#ffffff', - 'INPUT': '#32414B', - 'TEXT_INPUT': '#ffffff', - 'SCROLL': '#505F69', - 'BUTTON': ('#ffffff', '#32414B'), - 'PROGRESS': ('#505F69', '#32414B'), - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey9': { - 'BACKGROUND': '#36393F', - 'TEXT': '#DCDDDE', - 'INPUT': '#40444B', - 'TEXT_INPUT': '#ffffff', - 'SCROLL': '#202225', - 'BUTTON': ('#202225', '#B9BBBE'), - 'PROGRESS': ('#202225', '#40444B'), - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey10': { - 'BACKGROUND': '#1c1e23', - 'TEXT': '#cccdcf', - 'INPUT': '#272a31', - 'TEXT_INPUT': '#8b9fde', - 'SCROLL': '#313641', - 'BUTTON': ('#f5f5f6', '#2e3d5a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey11': { - 'BACKGROUND': '#1c1e23', - 'TEXT': '#cccdcf', - 'INPUT': '#313641', - 'TEXT_INPUT': '#cccdcf', - 'SCROLL': '#313641', - 'BUTTON': ('#f5f5f6', '#313641'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey12': { - 'BACKGROUND': '#1c1e23', - 'TEXT': '#8b9fde', - 'INPUT': '#313641', - 'TEXT_INPUT': '#8b9fde', - 'SCROLL': '#313641', - 'BUTTON': ('#cccdcf', '#2e3d5a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey13': { - 'BACKGROUND': '#1c1e23', - 'TEXT': '#cccdcf', - 'INPUT': '#272a31', - 'TEXT_INPUT': '#cccdcf', - 'SCROLL': '#313641', - 'BUTTON': ('#8b9fde', '#313641'), - 'PROGRESS': ('#cccdcf', '#272a31'), - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey14': { - 'BACKGROUND': '#24292e', - 'TEXT': '#fafbfc', - 'INPUT': '#1d2125', - 'TEXT_INPUT': '#fafbfc', - 'SCROLL': '#1d2125', - 'BUTTON': ('#fafbfc', '#155398'), - 'PROGRESS': ('#155398', '#1d2125'), - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBrown7': { - 'BACKGROUND': '#2c2417', - 'TEXT': '#baa379', - 'INPUT': '#baa379', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#392e1c', - 'BUTTON': ('#000000', '#baa379'), - 'PROGRESS': ('#baa379', '#453923'), - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'Python': { - 'BACKGROUND': '#3d7aab', - 'TEXT': '#ffde56', - 'INPUT': '#295273', - 'TEXT_INPUT': '#ffde56', - 'SCROLL': '#295273', - 'BUTTON': ('#ffde56', '#295273'), - 'PROGRESS': ('#ffde56', '#295273'), - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, -} - - -def ListOfLookAndFeelValues(): - """ - Get a list of the valid values to pass into your call to change_look_and_feel - :return: List[str] - list of valid string values - """ - return sorted(list(LOOK_AND_FEEL_TABLE.keys())) - - -def theme(new_theme=None): - """ - Sets / Gets the current Theme. If none is specified then returns the current theme. - This call replaces the ChangeLookAndFeel / change_look_and_feel call which only sets the theme. - - :param new_theme: (str) the new theme name to use - :return: (str) the currently selected theme - """ - if new_theme is not None: - change_look_and_feel(new_theme) - return CURRENT_LOOK_AND_FEEL - - -def theme_background_color(color=None): - """ - Sets/Returns the background color currently in use - Used for Windows and containers (Column, Frame, Tab) and tables - - :return: (str) - color string of the background color currently in use - """ - if color is not None: - set_options(background_color=color) - return DEFAULT_BACKGROUND_COLOR - - -def theme_element_background_color(color=None): - """ - Sets/Returns the background color currently in use for all elements except containers - - :return: (str) - color string of the element background color currently in use - """ - if color is not None: - set_options(element_background_color=color) - return DEFAULT_ELEMENT_BACKGROUND_COLOR - - -def theme_text_color(color=None): - """ - Sets/Returns the text color currently in use - - :return: (str) - color string of the text color currently in use - """ - if color is not None: - set_options(text_color=color) - return DEFAULT_TEXT_COLOR - - -def theme_input_background_color(color=None): - """ - Sets/Returns the input element background color currently in use - - :return: (str) - color string of the input element background color currently in use - """ - if color is not None: - set_options(input_elements_background_color=color) - return DEFAULT_INPUT_ELEMENTS_COLOR - - -def theme_input_text_color(color=None): - """ - Sets/Returns the input element entry color (not the text but the thing that's displaying the text) - - :return: (str) - color string of the input element color currently in use - """ - if color is not None: - set_options(input_text_color=color) - return DEFAULT_INPUT_TEXT_COLOR - - -def theme_button_color(color=None): - """ - Sets/Returns the button color currently in use - - :return: Tuple[str, str] - TUPLE with color strings of the button color currently in use (button text color, button background color) - """ - if color is not None: - set_options(button_color=color) - return DEFAULT_BUTTON_COLOR - - -def theme_progress_bar_color(color=None): - """ - Sets/Returns the progress bar colors by the current color theme - - :return: Tuple[str, str] - TUPLE with color strings of the ProgressBar color currently in use(button text color, button background color) - """ - if color is not None: - set_options(progress_meter_color=color) - return DEFAULT_PROGRESS_BAR_COLOR - - -def theme_slider_color(color=None): - """ - Sets/Returns the slider color (used for sliders) - - :return: (str) - color string of the slider color currently in use - """ - if color is not None: - set_options(scrollbar_color=color) - return DEFAULT_SCROLLBAR_COLOR - - -def theme_border_width(border_width=None): - """ - Sets/Returns the border width currently in use - Used by non ttk elements at the moment - - :return: (int) - border width currently in use - """ - if border_width is not None: - set_options(border_width=border_width) - return DEFAULT_BORDER_WIDTH - - -def theme_slider_border_width(border_width=None): - """ - Sets/Returns the slider border width currently in use - - :return: (int) - border width currently in use - """ - if border_width is not None: - set_options(slider_border_width=border_width) - return DEFAULT_SLIDER_BORDER_WIDTH - - -def theme_progress_bar_border_width(border_width=None): - """ - Sets/Returns the progress meter border width currently in use - - :return: (int) - border width currently in use - """ - if border_width is not None: - set_options(progress_meter_border_depth=border_width) - return DEFAULT_PROGRESS_BAR_BORDER_WIDTH - - -def theme_element_text_color(color=None): - """ - Sets/Returns the text color used by elements that have text as part of their display (Tables, Trees and Sliders) - - :return: (str) - color string currently in use - """ - if color is not None: - set_options(element_text_color=color) - return DEFAULT_ELEMENT_TEXT_COLOR - - -def theme_list(): - """ - Returns a sorted list of the currently available color themes - - :return: List[str] - A sorted list of the currently available color themes - """ - return list_of_look_and_feel_values() - - -def theme_add_new(new_theme_name, new_theme_dict): - """ - Add a new theme to the dictionary of themes - - :param new_theme_name: text to display in element - :type new_theme_name: (str) - :param new_theme_dict: text to display in element - :type new_theme_dict: (dict) - """ - global LOOK_AND_FEEL_TABLE - try: - LOOK_AND_FEEL_TABLE[new_theme_name] = new_theme_dict - except Exception as e: - print('Exception during adding new theme {}'.format(e)) - - -def theme_previewer(columns=12): - """ - Show a window with all of the color themes - takes a while so be patient - - :param columns: (int) number of themes in a single row - """ - preview_all_look_and_feel_themes(columns) - - -def ChangeLookAndFeel(index, force=False): - """ - Change the "color scheme" of all future PySimpleGUI Windows. - The scheme are string names that specify a group of colors. Background colors, text colors, button colors. - There are 13 different color settings that are changed at one time using a single call to ChangeLookAndFeel - The look and feel table itself has these indexes into the dictionary LOOK_AND_FEEL_TABLE. - The original list was (prior to a major rework and renaming)... these names still work... - In Nov 2019 a new Theme Formula was devised to make choosing a theme easier: - The "Formula" is: - ["Dark" or "Light"] Color Number - Colors can be Blue Brown Grey Green Purple Red Teal Yellow Black - The number will vary for each pair. There are more DarkGrey entries than there are LightYellow for example. - Default = The default settings (only button color is different than system default) - Default1 = The full system default including the button (everything's gray... how sad... don't be all gray... please....) - :param index: the name of the index into the Look and Feel table (does not have to be exact, can be "fuzzy") - :type index: (str) - :param force: no longer used - :type force: (bool) - """ - global CURRENT_LOOK_AND_FEEL - - # if sys.platform.startswith('darwin') and not force: - # print('*** Changing look and feel is not supported on Mac platform ***') - # return - - theme = index - # normalize available l&f values - lf_values = [item.lower() for item in list_of_look_and_feel_values()] - # option 1 - opt1 = theme.replace(' ', '').lower() - - # option 2 (reverse lookup) - optx = theme.lower().split(' ') - optx.reverse() - opt2 = ''.join(optx) - - # search for valid l&f name - if opt1 in lf_values: - ix = lf_values.index(opt1) - elif opt2 in lf_values: - ix = lf_values.index(opt2) - else: - ix = random.random.randint(0, len(lf_values) - 1) - print('** Warning - {} Theme is not a valid theme. Change your theme call. **'.format(index)) - print('valid values are', list_of_look_and_feel_values()) - print('Instead, please enjoy a random Theme named {}'.format(list_of_look_and_feel_values()[ix])) - - selection = list_of_look_and_feel_values()[ix] - CURRENT_LOOK_AND_FEEL = selection - try: - colors = LOOK_AND_FEEL_TABLE[selection] - - # Color the progress bar using button background and input colors...unless they're the same - if colors['PROGRESS'] != COLOR_SYSTEM_DEFAULT: - if colors['BUTTON'][1] != colors['INPUT'] and colors['BUTTON'][1] != colors['BACKGROUND']: - colors['PROGRESS'] = colors['BUTTON'][1], colors['INPUT'] - else: # if the same, then use text input on top of input color - colors['PROGRESS'] = (colors['TEXT_INPUT'], colors['INPUT']) - else: - colors['PROGRESS'] = DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL - # call to change all the colors - SetOptions( - background_color=colors['BACKGROUND'], - text_element_background_color=colors['BACKGROUND'], - element_background_color=colors['BACKGROUND'], - text_color=colors['TEXT'], - input_elements_background_color=colors['INPUT'], - # button_color=colors['BUTTON'] if not sys.platform.startswith('darwin') else None, - button_color=colors['BUTTON'], - progress_meter_color=colors['PROGRESS'], - border_width=colors['BORDER'], - slider_border_width=colors['SLIDER_DEPTH'], - progress_meter_border_depth=colors['PROGRESS_DEPTH'], - scrollbar_color=(colors['SCROLL']), - element_text_color=colors['TEXT'], - input_text_color=colors['TEXT_INPUT'], - ) - except: # most likely an index out of range - print('** Warning - Theme value not valid. Change your theme call. **') - print('valid values are', list_of_look_and_feel_values()) - - -def preview_all_look_and_feel_themes(columns=12): - """ - Displays a "Quick Reference Window" showing all of the different Look and Feel settings that are available. - They are sorted alphabetically. The legacy color names are mixed in, but otherwise they are sorted into Dark and Light halves - :param columns: (int) The number of themes to display per row - """ - - # Show a "splash" type message so the user doesn't give up waiting - popup_quick_message( - 'Hang on for a moment, this will take a bit to create....', - background_color='red', - text_color='white', - auto_close=True, - non_blocking=True, - ) - - web = False - - win_bg = 'black' - - def sample_layout(): - return [ - [Text('Text element'), InputText('Input data here', size=(10, 1))], - [Button('Ok'), Button('Cancel'), Slider((1, 10), orientation='h', size=(5, 15))], - ] - - layout = [[Text('Here is a complete list of themes', font='Default 18', background_color=win_bg)]] - - names = list_of_look_and_feel_values() - names.sort() - row = [] - for count, theme in enumerate(names): - change_look_and_feel(theme) - if not count % columns: - layout += [row] - row = [] - row += [Frame(theme, sample_layout() if not web else [[T(theme)]] + sample_layout())] - if row: - layout += [row] - - window = Window('Preview of all Look and Feel choices', layout, background_color=win_bg) - window.read() - window.close() - - -# ============================== sprint ======# -# Is identical to the Scrolled Text Box # -# Provides a crude 'print' mechanism but in a # -# GUI environment # -# ============================================# -sprint = ScrolledTextBox - - -# Converts an object's contents into a nice printable string. Great for dumping debug data -def ObjToStringSingleObj(obj): - """ - Dumps an Object's values as a formatted string. Very nicely done. Great way to display an object's member variables in human form - Returns only the top-most object's variables instead of drilling down to dispolay more - :param obj: The object to display - :type obj: (Any) - :return: Formatted output of the object's values - :rtype: (str) - """ - if obj is None: - return 'None' - return str(obj.__class__) + '\n' + '\n'.join((repr(item) + ' = ' + repr(obj.__dict__[item]) for item in sorted(obj.__dict__))) - - -def ObjToString(obj, extra=' '): - """ - Dumps an Object's values as a formatted string. Very nicely done. Great way to display an object's member variables in human form - :param obj: The object to display - :type obj: (Any) - :param extra: extra stuff (Default value = ' ') - :type extra: (str) - :return: Formatted output of the object's values - :rtype: (str) - """ - if obj is None: - return 'None' - return str(obj.__class__) + '\n' + '\n'.join((extra + (str(item) + ' = ' + (ObjToString(obj.__dict__[item], extra + ' ') if hasattr(obj.__dict__[item], '__dict__') else str(obj.__dict__[item]))) for item in sorted(obj.__dict__))) - - -# ------------------------------------------------------------------------------------------------------------------ # -# ===================================== Upper PySimpleGUI ======================================================== # -# Pre-built dialog boxes for all your needs These are the "high level API calls # -# ------------------------------------------------------------------------------------------------------------------ # - -# ----------------------------------- The mighty Popup! ------------------------------------------------------------ # - - -def Popup( - *args, - title=None, - button_color=None, - background_color=None, - text_color=None, - button_type=POPUP_BUTTONS_OK, - auto_close=False, - auto_close_duration=None, - custom_text=(None, None), - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - any_key_closes=False, - image=None, -): - """ - Popup - Display a popup box with as many parms as you wish to include - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param title: Optional title for the window. If none provided, the first arg will be used instead. - :type title: (str) - :param button_color: Color of the buttons shown (text color, button color) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param button_type: NOT USER SET! Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). There are many Popup functions and they call Popup, changing this parameter to get the desired effect. - :type button_type: (int) - :param auto_close: If True the window will automatically close - :type auto_close: (bool) - :param auto_close_duration: time in seconds to keep window open before closing it automatically - :type auto_close_duration: (int) - :param custom_text: A string or pair of strings that contain the text to display on the buttons - :type custom_text: Union[Tuple[str, str], str] - :param non_blocking: If True then will immediately return from the function without waiting for the user's input. - :type non_blocking: (bool) - :param icon: icon to display on the window. Same format as a Window call - :type icon: Union[str, bytes] - :param line_width: Width of lines in characters. Defaults to MESSAGE_BOX_LINE_WIDTH - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location on screen to display the top left corner of window. Defaults to window centered on screen - :type location: Tuple[int, int] - :param any_key_closes: If True then will turn on return_keyboard_events for the window which will cause window to close as soon as any key is pressed. Normally the return key only will close the window. Default is false. - :type any_key_closes: (bool) - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - :return: Returns text of the button that was pressed. None will be returned if user closed window with X - :rtype: Union[str, None] - """ - - if not args: - args_to_print = [''] - else: - args_to_print = args - if line_width != None: - local_line_width = line_width - else: - local_line_width = MESSAGE_BOX_LINE_WIDTH - - _title = title if title is not None else args_to_print[0] - _title = str(_title) - window = Window( - _title, - auto_size_text=True, - background_color=background_color, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - icon=icon, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - return_keyboard_events=any_key_closes, - ) - max_line_total, total_lines = 0, 0 - layout = [[]] - if image is not None: - if isinstance(image, str): - layout += [[Image(filename=image)]] - else: - layout += [[Image(data_base64=image)]] - - for message in args_to_print: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - if message.count('\n'): # if there are line breaks, then wrap each segment separately - # message_wrapped = message # used to just do this, but now breaking into smaller pieces - message_wrapped = '' - msg_list = message.split('\n') # break into segments that will each be wrapped - message_wrapped = '\n'.join([textwrap.fill(msg, local_line_width) for msg in msg_list]) - else: - message_wrapped = textwrap.fill(message, local_line_width) - message_wrapped_lines = message_wrapped.count('\n') + 1 - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = min(longest_line_len, local_line_width) - max_line_total = max(max_line_total, width_used) - # height = _GetNumLinesNeeded(message, width_used) - height = message_wrapped_lines - window.AddRow(Text(message_wrapped, auto_size_text=True, text_color=text_color, background_color=background_color)) - total_lines += height - - # if total_lines < 3: - # layout.append([Text('',text_color=text_color, background_color=background_color)]) - if non_blocking: - PopupButton = DummyButton # important to use or else button will close other windows too! - else: - PopupButton = Button - # show either an OK or Yes/No depending on paramater - # show either an OK or Yes/No depending on paramater - if custom_text != (None, None): - if type(custom_text) is not tuple: - layout.append([PopupButton(custom_text, button_color=button_color, focus=True, bind_return_key=True)]) - elif custom_text[1] is None: - layout.append([PopupButton(custom_text[0], button_color=button_color, focus=True, bind_return_key=True)]) - else: - layout.append( - [ - PopupButton(custom_text[0], button_color=button_color, focus=True, bind_return_key=True), - PopupButton(custom_text[1], button_color=button_color), - Stretch(), - ] - ) - elif button_type is POPUP_BUTTONS_YES_NO: - layout.append( - [ - PopupButton('Yes', button_color=button_color, focus=True, bind_return_key=True, size=(60, 20)), - PopupButton('No', button_color=button_color, size=(60, 20)), - ] - ) - elif button_type is POPUP_BUTTONS_CANCELLED: - layout.append([PopupButton('Cancelled', button_color=button_color, focus=True, bind_return_key=True), Stretch()]) - elif button_type is POPUP_BUTTONS_ERROR: - layout.append( - [ - PopupButton('Error', size=(60, 20), button_color=button_color, focus=True, bind_return_key=True), - Stretch(), - ] - ) - elif button_type is POPUP_BUTTONS_OK_CANCEL: - layout.append( - [ - PopupButton('OK', size=(60, 20), button_color=button_color, focus=True, bind_return_key=True), - PopupButton('Cancel', size=(60, 20), button_color=button_color), - Stretch(), - ] - ) - elif button_type is POPUP_BUTTONS_NO_BUTTONS: - pass - else: - layout.append([PopupButton('OK', size=(60, 20), button_color=button_color, focus=True, bind_return_key=True), Stretch()]) - - window.Layout(layout) - if non_blocking: - button, values = window.Read(timeout=0) - Window.active_popups[window] = title - else: - button, values = window.Read() - window.close() - - return button - - -# ============================== MsgBox============# -# Lazy function. Same as calling Popup with parms # -# This function WILL Disappear perhaps today # -# ==================================================# -# MsgBox is the legacy call and should not be used any longer -def MsgBox(*args): - raise DeprecationWarning('MsgBox is no longer supported... change your call to Popup') - - -# --------------------------- PopupNoButtons --------------------------- -def PopupNoButtons( - *args, - title=None, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Show a Popup but without any buttons - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - """ - Popup( - *args, - title=title, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=POPUP_BUTTONS_NO_BUTTONS, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -# --------------------------- PopupNonBlocking --------------------------- -def PopupNonBlocking( - *args, - title=None, - button_type=POPUP_BUTTONS_OK, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=True, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Show Popup box and immediately return (does not block) - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param title: Title to display in the window. - :type title: (str) - :param button_type: - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - """ - Popup( - *args, - title=title, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -PopupNoWait = PopupNonBlocking - - -# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup --------------------------- -def PopupQuick( - *args, - title=None, - button_type=POPUP_BUTTONS_OK, - button_color=None, - background_color=None, - text_color=None, - auto_close=True, - auto_close_duration=2, - non_blocking=True, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Show Popup box that doesn't block and closes itself - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param title: Title to display in the window. - :type title: (str) - :param button_type: Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). - :type button_type: (int) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - """ - Popup( - *args, - title=title, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup with no titlebar and no buttons --------------------------- -def PopupQuickMessage( - *args, - title=None, - button_type=POPUP_BUTTONS_NO_BUTTONS, - button_color=None, - background_color=None, - text_color=None, - auto_close=True, - auto_close_duration=3, - non_blocking=True, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=True, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Show Popup box that doesn't block and closes itself - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param title: Title to display in the window. - :type title: (str) - :param button_type: Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). - :type button_type: (int) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - """ - Popup( - *args, - title=title, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -# --------------------------- PopupNoTitlebar --------------------------- -def PopupNoTitlebar( - *args, - title=None, - button_type=POPUP_BUTTONS_OK, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - grab_anywhere=True, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Display a Popup without a titlebar. Enables grab anywhere so you can move it - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param title: Title to display in the window. - :type title: (str) - :param button_type: Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). - :type button_type: (int) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - """ - Popup( - *args, - title=title, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=True, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -PopupNoFrame = PopupNoTitlebar -PopupNoBorder = PopupNoTitlebar -PopupAnnoying = PopupNoTitlebar - - -# --------------------------- PopupAutoClose --------------------------- -def PopupAutoClose( - *args, - title=None, - button_type=POPUP_BUTTONS_OK, - button_color=None, - background_color=None, - text_color=None, - auto_close=True, - auto_close_duration=DEFAULT_AUTOCLOSE_TIME, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Popup that closes itself after some time period - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param title: Title to display in the window. - :type title: (str) - :param button_type: Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). - :type button_type: (int) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - """ - Popup( - *args, - title=title, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -PopupTimed = PopupAutoClose - - -# --------------------------- PopupError --------------------------- -def PopupError( - *args, - title=None, - button_color=(None, None), - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Popup with colored button and 'Error' as button text - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param title: Title to display in the window. - :type title: (str) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - """ - tbutton_color = DEFAULT_ERROR_BUTTON_COLOR if button_color == (None, None) else button_color - Popup( - *args, - title=title, - button_type=POPUP_BUTTONS_ERROR, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=tbutton_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -# --------------------------- PopupCancel --------------------------- -def PopupCancel( - *args, - title=None, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Display Popup with "cancelled" button text - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param title: Title to display in the window. - :type title: (str) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - """ - Popup( - *args, - title=title, - button_type=POPUP_BUTTONS_CANCELLED, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -# --------------------------- PopupOK --------------------------- -def PopupOK( - *args, - title=None, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Display Popup with OK button only - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - """ - Popup( - *args, - title=title, - button_type=POPUP_BUTTONS_OK, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -# --------------------------- PopupOKCancel --------------------------- -def PopupOKCancel( - *args, - title=None, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Display popup with OK and Cancel buttons - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - :return: OK, Cancel or None - :rtype: Union[str, None] - """ - return Popup( - *args, - title=title, - button_type=POPUP_BUTTONS_OK_CANCEL, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -# --------------------------- PopupYesNo --------------------------- -def PopupYesNo( - *args, - title=None, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Display Popup with Yes and No buttons - - :param *args: Variable number of your arguments. Load up the call with stuff to see! - :type *args: (Any) - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param auto_close: if True window will close itself - :type auto_close: (bool) - :param auto_close_duration: Older versions only accept int. Time in seconds until window will close - :type auto_close_duration: Union[int, float] - :param non_blocking: if True the call will immediately return rather than waiting on user input - :type non_blocking: (bool) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param line_width: Width of lines in characters - :type line_width: (int) - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - :return: Yes, No or None - :rtype: Union[str, None] - """ - return Popup( - *args, - title=title, - button_type=POPUP_BUTTONS_YES_NO, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - image=image, - ) - - -############################################################################## -# The PopupGet_____ functions - Will return user input # -############################################################################## - -# --------------------------- PopupGetFolder --------------------------- - - -def PopupGetFolder( - message, - title=None, - default_path='', - no_window=False, - size=(None, None), - button_color=None, - background_color=None, - text_color=None, - icon=DEFAULT_WINDOW_ICON, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - initial_folder=None, - image=None, -): - """ - Display popup with text entry field and browse button. Browse for folder - :param message: message displayed to user - :type message: (str) - :param title: Window title - :type title: (str) - :param default_path: path to display to user as starting point (filled into the input field) - :type default_path: (str) - :param no_window: if True, no PySimpleGUI window will be shown. Instead just the tkinter dialog is shown - :type no_window: (bool) - :param size: (width, height) of the InputText Element - :type size: Tuple[int, int] - :param button_color: button color (foreground, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param initial_folder: location in filesystem to begin browsing - :type initial_folder: (str) - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - :return: Contents of text field. None if closed using X or cancelled - :rtype: Union[str, None] - """ - - if no_window: - if Window.QTApplication is None: - Window.QTApplication = QApplication(sys.argv) - - folder_name = QFileDialog.getExistingDirectory(dir=initial_folder) - return folder_name - - if image is not None: - if isinstance(image, str): - layout = [[Image(filename=image)]] - else: - layout = [[Image(data_base64=image)]] - else: - layout = [[]] - layout += [ - [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], - [InputText(default_text=default_path, size=size, key='_INPUT_'), FolderBrowse(initial_folder=initial_folder)], - [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], - ] - - _title = title if title is not None else message - window = Window( - title=_title, - layout=layout, - icon=icon, - auto_size_text=True, - button_color=button_color, - background_color=background_color, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - button, values = window.Read() - window.close() - if button != 'Ok': - return None - else: - path = values['_INPUT_'] - return path - - -# --------------------------- PopupGetFile --------------------------- - - -def PopupGetFile( - message, - title=None, - default_path='', - default_extension='', - save_as=False, - file_types=(('ALL Files', '*'),), - no_window=False, - size=(None, None), - button_color=None, - background_color=None, - text_color=None, - icon=DEFAULT_WINDOW_ICON, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - initial_folder=None, - image=None, -): - """ - Display popup with text entry field and browse button. Browse for file - - :param message: message displayed to user - :type message: (str) - :param title: Window title - :type title: (str) - :param default_path: path to display to user as starting point (filled into the input field) - :type default_path: (str) - :param default_extension: If no extension entered by user, add this to filename (only used in saveas dialogs) - :type default_extension: (str) - :param save_as: if True, the "save as" dialog is shown which will verify before overwriting - :type save_as: (bool) - :param multiple_files: if True, then allows multiple files to be selected that are returned with ';' between each filename - :type multiple_files: (bool) - :param file_types: List of extensions to show using wildcards. All files (the default) = (("ALL Files", "*.*"),) - :type file_types: Tuple[Tuple[str,str]] - :param no_window: if True, no PySimpleGUI window will be shown. Instead just the tkinter dialog is shown - :type no_window: (bool) - :param size: (width, height) of the InputText Element - :type size: Tuple[int, int] - :param button_color: Color of the button (text, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: Location of upper left corner of the window - :type location: Tuple[int, int] - :param initial_folder: location in filesystem to begin browsing - :type initial_folder: (str) - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - :return: string representing the path chosen, None if cancelled or window closed with X - :rtype: Union[str, None] - """ - - if no_window: - if Window.QTApplication is None: - Window.QTApplication = QApplication(sys.argv) - - if save_as: - qt_types = convert_tkinter_filetypes_to_qt(file_types) - filename = QFileDialog.getSaveFileName(dir=initial_folder, filter=qt_types) - else: - qt_types = convert_tkinter_filetypes_to_qt(file_types) - filename = QFileDialog.getOpenFileName(dir=initial_folder, filter=qt_types) - return filename[0] - - browse_button = SaveAs(file_types=file_types, initial_folder=initial_folder) if save_as else FileBrowse(file_types=file_types, initial_folder=initial_folder) - - if image is not None: - if isinstance(image, str): - layout = [[Image(filename=image)]] - else: - layout = [[Image(data_base64=image)]] - else: - layout = [[]] - - layout += [ - [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], - [InputText(default_text=default_path, size=(30, 1), key='_INPUT_'), browse_button], - [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], - ] - - _title = title if title is not None else message - - window = Window( - title=_title, - layout=layout, - icon=icon, - auto_size_text=True, - button_color=button_color, - font=font, - background_color=background_color, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - button, values = window.Read() - window.close() - if button != 'Ok': - return None - else: - path = values['_INPUT_'] - return path - - -# --------------------------- PopupGetText --------------------------- - - -def PopupGetText( - message, - title=None, - default_text='', - password_char='', - size=(None, None), - button_color=None, - background_color=None, - text_color=None, - icon=DEFAULT_WINDOW_ICON, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - image=None, -): - """ - Display Popup with text entry field - :param message: message displayed to user - :type message: (str) - :param title: Window title - :type title: (str) - :param default_text: default value to put into input area - :type default_text: (str) - :param password_char: character to be shown instead of actually typed characters - :type password_char: (str) - :param size: (width, height) of the InputText Element - :type size: Tuple[int, int] - :param button_color: Color of the button (text, background) - :type button_color: Tuple[str, str] - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param icon: filename or base64 string to be used for the window's icon - :type icon: Union[bytes, str] - :param font: specifies the font family, size, etc - :type font: Union[str, Tuple[str, int]] - :param no_titlebar: If True no titlebar will be shown - :type no_titlebar: (bool) - :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) - :type grab_anywhere: (bool) - :param keep_on_top: If True the window will remain above all current windows - :type keep_on_top: (bool) - :param location: (x,y) Location on screen to display the upper left corner of window - :type location: Tuple[int, int] - :param image: Image to include at the top of the popup window - :type image: (str) or (bytes) - :return: Text entered or None if window was closed - :rtype: Union[str, None] - """ - - if image is not None: - if isinstance(image, str): - layout = [[Image(filename=image)]] - else: - layout = [[Image(data_base64=image)]] - else: - layout = [[]] - - layout += [ - [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color, font=font)], - [InputText(default_text=default_text, size=size, password_char=password_char, key='_INPUT_')], - [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], - ] - - _title = title if title is not None else message - - window = Window( - title=_title, - layout=layout, - icon=icon, - auto_size_text=True, - button_color=button_color, - no_titlebar=no_titlebar, - background_color=background_color, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - button, values = window.Read() - window.close() - - if button != 'Ok': - return None - else: - return values['_INPUT_'] - - -# --------------------------------------- a few icons in base64 --------------------------------------- - - -ICON_BASE64_BLOB_PALM = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNjo1ODowNy0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTY6NTg6MDctMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE2OjU4OjA3LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDo4NWFmYzM1My1hMzMyLWFlNGQtOGE0ZC1lYTExOTk1MDU5M2E8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDpiMWM2N2U0Zi1iY2E2LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDphNzVkMDNmMS1lNjE3LWM0NDktYjYxNy0zZjc4YWFjMDBjNTQ8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6YTc1ZDAzZjEtZTYxNy1jNDQ5LWI2MTctM2Y3OGFhYzAwYzU0PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE2OjU4OjA3LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjg1YWZjMzUzLWEzMzItYWU0ZC04YTRkLWVhMTE5OTUwNTkzYTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNjo1ODowNy0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjc4PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjcxPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz62g3pFAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAABdqSURBVHja7Jz3c1xXlt+/5977cucANCKRmINAShoqzYxGOzsu79Ta/g/8L/kvsctll721nl1b3t1ZaShRFClRgkiQIAgih250eOkG/9ANEgRBECABSa7Sq+p6XQQL7+LzTj73XDLG4Jfr6Jf4uSxk+tp1Osr/v3Xz85/0jdNPJXF7QNFL7vtdZr/7jw3yRwe3CxgBYLvuvHff+U57AJreRwHQvc/Od7Pr/qNA/FHA7QOLARCcGcvi2rGFcYUwnmtpx3OUF7jacS3NLaE5Z4YBQCKZjlOWhDFL2xEP45RFiaQokRSnkkVKU6INyd0wTxLgiYPrQdsBZtlC24Gr/Jwvc/lAFfK+rOZ9Wcr6shy4qpBxVTbwlO9Yxu6BEzCQiWRIUpaECQvbIe+0It5sRXy9GfKNRlusNjpic7stGs1QtDoJ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' - -ICON_BASE64_BLOB_PAT = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNjo1OTowNi0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTY6NTk6MDYtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE2OjU5OjA2LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDpiMWNmNDU1YS04Yjk4LTFmNDYtYmJlNi1iNmY1ZGMxMGYwYWE8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkM2FmN2UyNi1iY2E2LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDo0YjE4ZTE1Mi0yNWMzLWY3NDAtOWU4Yi0zYmRjNjFjMjI5Njk8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6NGIxOGUxNTItMjVjMy1mNzQwLTllOGItM2JkYzYxYzIyOTY5PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE2OjU5OjA2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmIxY2Y0NTVhLThiOTgtMWY0Ni1iYmU2LWI2ZjVkYzEwZjBhYTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNjo1OTowNi0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjgxPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjc1PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz6vCsFxAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAABGmSURBVHja7Jx5cJzlfce/z/Meex+SdqWVVpdZWbIlY1lWjIhx42ZIYopT7jFgDCZOAy2QozChSWaShjQpAylkmhCaGHAHN5hjCuWoaaEl0A50SsHGMpaNbQlfyJZk3Vrtvsdz9I93dfpaW7vGJnpmdnTs7rvv+3l/9+/3LJFSYnbNbNFZBLMQZyHOQpxdsxBnIc5CnIU4u46z1PP55Fdf0eiPhXlzUUA0+D2yRFWEX0rCLIbRlEF6+5NKW++w0rbxxdbefJ4HOV8zlrvXNly6amnyPwu8Al5dQFMASiQkCIQAmABMTjGcIjjUp7a2HnQ9+qNf71o/CzGz/uKGhsbLGlNPLptvNuqKhEIlKJl4XgKQEhCSwOZAyqToH6U41Kft3nHItWHHQdf6jf+ybfAPWp3nRO2VF1ZajUG3cCSBnOiV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' - -ICON_BASE64_BLOB_THINK = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNzowNjozMi0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTc6MDY6MzItMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE3OjA2OjMyLTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDpjZGY4YTM3NS05ODVkLTdhNGQtOTU5OS1iOGJmYjZiZGMwYmI8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkZWE3YjA5Yi1iY2E3LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDo5YjY3OTMyNi04MjExLTZjNDgtYjk0NS00NzY2MzI1NDU3M2Y8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6OWI2NzkzMjYtODIxMS02YzQ4LWI5NDUtNDc2NjMyNTQ1NzNmPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE3OjA2OjMyLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmNkZjhhMzc1LTk4NWQtN2E0ZC05NTk5LWI4YmZiNmJkYzBiYjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNzowNjozMi0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjU0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUyPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7BeLFDAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAArWSURBVHja7FppcFRVFv7ue6/3nXQ20kmAkJgQkjSGpKEhQiEIgkZQQBjEQXRwqUHU2YxDUaWjjFVayhQz4zbMoOWCzjC4MCAG2Q0DhBBMgjEGQkJIyEaW3vu9d+/8CGBQknSHBEeLW9U/Xte7957vnnO++91zH2GM4afYOPxE208WmDAUg86eke0cGS2lJ0RK6ZEmOcGip7E6NTMrBaYCgKBEAh4/6Wh3c40tnXxdXYtQUdMkVGwrPFo0WDaQwcqxSc6cJPuowIwpYwOLUm1Bp8XAFAaNDJXAIAgEHLl8HsoIJIkhIBG4fDzaXUSsrFcW7S1XbSo9pSr8oujIyR8U2I3ZudHjkoK3LJvuWpuRGLRp1Qz8AANcpoDXT1BWq6zfuNPw1LGTys9Kjh5uuubApuaNT192s3vtgjxPvlo5uOzq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' - -ICON_BASE64_BLOB_THINK2 = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNzowOTozMy0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTc6MDk6MzMtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE3OjA5OjMzLTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDpiYzcxZTAzMC1hY2UwLWFlNGMtYTI2YS1hNGJlM2E4NDNlMDA8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDo0OWE4ZGNmZC1iY2E4LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDpjMWE2NTcxYS1lNGJlLTczNDMtYjAzNy1lYmE3NmRjMTA3YWI8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6YzFhNjU3MWEtZTRiZS03MzQzLWIwMzctZWJhNzZkYzEwN2FiPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE3OjA5OjMzLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmJjNzFlMDMwLWFjZTAtYWU0Yy1hMjZhLWE0YmUzYTg0M2UwMDwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNzowOTozMy0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjU4PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjU1PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz5uOveQAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAqxSURBVHja7Fp5VJTXFf+9b5kFBoYdDciwiMhe0xhFwYQYTdUTl7hErVWJWYptNGoalwQ92mOiydGqaRajiWarRk1jbIItjUHEiNYYRKKgMiMDiCDrMGyzfN/rH+PgAMMyAybmxHvOd2DuvPvN7/fu/d69776PUErxaxAGvxK5R/Qe0XtE7xG9K4S7Uzde9WxsRKi/OSJsgGmom5y6dTe2vJYtvVHHlmpvcppNu/Iv3wk8pD/z6MqnY8Of+V3jclepqFDIqLuLTJzsiH1zK3OkyUAa8zSSs99ekH29eXf+1buKaMqM3yiWTdWtj1aZhgFI7idsmZdK+NytXyjX7Tl0vvFnJ7r/tYj5MxObFvYjwU6ED5503Tt79eWPfhaiqb+P90md0LA6MsgUzxCMvb28SQH3kSCyIEDiDar7HtD/DxCN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' - -ICON_BASE64_BROW = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNjo1MzowNC0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTY6NTM6MDQtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE2OjUzOjA0LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDo5ZGMyYTFmMC02ZWJhLWQ3NDYtOGIzMC1jYzYwMWM0ZGIzMzE8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDplNjE1MzI2Zi1iY2E1LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDpiMzM5ZDA0Mi1mNGFkLTQ5NDUtOTBiZS1hZTg0ZTE2ZGNiZDI8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6YjMzOWQwNDItZjRhZC00OTQ1LTkwYmUtYWU4NGUxNmRjYmQyPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE2OjUzOjA0LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjlkYzJhMWYwLTZlYmEtZDc0Ni04YjMwLWNjNjAxYzRkYjMzMTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNjo1MzowNC0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjUzPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUzPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz6xTRINAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAeISURBVHja3Jp7UFTXHce/d3fZZR8sIPJQeSwghFeAsTVBIriWphkMGB9kGmOYaXWa1mQm0VJjCGGmDzdoUtT+0w7Q0GbUkNYHhBUz49iGRwKkolVQoKTA7gIqILIsu3ff9/YP0x3JPi+5IPj7a/fc8zt7Pvs753d/v3N+BE3TeNyEg8dQHksoHtsDHnw1NzEznp8pW8WVRYfyogJEhFTsT4gAwGCiyVmS1mkmbSOqO3ZVn9rae/iPLTfZngPBxp46UiLPKtggLEyV8VIIguAA2OqjaiNN09StYWvvhU6T8u3K5s5HDvXRe3k7ijaJdor9CQkDELeARjNFnm8z1e8+dPnviw71wVubN762VbJPLGQFxgmONNFkdZOhav97/2xeFKj++vyjSdF+SQsA4wTXr7b2J+347NCCQe3fkytT7A1U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' - -ICON_BASE64_BLOB_HEADACHE = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNjo1NToyNi0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTY6NTU6MjYtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE2OjU1OjI2LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDoxYzUxOTkzZi05MjNiLTM2NGItYWY5ZS1mNGE3MzViYjgxYTI8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1MDgwYWJhOC1iY2E2LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDo3YWMwMzdjMC03ZmMzLWNhNDgtYmVlYi1lM2Q0ZDJjODJiNTU8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6N2FjMDM3YzAtN2ZjMy1jYTQ4LWJlZWItZTNkNGQyYzgyYjU1PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE2OjU1OjI2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjFjNTE5OTNmLTkyM2ItMzY0Yi1hZjllLWY0YTczNWJiODFhMjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNjo1NToyNi0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjcxPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjcxPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz5o6m+QAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAABbGSURBVHja7HxZcGTXed73n3Puvb13oxtrYx1gMJjBLMTOWUiKiiRGcuxS2Y7K5bhsObEdOXmIk5TzkJQqSfkpecxTqpyUK3mwrUiqKHbJkkxRIk1KwyFnBrNzFgwGGKyDrbH0fu9Z8nDRBIboBockhpJcc6pQ/dIXOOe7//J93zkHZIzBs1F9sGcQPAPnGTjPwHkGzs/HED/rCXzmxRFyLEBwQ4IbWBzEyIARiAjQxv9RiozSgNRkPAnjSYKnCD89f/GptVv6NFv50PAYBWzDGuNKNCRUMBnTibqwakpETFs4oJtDjmkJOSZhMRMRDEFi4FoDnoJyPSoWPcoVXbaWLdJytsDmt4psPpNlc2ubbHNti7sbeabGL79rfmHAGR4Zo5BjWGeTDHQ2ynRbvRxJRfVnE2E1Gg2ZdNjRkYBtLEsY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' - -ICON_BASE64_LEGO_THINK = b'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/GMzLdsAADpEaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNzoxNTozNC0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTc6MTU6MzQtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE3OjE1OjM0LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDpkYzQ2ZDAwNS1hY2U0LTkwNDUtODUzMC1iY2Y4Zjc3Y2U2NjA8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDoyM2FiN2M2NS1iY2E5LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDowNDRhZDA4Zi03ZmQ0LWMwNGItYmY1Yy01MTI4NzQzMjcyNTA8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6MDQ0YWQwOGYtN2ZkNC1jMDRiLWJmNWMtNTEyODc0MzI3MjUwPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE3OjE1OjM0LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmRjNDZkMDA1LWFjZTQtOTA0NS04NTMwLWJjZjhmNzdjZTY2MDwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNzoxNTozNC0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjEwMzwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4xMTI8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PnSBBO0AAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAADbNJREFUeNrsnXuMXFUdxz/nznNn3y92u215s9tSrEXEF1BwqdgVJchDJSKaYKJEjZoYgxhEJSYSefhIJBgSjDS8o/KyJkClLSpt0ZRKlb62tLYWumz3PTu7M/ce//idW/Yuu8PM7szuLT2/ZHP23jn3zp3z+33P73nOVVprLIWTHDsEljmWLHMscyxZ5liyzAkxRcP2QD/65kXOLb943tv6aJcCVG1NBIBkTAEQi4jpH3Pk2FFy7Jjj6cjzNID2tPTLyjFZV44zWTkeGHIV4Dy+cdQF1C2/eN6br7FQYfZztj7adXVtTSQJuMmYcgE3FtHSOsoDco7SHuA5jsqZy1zAm/Tnep7WgOtppQGd9XTOMMcFdCYr9xkYcvWKq9buC8PvDy1z9EufigA7gYVmgPWEwc6BmnisDVP0pL7+OT2BaUirveD9yALjwDBwtXr/k+N2WstPcSBRLv5Pc94132mZkwfUCnCUKuv9384xTRRQYRiBMFtrEaDamtLhpBhQaZkT0nntePfDrBNqmWPJMscyx5JljiXLHMsc4M6bOouKRig1nQ9fZvt9Bt97502d8WMdOe67WMizZRGUckal77q5M3LNRysaABpSTiOwLxZTFUCVUlMGNPeYcKQfGRicp8GuDbjCU5DWEs3O5vQ40HxkxOsDsq2dT5dsQMsd+Hw/cBtwojmOmIGvJdwR8YEC+mgkcn0Q2Ab8C9h6182dm75967qDoUTOr394sQK4vqtyCfCzRJRVFBL2NxKazUnKZXxcZsGcyVj6Txk1Gc9YVGbkeCyST8Dnio4m9cZyvAh8LfHBJ7aHVeecDfwG6KJ8+ZiwGVZR81tXAvff/cPOc0KDnK9ce0HknjUb3SPrP1kN3NpQ5Xw1H2P87+0dGgPg37uOAHDgkMwovX1pAEZGxg1ypH8yEQOgrq4CgEWtNQAsPaMRgLbGlIyWml8sHRn2ngE+03DhU/3zjpx71mx0v3LtBQ5wHnDZcYKYfPQR4EtmTOYXOZse6lLAaR84PfZzoEup/IwfTIv1ed+j2wDY3X0YgLExqdPI5Sb7H4IEU0WDI6qGZEL+qa+XvNw3v3wuAM01yXnljBnWVzbvzl7+wc+t3TPfOicGXAV81EYejlI7cPWmh7pmNB4lM2fPbY+dCKxSmlQh/SPG6qqqktmvtlpcm/d8uBWAFWdJ29okt4sa62wwLTpoT3cfAC9uOQBA2iBOhSP970cZ4ue2xy4HHgT2zSdyXp/gz1h6i04D3pxXneNtvawBeNVxaS7mukwuWFCZiBtl4noEHBw1tWh65vyY8YsqDMLUNNZasb9XzdLq0w5ZoE2d/cSb84mcGqDOAmVKXTyjcSldCEVTOZP7JaOT5MOvYTaH3YeGAog66YRKA3mju0ybMpGCaZEdUQEr8FDvCAD9A2OBfnW1ogMXNMr3xMx15PREwBYzLjMe51IiJ0FIivHeLVTK4GNJGJ3Jiu54+E87Adj4V3ERHEduf9knzgRg9fknyQ94h9UF+w8LQv64dodEInYYf2o8ayIJUwKXRFwiEUs6TgDgovNOluMTJWCdjEfmJCZk6ThATmns8b5RALb8Q9yCiFEqWgui/rZZzn9oxQIAmmqmiRIZROzdL7G6nbvfMH6VJC1TWiR/ZDgTHJConK+qlvtu//f/ANixU65fvapDENx5akD3WeRY5MwPaTPZv9kvkjw6mjWSHHBr6DPR6jGjm3y35W1WlDn/geUtYuUtPl+sQHP+QRPTm4ycnLHKrr5iuSDXnH9u/WsANNSnCHxxGW0gixyLnEJccePJVsYNYny5CUYQUhWxwOfv5HdUHPWPqgB4Y1D8miMGgZPJNZGJ4UFB1MoVbQCs6GgyHXSZ8WKRY5FTFHAMBBY0SoZz6ZJWYy0dCny+4r0LBWEGQcXe34+GJxNRo9um7l9pEOwjU7lzv3bWIscip3CqMjUCX7zyPQBse2+b8dhFdyw7tT6gS4qlmqT85DPOEM9/y5YRY9wJMlpa60RHLagJIG4+yCLHIqcY3SBtvfHkVy5vDVhzs5XkhIleX7m6XQZAiXz2D4jyWd15GgDNNfF5HwuLnOMLOTJ3e8YM8kbMnO5KIsWpFH8jUlXYKnbllGfOr0uJbrvuyjODH3hz58dY5FjkgJdOuwDj3ZJ/cYcHfZfbAMoPgok8JFdItWqkIjWvA6C88G7MVErk5KyshxA5a7+72FnZ1ie7OymVn+EmAZLt3i3IWbbccqFczFn73cXNwNeBL2NrCMLBnE03tyig7oKFozcA14JqKYg5Rve4I1JV441JlNhJFFb3rtUkh+htyDStHz0uf9ollMiJAp82iFlkURMC5rz0gxMSgD6zcXQRcJmSnQSLZ4znGX8oXRBy/BVuL74sufxN/5SVfeNjfsZUVF1jg1h/S9ol/3LWUilArfbr2nR+4B3r1pp/zUXAOdZXCgFyfnrt6QrgG+eOpoDLga8CbbP2jwqsXR7NiKX+j22CmL2vHZ7oNh2lvft6APjb5r3iTxlEnnfeKQB0fngxAE2muibiqGMfOTeu2a0NMy8BbkTWfVrUhAE5W3/UHFl9BvVamHOqmiVjRrIisbXJwiIElRXyqB+/UKLGZ7ZLPiZu8jp+7j9t1u8cPDwMQHe3FPev+8urALz8siDv0kuk/uxDpjonFnGOXeQYRlYBDVamQ4acxqQLUKWgfjZm81DG7DNQWwdAcyoZsKKmlSJjXi05Wa5bckpdPjeKjFmv02dWYz+7USpFN7ywC4DnNkiEYplZhd1QFT/2mPPZjy1XALevIgc0Aq3WpwkJcx5+ZpsG2H9bA0gBZEUxX+BL8nBGJLk3IzPp6Ss7gh58saTzBwj8GoOKuDzuFy5fKn7PEvF7/IrS6orw7vJSrBZU7zwBWZpznTOWUyDll6oYxAxImofBrEhy2/uWAW+tf5kr8vM2Z7c3BiMEIRY1i5x3A3IGjK5orfStp/yI6RkUj94YSzQtEw+9dkFzcU+og7tGzbb6RjG3iPGkdmIsMgfIcaylVjTlgP6yIufAUAIg2dGQmSSCQq6/vmbIDSCmsV1WgLW0nzzVZW8j10SrRwYlWt1/qBeA+gWiK6rqqgq6z3yRv89BNpcF8IaGc7uAocYyI8cBWrC7QRVD+4E7ZipLBSMn59ICdCgVXDDjF9+/aXTM4Kh8XHuSrAZobpcdVzyzAYATiUypO8az8vmB7a8BcHj3fpHAjGRKe+pkFfOyi6VqJ1mRCAVCPM81rclPuUePs8D6P/49+ySQu35V+ZGzHjhgLbaCqAdYA/Rcf/MGXTbk/O6GRarzxIHVwKc9rU8WTonk9/T7VpmZaw2u3tgveZXqhSYT2VQnH2TFM1cmCpw2umXvFtknYKRvIGCl+QjLDEmUufe/ksdZ2L64rIh4q/WCyDCt9vK+BDGz44B+AHhhpowpiDm/u2FRHVIr8HWgA9nLxVps01MaeBj42Ypr1mVmZfZPt4vSCzc2RYDWpU2j3wOuBJqAqL9jxrCJLh8ZcY2VRQBB/m39tqpB1rukTDs2JIgZ7OkLdpxWpKWpXyx5nFPPkdhcJFacB6En+0960ieT/CoK9K+0xgVe39ejHwFuv/2xzKF7H/jrrKb/fMipAr6I7D7YYgExvZ+J7EP9EvBr4JklVzw3cu8VZYwQdDRmfozUCjRP5c8Mjgb3ARjP6SkB4AvcSJ/UTg8bnVL0joKm++iArFrI5XzdVeIZ1jxwnru6QCab0zEg/b9evQF4ZNMOdzOw57qb1pfsjbz5kPN5JOtp9UtwVtwC/ArZv7MX+AOyKzvX3bS+pFZsPubEA6EoP8NotsAYd4NT9bhbKABmx+tYTJXkPsVbcTIB/OeAdyuw9n3XrNMYC2liW0qy1TPF0RFgs8+YclPBEQJfd4yOB5/Lm2T1lKuS0o+C1zTIijgnOtdypQFig2k9Z295t8gpjlLMYWyx6AR6zE9gjs3N2kkfMZUVZh+0ZtmHwI/RzSluIKYhZZETXoqHFjnJqDISLa2/XkaVOBbq664KY51VmjxOdZupAXDUbJCrkSSYOwkbymzlodDKAZRrlGpWKlTddGbu9m6IHiPS2jXh/3QJUD9mfJQhw5Uu0/oSlpvA9wzyhinX+H2vh5Y5jr/rUlwFrLeYuVN2lq/NO1p3ZhCT8SIekD77Oz0byjgO971TB79Y79LOuZPIsCOnB9g6l9IaJioeOUa0UwnHRAzcgC7y/R63SG/AL/L375POxTxg85r/LPzx3U/v2Hk8Mies1pqL5N9/D7x8w6UdKYucYqw2oxNqU+Jv9A3nArpi1MTgcm5+BMbNE/jvDMjpSBbY9ftdLd8Hnrz76R0uxymFTee8DjwPPAA8fcfjezyOY5o1cyoTxt8xO5z71TeVcV8HmXnKC75Fw8+oarHP9EAmOgY89eiuttuA7l89ufO4ZkwYkJMDDiE5kiHgT0A3koUdtMwpEVUmxbaIGt3xxpC0/trP6ljOIEz6H07HAV55pbf6J8Cz37r/YD/IG1AtY8qLnGHgVWATkuZuBE6Y1Gc38Czw+LfuP5i1rCiCOaaaBK/A6JXf6/BoAuClX/5zwS3AC7/983YP4KrOsyIAj617JWB9rbI8mDFy+nnrXQuF0laDiI2//fN2Wxk6Cyr5W90tvfsjBJYscyxzLFnmWOZYssyxZJljmWNppvT/AQAqwXyPTLlDowAAAABJRU5ErkJggg==' - -ICON_BASE64_THINK = b'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/GMzLdsAAD+paVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMjAtMDctMDJUMTc6MDI6MDUtMDQ6MDA8L3htcDpDcmVhdGVEYXRlPgogICAgICAgICA8eG1wOk1ldGFkYXRhRGF0ZT4yMDIwLTA3LTAyVDE3OjAyOjUxLTA0OjAwPC94bXA6TWV0YWRhdGFEYXRlPgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAyMC0wNy0wMlQxNzowMjo1MS0wNDowMDwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6NGZjMmNkMTQtYjBhZi05ODQ0LTkyNTAtZTk1NTRhYjMzYzJmPC94bXBNTTpJbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwaG90b3Nob3A6NTU3OWQyOWUtYmNhNy0xMWVhLWExMGItYjE5ZmEzMTU3NWFkPC94bXBNTTpEb2N1bWVudElEPgogICAgICAgICA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6OWUzNGE0MWQtOTM2YS1jZTRhLTgzNTMtZWVhYWEyYWQ2NjU1PC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y3JlYXRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjllMzRhNDFkLTkzNmEtY2U0YS04MzUzLWVlYWFhMmFkNjY1NTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNzowMjowNS0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDoxMGZiMmQwMi04NmUzLTAyNDMtYTc0Ny1hOGVkODM3NDU3NDk8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMjAtMDctMDJUMTc6MDI6NTEtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmNvbnZlcnRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6cGFyYW1ldGVycz5mcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvcG5nPC9zdEV2dDpwYXJhbWV0ZXJzPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+ZGVyaXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6cGFyYW1ldGVycz5jb252ZXJ0ZWQgZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZzwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6NGZjMmNkMTQtYjBhZi05ODQ0LTkyNTAtZTk1NTRhYjMzYzJmPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE3OjAyOjUxLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICA8L3JkZjpTZXE+CiAgICAgICAgIDwveG1wTU06SGlzdG9yeT4KICAgICAgICAgPHhtcE1NOkRlcml2ZWRGcm9tIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgPHN0UmVmOmluc3RhbmNlSUQ+eG1wLmlpZDoxMGZiMmQwMi04NmUzLTAyNDMtYTc0Ny1hOGVkODM3NDU3NDk8L3N0UmVmOmluc3RhbmNlSUQ+CiAgICAgICAgICAgIDxzdFJlZjpkb2N1bWVudElEPnhtcC5kaWQ6OWUzNGE0MWQtOTM2YS1jZTRhLTgzNTMtZWVhYWEyYWQ2NjU1PC9zdFJlZjpkb2N1bWVudElEPgogICAgICAgICAgICA8c3RSZWY6b3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6OWUzNGE0MWQtOTM2YS1jZTRhLTgzNTMtZWVhYWEyYWQ2NjU1PC9zdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg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' - - -ICON_BASE64_PALM = b'iVBORw0KGgoAAAANSUhEUgAAAEkAAAA8CAMAAAAdQmecAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAAB8SDx4UEiAUDyAUEiMZFSUbGSsUEy8aFiwbGTsUFTkdICwiHTIjHDQrHzsiGysjIjIlIDMqIzMsKjsgIzotID0uKT01IzszMUkYHE0ZIFkbKEQhHkUqH0sgHUUgI0MtIkUoKEsmIEwqIkwqL00rM0I1I0s1JEswKE04J0w7LUc9OVYpJ1omMVM0JVQ1K1I4JVU7KlszK1k6J1s7KVw4NGEdLmQeMWghNmE5LWQ4NXIiPWs2RHkkQnAzRVRENVNDOF1ENFxEOVpKOWNDM2NHPWNMPGxFOWxKPG9QPnJKPHVQP05HRFJMSltUUWpLQ2lUQ2NcWndNQndIU3JUQ3JUSHRZTHxSRHxVSHxZSnlaVH1YYmZgX39gVmZjYnZran14doFURoNWSYNbTYNeUoleUItbaYRgT4VhU4JiWIVoXIpjVIxlWo1oVo1pWpFiVpFlWZJsXZRxX4JmZYVsY4trY4pzaoN9e5NtYpRtaJltY5ZxYpRyaZpzZZx1ap14a5N4eJx1cJp3eJ15cZp6eqB2a6J6baN8cqJ7eax9cah+epx7hImBf5uBeqWAdKOCe6qCdKqDequJfbKJfY2EgpmIiZqVk52Zk6SBhKaFiKyDgqqFiayJgqyNi6aLkayNlKGQi6uRja+QlqySm6qZlLKMhLKOi7SRi76Th7qTi7KTk7KVm7KYk7SZm7iWk7yZk7ucm7Sco7ScqbqeorOfsK2knrmhnKukoqyop7GrqrqhpLqlq7mup7urqrSosbKuubqjsrymubyps7qqub6xrbSxsLizvbe1wr68ycKYi8OcmsCmo8KmqcGup8Ooq8qqqcersMGvv8yutMKxu8uwtM2zusy+v9G3usCwwsS0yca8xsW7y82/x8m9ysm90Ne8wMjAvMPCxsLAyc3Ax8vAy8/IzMbD0c3F08zK2NPLx9DJztnBydLO09LL2tPS19TS29rU29bU4dnT5NzV6NvZ4+Hf5uDd6+Pi7eXj8ejl9Ozp9/Lw+wAAAAAAAAAAAAAAAErQjXwAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAHU0lEQVRYR5WWf1gbZx3A8bw2y7nJVq00ZM9Eq63t0o3QFNKkrLbjoRjXsWFHCI+T0gNNe2nWm4Ndm9xFTLmj1UFONsUU1s2aZRRxdP7otNZN1Omc1inTdbad06danzlkVoR2/hG/7w8gNG/Y9iE/uPf9vp/7vt/37r0UZPLx34nxiYmJNybp4VuSxzQ9MXn5zempqelLlybfpotteu31ifFzfzp7/sL45PT09MS/aPOCME1jZ8+fGxs9Njw0fGLswuT01D/+SjsWgmU6fnJs7IUTx7410N8/MDx64eKlqV++DRXD9M2BkZOnfjicTvV0d/f0Hx09f/HNf//iL7QzP7mmn0XN9BMjR9PpfhOpkkdHz0z97+9PT9DuvOSa7gvHus2vYEzT6E4MDP10/PKlM0/T7rzkmoLBeyORWMwwzf6UaZowvydPX7z8nzPP0v585JhO7pIkWZbvVXUzlUomzYNmcui7L49fvPDs6zQiDzmmr9UHg7vDu8MRVTcOHESq5KEjz7z8yitnfkwj8pBr2njHNjEo7ZbliGFAQsk+eB155vTpc6f/RkPY5JiOr7i1uh5UYUVROw4ehEolk1193zj2k9+NLpxUbsWXrt5YfdfnpDAUS9UgLaCrq2/g6JETT9IINrmmJStWb9wUCKK6K0h1wDCMDs1IHjr8Tk23F63aeOsdooSXUNU6O7QOTdM7937x0NAbNIRJrumrS4tWb64OiFKrJEshWdnXqWr7QqGO/fuHztIQJrmmzKKiFauc9aLYIkp7xHvkfXtCLSG5Y+TU1w8foRFMGKat7y1asbLaH/CLkNeO5h2trZJk/Oq3I/uHh/9IQ1gwTD+4qnBpkbPK5xf9zTua/c1+fyT1WLLnvi+8MPwoDWHBMEWDjuuFpc5P+Hz3iM3Ndb5mPZVOKfU93xnqj/2GxjDINb3q8PicJYUly2vkutZWURR1PdErtwz8+vloQPk+DWLAyMnjWb58ZUnhrsf3SJ9s8fl1TVWlwODzI/HaxrYFpscw1fscxY7Slc7Yc5LkatDhTpZE3YzH133+1EsP0RgGDNOPqm74cJWrtHRbond9Q0LVdSmsmWa4rf3Ffw4/QmMYMEyZ9rIy502u0hpDlRK6HtNN40BCjI68+IeBtndWp0xmU5VrpWttFdqiensff27QjIuxwf6+/Y13v0YjGDBNr25yrV3rqoL7t7fX1A/0Girs6Eqw7fcPfJlGMGCaMt+rdt7k3Natqwkjpmnm4BNpQ27o+fNLD7yL4xYt+/RxGjYPtimTKSxxqYluEOm63psaTBtB89tDfe2LeY7neW7xnT+ncXPkMT109aoaJaYaugY7CmxSj6UHU12haNPV4EFw3I00cpY8psWLVlcHIxFdVTUtFod9Lm0aO+/adSMVAZy1icZSmKb738fzSzY1yJCRYcBGF48qWpdW11j/AapBQMW203gMw7SVRG4ORBKaqsXjWjSqaRGldkvV5utIF8JqFazWB+kQRI5pK4eLyvPVElQbyqRF92rxDll0rK+pXQRd8LK+/6MbNtzidrvXHKOjgCtMTVTD8++uDet6J1RJaVOie5XglrK6xlrUYf3QOo8HLG6v1+uuOEwHXmHajs45w05Fhx3c0JRQOBRUAi7P+pprIJuPrHHY7cXwZ3eAraJ8Nqss01NLqANzTbumxLSIrijhnTvDQWepq+x6oVAQBAuCh7dgs9sryj10dJbpttmJYT7eDlWOaJEwiO5uuRlJLNDMcSgK3lBOjkeyE3Q8NW1fNk8D1WiD9YJnZ1gOB1sCH0MK2oO/LYIF0gO3RWgkBmJ6cG51yRk5vkbR1IgcDisxWWwoIX0ILBAEm2AjCPx7sIeYbpvLx3odmgLPCaFIREY/WGDnrYOM5sAqUNgJNkEgImRaTGN44YPF0AFYhJsDIXicS/I+EC2n3QBHc7LZYO0q0KXgtttmTdfijDhLMQAiGxxYINK3Ax6ccqcq3jCzVrBaWAKdOKNyckk56N5esJ3UBqLQrAGYHg531CGV5ENtuAEDuSDWVKCMvN5Kb6X7s9R0O8poZvZ4FB0n2MrqGhr8vjLSij5JnYnMDSpIqbLS66amZbOiOSB9PNzmqdtSVVhL2rAcXtCHTeVIVQl4yfQKrs0WoWuNDKLp2dZ5eCvVzkGSmlWRpMCEhtORxIqj8TEBH88ja4IoKfxrqAC2wbmxUG0O7id6NAM+no+9GMuo6hZs+gxZKQQaRTPMFjFMUCuaFlo/Mr2CJkgJReNPfIFfmRM+ZIBVFfhS+BLOCZWb3C7kPofLEkM0CNqQy4zKuwGZ7iTRWIURIPW3mt0MMyo0vYL7s64luCt4Hu6ZeR4EHcgAuaBYcJ0XZPATAC0ampyAJ0fHz0IGZTPXiFTgQiYyJwAnh+pNd4QsZrcjgDYB5Bir7BvAhB5vKCvUZ+F4vMQ4cB54EP0/C2ik9/TDBZmnyG6BGqHuWJR9XvqdD2yCXcq+Dna6JtpIYJ15QWB+5W6v+1P/B0gPXHqaGwimAAAAAElFTkSuQmCC' - - -ICON_BASE64_LIST = [ - ICON_BASE64_BLOB_HEADACHE, - ICON_BASE64_BLOB_PALM, - ICON_BASE64_BLOB_PAT, - ICON_BASE64_BLOB_THINK, - ICON_BASE64_BLOB_THINK2, - ICON_BASE64_BROW, - ICON_BASE64_LEGO_THINK, - ICON_BASE64_PALM, - ICON_BASE64_THINK, -] - - -def _random_error_icon(): - return random.choice(ICON_BASE64_LIST) - - -# d8b -# Y8P -# -# 88888b.d88b. 8888b. 888 88888b. -# 888 "888 "88b "88b 888 888 "88b -# 888 888 888 .d888888 888 888 888 -# 888 888 888 888 888 888 888 888 -# 888 888 888 "Y888888 888 888 888 - - -def main(): - # theme('SystemDefaultForReal') - - # preview_all_look_and_feel_themes() - # ChangeLookAndFeel('Dark Red') - # theme('Dark Red') - # SetOptions(progress_meter_color=(COLOR_SYSTEM_DEFAULT)) - # SetOptions(element_padding=(0,0)) - # ------ Menu Definition ------ # - - ver = version.split('\n')[0] - - menu_def = [ - ['&File', ['!&Open::KeyOpen', '&Save::KeySave', '---', '&Properties::KeyProp', 'E&xit']], - [ - '&Edit', - [ - '&Paste', - [ - 'Special::KeySpecial', - '!Normal', - ], - 'Undo', - ], - ], - ['!&Toolbar', ['Command &1', 'Command &2', 'Command &3', 'Command &4']], - ['&Help', '&About...'], - ] - - treedata = TreeData() - - treedata.Insert( - '', - '_A_', - 'Tree Item 1', - [1, 2, 3], - ) - treedata.Insert( - '', - '_B_', - 'B', - [4, 5, 6], - ) - treedata.Insert( - '_A_', - '_A1_', - 'Sub Item 1', - ['can', 'be', 'anything'], - ) - treedata.Insert( - '', - '_C_', - 'C', - [], - ) - treedata.Insert( - '_C_', - '_C1_', - 'C1', - ['or'], - ) - treedata.Insert('_A_', '_A2_', 'Sub Item 2', [None, None]) - treedata.Insert('_A1_', '_A3_', 'A30', ['getting deep']) - treedata.Insert('_C_', '_C2_', 'C2', ['nothing', 'at', 'all']) - - for i in range(100): - treedata.Insert('_C_', i, i, []) - - frame1 = [ - [Input('Input Text', do_not_clear=True, size=(250, 35), tooltip='Input'), FileBrowse(), Stretch()], - [ - Multiline(size=(250, 75), do_not_clear=True, default_text='Multiline Input', tooltip='Multiline input'), - MultilineOutput(size=(250, 75), default_text='Multiline Output', tooltip='Multiline output', key='-MLINE-'), - ], - ] - - frame2 = [ - [ - Listbox( - ['Listbox 1', 'Listbox 2', 'Listbox 3', 'Item 4', 'Item 5'], - default_values=['Listbox 2', 'Listbox 3'], - size=(200, 85), - tooltip='Listbox', - key='_LISTBOX_', - font='Courier 12', - text_color='red', - ) - ], - [Combo([1, 2, 3], size=(200, 35), tooltip='Combo', visible_items=2, key='_COMBO_')], - [Spin([1, 2, 3], size=(40, 30), tooltip='Spinner', key='_SPIN1_')], - [Spin(['Spin item 1', 'Spin item 2', 'Spin item 3'], size=(240, 30), tooltip='Spinner', key='_SPIN2_')], - ] - - frame3 = [ - [Checkbox('Checkbox1', True, tooltip='Checkbox'), Checkbox('Checkbox1')], - [Radio('Radio Button1', 1, tooltip='Radio'), Radio('Radio Button2', 1, default=True), Stretch()], - ] - - frame4 = [ - [ - Slider(range=(0, 100), tick_interval=None, orientation='v', size=(3, 30), default_value=40, tooltip='Slider'), - Dial(range=(0, 100), tick_interval=1, resolution=1, size=(150, 150), default_value=40, tooltip='Dial'), - Stretch(), - ], - ] - matrix = [[str(x * y) for x in range(4)] for y in range(8)] - - frame5 = [ - [ - Table( - values=matrix, - max_col_width=25, - headings=('aaa', 'bbb', 'ccc', 'ddd'), - auto_size_columns=True, - display_row_numbers=True, - enable_events=True, - bind_return_key=True, - justification='right', - num_rows=6, - alternating_row_color='lightblue', - key='_table_', - tooltip='Table', - ), - Tree( - data=treedata, - headings=['col1', 'col2', 'col3'], - enable_events=True, - auto_size_columns=True, - num_rows=10, - col0_width=10, - key='_TREE_', - show_expanded=True, - size=(200, 150), - tooltip='Tree', - ), - Stretch(), - ], - ] - - graph_elem = Graph((880, 150), (0, 0), (600, 300), key='+GRAPH+', tooltip='Graph') - - frame6 = [ - [graph_elem, Stretch()], - ] - - tab1 = Tab('Graph Number 1', frame6, tooltip='Tab 1') - tab2 = Tab('Graph Number 2', [[]]) - - layout = [ - [Menu(menu_def, key='_REALMENU_', background_color='white')], - [Text('You are running the PySimpleGUI.py file itself', font=('ANY', 15, 'Bold'), text_color='yellow')], - [Text('You should be importing it rather than running it', font='ANY 15')], - [Text('VERSION {}'.format(ver), size=(85, 1), text_color='yellow', font='ANY 18')], - # [Image(data_base64=logo, tooltip='Image', click_submits=True, key='_IMAGE_'), - [ - Frame('Input Text Group', frame1, title_color='yellow', tooltip='Text Group', frame_color='yellow', pad=(0, 0)), - Stretch(), - ], - [ - Frame('Multiple Choice Group', frame2, title_color=theme_text_color(), frame_color='yellow'), - # Column([[Frame('Binary Choice Group', frame3, frame_color='white', title_color='white')]], pad=(0,0)), - Frame('Binary Choice Group', frame3, frame_color='white', title_color='white'), - Frame('Variable Choice Group', frame4, title_color='blue'), - Stretch(), - ], - [ - Frame('Structured Data Group', frame5, title_color='yellow'), - ], - # [Frame('Graphing Group', frame6)], - [TabGroup([[tab1, tab2]], title_color='black')], - [ - ProgressBar(max_value=600, start_value=400, size=(600, 25), key='+PROGRESS+'), - Text('', key='_PROGTEXT_'), - Stretch(), - ButtonMenu('&Menu', ['Menu', ['&Pause Graph', 'Menu item::optional_key']], key='_MENU_', tooltip='Button Menu'), - Button('Button'), - Button('Exit', tooltip='Exit button'), - ], - ] - - window = Window( - 'Window Title', - layout, - font=('Helvetica', 13), - # default_button_element_size=(100, 30), - # auto_size_buttons=False, - default_element_size=(200, 22), - # margins = (40,40), - # border_depth=1, - ) - # graph_elem.DrawCircle((200, 200), 50, 'blue') - i = 0 - graph_paused = False - - # window.Element('_LISTBOX_').SetValue(['Listbox 1','Listbox 3']) - while True: # Event Loop - # TimerStart() - event, values = window.read(timeout=10) - print(event, values) if event != TIMEOUT_KEY else None - window['-MLINE-'].update(value=str(values), append=True) if event != TIMEOUT_KEY else None - if event is None or event == 'Exit': - break - if values['_MENU_'] == 'Pause Graph': - graph_paused = not graph_paused - if event == 'About...': - Popup('You are running PySimpleGUIQt', 'The version number is', version) - if not graph_paused: - - if i < 600: - graph_elem.DrawLine((i, 0), (i, random.randint(0, 300)), width=1, color='#{:06x}'.format(random.randint(0, 0xFFFFFF))) - else: - graph_elem.Move(-1, 0) - graph_elem.DrawLine((i, 0), (i, random.randint(0, 300)), width=1, color='#{:06x}'.format(random.randint(0, 0xFFFFFF))) - - window.FindElement('+PROGRESS+').UpdateBar(i % 600) - window.FindElement('_PROGTEXT_').Update((i % 600) // 6) - i += 1 - - # TimerStop() - window.close() - - # layout = [[Text('You are running the PySimpleGUI.py file itself')], - # [Text('You should be importing it rather than running it')], - # [Text('Here is your sample input window....')], - # [Text('Source File', size=(150, 25), justification='right'), InputText('Source', focus=True), FileBrowse()], - # [Text('Destination Folder', size=(150, 25), justification='right'), InputText('Dest'), FolderBrowse()], - # [Ok(bind_return_key=True), Cancel()]] - # - # window = Window('Demo window..', - # auto_size_buttons=False, - # default_element_size=(280,22), - # auto_size_text=False, - # default_button_element_size=(80,22) - # ).Layout(layout) - # event, values = window.Read() - # print(event, values) - # window.Close() - - -# ------------------------------------------------------------------# -# ------------------------ PEP8-ify The SDK ------------------------# -# ------------------------------------------------------------------# - -change_look_and_feel = ChangeLookAndFeel -easy_print = EasyPrint -easy_print_close = EasyPrintClose -get_complimentary_hex = GetComplimentaryHex -list_of_look_and_feel_values = ListOfLookAndFeelValues -obj_to_string = ObjToString -obj_to_string_single_obj = ObjToStringSingleObj -one_line_progress_meter = OneLineProgressMeter -one_line_progress_meter_cancel = OneLineProgressMeterCancel -popup = Popup -popup_annoying = PopupAnnoying -popup_auto_close = PopupAutoClose -popup_cancel = PopupCancel -popup_error = PopupError -popup_get_file = PopupGetFile -popup_get_folder = PopupGetFolder -popup_get_text = PopupGetText -popup_no_border = PopupNoBorder -popup_no_buttons = PopupNoButtons -popup_no_frame = PopupNoFrame -popup_no_titlebar = PopupNoTitlebar -popup_no_wait = PopupNoWait -popup_non_blocking = PopupNonBlocking -popup_ok = PopupOK -popup_ok_cancel = PopupOKCancel -popup_quick = PopupQuick -popup_quick_message = PopupQuickMessage -popup_scrolled = PopupScrolled -popup_timed = PopupTimed -popup_yes_no = PopupYesNo -rgb = RGB -scrolled_text_box = ScrolledTextBox -set_global_icon = SetGlobalIcon -set_options = SetOptions -timer_start = TimerStart -timer_stop = TimerStop - - -# ------------------------ Set the "Official PySimpleGUI Theme Colors" ------------------------ -theme(CURRENT_LOOK_AND_FEEL) - - -if __name__ == '__main__': - main() - exit(69) diff --git a/FreeSimpleGUIQt/FreeSimpleGUIQt/__init__.py b/FreeSimpleGUIQt/FreeSimpleGUIQt/__init__.py index e0cc10e1..3ec86102 100644 --- a/FreeSimpleGUIQt/FreeSimpleGUIQt/__init__.py +++ b/FreeSimpleGUIQt/FreeSimpleGUIQt/__init__.py @@ -1,2 +1,14293 @@ -from .FreeSimpleGUIQt import * -from .FreeSimpleGUIQt import __version__ +#!/usr/bin/python3 +version = __version__ = ( + '0.35.0.18.1 Unreleased\nMassive update of docstrings (thanks nngogol), default for slider tick interval set automatically now, margins added to Window but not yet hooked up, VSeparator added (spelling error), added Radio.reset_group and removed clearing all when one of them is cleared (recent change), added default key for one_line_progress_meter, auto-add keys to tables & trees, InputText element gets new disabled-readonly foreground and background color settings and also a readonly parameter, InputText gets border_width parameter, fixed up some docstrings, popup gets new image and any_key_closes parms, input type popups also get image parameter, error checks for trying to manipulate a window prior to finalize, added a dummy Element.expand method, added theme_add_new, added Window.set_title, updated to the latest themes from tktiner port, big styles update (thanks nngogol!), more Styles work, changed popup text layout to match tkinter port, fixed vertical alignment in row, added margin to some elements, renamed styles related variables, window margin support but be careful. Added back the truncated portion' +) + +__version__ = version.split()[0] # For PEP 396 and PEP 345 + +# The shortened version of version +try: + ver = version.split(' ')[0] +except: + ver = '' + + +port = 'PySimpleGUIQt' + +import sys, datetime, textwrap, pickle, random, warnings, time + +try: # Because Raspberry Pi is still on 3.4....it's not critical if this module isn't imported on the Pi + from typing import ( + List, + Any, + Union, + Tuple, + Dict, + ) # because this code has to run on 2.7 can't use real type hints. Must do typing only in comments +except: + print('*** Skipping import of Typing module. "pip3 install typing" to remove this warning ***') + +# Copyright 2024 FreeSimpleGui authors +# Copyright 2020 PySimpleGUI.org + + +from PySide6.QtWidgets import ( + QApplication, + QLabel, + QWidget, + QLineEdit, + QComboBox, + QFormLayout, + QVBoxLayout, + QHBoxLayout, + QListWidget, + QDial, + QTableWidget, +) +from PySide6.QtWidgets import ( + QSlider, + QCheckBox, + QRadioButton, + QSpinBox, + QPushButton, + QTextEdit, + QMainWindow, + QDialog, + QAbstractItemView, +) +from PySide6.QtWidgets import ( + QSpacerItem, + QFrame, + QGroupBox, + QTextBrowser, + QPlainTextEdit, + QButtonGroup, + QFileDialog, + QTableWidget, + QTabWidget, + QTabBar, + QTreeWidget, + QTreeWidgetItem, + QLayout, + QTreeWidgetItemIterator, + QProgressBar, +) +from PySide6.QtWidgets import ( + QTableWidgetItem, + QGraphicsView, + QGraphicsScene, + QGraphicsItemGroup, + QMenu, + QMenuBar, + QSystemTrayIcon, + QColorDialog, +) +from PySide6.QtGui import QPainter, QPixmap, QPen, QColor, QBrush, QPainterPath, QFont, QImage, QIcon, QAction + +from PySide6.QtCore import Qt, QEvent, QSize +import PySide6.QtGui as QtGui +import PySide6.QtCore as QtCore +import PySide6.QtWidgets as QtWidgets + +using_pyqt5 = False + + +DEFAULT_BASE64_ICON = b'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' + + +FACE_PALM = b'iVBORw0KGgoAAAANSUhEUgAAAEkAAAA8CAMAAAAdQmecAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAAB8SDx4UEiAUDyAUEiMZFSUbGSsUEy8aFiwbGTsUFTkdICwiHTIjHDQrHzsiGysjIjIlIDMqIzMsKjsgIzotID0uKT01IzszMUkYHE0ZIFkbKEQhHkUqH0sgHUUgI0MtIkUoKEsmIEwqIkwqL00rM0I1I0s1JEswKE04J0w7LUc9OVYpJ1omMVM0JVQ1K1I4JVU7KlszK1k6J1s7KVw4NGEdLmQeMWghNmE5LWQ4NXIiPWs2RHkkQnAzRVRENVNDOF1ENFxEOVpKOWNDM2NHPWNMPGxFOWxKPG9QPnJKPHVQP05HRFJMSltUUWpLQ2lUQ2NcWndNQndIU3JUQ3JUSHRZTHxSRHxVSHxZSnlaVH1YYmZgX39gVmZjYnZran14doFURoNWSYNbTYNeUoleUItbaYRgT4VhU4JiWIVoXIpjVIxlWo1oVo1pWpFiVpFlWZJsXZRxX4JmZYVsY4trY4pzaoN9e5NtYpRtaJltY5ZxYpRyaZpzZZx1ap14a5N4eJx1cJp3eJ15cZp6eqB2a6J6baN8cqJ7eax9cah+epx7hImBf5uBeqWAdKOCe6qCdKqDequJfbKJfY2EgpmIiZqVk52Zk6SBhKaFiKyDgqqFiayJgqyNi6aLkayNlKGQi6uRja+QlqySm6qZlLKMhLKOi7SRi76Th7qTi7KTk7KVm7KYk7SZm7iWk7yZk7ucm7Sco7ScqbqeorOfsK2knrmhnKukoqyop7GrqrqhpLqlq7mup7urqrSosbKuubqjsrymubyps7qqub6xrbSxsLizvbe1wr68ycKYi8OcmsCmo8KmqcGup8Ooq8qqqcersMGvv8yutMKxu8uwtM2zusy+v9G3usCwwsS0yca8xsW7y82/x8m9ysm90Ne8wMjAvMPCxsLAyc3Ax8vAy8/IzMbD0c3F08zK2NPLx9DJztnBydLO09LL2tPS19TS29rU29bU4dnT5NzV6NvZ4+Hf5uDd6+Pi7eXj8ejl9Ozp9/Lw+wAAAAAAAAAAAAAAAErQjXwAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAHU0lEQVRYR5WWf1gbZx3A8bw2y7nJVq00ZM9Eq63t0o3QFNKkrLbjoRjXsWFHCI+T0gNNe2nWm4Ndm9xFTLmj1UFONsUU1s2aZRRxdP7otNZN1Omc1inTdbad06danzlkVoR2/hG/7w8gNG/Y9iE/uPf9vp/7vt/37r0UZPLx34nxiYmJNybp4VuSxzQ9MXn5zempqelLlybfpotteu31ifFzfzp7/sL45PT09MS/aPOCME1jZ8+fGxs9Njw0fGLswuT01D/+SjsWgmU6fnJs7IUTx7410N8/MDx64eKlqV++DRXD9M2BkZOnfjicTvV0d/f0Hx09f/HNf//iL7QzP7mmn0XN9BMjR9PpfhOpkkdHz0z97+9PT9DuvOSa7gvHus2vYEzT6E4MDP10/PKlM0/T7rzkmoLBeyORWMwwzf6UaZowvydPX7z8nzPP0v585JhO7pIkWZbvVXUzlUomzYNmcui7L49fvPDs6zQiDzmmr9UHg7vDu8MRVTcOHESq5KEjz7z8yitnfkwj8pBr2njHNjEo7ZbliGFAQsk+eB155vTpc6f/RkPY5JiOr7i1uh5UYUVROw4ehEolk1193zj2k9+NLpxUbsWXrt5YfdfnpDAUS9UgLaCrq2/g6JETT9IINrmmJStWb9wUCKK6K0h1wDCMDs1IHjr8Tk23F63aeOsdooSXUNU6O7QOTdM7937x0NAbNIRJrumrS4tWb64OiFKrJEshWdnXqWr7QqGO/fuHztIQJrmmzKKiFauc9aLYIkp7xHvkfXtCLSG5Y+TU1w8foRFMGKat7y1asbLaH/CLkNeO5h2trZJk/Oq3I/uHh/9IQ1gwTD+4qnBpkbPK5xf9zTua/c1+fyT1WLLnvi+8MPwoDWHBMEWDjuuFpc5P+Hz3iM3Ndb5mPZVOKfU93xnqj/2GxjDINb3q8PicJYUly2vkutZWURR1PdErtwz8+vloQPk+DWLAyMnjWb58ZUnhrsf3SJ9s8fl1TVWlwODzI/HaxrYFpscw1fscxY7Slc7Yc5LkatDhTpZE3YzH133+1EsP0RgGDNOPqm74cJWrtHRbond9Q0LVdSmsmWa4rf3Ffw4/QmMYMEyZ9rIy502u0hpDlRK6HtNN40BCjI68+IeBtndWp0xmU5VrpWttFdqiensff27QjIuxwf6+/Y13v0YjGDBNr25yrV3rqoL7t7fX1A/0Girs6Eqw7fcPfJlGMGCaMt+rdt7k3Natqwkjpmnm4BNpQ27o+fNLD7yL4xYt+/RxGjYPtimTKSxxqYluEOm63psaTBtB89tDfe2LeY7neW7xnT+ncXPkMT109aoaJaYaugY7CmxSj6UHU12haNPV4EFw3I00cpY8psWLVlcHIxFdVTUtFod9Lm0aO+/adSMVAZy1icZSmKb738fzSzY1yJCRYcBGF48qWpdW11j/AapBQMW203gMw7SVRG4ORBKaqsXjWjSqaRGldkvV5utIF8JqFazWB+kQRI5pK4eLyvPVElQbyqRF92rxDll0rK+pXQRd8LK+/6MbNtzidrvXHKOjgCtMTVTD8++uDet6J1RJaVOie5XglrK6xlrUYf3QOo8HLG6v1+uuOEwHXmHajs45w05Fhx3c0JRQOBRUAi7P+pprIJuPrHHY7cXwZ3eAraJ8Nqss01NLqANzTbumxLSIrijhnTvDQWepq+x6oVAQBAuCh7dgs9sryj10dJbpttmJYT7eDlWOaJEwiO5uuRlJLNDMcSgK3lBOjkeyE3Q8NW1fNk8D1WiD9YJnZ1gOB1sCH0MK2oO/LYIF0gO3RWgkBmJ6cG51yRk5vkbR1IgcDisxWWwoIX0ILBAEm2AjCPx7sIeYbpvLx3odmgLPCaFIREY/WGDnrYOM5sAqUNgJNkEgImRaTGN44YPF0AFYhJsDIXicS/I+EC2n3QBHc7LZYO0q0KXgtttmTdfijDhLMQAiGxxYINK3Ax6ccqcq3jCzVrBaWAKdOKNyckk56N5esJ3UBqLQrAGYHg531CGV5ENtuAEDuSDWVKCMvN5Kb6X7s9R0O8poZvZ4FB0n2MrqGhr8vjLSij5JnYnMDSpIqbLS66amZbOiOSB9PNzmqdtSVVhL2rAcXtCHTeVIVQl4yfQKrs0WoWuNDKLp2dZ5eCvVzkGSmlWRpMCEhtORxIqj8TEBH88ja4IoKfxrqAC2wbmxUG0O7id6NAM+no+9GMuo6hZs+gxZKQQaRTPMFjFMUCuaFlo/Mr2CJkgJReNPfIFfmRM+ZIBVFfhS+BLOCZWb3C7kPofLEkM0CNqQy4zKuwGZ7iTRWIURIPW3mt0MMyo0vYL7s64luCt4Hu6ZeR4EHcgAuaBYcJ0XZPATAC0ampyAJ0fHz0IGZTPXiFTgQiYyJwAnh+pNd4QsZrcjgDYB5Bir7BvAhB5vKCvUZ+F4vMQ4cB54EP0/C2ik9/TDBZmnyG6BGqHuWJR9XvqdD2yCXcq+Dna6JtpIYJ15QWB+5W6v+1P/B0gPXHqaGwimAAAAAElFTkSuQmCC' + + +g_time_start = 0 +g_time_end = 0 +g_time_delta = 0 + + +def TimerStart(): + global g_time_start + + g_time_start = time.time() + + +def TimerStop(): + global g_time_delta, g_time_end + + g_time_end = time.time() + g_time_delta = g_time_end - g_time_start + print(int(g_time_delta * 1000)) + + +""" + Welcome to the "core" PySimpleGUI code.... + + It's a mess.... really... it's a mess internally... it's the external-facing interfaces that + are not a mess. The Elements and the methods for them are well-designed. + PEP8 - this code is far far from PEP8 compliant. + It was written PRIOR to learning that PEP8 existed. + + The variable and function naming in particular are not compliant. There is + liberal use of CamelVariableAndFunctionNames. If you've got a serious enough problem with this + that you'll pass on this package, then that's your right and I invite you to do so. However, if + perhaps you're a practical thinker where it's the results that matter, then you'll have no + trouble with this code base. There is consisency however. + + I truly hope you get a lot of enjoyment out of using PySimpleGUI. It came from good intentions. +""" + +# ----====----====----==== Constants the user CAN safely change ====----====----====----# +DEFAULT_WINDOW_ICON = DEFAULT_BASE64_ICON +DEFAULT_ELEMENT_SIZE = (250, 22) # In PIXELS +DEFAULT_BUTTON_ELEMENT_SIZE = (80, 25) # In PIXELS +DEFAULT_MARGINS = (0, 0) # For Qt, use a Column element with padding to get same effect as tkinter port +DEFAULT_ELEMENT_PADDING = (4, 2) # Padding between elements (row, col) in pixels +# DEFAULT_ELEMENT_PADDING = (0, 0) # Padding between elements (row, col) in pixels +DEFAULT_PIXELS_TO_CHARS_SCALING = (10, 35) # 1 character represents x by y pixels +DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT = (10, 20) # 1 character represents x by y pixels +DEFAULT_PIXEL_TO_CHARS_CUTOFF = 15 # number of chars that triggers using pixels instead of chars +DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE = 70 # number of chars that triggers using pixels instead of chars +DEFAULT_AUTOSIZE_TEXT = True +DEFAULT_AUTOSIZE_BUTTONS = True +DEFAULT_FONT = ('Helvetica', 10) +DEFAULT_TEXT_JUSTIFICATION = 'left' +DEFAULT_BORDER_WIDTH = 1 +DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form +DEFAULT_DEBUG_WINDOW_SIZE = (800, 400) +DEFAULT_WINDOW_LOCATION = (None, None) +MAX_SCROLLED_TEXT_BOX_HEIGHT = 50 +DEFAULT_TOOLTIP_TIME = 400 +DEFAULT_TOOLTIP_OFFSET = (20, -20) +#################### COLOR STUFF #################### +BLUES = ('#082567', '#0A37A3', '#00345B') +PURPLES = ('#480656', '#4F2398', '#380474') +GREENS = ('#01826B', '#40A860', '#96D2AB', '#00A949', '#003532') +YELLOWS = ('#F3FB62', '#F0F595') +TANS = ('#FFF9D5', '#F4EFCF', '#DDD8BA') +NICE_BUTTON_COLORS = ( + (GREENS[3], TANS[0]), + ('#000000', '#FFFFFF'), + ('#FFFFFF', '#000000'), + (YELLOWS[0], PURPLES[1]), + (YELLOWS[0], GREENS[3]), + (YELLOWS[0], BLUES[2]), +) + +COLOR_SYSTEM_DEFAULT = '1234567890' # Colors should never be this long + +DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default +OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = ('white', BLUES[0]) # Colors should never be this long + + +CURRENT_LOOK_AND_FEEL = 'DarkBlue3' +# CURRENT_LOOK_AND_FEEL = 'Dark Red' + +DEFAULT_ERROR_BUTTON_COLOR = ('#FFFFFF', '#FF0000') +DEFAULT_BACKGROUND_COLOR = None +DEFAULT_ELEMENT_BACKGROUND_COLOR = None +DEFAULT_ELEMENT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = None +DEFAULT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_INPUT_ELEMENTS_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_INPUT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_SCROLLBAR_COLOR = None + +# A transparent button is simply one that matches the background +TRANSPARENT_BUTTON = 'This constant has been depricated. You must set your button background = background it is on for it to be transparent appearing' +# -------------------------------------------------------------------------------- +# Progress Bar Relief Choices +RELIEF_RAISED = 'raised' +RELIEF_SUNKEN = 'sunken' +RELIEF_FLAT = 'flat' +RELIEF_RIDGE = 'ridge' +RELIEF_GROOVE = 'groove' +RELIEF_SOLID = 'solid' + +RELIEF_TICK_POSITION_NO_TICKS = 'none' +RELIEF_TICK_POSITION_BOTH_SIDES = 'both' +RELIEF_TICK_POSITION_ABOVE = 'above' +RELIEF_TICK_POSITION_BELOW = 'below' +RELIEF_TICK_POSITION_LEFT = 'left' +RELIEF_TICK_POSITION_RIGHT = 'right' + +DEFAULT_PROGRESS_BAR_COMPUTE = ( + '#000000', + '#000000', +) # Means that the progress bar colors should be computed from other colors +DEFAULT_PROGRESS_BAR_COLOR = (GREENS[0], '#D0D0D0') # a nice green progress bar +DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL = (GREENS[0], '#D0D0D0') # a nice green progress bar +DEFAULT_PROGRESS_BAR_SIZE = (200, 20) # Size of Progress Bar (characters for length, pixels for width) +DEFAULT_PROGRESS_BAR_BORDER_WIDTH = 1 +DEFAULT_PROGRESS_BAR_RELIEF = RELIEF_GROOVE +PROGRESS_BAR_STYLES = ('default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative') +DEFAULT_PROGRESS_BAR_STYLE = 'default' +DEFAULT_METER_ORIENTATION = 'Horizontal' +DEFAULT_SLIDER_ORIENTATION = 'vertical' +DEFAULT_SLIDER_BORDER_WIDTH = 1 + +DEFAULT_SLIDER_RELIEF = RELIEF_TICK_POSITION_BOTH_SIDES +DEFAULT_FRAME_RELIEF = 'groove' + +DEFAULT_LISTBOX_SELECT_MODE = 'extended' +SELECT_MODE_MULTIPLE = 'multiple' +LISTBOX_SELECT_MODE_MULTIPLE = 'multiple' +SELECT_MODE_BROWSE = 'browse' +LISTBOX_SELECT_MODE_BROWSE = 'browse' +SELECT_MODE_EXTENDED = 'extended' +LISTBOX_SELECT_MODE_EXTENDED = 'extended' +SELECT_MODE_SINGLE = 'single' +LISTBOX_SELECT_MODE_SINGLE = 'single' +SELECT_MODE_CONTIGUOUS = 'contiguous' +LISTBOX_SELECT_MODE_CONTIGUOUS = 'contiguous' + +TABLE_SELECT_MODE_NONE = 'NONE' +TABLE_SELECT_MODE_BROWSE = 'BROWSE' +TABLE_SELECT_MODE_EXTENDED = 'EXTENDED' +DEFAULT_TABLE_SECECT_MODE = TABLE_SELECT_MODE_EXTENDED + +TITLE_LOCATION_TOP = 'N' +TITLE_LOCATION_BOTTOM = 'S' +TITLE_LOCATION_LEFT = 'W' +TITLE_LOCATION_RIGHT = 'E' +TITLE_LOCATION_TOP_LEFT = 'NW' +TITLE_LOCATION_TOP_RIGHT = 'NE' +TITLE_LOCATION_BOTTOM_LEFT = 'SW' +TITLE_LOCATION_BOTTOM_RIGHT = 'SE' + +THEME_DEFAULT = 'default' +THEME_WINNATIVE = 'winnative' +THEME_CLAM = 'clam' +THEME_ALT = 'alt' +THEME_CLASSIC = 'classic' +THEME_VISTA = 'vista' +THEME_XPNATIVE = 'xpnative' + +# DEFAULT_METER_ORIENTATION = 'Vertical' +# ----====----====----==== Constants the user should NOT f-with ====----====----====----# +ThisRow = 555666777 # magic number + +# DEFAULT_WINDOW_ICON = '' +MESSAGE_BOX_LINE_WIDTH = 60 + +# Icons for displaying system tray messages +SYSTEM_TRAY_MESSAGE_ICON_INFORMATION = QSystemTrayIcon.Information +SYSTEM_TRAY_MESSAGE_ICON_WARNING = QSystemTrayIcon.Warning +SYSTEM_TRAY_MESSAGE_ICON_CRITICAL = QSystemTrayIcon.Critical +SYSTEM_TRAY_MESSAGE_ICON_NOICON = QSystemTrayIcon.NoIcon + +# "Special" Key Values.. reserved +# Key representing a Read timeout +EVENT_TIMEOUT = TIMEOUT_EVENT = TIMEOUT_KEY = '__TIMEOUT__' +# Window closed event (user closed with X or destroyed using OS) +WIN_CLOSED = WINDOW_CLOSED = None + +# Key indicating should not create any return values for element +WRITE_ONLY_KEY = '__WRITE ONLY__' +EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED = '__DOUBLE_CLICKED__' +EVENT_SYSTEM_TRAY_ICON_ACTIVATED = '__ACTIVATED__' +EVENT_SYSTEM_TRAY_MESSAGE_CLICKED = '__MESSAGE_CLICKED__' + +# Meny key indicator character / string +MENU_KEY_SEPARATOR = '::' +MENU_DISABLED_CHARACTER = '!' + +SUPPRESS_ERROR_POPUPS = False + + +# ====================================================================== # +# One-liner functions that are handy as f_ck # +# ====================================================================== # +def RGB(red, green, blue): + return '#%02x%02x%02x' % (red, green, blue) + + +# ====================================================================== # +# Enums for types # +# ====================================================================== # +# ------------------------- Button types ------------------------- # +# todo Consider removing the Submit, Cancel types... they are just 'RETURN' type in reality +# uncomment this line and indent to go back to using Enums +# was an Enum previously ButtonType(Enum): +BUTTON_TYPE_BROWSE_FOLDER = 1 +BUTTON_TYPE_BROWSE_FILE = 2 +BUTTON_TYPE_BROWSE_FILES = 21 +BUTTON_TYPE_SAVEAS_FILE = 3 +BUTTON_TYPE_CLOSES_WIN = 5 +BUTTON_TYPE_CLOSES_WIN_ONLY = 6 +BUTTON_TYPE_READ_FORM = 7 +BUTTON_TYPE_REALTIME = 9 +BUTTON_TYPE_CALENDAR_CHOOSER = 30 +BUTTON_TYPE_COLOR_CHOOSER = 40 + +BROWSE_FILES_DELIMITER = ';' # the delimeter to be used between each file in the returned string + +# ------------------------- Element types ------------------------- # +# Used in Element - Was an enum once ElementType(Enum): +ELEM_TYPE_TEXT = 'text' +ELEM_TYPE_INPUT_TEXT = 'input' +ELEM_TYPE_INPUT_COMBO = 'combo' +ELEM_TYPE_INPUT_OPTION_MENU = 'option menu' +ELEM_TYPE_INPUT_RADIO = 'radio' +ELEM_TYPE_INPUT_MULTILINE = 'multiline' +ELEM_TYPE_MULTILINE_OUTPUT = 'multioutput' +ELEM_TYPE_INPUT_CHECKBOX = 'checkbox' +ELEM_TYPE_INPUT_SPIN = 'spind' +ELEM_TYPE_BUTTON = 'button' +ELEM_TYPE_IMAGE = 'image' +ELEM_TYPE_CANVAS = 'canvas' +ELEM_TYPE_FRAME = 'frame' +ELEM_TYPE_GRAPH = 'graph' +ELEM_TYPE_TAB = 'tab' +ELEM_TYPE_TAB_GROUP = 'tabgroup' +ELEM_TYPE_INPUT_SLIDER = 'slider' +ELEM_TYPE_INPUT_DIAL = 'dial' +ELEM_TYPE_INPUT_LISTBOX = 'listbox' +ELEM_TYPE_OUTPUT = 'output' +ELEM_TYPE_COLUMN = 'column' +ELEM_TYPE_MENUBAR = 'menubar' +ELEM_TYPE_PROGRESS_BAR = 'progressbar' +ELEM_TYPE_BLANK = 'blank' +ELEM_TYPE_TABLE = 'table' +ELEM_TYPE_TREE = 'tree' +ELEM_TYPE_ERROR = 'error' +ELEM_TYPE_SEPARATOR = 'separator' +ELEM_TYPE_STRETCH = 'stretch' +ELEM_TYPE_BUTTONMENU = 'buttonmenu' + +# ------------------------- Popup Buttons Types ------------------------- # +POPUP_BUTTONS_YES_NO = 1 +POPUP_BUTTONS_CANCELLED = 2 +POPUP_BUTTONS_ERROR = 3 +POPUP_BUTTONS_OK_CANCEL = 4 +POPUP_BUTTONS_OK = 0 +POPUP_BUTTONS_NO_BUTTONS = 5 + +# def apply_new_font(css_dict, font_string): +# return css_dict.update({k.strip().replace('_', '-') : v.strip() for k,v in font_string.replace('\n', '').split(';')}) + + +# ---------------------------------------------------------------------- # +# Cascading structure.... Objects get larger # +# Button # +# Element # +# Row # +# Form # +# ---------------------------------------------------------------------- # +# ------------------------------------------------------------------------- # +# Element CLASS # +# ------------------------------------------------------------------------- # +class Element: + def __init__( + self, + elem_type, + size=(None, None), + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + key=None, + k=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param elem_type: ??? + :type elem_type: ??? + :param size: w=characters-wide, h=rows-high + :type size: Tuple[int, int] (width, height) + :param auto_size_text: True if the Widget should be shrunk to exactly fit the number of chars to show + :type auto_size_text: bool + :param font: specifies the font family, size, etc (see docs for exact formats) + :type font: Union[str, Tuple[str, int]] + :param background_color: color of background. Can be in #RRGGBB format or a color name "black" + :type background_color: (str) + :param text_color: element's text color. Can be in #RRGGBB format or a color name "black" + :type text_color: (str) + :param key: Identifies an Element. Should be UNIQUE to this window. + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element (Default = True) + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + + if size_px != (None, None): + self.Size = size_px + else: + self.Size = _convert_tkinter_size_to_Qt(size) + + self.Type = elem_type + self.AutoSizeText = auto_size_text + # self.Pad = DEFAULT_ELEMENT_PADDING if pad is None else pad + self.Pad = pad + if font is not None and type(font) is not str: + self.Font = font + elif font is not None: + self.Font = font.split(' ') + else: + self.Font = font + + self.TKStringVar = None + self.TKIntVar = None + self.TKText = None + self.TKEntry = None + self.TKImage = None + + self.ParentForm = None + self.ParentContainer = None # will be a Form, Column, or Frame element + self.TextInputDefault = None + self.Position = (0, 0) # Default position Row 0, Col 0 + self.BackgroundColor = background_color if background_color is not None else DEFAULT_ELEMENT_BACKGROUND_COLOR + self.TextColor = text_color if text_color is not None else DEFAULT_ELEMENT_TEXT_COLOR + self.Key = key # dictionary key for return values + self.Tooltip = tooltip + self.TooltipObject = None + self.Visible = visible + self.metadata = metadata # type: Any + self.row_frame = None # type: QHBoxLayout + self.qt_styles = [] # type: List[QtStyle] + self.Widget = None # type: QWidget + + def _FindReturnKeyBoundButton(self, form): + for row in form.Rows: + for element in row: + if element.Type == ELEM_TYPE_BUTTON: + if element.BindReturnKey: + return element + if element.Type == ELEM_TYPE_COLUMN: + rc = self._FindReturnKeyBoundButton(element) + if rc is not None: + return rc + if element.Type == ELEM_TYPE_FRAME: + rc = self._FindReturnKeyBoundButton(element) + if rc is not None: + return rc + if element.Type == ELEM_TYPE_TAB_GROUP: + rc = self._FindReturnKeyBoundButton(element) + if rc is not None: + return rc + if element.Type == ELEM_TYPE_TAB: + rc = self._FindReturnKeyBoundButton(element) + if rc is not None: + return rc + return None + + def _ReturnKeyHandler(self, event): + MyForm = self.ParentForm + button_element = self._FindReturnKeyBoundButton(MyForm) + if button_element is not None: + button_element._ButtonCallBack() + + def _widget_was_created(self): + """ + Determines if a Widget was created for this element. + + :return: True if a Widget has been created previously (Widget is not None) + :rtype: (bool) + """ + if self.Widget is not None: + return True + else: + warnings.warn( + 'You cannot Update element with key = {} until the window has been Read or Finalized'.format(self.Key), + UserWarning, + ) + if not SUPPRESS_ERROR_POPUPS: + popup_error( + 'Unable to complete operation on element with key {}'.format(self.Key), + 'You cannot perform operations (such as calling update) on an Element until Window is read or finalized.', + 'Adding a "finalize=True" parameter to your Window creation will likely fix this.', + image=_random_error_icon(), + ) + return False + + def Update(self, widget, background_color=None, text_color=None, font=None, visible=None): + if not self._widget_was_created(): + return + + a_style = self.qt_styles[0] + # print(f'a_style = {a_style}') + if font is not None: # = apply_new_font(css_props_dict, create_style_from_font(font)) + a_style['font'] = create_style_from_font(font) + if text_color is not None: + a_style['color'] = text_color + self.TextColor = text_color + if background_color is not None: + a_style['background-color'] = background_color + self.BackgroundColor = background_color + + # print(f'a_style = {a_style}') + widget.setStyleSheet(a_style.build_css_string()) + set_widget_visiblity(widget, visible) + + def set_stylesheet(self, stylesheet): + """ + Sets the stylesheet for a Qt Widget + :param stylesheet: Stylesheet (string) to set stylesheet to + :type stylesheet: (str) + """ + try: + self.Widget.setStyleSheet(stylesheet) + except Exception as e: + print('** Error Setting Stylesheet **', e) + + def get_stylesheet(self): + """ + Returns the stylesheet for element's associated Qt Widget + :return: stylesheet + :rtype: (str) + """ + stylesheet = '' + try: + stylesheet = self.Widget.styleSheet() + except Exception as e: + print('** Error Setting Stylesheet **', e) + + return stylesheet + + update = Update + + # ---------------------------------- DUMMY METHODS - They don't do anything! ---------------------------------- + + # These methods are here for porting purposes only. They are meant to allow you to change your import statement + # from PySimpleGUI to PySimpleGUIQt and still be able to run your program. + + def expand(self, expand_x=False, expand_y=False, expand_row=True): + """ + WARNING - NOT USED IN PySimpleGUIQt port. Provided as dummy method + + :param expand_x: If True Element will expand in the Horizontal directions + :type expand_x: (bool) + :param expand_y: If True Element will expand in the Vertical directions + :type expand_y: (bool) + :param expand_row: If True the row containing the element will also expand. Without this your element is "trapped" within the row + :type expand_row: (bool) + :return: None + :rtype: None + """ + + return + + def __call__(self, *args, **kwargs): + """ + Makes it possible to "call" an already existing element. When you do make the "call", it actually calls + the Update method for the element. + Example: If this text element was in yoiur layout: + sg.Text('foo', key='T') + Then you can call the Update method for that element by writing: + window.FindElement('T')('new text value') + + + :param kwargs: + :return: + """ + return self.Update(*args, **kwargs) + + update = Update + + +# ---------------------------------------------------------------------- # +# Input Class # +# ---------------------------------------------------------------------- # +class InputText(Element): + def __init__( + self, + default_text='', + size=(None, None), + disabled=False, + password_char='', + justification=None, + background_color=None, + text_color=None, + font=None, + tooltip=None, + disabled_readonly_background_color=None, + disabled_readonly_text_color=None, + change_submits=False, + enable_events=False, + readonly=False, + border_width=None, + do_not_clear=True, + key=None, + k=None, + focus=False, + pad=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + Input a line of text Element + :param default_text: Text initially shown in the input box as a default value(Default value = '') + :type default_text: (str) + :param size: w=characters-wide, h=rows-high + :type size: Tuple[int, int] (width, height) + :param disabled: set disable state for element (Default = False) + :type disabled: (bool) + :param password_char: Password character if this is a password field (Default value = '') + :type password_char: (char) + :param justification: justification for data display. Valid choices - left, right, center + :type justification: (str) + :param background_color: color of background in one of the color formats + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param disabled_readonly_background_color: If state is set to readonly or disabled, the color to use for the background + :type disabled_readonly_background_color: (str) + :param disabled_readonly_text_color: If state is set to readonly or disabled, the color to use for the text + :type disabled_readonly_text_color: (str) + :param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead + :type change_submits: (bool) + :param enable_events: If True then changes to this element are immediately reported as an event. Use this instead of change_submits (Default = False) + :type enable_events: (bool) + :param do_not_clear: If False then the field will be set to blank after ANY event (button, any event) (Default = True) + :type do_not_clear: (bool) + :param readonly: If True then the user cannot modify the field (Default = False) + :type readonly: (bool) + :param border_width: width of border around element in pixels + :type border_width: (int) + :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param focus: Determines if initial focus should go to this element. + :type focus: (bool) + :param pad: Amount of padding to put around element. Normally (horizontal pixels, vertical pixels) but can be split apart further into ((horizontal left, horizontal right), (vertical above, vertical below)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param visible: set visibility state of the element (Default = True) + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.DefaultText = default_text + self.PasswordCharacter = password_char + bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Focus = focus + self.do_not_clear = do_not_clear + self.Justification = justification or 'left' + self.Disabled = disabled + self.ReadOnly = readonly + self.disabled_readonly_background_color = disabled_readonly_background_color + self.disabled_readonly_text_color = disabled_readonly_text_color + self.ChangeSubmits = change_submits or enable_events + self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH + self.Widget = self.QT_QLineEdit = None # type: QLineEdit + self.ValueWasChanged = False + super().__init__( + ELEM_TYPE_INPUT_TEXT, + size=size, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + font=font, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + + def _dragEnterEvent(self, e): + if e.mimeData().hasText(): + e.accept() + else: + e.ignore() + + def _dropEvent(self, e): + self.QT_QLineEdit.setText(e.mimeData().text()) + + class InputTextWidget(QWidget): + def __init__(self, qt_qlineedit, element): + self.QT_QLineEdit = qt_qlineedit + self.Element = element + super().__init__() + + def eventFilter(self, widget, event): + # print(f'Got input text event {event}') + if event.type() == QEvent.FocusIn and widget is self.QT_QLineEdit: + self.Element.ParentForm.FocusElement = self.Element + return QWidget.eventFilter(self, widget, event) + + def _QtCallbackFocusInEvent(self, value): + return + + def _QtCallbackFocusInEvent(self, value): + if not self.ChangeSubmits: + return + # if was changed using an "update" call, then skip the next changed callback + if self.ValueWasChanged: + self.ValueWasChanged = False + print('skipping update') + return + _element_callback_quit_mainloop(self) + + def _QtCallbackReturnPressed(self): + self._ReturnKeyHandler(None) + return + + def Update(self, value=None, disabled=None, select=None, background_color=None, text_color=None, font=None, visible=None): + if disabled is True: + self.QT_QLineEdit.setDisabled(True) + elif disabled is False: + self.QT_QLineEdit.setDisabled(False) + if value is not None: + self.QT_QLineEdit.setText(str(value)) + self.DefaultText = value + # was getting into an infinite loop when the update was triggering a text changed callback, but unable + # to dupliate this + # self.ValueWasChanged = True + if select: + self.QT_QLineEdit.setSelection(0, QtGui.QTextCursor.End) + super().Update(self.QT_QLineEdit, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def Get(self): + return self.QT_QLineEdit.text() + # return self.TKStringVar.get() + + def SetFocus(self): + self.QT_QLineEdit.setFocus() + + get = Get + set_focus = SetFocus + update = Update + + +# ------------------------- INPUT TEXT lazy functions ------------------------- # +In = InputText +Input = InputText +I = InputText + + +# ---------------------------------------------------------------------- # +# Combo # +# ---------------------------------------------------------------------- # +class Combo(Element): + def __init__( + self, + values, + default_value=None, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + disabled=False, + key=None, + k=None, + pad=None, + tooltip=None, + readonly=False, + visible_items=10, + font=None, + auto_complete=True, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + Input Combo Box Element (also called Dropdown box) + + :param values: values to choose. While displayed as text, the items returned are what the caller supplied, not text + :type values: List[Any] or Tuple[Any] + :param default_value: Choice to be displayed as initial value. Must match one of values variable contents + :type default_value: (Any) + :param size: width = characters-wide, height = rows-high + :type size: Tuple[int, int] (width, height) + :param auto_size_text: True if element should be the same size as the contents + :type auto_size_text: (bool) + :param background_color: Color for Element. Text or RGB Hex + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param change_submits: DEPRICATED DO NOT USE. Use `enable_events` instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. Combo event is when a choice is made + :type enable_events: (bool) + :param disabled: set disable state for element + :type disabled: (bool) + :param key: Used with window.FindElement and with return values to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text that will appear when mouse hovers over this element + :type tooltip: (str) + :param readonly: make element readonly (user can't change). True means user cannot change + :type readonly: (bool) + :param visible_items: ??? + :type visible_items: ??? + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param auto_complete: ??? + :type auto_complete: ??? + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.Values = values + self.DefaultValue = default_value + self.ChangeSubmits = change_submits or enable_events + self.TKCombo = None + # self.InitializeAsDisabled = disabled + self.Disabled = disabled + self.Readonly = readonly + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.VisibleItems = visible_items + self.AutoComplete = auto_complete + self.Widget = self.QT_ComboBox = None # type: QComboBox + super().__init__( + ELEM_TYPE_INPUT_COMBO, + size=size, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + font=font or DEFAULT_FONT, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + + def _QtCurrentItemChanged(self, state): + if self.ChangeSubmits: + _element_callback_quit_mainloop(self) + + def Update( + self, + value=None, + values=None, + set_to_index=None, + disabled=None, + readonly=None, + background_color=None, + text_color=None, + font=None, + visible=None, + ): + if values is not None: + self.Values = values + for i in range(self.QT_ComboBox.count()): + self.QT_ComboBox.removeItem(0) + self.QT_ComboBox.addItems(values) + if value is not None: + for index, v in enumerate(self.Values): + if v == value: + self.QT_ComboBox.setCurrentIndex(index) + break + if set_to_index is not None: + self.QT_ComboBox.setCurrentIndex(set_to_index) + if disabled == True: + self.QT_ComboBox.setDisabled(True) + elif disabled == False: + self.QT_ComboBox.setDisabled(False) + if readonly is not None: + self.Readonly = readonly + + super().Update(self.QT_ComboBox, background_color=background_color, text_color=text_color, font=font, visible=visible) + + update = Update + + +# ------------------------- INPUT COMBO Element lazy functions ------------------------- # +InputCombo = Combo +DropDown = InputCombo +Drop = InputCombo + + +# ---------------------------------------------------------------------- # +# Option Menu # +# ---------------------------------------------------------------------- # +class OptionMenu(Element): + def __init__( + self, + values, + default_value=None, + size=(None, None), + disabled=False, + auto_size_text=None, + background_color=None, + text_color=None, + key=None, + k=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + InputOptionMenu - NOT USED IN QT + :param values: Values to be displayed + :type values: List[Any] or Tuple[Any] + :param default_value: the value to choose by default + :type default_value: (Any) + :param size: size in characters (wide) and rows (high) + :type size: Tuple[int, int] (width, height) + :param disabled: control enabled / disabled + :type disabled: (bool) + :param auto_size_text: True if size of Element should match the contents of the items + :type auto_size_text: (bool) + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param key: Used with window.FindElement and with return values to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text that will appear when mouse hovers over this element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.Values = values + self.DefaultValue = default_value + self.TKOptionMenu = None + self.Disabled = disabled + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + + super().__init__( + ELEM_TYPE_INPUT_OPTION_MENU, + size=size, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + + def Update(self, value=None, values=None, disabled=None): + return + + update = Update + + +# ------------------------- OPTION MENU Element lazy functions ------------------------- # +InputOptionMenu = OptionMenu + + +# ---------------------------------------------------------------------- # +# Listbox # +# ---------------------------------------------------------------------- # +class Listbox(Element): + def __init__( + self, + values, + default_values=None, + select_mode=None, + change_submits=False, + enable_events=False, + bind_return_key=False, + size=(None, None), + disabled=False, + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + key=None, + k=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param values: list of values to display. Can be any type including mixed types as long as they have __str__ method + :type values: List[Any] or Tuple[Any] + :param default_values: which values should be initially selected + :type default_values: List[Any] + :param select_mode: Select modes are used to determine if only 1 item can be selected or multiple and how they can be selected. Valid choices begin with "LISTBOX_SELECT_MODE_" and include: LISTBOX_SELECT_MODE_SINGLE LISTBOX_SELECT_MODE_MULTIPLE LISTBOX_SELECT_MODE_BROWSE LISTBOX_SELECT_MODE_EXTENDED + :type select_mode: [enum] + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. Listbox generates events when an item is clicked + :type enable_events: (bool) + :param bind_return_key: If True, then the return key will cause a the Listbox to generate an event + :type bind_return_key: (bool) + :param size: width = characters-wide, height = rows-high + :type size: Tuple(int, int) (width, height) + :param disabled: set disable state for element + :type disabled: (bool) + :param auto_size_text: True if element should be the same size as the contents + :type auto_size_text: (bool) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param key: Used with window.FindElement and with return values to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.Values = values + self.DefaultValues = default_values + self.TKListbox = None + self.ChangeSubmits = change_submits or enable_events + self.BindReturnKey = bind_return_key + self.Disabled = disabled + if select_mode == LISTBOX_SELECT_MODE_BROWSE: + self.SelectMode = SELECT_MODE_BROWSE + elif select_mode == LISTBOX_SELECT_MODE_EXTENDED: + self.SelectMode = SELECT_MODE_EXTENDED + elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE: + self.SelectMode = SELECT_MODE_MULTIPLE + elif select_mode == LISTBOX_SELECT_MODE_SINGLE: + self.SelectMode = SELECT_MODE_SINGLE + elif select_mode == LISTBOX_SELECT_MODE_CONTIGUOUS: + self.SelectMode = SELECT_MODE_CONTIGUOUS + else: + self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Widget = self.QT_ListWidget = None # type: QListWidget + tsize = size # convert tkinter size to pixels + if size[0] is not None and size[0] < 100: + tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] + + super().__init__( + ELEM_TYPE_INPUT_LISTBOX, + size=tsize, + auto_size_text=auto_size_text, + font=font, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + + def _QtCurrentRowChanged(self, state): + if self.ChangeSubmits: + _element_callback_quit_mainloop(self) + + def Update( + self, + values=None, + disabled=None, + set_to_index=None, + background_color=None, + text_color=None, + font=None, + visible=None, + ): + if values is not None: + self.Values = values + for i in range(self.QT_ListWidget.count()): + self.QT_ListWidget.takeItem(0) + items = [str(v) for v in self.Values] + self.QT_ListWidget.addItems(items) + if disabled == True: + self.QT_ListWidget.setDisabled(True) + elif disabled == False: + self.QT_ListWidget.setDisabled(False) + if set_to_index is not None: + self.QT_ListWidget.setCurrentRow(set_to_index) + super().Update(self.QT_ListWidget, background_color=background_color, text_color=text_color, font=font, visible=visible) + + return + + def SetValue(self, values): + # for index, item in enumerate(self.Values): + for index, value in enumerate(self.Values): + item = self.QT_ListWidget.item(index) + if value in values: + self.QT_ListWidget.setItemSelected(item, True) + + def GetListValues(self): + return self.Values + + def get(self): + """ + Gets the current value of the Element as it would be represented in the results dictionary. + Normally you would NOT be using this method, but instead using the return values dictionary + that is returned from reading your window + + :return: (List[Any]) The currently selected items in the listbox + """ + value = [] + selected_items = [item.text() for item in self.QT_ListWidget.selectedItems()] + for v in self.Values: + if str(v) in selected_items: + value.append(v) + return value + + get_list_values = GetListValues + set_value = SetValue + update = Update + + +LBox = Listbox +LB = Listbox + + +# ---------------------------------------------------------------------- # +# Radio # +# ---------------------------------------------------------------------- # +class Radio(Element): + def __init__( + self, + text, + group_id, + default=False, + disabled=False, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + font=None, + key=None, + k=None, + pad=None, + tooltip=None, + change_submits=False, + enable_events=False, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param text: Text to display next to button + :type text: (str) + :param group_id: Groups together multiple Radio Buttons. Any type works + :type group_id: (Any) + :param default: Set to True for the one element of the group you want initially selected + :type default: (bool) + :param disabled: set disable state + :type disabled: (bool) + :param size: (width, height) width = characters-wide, height = rows-high + :type size: Tuple[int, int] + :param auto_size_text: if True will size the element to match the length of the text + :type auto_size_text: (bool) + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param key: Used with window.FindElement and with return values to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. Radio Button events happen when an item is selected + :type enable_events: (bool) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.InitialState = default + self.Text = text + self.GroupID = group_id + self.Value = None + self.Disabled = disabled + self.TextColor = text_color or DEFAULT_TEXT_COLOR + self.ChangeSubmits = change_submits or enable_events + self.Widget = self.QT_Radio_Button = None # type: QRadioButton + self.QT_RadioButtonGroup = None # type: QButtonGroup + + super().__init__( + ELEM_TYPE_INPUT_RADIO, + size=size, + auto_size_text=auto_size_text, + font=font, + background_color=background_color, + text_color=self.TextColor, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + + def Update(self, value=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): + if value is not None: + self.InitialState = value + if disabled: + self.QT_Radio_Button.setDisabled(True) + else: + self.QT_Radio_Button.setDisabled(False) + if value is True: + self.QT_Radio_Button.setChecked(True) + if value is False: + self.QT_RadioButtonGroup.setExclusive(False) + self.QT_Radio_Button.setChecked(False) + self.QT_RadioButtonGroup.setExclusive(True) + super().Update(self.QT_Radio_Button, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def reset_group(self): + self.QT_Radio_Button.setChecked(True) + self.QT_RadioButtonGroup.setExclusive(False) + self.QT_Radio_Button.setChecked(False) + self.QT_RadioButtonGroup.setExclusive(True) + + def _QtCallbackValueChanged(self, value): + if not self.ChangeSubmits: + return + _element_callback_quit_mainloop(self) + + update = Update + + +R = Radio +Rad = Radio + + +# ---------------------------------------------------------------------- # +# Checkbox # +# ---------------------------------------------------------------------- # +class Checkbox(Element): + def __init__( + self, + text, + default=False, + size=(None, None), + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + disabled=False, + key=None, + k=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param text: Text to display next to checkbox + :type text: (str) + :param default: Set to True if you want this checkbox initially checked + :type default: (bool) + :param size: (width, height) width = characters-wide, height = rows-high + :type size: Tuple[int, int] + :param auto_size_text: if True will size the element to match the length of the text + :type auto_size_text: (bool) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. Checkbox events happen when an item changes + :type enable_events: (bool) + :param disabled: set disable state + :type disabled: (bool) + :param key: Used with window.FindElement and with return values to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.Text = text + self.InitialState = default + self.Value = None + self.TKCheckbutton = None + self.Disabled = disabled + self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR + self.ChangeSubmits = change_submits or enable_events + self.Widget = self.QT_Checkbox = None # type: QCheckBox + + super().__init__( + ELEM_TYPE_INPUT_CHECKBOX, + size=size, + auto_size_text=auto_size_text, + font=font, + background_color=background_color, + text_color=self.TextColor, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + + def QtCallbackStateChanged(self, state): + if self.ChangeSubmits: + _element_callback_quit_mainloop(self) + + def Get(self): + return self.QT_Checkbox.isChecked() + + def Update(self, value=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): + self.QT_Checkbox.setChecked(value or False) + if disabled == True: + self.QT_Checkbox.setDisabled(True) + elif disabled == False: + self.QT_Checkbox.setDisabled(False) + super().Update(self.QT_Checkbox, background_color=background_color, text_color=text_color, font=font, visible=visible) + + get = Get + update = Update + + +# ------------------------- CHECKBOX Element lazy functions ------------------------- # +CB = Checkbox +CBox = Checkbox +Check = Checkbox + + +# ---------------------------------------------------------------------- # +# Spin # +# ---------------------------------------------------------------------- # + + +class Spin(Element): + # Values = None + # TKSpinBox = None + def __init__( + self, + values, + initial_value=None, + disabled=False, + change_submits=False, + enable_events=False, + size=(None, None), + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + key=None, + k=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + Spinner Element + :param values: List of valid values + :type values: Tuple[Any] or List[Any] + :param initial_value: Initial item to show in window. Choose from list of values supplied + :type initial_value: (Any) + :param disabled: set disable state + :type disabled: (bool) + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. Spin events happen when an item changes + :type enable_events: (bool) + :param size: (width, height) width = characters-wide, height = rows-high + :type size: Tuple[int, int] + :param auto_size_text: if True will size the element to match the length of the text + :type auto_size_text: (bool) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param key: Used with window.FindElement and with return values to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.Values = values + self.DefaultValue = initial_value + self.ChangeSubmits = change_submits or enable_events + self.Disabled = disabled + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Widget = self.QT_Spinner = None # type: StringBox + + super().__init__( + ELEM_TYPE_INPUT_SPIN, + size, + auto_size_text, + font=font, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + return + + class StringBox(QSpinBox): + def __init__(self, strings, parent=None): + super(Spin.StringBox, self).__init__(parent) + self.setStrings(strings) + + def strings(self): + return self._strings + + def setStrings(self, strings): + self._strings = tuple(strings) + self._values = dict(zip(strings, range(len(strings)))) + self.setRange(0, len(strings) - 1) + + def textFromValue(self, value): + return str(self._strings[value]) + + def valueFromText(self, text): + return self._values[text] + + def _QtCallbackValueChanged(self, value): + if not self.ChangeSubmits: + return + _element_callback_quit_mainloop(self) + + def Update(self, value=None, values=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): + if values != None: + self.Values = values + self.QT_Spinner.setStrings(values) + # self.QT_Spinner.setRange(self.Values[0], self.Values[1]) + if value is not None: + # self.QT_Spinner.setValue(value) + try: + self.QT_Spinner.setValue(self.QT_Spinner.valueFromText(value)) + self.DefaultValue = value + except: + pass + if disabled == True: + self.QT_Spinner.setDisabled(True) + elif disabled == False: + self.QT_Spinner.setDisabled(False) + super().Update(self.QT_Spinner, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def Get(self): + return self.QT_Spinner.value() + + get = Get + update = Update + + +# ---------------------------------------------------------------------- # +# Multiline # +# ---------------------------------------------------------------------- # +class Multiline(Element): + def __init__( + self, + default_text='', + enter_submits=False, + disabled=False, + autoscroll=False, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + do_not_clear=True, + key=None, + k=None, + write_only=False, + focus=False, + font=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param default_text: Initial text to show + :type default_text: (str) + :param enter_submits: if True, the Window.Read call will return is enter key is pressed in this element + :type enter_submits: (bool) + :param disabled: set disable state + :type disabled: (bool) + :param autoscroll: If True the contents of the element will automatically scroll as more data added to the end + :type autoscroll: (bool) + :param size: (width, height) width = characters-wide, height = rows-high + :type size: Tuple[int, int] + :param auto_size_text: if True will size the element to match the length of the text + :type auto_size_text: (bool) + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. Spin events happen when an item changes + :type enable_events: (bool) + :param do_not_clear: if False the element will be cleared any time the Window.Read call returns + :type do_not_clear: bool + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param write_only: If True then no entry will be added to the values dictionary when the window is read + :type write_only: bool + :param focus: if True initial focus will go to this element + :type focus: (bool) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.DefaultText = default_text + self.EnterSubmits = enter_submits + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + self.Focus = focus + self.do_not_clear = do_not_clear + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Autoscroll = autoscroll + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + self.WriteOnly = write_only + tsize = ( + _convert_tkinter_size_to_Qt( + size, + scaling=DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT, + height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE, + ) + if size[0] is not None + else size_px + ) + + self.Widget = self.QT_TextEdit = None # type: QTextEdit + + super().__init__( + ELEM_TYPE_INPUT_MULTILINE, + size=(None, None), + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + font=font or DEFAULT_FONT, + visible=visible, + size_px=tsize, + metadata=metadata, + ) + return + + class MultiQWidget(QWidget): + def __init__(self, qt_textedit, element): + self.QT_TextEdit = qt_textedit + self.Element = element + super().__init__() + + def eventFilter(self, widget, event): + if self.Element.EnterSubmits and event.type() == QEvent.KeyPress and widget is self.QT_TextEdit: + key = event.key() + if key in (Qt.Key_Return, Qt.Key_Enter): + self.Element._ReturnKeyHandler(0) + if event.type() == QEvent.FocusIn and widget is self.QT_TextEdit: + self.Element.ParentForm.FocusElement = self.Element + return QWidget.eventFilter(self, widget, event) + + def _QtCallbackFocusInEvent(self): + if not self.ChangeSubmits: + return + _element_callback_quit_mainloop(self) + + def _dragEnterEvent(self, e): + if e.mimeData().hasText(): + e.accept() + else: + e.ignore() + + def _dropEvent(self, e): + self.Widget.setText(e.mimeData().text()) + + def Update( + self, + value=None, + disabled=None, + append=False, + autoscroll=False, + background_color=None, + text_color=None, + font=None, + text_color_for_value=None, + background_color_for_value=None, + visible=None, + readonly=None, + ): + """ + Changes some of the settings for the Multiline Element. Must call `Window.read` or `Window.finalize` or "finalize" the window using finalize parameter prior + + :param value: (str) new text to display + :param disabled: (bool) disable or enable state of the element + :param append: (bool) if True then new value will be added onto the end of the current value. if False then contents will be replaced. + :param autoscroll: (bool) if True cursor will be moved to end of element after updating + :param background_color: (str) color of background + :param text_color: (str) color of the text + :param font: Union[str, Tuple[str, int]] specifies the font family, size, etc + :param text_color_for_value: (str) color of the new text being added + :param visible: (bool) set visibility state of the element + :param autoscroll: (bool) if True then contents of element are scrolled down when new text is added to the end + """ + + if value is not None and not append: + self.DefaultText = value + self.QT_TextEdit.setText(str(value)) + elif value is not None and append: + self.DefaultText = value + # self.QT_TextEdit.setText(self.QT_TextEdit.toPlainText() + str(value)) # original code + # self.QT_TextEdit.append(str(value)) # can't use because adds a newline + if text_color_for_value is not None: + self.QT_TextEdit.setTextColor(text_color_for_value) + if background_color_for_value is not None: + self.QT_TextEdit.setTextBackgroundColor(background_color_for_value) + self.QT_TextEdit.insertPlainText(str(value)) + if self.Autoscroll or autoscroll and autoscroll is not False: + self.QT_TextEdit.moveCursor(QtGui.QTextCursor.End) + if text_color_for_value is not None: + self.QT_TextEdit.setTextColor(self.TextColor) + if background_color_for_value is not None: + self.QT_TextEdit.setTextBackgroundColor(self.BackgroundColor) + if disabled is True: + self.QT_TextEdit.setDisabled(True) + elif disabled is False: + self.QT_TextEdit.setDisabled(False) + if readonly is True: + self.QT_TextEdit.setReadOnly(True) + elif readonly is False: + self.QT_TextEdit.setReadOnly(False) + super().Update(self.QT_TextEdit, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def Get(self): + return self.QT_TextEdit.toPlainText() + + def SetFocus(self): + self.QT_TextEdit.setFocus() + + def print(self, *args, end=None, sep=None, text_color=None, background_color=None, autoscroll=True): + """ + Print like Python normally prints except route the output to a multline element and also add colors if desired + + :param args: The arguments to print + :type args: List[Any] + :param end: (str) The end char to use just like print uses + :type end: (str) + :param sep: (str) The separation character like print uses + :type sep: (str) + :param text_color: The color of the text + :type text_color: (str) + :param background_color: The background color of the line + :type background_color: (str) + :param autoscroll: (bool) If True cursor is moved to end after print + :type autoscroll: (bool) + """ + _print_to_element( + self, + *args, + end=end, + sep=sep, + text_color=text_color, + background_color=background_color, + autoscroll=autoscroll, + ) + + get = Get + set_focus = SetFocus + update = Update + + +ML = Multiline +MLine = Multiline + + +# ---------------------------------------------------------------------- # +# ScrolledOutput # +# ---------------------------------------------------------------------- # +class MultilineOutput(Element): + def __init__( + self, + default_text='', + enter_submits=False, + disabled=False, + autoscroll=False, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + do_not_clear=True, + key=None, + k=None, + focus=False, + font=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param default_text: default value to put into input area + :type default_text: (str) + :param enter_submits: if True, the Window.Read call will return is enter key is pressed in this element + :type enter_submits: (bool) + :param disabled: set disable state + :type disabled: (bool) + :param autoscroll: If True cursor is moved to end after print + :typep autoscroll: (bool) + :param size: (width, height) width = characters-wide, height = rows-high + :type size: Tuple[int, int] + :param auto_size_text: if True size of the Text Element will be sized to fit the string provided in 'text' parm + :type auto_size_text: (bool) + :param background_color: color of background in one of the color formats + :type background_color: (str) + ::param text_color: color of the text + :type text_color: (str) + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. If this button is a target, should it generate an event when filled in + :type enable_events: (bool) + :param do_not_clear: if False the element will be cleared any time the Window.Read call returns + :type do_not_clear: (bool) + :param focus: if True, initial focus will be put on this button + :type focus: (bool) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.DefaultText = default_text + self.EnterSubmits = enter_submits + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + self.Focus = focus + self.do_not_clear = do_not_clear + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Autoscroll = autoscroll + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + self.Widget = self.QT_TextBrowser = None # type: QTextBrowser + tsize = ( + _convert_tkinter_size_to_Qt( + size, + scaling=DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT, + height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE, + ) + if size[0] is not None + else size_px + ) + super().__init__( + ELEM_TYPE_MULTILINE_OUTPUT, + size=(None, None), + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + font=font or DEFAULT_FONT, + visible=visible, + size_px=tsize, + metadata=metadata, + ) + return + + def Update( + self, + value=None, + disabled=None, + append=False, + autoscroll=None, + background_color=None, + text_color=None, + font=None, + text_color_for_value=None, + background_color_for_value=None, + visible=None, + ): + if value is not None and not append: + self.QT_TextBrowser.setText(str(value)) + elif value is not None and append: + self.QT_TextBrowser.insertPlainText(str(value)) + # self.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) + if disabled == True: + self.QT_TextBrowser.setDisabled(True) + elif disabled == False: + self.QT_TextBrowser.setDisabled(False) + if self.Autoscroll or autoscroll and autoscroll is not False: + self.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) + super().Update(self.QT_TextBrowser, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def Get(self): + return self.QT_TextBrowser.toPlainText() + + def print(self, *args, end=None, sep=None, text_color=None, background_color=None, autoscroll=True): + """ + Print like Python normally prints except route the output to a multline element and also add colors if desired + + :param args: The arguments to print + :type args: List[Any] + :param end: The end char to use just like print uses + :type end: (str) + :param sep: The separation character like print uses + :type sep: (str) + :param text_color: The color of the text + :type text_color: (str) + :param background_color: The background color of the line + :type background_color: (str) + :param autoscroll: If True cursor is moved to end after print + :typep autoscroll: (bool) + """ + _print_to_element( + self, + *args, + end=end, + sep=sep, + text_color=text_color, + background_color=background_color, + autoscroll=autoscroll, + ) + + get = Get + update = Update + + +MLineOut = Multiline + + +# ---------------------------------------------------------------------- # +# Text # +# ---------------------------------------------------------------------- # +class Text(Element): + def __init__( + self, + text='', + size=(None, None), + auto_size_text=None, + click_submits=None, + enable_events=False, + relief=None, + font=None, + text_color=None, + background_color=None, + justification=None, + pad=None, + margins=None, + key=None, + k=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param text: The text to display. Can include /n to achieve multiple lines. Will convert (optional) parameter into a string + :type text: (Any) + :param size: (width, height) width = characters-wide, height = rows-high + :type size: Tuple[int, int] + :param auto_size_text: if True size of the Text Element will be sized to fit the string provided in 'text' parm + :type auto_size_text: (bool) + :param click_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type click_submits: (bool) + :param enable_events: Turns on the element specific events. Text events happen when the text is clicked + :type enable_events: (bool) + :param relief: relief style around the text. Values are same as progress meter relief values. Should be a constant that is defined at starting with "RELIEF_" - `RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID` + :type relief: (str/enum) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param text_color: color of the text + :type text_color: (str) + :param background_color: color of background + :type background_color: (str) + :param justification: how string should be aligned within space provided by size. Valid choices = `left`, `right`, `center` + :type justification: (str) + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param margins: ??? + :type margins: ??? + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.DisplayText = str(text) + self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR + self.Justification = justification or 'left' + self.Relief = relief + self.ClickSubmits = click_submits or enable_events + self.Margins = margins + if background_color is None: + bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR + else: + bg = background_color + self.Widget = self.QT_Label = None # type: QLabel + + super().__init__( + ELEM_TYPE_TEXT, + size, + auto_size_text, + background_color=bg, + font=font if font else DEFAULT_FONT, + text_color=self.TextColor, + visible=visible, + pad=pad, + key=key, + tooltip=tooltip, + size_px=size_px, + metadata=metadata, + ) + return + + def _QtCallbackTextClicked(self, event): + if not self.ClickSubmits: + return + _element_callback_quit_mainloop(self) + + def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): + """ + + :param value: + :param background_color: + :param text_color: + :param font: + :param visible: + :return: + """ + if value is not None: + self.DisplayText = str(value) + self.QT_Label.setText(str(value)) + super().Update(self.QT_Label, background_color=background_color, text_color=text_color, font=font, visible=visible) + + update = Update + + +# ------------------------- Text Element lazy functions ------------------------- # +Txt = Text +T = Text + + +# ---------------------------------------------------------------------- # +# Output # +# Routes stdout, stderr to a scrolled window # +# ---------------------------------------------------------------------- # +class Output(Element): + def __init__( + self, + size=(None, None), + background_color=None, + text_color=None, + pad=None, + font=None, + tooltip=None, + key=None, + k=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param size: (width, height) w=characters-wide, h=rows-high + :type size: Tuple[int, int] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self._TKOut = None + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Widget = self.QT_TextBrowser = None # type: QTextBrowser + tsize = ( + size_px + if size_px != (None, None) + else ( + _convert_tkinter_size_to_Qt( + size, + scaling=DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT, + height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE, + ) + if size[0] is not None + else size + ) + ) + + super().__init__( + ELEM_TYPE_OUTPUT, + size=(None, None), + background_color=bg, + text_color=fg, + pad=pad, + font=font, + tooltip=tooltip, + key=key, + visible=visible, + size_px=tsize, + metadata=metadata, + ) + + def _reroute_stdout(self): + self.my_stdout = sys.stdout + self.my_stderr = sys.stderr + sys.stdout = self + sys.stderr = self + + def write(self, m): + """ + MUST be called write. Don't mess with. It's called by Python itself because of reroute + :param m: + :return: + """ + self.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) + self.QT_TextBrowser.insertPlainText(str(m)) + + # if self.my_stdout: + # self.my_stdout.write(str(m)) + + def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): + if value is not None: + self.QT_TextBrowser.setText(value) + super().Update(self.QT_TextBrowser, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def __del__(self): + sys.stdout = self.my_stdout + sys.stderr = self.my_stderr + + update = Update + write = write + + +# ---------------------------------------------------------------------- # +# Button Class # +# ---------------------------------------------------------------------- # +class Button(Element): + def __init__( + self, + button_text='', + button_type=BUTTON_TYPE_READ_FORM, + target=(None, None), + tooltip=None, + file_types=(('ALL Files', '*'),), + initial_folder=None, + disabled=False, + change_submits=False, + enable_events=False, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param button_text: Text to be displayed on the button + :type button_text: (str) + :param button_type: You should NOT be setting this directly. ONLY the shortcut functions set this + :type button_type: (int) + :param target: key or (row,col) target for the button. Note that -1 for column means 1 element to the left of this one. The constant ThisRow is used to indicate the current row. The Button itself is a valid target for some types of button + :type target: Union[str, Tuple[int, int]] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param file_types: the filetypes that will be used to match files. To indicate all files: (("ALL Files", "*.*"),). Note - NOT SUPPORTED ON MAC + :type file_types: Tuple[Tuple[str, str], ...] + :param initial_folder: starting path for folders and files + :type initial_folder: (str) + :param disabled: If True button will be created disabled + :type disabled: (bool) + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. If this button is a target, should it generate an event when filled in + :type enable_events: (bool) + :param image_filename: image filename if there is a button image. GIFs and PNGs only. + :type image_filename: (str) + :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data + :type image_data: Union[bytes, str] + :param image_size: Size of the image in pixels (width, height) + :type image_size: Tuple[int, int] + :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc + :type image_subsample: (int) + :param border_width: width of border around button in pixels + :type border_width: (int) + :param size: (width, height) of the button in characters wide, rows high + :type size: Tuple[int, int] + :param auto_size_button: if True the button size is sized to fit the text + :type auto_size_button: (bool) + :param button_color: of button. Easy to remember which is which if you say "ON" between colors. "red" on "green". + :type button_color: Tuple[str, str] == (text color, background color) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param bind_return_key: If True the return key will cause this button to be pressed + :type bind_return_key: (bool) + :param focus: if True, initial focus will be put on this button + :type focus: (bool) + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.AutoSizeButton = auto_size_button + self.BType = button_type + self.FileTypes = file_types + self.TKButton = None + self.Target = target + self.ButtonText = str(button_text) + + # Button colors can be a tuple (text, background) or a string with format "text on background" + if button_color is None: + button_color = DEFAULT_BUTTON_COLOR + else: + try: + if isinstance(button_color, str): + button_color = button_color.split(' on ') + except Exception as e: + print('* cprint warning * you messed up with color formatting', e) + + self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR + self.TextColor = self.ButtonColor[0] + self.BackgroundColor = self.ButtonColor[1] + self.ImageFilename = image_filename + self.ImageData = image_data + self.ImageSize = image_size + self.ImageSubsample = image_subsample + self.UserData = None + self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH + self.BindReturnKey = bind_return_key + self.Focus = focus + self.TKCal = None + self.CalendarCloseWhenChosen = None + self.DefaultDate_M_D_Y = (None, None, None) + self.InitialFolder = initial_folder + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + self.Widget = self.QT_QPushButton = None # type: QPushButton + self.ColorChosen = None + # self.temp_size = size if size != (NONE, NONE) else + + super().__init__( + ELEM_TYPE_BUTTON, + size=size, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + text_color=self.TextColor, + background_color=self.BackgroundColor, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + return + + # Realtime button release callback + def _ButtonReleaseCallBack(self, parm): + self.LastButtonClickedWasRealtime = False + self.ParentForm.LastButtonClicked = None + + # Realtime button callback + def _ButtonPressCallBack(self, parm): + self.ParentForm.LastButtonClickedWasRealtime = True + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = self.ButtonText + if self.ParentForm.CurrentlyRunningMainloop: + pass # kick out of loop if read was called + + # ------- Button Callback ------- # + def _ButtonCallBack(self): + + # print('Button callback') + + # print(f'Parent = {self.ParentForm} Position = {self.Position}') + # Buttons modify targets or return from the form + # If modifying target, get the element object at the target and modify its StrVar + target = self.Target + target_element = None + if target[0] == ThisRow: + target = [self.Position[0], target[1]] + if target[1] < 0: + target[1] = self.Position[1] + target[1] + strvar = None + should_submit_window = False + if target == (None, None): + strvar = self.TKStringVar + else: + if not isinstance(target, str): + if target[0] < 0: + target = [self.Position[0] + target[0], target[1]] + target_element = self.ParentContainer._GetElementAtLocation(target) + else: + target_element = self.ParentForm.FindElement(target) + try: + strvar = target_element.TKStringVar + except: + pass + try: + if target_element.ChangeSubmits: + should_submit_window = True + except: + pass + filetypes = (('ALL Files', '*'),) if self.FileTypes is None else self.FileTypes + if self.BType == BUTTON_TYPE_BROWSE_FOLDER: + folder_name = QFileDialog.getExistingDirectory(dir=self.InitialFolder) + if folder_name != '': + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = folder_name + else: + target_element.Update(folder_name) + elif self.BType == BUTTON_TYPE_BROWSE_FILE: + qt_types = convert_tkinter_filetypes_to_qt(self.FileTypes) + file_name = QFileDialog.getOpenFileName(dir=self.InitialFolder, filter=qt_types) + if file_name != '': + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = file_name + else: + target_element.Update(file_name[0]) + elif self.BType == BUTTON_TYPE_COLOR_CHOOSER: + qcolor = QColorDialog.getColor() + rgb_color = qcolor.getRgb() + color = '#' + ''.join('%02x' % i for i in rgb_color[:3]) + if self.Target == (None, None): + self.FileOrFolderName = color + else: + target_element.Update(color) + elif self.BType == BUTTON_TYPE_BROWSE_FILES: + qt_types = convert_tkinter_filetypes_to_qt(self.FileTypes) + file_name = QFileDialog.getOpenFileNames(dir=self.InitialFolder, filter=qt_types) + if file_name != '': + file_name = BROWSE_FILES_DELIMITER.join(file_name[0]) + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = file_name + else: + target_element.Update(file_name) + elif self.BType == BUTTON_TYPE_SAVEAS_FILE: + qt_types = convert_tkinter_filetypes_to_qt(self.FileTypes) + file_name = QFileDialog.getSaveFileName(dir=self.InitialFolder, filter=qt_types) + if file_name != '': + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = file_name + else: + target_element.Update(file_name[0]) + elif self.BType == BUTTON_TYPE_CLOSES_WIN: # this is a return type button so GET RESULTS and destroy window + # first, get the results table built + # modify the Results table in the parent FlexForm object + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = self.ButtonText + self.ParentForm.FormRemainedOpen = False + self.ParentForm._Close() + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.QTApplication.exit() # Exit the mainloop + self.ParentForm.QT_QMainWindow.close() + if self.ParentForm.NonBlocking: + # TODO DESTROY WIN + Window.DecrementOpenCount() + elif self.BType == BUTTON_TYPE_READ_FORM: # LEAVE THE WINDOW OPEN!! DO NOT CLOSE + # first, get the results table built + # modify the Results table in the parent FlexForm object + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = self.ButtonText + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out + self.ParentForm.QTApplication.exit() + elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop + self.ParentForm._Close() + self.ParentForm.QT_QMainWindow.close() + if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out + self.ParentForm.QTApplication.exit() + Window.DecrementOpenCount() + elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window + should_submit_window = False + + if should_submit_window: + self.ParentForm.LastButtonClicked = target_element.Key + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.QTApplication.exit() + pass # TODO # kick the users out of the mainloop + return + + def Update( + self, + text=None, + button_color=(None, None), + disabled=None, + image_data=None, + image_filename=None, + font=None, + visible=None, + ): + if text is not None: + self.QT_QPushButton.setText(str(text)) + self.ButtonText = text + if self.ParentForm.Font and (self.Font == DEFAULT_FONT or not self.Font): + font = self.ParentForm.Font + elif self.Font is not None: + font = self.Font + else: + font = DEFAULT_FONT + + fg = bg = None + if button_color != (None, None): + self.ButtonColor = button_color + fg, bg = button_color + if self.Disabled != disabled and disabled is not None: + if not disabled: # if enabling buttons, set the color + fg, bg = self.ButtonColor + self.Disabled = disabled + if disabled: + self.QT_QPushButton.setDisabled(True) + else: + self.QT_QPushButton.setDisabled(False) + # fg, bg = self.ButtonColor + # print(f'Button update fg, bg {fg}, {bg}') + super().Update(self.QT_QPushButton, background_color=bg, text_color=fg, font=font, visible=visible) + + def GetText(self): + return self.ButtonText + + def SetFocus(self): + self.QT_QPushButton.setFocus() + + def Click(self): + if self.Widget is None: + return + try: + self.Widget.click() + except Exception as e: + print('Exception {} \nclicking button {}'.format(e, self.ButtonText)) + + click = Click + get_text = GetText + set_focus = SetFocus + update = Update + + +# ------------------------- Button lazy functions ------------------------- # +B = Button +Btn = Button + + +# ---------------------------------------------------------------------- # +# ButtonMenu Class # +# ---------------------------------------------------------------------- # +class ButtonMenu(Element): + def __init__( + self, + button_text, + menu_def, + tooltip=None, + disabled=False, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + pad=None, + key=None, + k=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param button_text: Text to be displayed on the button + :type button_text: (str) + :param menu_def: A list of lists of Menu items to show when this element is clicked. See docs for format as they are the same for all menu types + :type menu_def: List[List[str]] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param disabled: If True button will be created disabled + :type disabled: (bool) + :param image_filename: image filename if there is a button image. GIFs and PNGs only. + :type image_filename: (str) + :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data + :type image_data: Union[bytes, str] + :param image_size: Size of the image in pixels (width, height) + :type image_size: Tuple[int, int] + :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc + :type image_subsample: (int) + :param border_width: width of border around button in pixels + :type border_width: (int) + :param size: (width, height) of the button in characters wide, rows high + :type size: Tuple[int, int] + :param auto_size_button: if True the button size is sized to fit the text + :type auto_size_button: (bool) + :param button_color: of button. Easy to remember which is which if you say "ON" between colors. "red" on "green" + :type button_color: Tuple[str, str] == (text color, background color) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.MenuDefinition = menu_def + self.AutoSizeButton = auto_size_button + self.ButtonText = button_text + self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR + self.TextColor = self.ButtonColor[0] + self.BackgroundColor = self.ButtonColor[1] + self.BorderWidth = border_width + self.ImageFilename = image_filename + self.ImageData = image_data + self.ImageSize = image_size + self.ImageSubsample = image_subsample + self.Disabled = disabled + self.Widget = self.QT_QPushButton = None # type: QPushButton + self.IsButtonMenu = True + self.MenuItemChosen = None + + # self.temp_size = size if size != (NONE, NONE) else + + super().__init__( + ELEM_TYPE_BUTTONMENU, + size=size, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + text_color=self.TextColor, + background_color=self.BackgroundColor, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + return + + def _QT_MenuItemChosenCallback(self, item_chosen): + # print('IN BUTTON MENU ITEM CALLBACK', item_chosen) + self.Key = item_chosen.replace('&', '') # fool the quit function into thinking this was a key + _element_callback_quit_mainloop(self) + + def Update(self, menu_definition=None, text=None, button_color=(None, None), font=None, visible=None): + if menu_definition is not None: + menu_def = menu_definition + qmenu = QMenu(self.QT_QPushButton) + qmenu.setTitle(menu_def[0]) + AddMenuItem(qmenu, menu_def[1], self) + self.QT_QPushButton.setMenu(qmenu) + super().Update( + self.QT_QPushButton, + background_color=button_color[1], + text_color=button_color[0], + font=font, + visible=visible, + ) + + def Click(self): + """ """ + try: + self.QT_QPushButton.click() + except Exception as e: + print('Exception {} clicking button. Has your Window been Finalized() or Read()?'.format(e)) + + click = Click + update = Update + + +BMenu = ButtonMenu + + +# ---------------------------------------------------------------------- # +# ProgreessBar # +# ---------------------------------------------------------------------- # +class ProgressBar(Element): + def __init__( + self, + max_value, + orientation=None, + size=(None, None), + start_value=0, + auto_size_text=None, + bar_color=(None, None), + style=None, + border_width=None, + relief=None, + key=None, + k=None, + pad=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param max_value: max value of progressbar + :type max_value: (int) + :param orientation: 'horizontal' or 'vertical' + :type orientation: (str) + :param size: Size of the bar. If horizontal (chars wide, pixels high), vert (pixels wide, rows high) + :type size: Tuple[int, int] + :param start_value: ??? + :type start_value: ??? + :param auto_size_text: Not sure why this is here + :type auto_size_text: (bool) + :param bar_color: The 2 colors that make up a progress bar. One is the background, the other is the bar + :type bar_color: Tuple[str, str] + :param style: Progress bar style defined as one of these 'default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative' + :type style: (str) + :param border_width: The amount of pixels that go around the outside of the bar + :type border_width: (int) + :param relief: relief style. Values are same as progress meter relief values. Can be a constant or a string: `RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID` (Default value = DEFAULT_PROGRESS_BAR_RELIEF) + :type relief: (str) + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.MaxValue = max_value + self.TKProgressBar = None + self.Cancelled = False + self.NotRunning = True + self.Orientation = orientation if orientation else DEFAULT_METER_ORIENTATION + self.BarColor = bar_color if bar_color != (None, None) else DEFAULT_PROGRESS_BAR_COLOR + self.BarStyle = style if style else DEFAULT_PROGRESS_BAR_STYLE + self.BorderWidth = border_width if border_width is not None else DEFAULT_PROGRESS_BAR_BORDER_WIDTH + self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF + self.BarExpired = False + self.StartValue = start_value + tsize = size + if size[0] is not None and size[0] < 100: + # tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] + tsize = size[0] * 10, size[1] + self.Widget = self.QT_QProgressBar = None # type: QProgressBar + + super().__init__( + ELEM_TYPE_PROGRESS_BAR, + size=tsize, + auto_size_text=auto_size_text, + key=key, + pad=pad, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + + # returns False if update failed + def UpdateBar(self, current_count, max=None): + if max is not None: + self.QT_QProgressBar.setMaximum(max) + self.QT_QProgressBar.setValue(current_count) + self.ParentForm.QTApplication.processEvents() # refresh the window + return True + + def Update(self, visible=None): + super().Update(self.QT_QProgressBar, visible=visible) + + update = Update + update_bar = UpdateBar + + +PBar = ProgressBar +Prog = ProgressBar + + +# ---------------------------------------------------------------------- # +# Image # +# ---------------------------------------------------------------------- # +class Image(Element): + def __init__( + self, + filename=None, + data=None, + data_base64=None, + background_color=None, + size=(None, None), + pad=None, + key=None, + k=None, + tooltip=None, + click_submits=False, + enable_events=False, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param filename: image filename if there is a button image. GIFs and PNGs only. + :type filename: (str) + :param data: Raw or Base64 representation of the image to put on button. Choose either filename or data + :type data: Union[bytes, str] + :param data_base64: ??? + :type data_base64: ??? + :param background_color: color of background + :type background_color: + :param size: (width, height) size of image in pixels + :type size: Tuple[int, int] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param click_submits: ??? + :type click_submits: (bool) + :param enable_events: Turns on the element specific events. For an Image element, the event is "image clicked" + :type enable_events: (bool) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.Filename = filename + self.Data = data + self.DataBase64 = data_base64 + self.tktext_label = None + self.BackgroundColor = background_color + self.ClickSubmits = click_submits or enable_events + if data is None and filename is None and data_base64 is None: + print('* Warning... no image specified in Image Element! *') + self.Widget = self.QT_QLabel = None # type: QLabel + + super().__init__( + ELEM_TYPE_IMAGE, + size=size, + background_color=background_color, + pad=pad, + key=key, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + return + + def QtCallbackImageClicked(self, event): + if not self.ClickSubmits: + return + _element_callback_quit_mainloop(self) + + def Update(self, filename=None, data=None, data_base64=None, size=(None, None), visible=None): + if filename is not None: + qlabel = self.QT_QLabel + qlabel.setText('') + w = QtGui.QPixmap(filename).width() + h = QtGui.QPixmap(filename).height() + qlabel.setGeometry(QtCore.QRect(0, 0, w, h)) + qlabel.setPixmap(QtGui.QPixmap(filename)) + elif data is not None: + qlabel = self.QT_QLabel + qlabel.setText('') + ba = QtCore.QByteArray.fromRawData(data) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qlabel.setPixmap(pixmap) + elif data_base64 is not None: + qlabel = self.QT_QLabel + qlabel.setText('') + ba = QtCore.QByteArray.fromBase64(data_base64) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qlabel.setPixmap(pixmap) + super().Update(self.QT_QLabel, visible=visible) + + update = Update + + +# ---------------------------------------------------------------------- # +# Canvas # +# ---------------------------------------------------------------------- # +class Canvas(Element): + def __init__( + self, + canvas=None, + background_color=None, + size=(None, None), + pad=None, + key=None, + k=None, + tooltip=None, + metadata=None, + ): + """ + Canvas Element - NOT USED IN QT PORT ? + :param canvas: Your own tk.Canvas if you already created it. Leave blank to create a Canvas + :type canvas: (tk.Canvas) + :param background_color: color of background + :type background_color: (str) + :param size: (width in char, height in rows) size in pixels to make canvas + :type size: Tuple[int,int] + :param pad: Amount of padding to put around element + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: Used with window.FindElement and with return values to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self._TKCanvas = canvas + + super().__init__( + ELEM_TYPE_CANVAS, + background_color=background_color, + size=size, + pad=pad, + key=key, + tooltip=tooltip, + metadata=metadata, + ) + return + + @property + def TKCanvas(self): + if self._TKCanvas is None: + print('*** Did you forget to call Finalize()? Your code should look something like: ***') + print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') + return self._TKCanvas + + +# ---------------------------------------------------------------------- # +# Graph # +# ---------------------------------------------------------------------- # +class Graph(Element): + def __init__( + self, + canvas_size, + graph_bottom_left, + graph_top_right, + background_color=None, + pad=None, + key=None, + k=None, + tooltip=None, + visible=True, + change_submits=False, + enable_events=False, + drag_submits=False, + metadata=None, + ): + """ + :param canvas_size: size of the canvas area in pixels + :type canvas_size: Tuple[int, int] + :param graph_bottom_left: (x,y) The bottoms left corner of your coordinate system + :type graph_bottom_left: Tuple[int, int] + :param graph_top_right: (x,y) The top right corner of your coordinate system + :type graph_top_right: Tuple[int, int] + :param background_color: background color of the drawing area + :type background_color: (str) + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element (Default = True) + :type visible: (bool) + :param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead + :type change_submits: (bool) + :param enable_events: If True then clicks on the Graph are immediately reported as an event. Use this instead of change_submits + :type enable_events: (bool) + :param drag_submits: if True and Events are enabled for the Graph, will report Events any time the mouse moves while button down + :type drag_submits: (bool) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.CanvasSize = canvas_size + self.BottomLeft = graph_bottom_left + self.TopRight = graph_top_right + self.x = self.y = 0 + self.Widget = self.QT_QGraphicsScene = None # type: QGraphicsScene + + super().__init__( + ELEM_TYPE_GRAPH, + background_color=background_color, + size=(None, None), + pad=pad, + key=key, + tooltip=tooltip, + visible=visible, + size_px=canvas_size, + metadata=metadata, + ) + return + + def _convert_xy_to_canvas_xy(self, x_in, y_in): + scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) + scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) + new_x = 0 + scale_x * (x_in - self.BottomLeft[0]) + new_y = self.CanvasSize[1] + scale_y * (y_in - self.BottomLeft[1]) + return new_x, new_y + + def DrawLine(self, point_from, point_to, color='black', width=1): + converted_point_from = self._convert_xy_to_canvas_xy(point_from[0], point_from[1]) + converted_point_to = self._convert_xy_to_canvas_xy(point_to[0], point_to[1]) + + qcolor = QColor(color) + pen = QPen(qcolor, width) + line = self.QT_QGraphicsScene.addLine( + self.x + converted_point_from[0], + self.y + converted_point_from[1], + self.x + converted_point_to[0], + self.y + converted_point_to[1], + pen=pen, + ) + # self.QT_QGraphicsItemGroup.addToGroup(line) + return line + + def DrawRectangle(self, top_left, bottom_right, fill_color=None, line_color=None): + converted_point_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + converted_point_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + + qcolor = QColor(line_color) + pen = QPen(qcolor, 1) + qcolor = QColor(fill_color) + brush = QBrush(qcolor) + line = self.QT_QGraphicsScene.addRect( + converted_point_top_left[0], + converted_point_top_left[1], + converted_point_bottom_right[0] - converted_point_top_left[0], + converted_point_bottom_right[1] - converted_point_top_left[1], + pen, + brush, + ) + # self.QT_QGraphicsItemGroup.addToGroup(line) + + def DrawCircle(self, center_location, radius, fill_color=None, line_color='black'): + converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1]) + qcolor = QColor(line_color) + pen = QPen(qcolor) + qcolor = QColor(fill_color) + brush = QBrush(qcolor) + circle_id = self.QT_QGraphicsScene.addEllipse( + self.x + converted_point[0] - radius, + self.y + converted_point[1] - radius, + radius * 2, + radius * 2, + pen=pen, + brush=brush, + ) + return circle_id # type: QGraphicsEllipseItem + + def RelocateFigure(self, id, x, y): + id = id # type: QtWidgets.QGraphicsEllipseItem + converted_point = self._convert_xy_to_canvas_xy(x, y) + id.setX(converted_point[0]) + id.setY(converted_point[1]) + + def DrawText(self, text, location, color='black', font=None, angle=0): + converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) + + qcolor = QColor(color) + qpath = QPainterPath() + _font = font or ('courier', 12) + qfont = QFont(_font[0], _font[1]) + # qfont.setWeight(.5) + + text_id = qpath.addText(self.x + converted_point[0], self.y + converted_point[1], qfont, str(text)) + self.QT_QGraphicsScene.addPath(qpath, qcolor) + return text_id + + def Move(self, x_direction, y_direction): + x_direction = -x_direction + y_direction = -y_direction + zero_converted = self._convert_xy_to_canvas_xy(0, 0) + shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) + shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) + rect = self.QT_QGraphicsScene.sceneRect() + rect.translate(shift_amount[0], shift_amount[1]) + self.x += shift_amount[0] + self.y += shift_amount[1] + self.QT_QGraphicsScene.setSceneRect(rect) + # items = self.QT_QGraphicsScene.items() + # print(len(items)) + # for item in items: + # if not item.isActive(): + # print('*', end='') + # item.removeFromIndex() + + # print(rect) + + def DrawOval(self, top_left, bottom_right, fill_color=None, line_color=None): + converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_oval( + converted_top_left[0], + converted_top_left[1], + converted_bottom_right[0], + converted_bottom_right[1], + fill=fill_color, + outline=line_color, + ) + + def DrawPoint(self, point, size=2, color='black'): + converted_point = self._convert_xy_to_canvas_xy(point[0], point[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_oval( + converted_point[0] - size, + converted_point[1] - size, + converted_point[0] + size, + converted_point[1] + size, + fill=color, + outline=color, + ) + + def DrawArc(self, top_left, bottom_right, extent, start_angle, style=None, arc_color='black'): + converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_arc( + converted_top_left[0], + converted_top_left[1], + converted_bottom_right[0], + converted_bottom_right[1], + extent=extent, + start=start_angle, + style='tkstyle', + outline=arc_color, + ) + + def DrawRectangleOld(self, top_left, bottom_right, fill_color=None, line_color=None): + converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_rectangle( + converted_top_left[0], + converted_top_left[1], + converted_bottom_right[0], + converted_bottom_right[1], + fill=fill_color, + outline=line_color, + ) + + def Erase(self): + if self.QT_QGraphicsScene is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + self.QT_QGraphicsScene.clear() + + def Update(self, background_color, visible=None): + # TODO + # if self._TKCanvas2 is None: + # print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + # print('Call Window.Finalize() prior to this operation') + # return None + # self._TKCanvas2.configure(background=background_color) + super().Update(self.QT_QGraphicsScene, visible=visible) + + def MoveFigure(self, figure, x_direction, y_direction): + zero_converted = self._convert_xy_to_canvas_xy(0, 0) + shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) + shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) + if figure is None: + print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') + print('Call Window.Finalize() prior to all graph operations') + return None + self._TKCanvas2.move(figure, shift_amount[0], shift_amount[1]) + + def change_coordinates(self, graph_bottom_left, graph_top_right): + """ + Changes the corrdinate system to a new one. The same 2 points in space are used to define the coorinate + system - the bottom left and the top right values of your graph. + + :param graph_bottom_left: Tuple[int, int] (x,y) The bottoms left corner of your coordinate system + :param graph_top_right: Tuple[int, int] (x,y) The top right corner of your coordinate system + """ + self.BottomLeft = graph_bottom_left + self.TopRight = graph_top_right + + @property + def TKCanvas(self): + if self._TKCanvas2 is None: + print('*** Did you forget to call Finalize()? Your code should look something like: ***') + print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') + return self._TKCanvas2 + + draw_arc = DrawArc + draw_circle = DrawCircle + draw_line = DrawLine + draw_oval = DrawOval + draw_point = DrawPoint + draw_rectangle = DrawRectangle + draw_rectangle_old = DrawRectangleOld + draw_text = DrawText + erase = Erase + move = Move + move_figure = MoveFigure + relocate_figure = RelocateFigure + update = Update + + +# ---------------------------------------------------------------------- # +# Frame # +# ---------------------------------------------------------------------- # +class Frame(Element): + def __init__( + self, + title, + layout, + title_color=None, + background_color=None, + title_location=None, + frame_color=None, + relief=DEFAULT_FRAME_RELIEF, + element_justification='float', + size=(None, None), + font=None, + pad=None, + border_width=None, + key=None, + k=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param title: text that is displayed as the Frame's "label" or title + :type title: (str) + :param layout: The layout to put inside the Frame + :type layout: List[List[Elements]] + :param title_color: color of the title text + :type title_color: (str) + :param background_color: background color of the Frame + :type background_color: (str) + :param title_location: location to place the text title. Choices include: TITLE_LOCATION_TOP TITLE_LOCATION_BOTTOM TITLE_LOCATION_LEFT TITLE_LOCATION_RIGHT TITLE_LOCATION_TOP_LEFT TITLE_LOCATION_TOP_RIGHT TITLE_LOCATION_BOTTOM_LEFT TITLE_LOCATION_BOTTOM_RIGHT + :type title_location: (enum) + :param frame_color: color of the frame lines + :type frame_color: (str) + :param relief: relief style. Values are same as other elements with reliefs. Choices include RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID + :type relief: (enum) + :param element_justification: All elements inside the Frame will have this justification 'left', 'right', 'center' are valid values + :type element_justification: (str) + :param size: (width, height) (note this parameter may not always work) + :type size: Tuple[int, int] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param border_width: width of border around element in pixels + :type border_width: (int) + :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.Rows = [] + # self.ParentForm = None + self.TKFrame = None + self.Title = title + self.Relief = relief + self.TitleLocation = title_location + self.BorderWidth = border_width + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.ElementJustification = element_justification + self.FrameColor = frame_color + self.Widget = self.QT_QGroupBox = None # type: QGroupBox + self.Layout(layout) + + super().__init__( + ELEM_TYPE_FRAME, + background_color=background_color, + text_color=title_color, + size=size, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + return + + def AddRow(self, *args): + """Parms are a variable number of Elements""" + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def Layout(self, rows): + for row in rows: + self.AddRow(*row) + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def Update(self, visible=None): + super().Update(self.QT_QGroupBox, visible=visible) + + add_row = AddRow + layout = Layout + update = Update + + +# ---------------------------------------------------------------------- # +# Separator # +# ---------------------------------------------------------------------- # +class VerticalSeparator(Element): + def __init__(self, pad=None): + """ + VerticalSeperator - A separator that spans only 1 row in a vertical fashion + :param pad: + """ + self.Orientation = 'vertical' # for now only vertical works + + super().__init__(ELEM_TYPE_SEPARATOR, pad=pad) + + +VSeperator = VerticalSeparator +VSeparator = VerticalSeparator +VSep = VerticalSeparator + + +# ---------------------------------------------------------------------- # +# Separator # +# ---------------------------------------------------------------------- # +class HorizontalSeparator(Element): + def __init__(self, pad=None, size_px=(None, None)): + """ + VerticalSeperator - A separator that spans only 1 row in a vertical fashion + :param pad: + """ + self.Orientation = 'horizontal' # for now only vertical works + + super().__init__(ELEM_TYPE_SEPARATOR, pad=pad) + + +HSeperator = HorizontalSeparator +HSep = HorizontalSeparator + + +# ---------------------------------------------------------------------- # +# Tab # +# ---------------------------------------------------------------------- # +class Tab(Element): + def __init__( + self, + title, + layout, + title_color=None, + element_justification='float', + background_color=None, + font=None, + pad=None, + disabled=False, + border_width=None, + key=None, + k=None, + tooltip=None, + visible=True, + metadata=None, + ): + """ + :param title: text to show on the tab + :type title: (str) + :param layout: The element layout that will be shown in the tab + :type layout: List[List[Element]] + :param title_color: color of the tab text (note not currently working on tkinter) + :type title_color: (str) + :param element_justification: All elements inside the Tab will have this justification 'left', 'right', 'center' are valid values + :type element_justification: (str) + :param background_color: color of background of the entire layout + :type background_color: (str) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param disabled: If True button will be created disabled + :type disabled: (bool) + :param border_width: width of border around element in pixels + :type border_width: (int) + :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.Rows = [] + self.TKFrame = None + self.Title = title + self.BorderWidth = border_width + self.Disabled = disabled + self.ParentTabGroup = None # type: TabGroup + self.TabID = None + self.ElementJustification = element_justification + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.Widget = self.QT_QWidget = None # type: QWidget + + self.Layout(layout) + + super().__init__( + ELEM_TYPE_TAB, + background_color=background_color, + text_color=title_color, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + visible=visible, + metadata=metadata, + ) + return + + def AddRow(self, *args): + """Parms are a variable number of Elements""" + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def Layout(self, rows): + for row in rows: + self.AddRow(*row) + return self + + def Update(self, disabled=None, visible=None): # TODO Disable / enable of tabs is not complete + if disabled is None: + return + self.Disabled = disabled + # state = 'disabled' if disabled is True else 'normal' + # self.ParentNotebook.tab(self.TabID, state=state) + super().Update(self.QT_QWidget, visible=visible) + + return self + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def Select(self): + """ + Selects this tab. Mimics user clicking on this tab. Must have called window.Finalize / Read first! + """ + try: + index = self.ParentTabGroup.TabList.index(self) + self.ParentTabGroup.QT_QTabWidget.setCurrentIndex(index) + except: + print('** EXCEPTION while trying to Select tab with key =', self.Key) + + add_row = AddRow + layout = Layout + select = Select + update = Update + + +# ---------------------------------------------------------------------- # +# TabGroup # +# ---------------------------------------------------------------------- # +class TabGroup(Element): + def __init__( + self, + layout, + tab_location=None, + title_color=None, + selected_title_color=None, + background_color=None, + font=None, + change_submits=False, + enable_events=False, + pad=None, + border_width=None, + theme=None, + key=None, + k=None, + tooltip=None, + visible=True, + metadata=None, + ): + """ + :param layout: Layout of Tabs. Different than normal layouts. ALL Tabs should be on first row + :type layout: List[List[Tab]] + :param tab_location: location that tabs will be displayed. Choices are left, right, top, bottom, lefttop, leftbottom, righttop, rightbottom, bottomleft, bottomright, topleft, topright + :type tab_location: (str) + :param title_color: color of text on tabs + :type title_color: (str) + :param selected_title_color: color of tab text when it is selected + :type selected_title_color: (str) + :param background_color: color of background area that tabs are located on + :type background_color: (str) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead + :type change_submits: (bool) + :param enable_events: If True then switching tabs will generate an Event + :type enable_events: (bool) + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param border_width: width of border around element in pixels + :type border_width: (int) + :param theme: DEPRICATED - You can only specify themes using set options or when window is created. It's not possible to do it on an element basis + :type theme: (enum) + :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.SelectedTitleColor = selected_title_color + self.Rows = [] + self.TKNotebook = None + self.TabCount = 0 + self.BorderWidth = border_width + self.Theme = theme + self.BackgroundColor = background_color if background_color is not None else COLOR_SYSTEM_DEFAULT + self.ChangeSubmits = change_submits or enable_events + self.TabLocation = tab_location + self.TabList = [] # type: List[Tab] + self.Widget = self.QT_QTabWidget = None # type: QTabWidget + self.ElementJustification = 'float' # not actually used, but needed for packer to work + self.Layout(layout) + + super().__init__( + ELEM_TYPE_TAB_GROUP, + background_color=self.BackgroundColor, + text_color=title_color, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + visible=visible, + metadata=metadata, + ) + return + + def AddRow(self, *args): + """Parms are a variable number of Elements""" + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + element.ParentTabGroup = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + self.TabList.append(element) + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def Layout(self, rows): + for row in rows: + self.AddRow(*row) + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def FindKeyFromTabName(self, tab_name): + for row in self.Rows: + for element in row: + if element.Title == tab_name: + return element.Key + return None + + def Update(self, visible=None): + super().Update(self.QT_QTabWidget, visible=visible) + return self + + def QtCallbackStateChanged(self, state): + if self.ChangeSubmits: + _element_callback_quit_mainloop(self) + + def Get(self): + """ + Returns the current value for the Tab Group, which will be the currently selected tab's KEY or the text on + the tab if no key is defined. Returns None if an error occurs. + Note that this is exactly the same data that would be returned from a call to Window.Read. Are you sure you + are using this method correctly? + + :return: Union[Any, None] The key of the currently selected tab or the tab's text if it has no key + """ + value = None + try: + cur_index = self.QT_QTabWidget.currentIndex() + tab_element = self.TabList[cur_index] + value = tab_element.Key + except: + value = None + return value + + add_row = AddRow + find_key_from_tab_name = FindKeyFromTabName + get = Get + update = Update + + +# ---------------------------------------------------------------------- # +# Slider # +# ---------------------------------------------------------------------- # +class Slider(Element): + def __init__( + self, + range=(None, None), + default_value=None, + resolution=None, + tick_interval=None, + orientation=None, + border_width=None, + relief=None, + change_submits=False, + enable_events=False, + disabled=False, + size=(None, None), + font=None, + background_color=None, + text_color=None, + key=None, + k=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param range: slider's range (min value, max value) + :type range: Union[Tuple[int, int], Tuple[float, float]] + :param default_value: starting value for the slider + :type default_value: Union[int, float] + :param resolution: the smallest amount the slider can be moved + :type resolution: Union[int, float] + :param tick_interval: how often a visible tick should be shown next to slider + :type tick_interval: Union[int, float] + :param orientation: 'horizontal' or 'vertical' ('h' or 'v' also work) + :type orientation: (str) + :param border_width: width of border around element in pixels + :type border_width: (int) + :param relief: relief style. RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID + :type relief: (enum) + :param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead + :type change_submits: (bool) + :param enable_events: If True then moving the slider will generate an Event + :type enable_events: (bool) + :param disabled: set disable state for element + :type disabled: (bool) + :param size: (w=characters-wide, h=rows-high) + :type size: Tuple[int, int] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param background_color: color of slider's background + :type background_color: (str) + :param text_color: color of the slider's text + :type text_color: (str) + :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.TKScale = None + self.Range = (1, 10) if range == (None, None) else range + self.DefaultValue = self.Range[0] if default_value is None else default_value + self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION + self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH + self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF + self.Resolution = 1 if resolution is None else resolution + self.ChangeSubmits = change_submits or enable_events + self.Disabled = disabled + self.TickInterval = tick_interval if tick_interval is not None else self.Range[1] // 10 + temp_size = size + if temp_size == (None, None): + temp_size = (150, 30) if self.Orientation.startswith('h') else (30, 150) + elif size[0] is not None and size[0] < 100: + temp_size = size[0] * 10, size[1] * 3 + self.Widget = self.QT_Slider = None # type:QSlider + + super().__init__( + ELEM_TYPE_INPUT_SLIDER, + size=temp_size, + font=font, + background_color=background_color, + text_color=text_color, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + return + + def _QtCallbackValueChanged(self, value): + if not self.ChangeSubmits: + return + _element_callback_quit_mainloop(self) + + def Update(self, value=None, range=(None, None), disabled=None, visible=None): + if value is not None: + self.QT_Slider.setValue(int(value)) + self.DefaultValue = value + if disabled == True: + self.QT_Slider.setDisabled(True) + elif disabled == False: + self.QT_Slider.setDisabled(False) + if range != (None, None): + self.Range = range + self.QT_Slider.setMinimum(range[0]) + self.QT_Slider.setMaximum(range[1]) + super().Update(self.QT_Slider, visible=visible) + + update = Update + + +# ---------------------------------------------------------------------- # +# Dial # +# ---------------------------------------------------------------------- # +class Dial(Element): + def __init__( + self, + range=(None, None), + default_value=None, + resolution=None, + tick_interval=None, + orientation=None, + border_width=None, + relief=None, + change_submits=False, + enable_events=False, + disabled=False, + size=(None, None), + font=None, + background_color=None, + text_color=None, + key=None, + k=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param range: slider's range (min value, max value) + :type range: Union[Tuple[int, int], Tuple[float, float]] + :param default_value: Choice to be displayed as initial value. Must match one of values variable contents + :type default_value: (Any) + :param resolution: the smallest amount the slider can be moved + :type resolution: Union[int, float] + :param tick_interval: how often a visible tick should be shown next to slider + :type tick_interval: Union[int, float] + :param orientation: 'horizontal' or 'vertical' ('h' or 'v' also work) + :type orientation: (str) + :param border_width: width of border around button in pixels + :type border_width: (int) + :param relief: relief style. RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID + :type relief: (enum) + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. If this button is a target, should it generate an event when filled in + :type enable_events: (bool) + :param disabled: set disable state for element + :type disabled: (bool) + :param size: (width, height) of the button in characters wide, rows high + :type size: Tuple[int, int] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param background_color: color of slider's background + :type background_color: (str) + :param text_color: color of the element's text + :type text_color: (str) + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.TKScale = None + self.Range = (1, 10) if range == (None, None) else range + self.DefaultValue = self.Range[0] if default_value is None else default_value + self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION + self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH + self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF + self.Resolution = 1 if resolution is None else resolution + self.ChangeSubmits = change_submits or enable_events + self.Disabled = disabled + self.TickInterval = tick_interval + temp_size = size + if temp_size == (None, None): + temp_size = (20, 20) if self.Orientation.startswith('h') else (8, 20) + self.Widget = self.QT_Dial = None # type: QDial + + super().__init__( + ELEM_TYPE_INPUT_DIAL, + size=temp_size, + font=font, + background_color=background_color, + text_color=text_color, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + return + + def Update(self, value=None, range=(None, None), disabled=None, visible=None): + if value is not None: # TODO clearly not done! + pass + self.DefaultValue = value + if disabled == True: + pass + elif disabled == False: + pass + super().Update(self.QT_Dial, visible=visible) + + def _QtCallbackValueChanged(self, value): + if not self.ChangeSubmits: + return + _element_callback_quit_mainloop(self) + + update = Update + + +# ---------------------------------------------------------------------- # +# Stretch # +# ---------------------------------------------------------------------- # +class Stretch(Element): + def __init__( + self, + size=(None, None), + font=None, + background_color=None, + text_color=None, + key=None, + k=None, + pad=None, + tooltip=None, + ): + """ + :param size: (width, height) of the button in characters wide, rows high + :type size: Tuple[int, int] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param background_color: color of slider's background + :type background_color: (str) + :param text_color: color of the element's text + :type text_color: (str) + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + """ + key = key if key is not None else k + self.Widget = None # type: Stretch + super().__init__( + ELEM_TYPE_STRETCH, + size=size, + font=font, + background_color=background_color, + text_color=text_color, + key=key, + pad=pad, + tooltip=tooltip, + ) + return + + +# ---------------------------------------------------------------------- # +# Column # +# ---------------------------------------------------------------------- # +class Column(Element): + def __init__( + self, + layout, + background_color=None, + element_justification='float', + size=(None, None), + pad=None, + scrollable=False, + key=None, + k=None, + visible=True, + metadata=None, + ): + """ + :param layout: Layout that will be shown in the Column container + :type layout: List[List[Element]] + :param background_color: color of background of entire Column + :type background_color: (str) + :param element_justification: All elements inside the Column will have this justification 'left', 'right', 'center' are valid values + :type element_justification: (str) + :param size: (width, height) size in pixels (doesn't work quite right, sometimes only 1 dimension is set by tkinter + :type size: Tuple[int, int] + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param scrollable: if True then scrollbars will be added to the column + :type scrollable: (bool) + :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param visible: set visibility state of the element + :type visible: (bool) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.Rows = [] + self.TKFrame = None + self.Scrollable = scrollable + # self.ImageFilename = image_filename + # self.ImageData = image_data + # self.ImageSize = image_size + # self.ImageSubsample = image_subsample + bg = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.ElementJustification = element_justification + self.Widget = self.QT_QGroupBox = None # type: QGroupBox + self.vbox_layout = None # type: QVBoxLayout + self.Layout(layout) + + super().__init__(ELEM_TYPE_COLUMN, background_color=bg, size=size, pad=pad, key=key, visible=visible, metadata=metadata) + return + + def AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def Layout(self, rows): + for row in rows: + self.AddRow(*row) + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def Update(self, visible=None): + + super().Update(self.QT_QGroupBox, visible=visible) + + add_row = AddRow + layout = Layout + update = Update + + +Col = Column + + +# ---------------------------------------------------------------------- # +# Menu # +# ---------------------------------------------------------------------- # +class Menu(Element): + def __init__( + self, + menu_definition, + background_color=None, + size=(None, None), + tearoff=False, + pad=None, + key=None, + k=None, + visible=True, + metadata=None, + ): + """ + :param menu_definition: a menu definition (in menu definition format) + :type menu_definition: List[List[Tuple[str, List[str]]] + :param background_color: color of the background + :type background_color: (str) + :param size: Not used in the tkinter port + :type size: Tuple[int, int] + :param tearoff: if True, then can tear the menu off from the window ans use as a floating window. Very cool effect + :type tearoff: (bool) + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param visible: set visibility state of the element + :type visible: (bool) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.MenuItemTextColor = theme_text_color() + self.MenuItemBackgroundColor = theme_background_color() + self.MenuDefinition = menu_definition + self.TKMenu = None + self.Tearoff = tearoff + self.IsButtonMenu = False + self.MenuItemChosen = None + self.Widget = self.QT_QMenuBar = None # type: QMenuBar + + super().__init__( + ELEM_TYPE_MENUBAR, + background_color=background_color, + size=size, + pad=pad, + key=key, + visible=visible, + metadata=metadata, + ) + + def _QT_MenuItemChosenCallback(self, item_chosen): + # print('IN MENU ITEM CALLBACK', item_chosen) + self.MenuItemChosen = item_chosen.replace('&', '') + _element_callback_quit_mainloop(self) + # self.ParentForm.LastButtonClicked = item_chosen + # self.ParentForm.FormRemainedOpen = True + # if self.ParentForm.CurrentlyRunningMainloop: + # pass # TODO # kick the users out of the mainloop + + def Update(self, menu_definition=None, visible=None): + if menu_definition is not None: + menu_def = menu_definition + self.MenuDefinition = menu_def + self.QT_QMenuBar = QMenuBar(self.ParentForm.QT_QMainWindow) + + for menu_entry in menu_def: + # print(f'Adding a Menubar ENTRY {menu_entry}') + baritem = QMenu(self.QT_QMenuBar) + if menu_entry[0][0] == MENU_DISABLED_CHARACTER: + baritem.setDisabled(True) + baritem.setTitle(menu_entry[0][1:]) + else: + baritem.setTitle(menu_entry[0]) + self.QT_QMenuBar.addAction(baritem.menuAction()) + AddMenuItem(baritem, menu_entry[1], self) + + self.ParentForm.QT_QMainWindow.setMenuBar(self.QT_QMenuBar) + super().Update(self.QT_QMenuBar, visible=visible) + + update = Update + + +# ---------------------------------------------------------------------- # +# Table # +# ---------------------------------------------------------------------- # +class Table(Element): + def __init__( + self, + values, + headings=None, + visible_column_map=None, + col_widths=None, + def_col_width=10, + auto_size_columns=True, + max_col_width=20, + select_mode=None, + display_row_numbers=False, + num_rows=None, + font=None, + justification='right', + header_text_color=None, + header_background_color=None, + header_font=None, + text_color=None, + background_color=None, + alternating_row_color=None, + size=(None, None), + change_submits=False, + enable_events=False, + bind_return_key=False, + pad=None, + key=None, + k=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param values: ??? + :type values: List[List[Union[str, int, float]]] + :param headings: The headings to show on the top line + :type headings: Union[List[str], Tuple[str]] + :param visible_column_map: One entry for each column. False indicates the column is not shown + :type visible_column_map: List[bool] + :param col_widths: Number of characters that each column will occupy + :type col_widths: List[int] + :param def_col_width: Default column width in characters + :type def_col_width: (int) + :param auto_size_columns: if True columns will be sized automatically + :type auto_size_columns: (bool) + :param max_col_width: Maximum width for all columns in characters + :type max_col_width: (int) + :param select_mode: Select Mode. Valid values start with "TABLE_SELECT_MODE_". Valid values are: TABLE_SELECT_MODE_NONE TABLE_SELECT_MODE_BROWSE TABLE_SELECT_MODE_EXTENDED + :type select_mode: (enum) + :param display_row_numbers: if True, the first column of the table will be the row # + :type display_row_numbers: (bool) + :param num_rows: The number of rows of the table to display at a time + :type num_rows: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param justification: 'left', 'right', 'center' are valid choices + :type justification: (str) + :param header_text_color: sets the text color for the header + :type header_text_color: (str) + :param header_background_color: sets the background color for the header + :type header_background_color: (str) + :param header_font: specifies the font family, size, etc + :type header_font: Union[str, Tuple[str, int]] + :param text_color: color of the text + :type text_color: (str) + :param background_color: color of background + :type background_color: (str) + :param alternating_row_color: if set then every other row will have this color in the background. + :type alternating_row_color: (str) + :param size: DO NOT USE! Use num_rows instead + :type size: Tuple[int, int] + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. Table events happen when row is clicked + :type enable_events: (bool) + :param bind_return_key: if True, pressing return key will cause event coming from Table, ALSO a left button double click will generate an event if this parameter is True + :type bind_return_key: (bool) + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.Values = values + self.ColumnHeadings = headings + self.ColumnsToDisplay = visible_column_map + self.ColumnWidths = col_widths + self.MaxColumnWidth = max_col_width + self.DefaultColumnWidth = def_col_width + self.AutoSizeColumns = auto_size_columns + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.TextColor = text_color + self.HeaderTextColor = header_text_color if header_text_color is not None else theme_input_text_color() + self.HeaderBackgroundColor = header_background_color if header_background_color is not None else theme_input_background_color() + self.HeaderFont = header_font + self.Justification = justification + self.InitialState = None + self.SelectMode = select_mode + self.DisplayRowNumbers = display_row_numbers + self.NumRows = num_rows if num_rows is not None else size[1] + self.TKTreeview = None + self.AlternatingRowColor = alternating_row_color + self.SelectedRows = [] + self.ChangeSubmits = change_submits or enable_events + self.BindReturnKey = bind_return_key + self.Widget = self.QT_TableWidget = None # type: QTableWidget + + super().__init__( + ELEM_TYPE_TABLE, + text_color=text_color, + background_color=background_color, + font=font, + size=size, + pad=pad, + key=key, + tooltip=tooltip, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + return + + def _QtCallbackCellActivated(self, value=None): + # print('CELL ACTIVATED ', value) + # first, get the results table built + # modify the Results table in the parent FlexForm object + if not self.ChangeSubmits: + return + _element_callback_quit_mainloop(self) + + def _QtCallbackVerticalHeader(self, value): + print('Vertical Header value ', value) + + def Update(self, values=None, num_rows=None, visible=None): + if values is not None: + self.Values = values + self.SelectedRows = [] + self.QT_TableWidget.clearContents() + if len(values) != 0: + self.QT_TableWidget.setRowCount(len(self.Values)) + self.QT_TableWidget.setColumnCount(len(self.Values[0])) + for rownum, rows in enumerate(self.Values): + # self.QT_TableWidget.insertRow(rownum) + for colnum, columns in enumerate(rows): + self.QT_TableWidget.setItem(rownum, colnum, QTableWidgetItem(self.Values[rownum][colnum])) + if num_rows is not None: + self.QT_TableWidget.setFixedHeight(num_rows * 35 + 25) # convert num rows into pixels...crude but effective + + super().Update(self.QT_TableWidget, visible=visible) + + def Get(self): + num_rows = self.QT_TableWidget.rowCount() + num_cols = self.QT_TableWidget.columnCount() + table = [] + for row in range(num_rows): + row_list = [] + for col in range(num_cols): + item = self.QT_TableWidget.item(row, col).text() + row_list.append(item) + table.append(row_list) + + return table + + def _treeview_selected(self, event): + if self.ChangeSubmits: + MyForm = self.ParentForm + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '' + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + pass # TODO Quit mainloop + + def treeview_double_click(self, event): + if self.BindReturnKey: + MyForm = self.ParentForm + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '' + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + pass # TODO Quit mainloop + + class QTTableWidget(QTableWidget): + def __init__(self, enable_key_events, window): + self.KeyEventsEnabled = enable_key_events + self.Window = window + super().__init__() + + def eventFilter(self, widget, event): + # print(event.type()) + if event.type() == QEvent.MouseButtonPress and self.Window.GrabAnywhere: + self.mouse_offset = event.pos() + if event.type() == QEvent.MouseMove and self.Window.GrabAnywhere: + x = event.globalX() + y = event.globalY() + x_w = self.mouse_offset.x() + y_w = self.mouse_offset.y() + self.move(x - x_w, y - y_w) + + if event.type() == QEvent.KeyRelease and self.KeyEventsEnabled: + # print("got key event") + key = event.key() + try: + self.Window.LastButtonClicked = chr(key).lower() + except: + self.Window.LastButtonClicked = 'special %s' % key + self.Window.FormRemainedOpen = True + if self.Window.CurrentlyRunningMainloop: + self.Window.QTApplication.exit() + return QWidget.eventFilter(self, widget, event) + + get = Get + update = Update + + +# ---------------------------------------------------------------------- # +# Tree # +# ---------------------------------------------------------------------- # +class Tree(Element): + def __init__( + self, + data=None, + headings=None, + visible_column_map=None, + col_widths=None, + col0_width=10, + def_col_width=10, + auto_size_columns=True, + max_col_width=20, + select_mode=None, + show_expanded=False, + change_submits=False, + enable_events=False, + font=None, + size=(200, 600), + justification='right', + header_text_color=None, + header_background_color=None, + header_font=None, + text_color=None, + background_color=None, + num_rows=None, + pad=None, + key=None, + k=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + :param data: The data represented using a PySimpleGUI provided TreeData class + :type data: (TreeData) + :param headings: List of individual headings for each column + :type headings: List[str] + :param visible_column_map: Determines if a column should be visible. If left empty, all columns will be shown + :type visible_column_map: List[bool] + :param col_widths: List of column widths so that individual column widths can be controlled + :type col_widths: List[int] + :param col0_width: Size of Column 0 which is where the row numbers will be optionally shown + :type col0_width: (int) + :param def_col_width: default column width + :type def_col_width: (int) + :param auto_size_columns: if True, the size of a column is determined using the contents of the column + :type auto_size_columns: (bool) + :param max_col_width: the maximum size a column can be + :type max_col_width: (int) + :param select_mode: Use same values as found on Table Element. Valid values include: TABLE_SELECT_MODE_NONE TABLE_SELECT_MODE_BROWSE TABLE_SELECT_MODE_EXTENDED + :type select_mode: (enum) + :param show_expanded: if True then the tree will be initially shown with all nodes completely expanded + :type show_expanded: (bool) + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. Tree events happen when row is clicked + :type enable_events: (bool) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param size: ??? + :type size: ??? + :param justification: 'left', 'right', 'center' are valid choices + :type justification: (str) + :param header_text_color: sets the text color for the header + :type header_text_color: (str) + :param header_background_color: sets the background color for the header + :type header_background_color: (str) + :param header_font: specifies the font family, size, etc + :type header_font: Union[str, Tuple[str, int]] + :param text_color: color of the text + :type text_color: (str) + :param background_color: color of background + :type background_color: (str) + :param num_rows: The number of rows of the table to display at a time + :type num_rows: (int) + :param pad: Amount of padding to put around element (left/right, top/bottom) or ((left, right), (top, bottom)) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: Used with window.FindElement and with return values to uniquely identify this element to uniquely identify this element + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param visible: set visibility state of the element + :type visible: (bool) + :param size_px: size in pixels (width, height). Will override the size parameter + :type size_px: Tupple[int, int] (width, height) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + key = key if key is not None else k + self.TreeData = data + self.ColumnHeadings = headings + self.ColumnsToDisplay = visible_column_map + self.ColumnWidths = col_widths + self.MaxColumnWidth = max_col_width + self.DefaultColumnWidth = def_col_width + self.AutoSizeColumns = auto_size_columns + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.TextColor = text_color + self.HeaderTextColor = header_text_color if header_text_color is not None else theme_input_text_color() + self.HeaderBackgroundColor = header_background_color if header_background_color is not None else theme_input_background_color() + self.HeaderFont = header_font + self.Justification = justification + self.InitialState = None + self.SelectMode = select_mode + self.ShowExpanded = show_expanded + self.NumRows = num_rows + self.Col0Width = col0_width + self.TKTreeview = None + self.SelectedRows = [] + self.ChangeSubmits = change_submits or enable_events + self.Size = size + self.Widget = self.QT_QTreeWidget = None # type: QTreeWidget + super().__init__( + ELEM_TYPE_TREE, + text_color=text_color, + background_color=background_color, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + size=size, + visible=visible, + size_px=size_px, + metadata=metadata, + ) + return + + def _treeview_selected(self, event): + selections = 000000 + self.SelectedRows = [x for x in selections] + # print('Got selection') + if self.ChangeSubmits: + _element_callback_quit_mainloop(self) + + def _QtCallbackCellActivated(self, value=None): + # print('CELL ACTIVATED ', value) + # first, get the results table built + # modify the Results table in the parent FlexForm object + if not self.ChangeSubmits: + return + _element_callback_quit_mainloop(self) + + # if self.ChangeSubmits: + # MyForm = self.ParentForm + # if self.Key is not None: + # self.ParentForm.LastButtonClicked = self.Key + # else: + # self.ParentForm.LastButtonClicked = '' + # self.ParentForm.FormRemainedOpen = True + # if self.ParentForm.CurrentlyRunningMainloop: + # self.ParentForm.TKroot.quit() + + def Update(self, values=None, key=None, value=None, text=None, visible=None): + if values is not None: + self.TreeData = values + self.SelectedRows = [] + # self.QT_QTreeWidget = QTreeWidget() + TreeWidgetItems = QTreeWidgetItemIterator(self.QT_QTreeWidget) + + for item in TreeWidgetItems: + item2 = item.value() + self.QT_QTreeWidget.removeItemWidget(item2, 0) + + # def add_treeview_data(node, widget): + # # print(f'Inserting {node.key} under parent {node.parent}') + # child = QTreeWidgetItem(widget) + # if node.key != '': + # child.setText(0, str(node.text)) + # # child.setData(0,0,node.values) + # if node.icon is not None: + # qicon = QIcon(node.icon) + # child.setIcon(0, qicon) + # for node in node.children: + # add_treeview_data(node, child) + + def add_treeview_data(node, widget): + # print(f'Inserting {node.key} under parent {node.parent}') + if node != self.TreeData.root_node: + child = QTreeWidgetItem(widget) + child.setText(0, str(node.text)) + else: + child = widget + # if node.key != '': + # child.setData(0,0,node.values) + if type(node.icon) is bytes: + ba = QtCore.QByteArray.fromBase64(node.icon) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qicon = QIcon(pixmap) + child.setIcon(0, qicon) + elif node.icon is not None: + qicon = QIcon(node.icon) + child.setIcon(0, qicon) + for node in node.children: + add_treeview_data(node, child) + return + + add_treeview_data(self.TreeData.root_node, self.QT_QTreeWidget) + + if key is not None: + pass + super().Update(self.QT_QTreeWidget, visible=visible) + + return self + + update = Update + + +class TreeData(object): + class Node(object): + def __init__(self, parent, key, text, values, icon=None): + self.parent = parent + self.children = [] + self.key = key + self.text = text + self.values = values + self.icon = icon + + def _Add(self, node): + self.children.append(node) + + def __init__(self): + self.tree_dict = {} + self.root_node = self.Node('', '', 'root', [], None) + self.tree_dict[''] = self.root_node + + def _AddNode(self, key, node): + self.tree_dict[key] = node + + def Insert(self, parent, key, text, values, icon=None): + node = self.Node(parent, key, text, values, icon) + self.tree_dict[key] = node + parent_node = self.tree_dict[parent] + parent_node._Add(node) + + def __repr__(self): + return self._NodeStr(self.root_node, 1) + + def _NodeStr(self, node, level): + return '\n'.join([str(node.key) + ' : ' + str(node.text)] + [' ' * 4 * level + self._NodeStr(child, level + 1) for child in node.children]) + + insert = Insert + + +# ---------------------------------------------------------------------- # +# Error Element # +# ---------------------------------------------------------------------- # +class ErrorElement(Element): + def __init__(self, key=None, k=None): + """ + Error Element + :param key: key for uniquely identify this element (for window.FindElement) + :type key: (Any) + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + """ + self.Key = key + self.Widget = None + + super().__init__(ELEM_TYPE_ERROR, key=key) + return + + def Update(self, *args, **kwargs): + PopupError( + 'Keyword error in Update', + 'You need to stop this madness and check your spelling', + 'Bad key = {}'.format(self.Key), + 'Your bad line of code may resemble this:', + 'window.FindElement("{}")'.format(self.Key), + ) + return self + + def Get(self): + return 'This is NOT a valid Element!\nSTOP trying to do things with it or I will have to crash at some point!' + + get = Get + update = Update + + +# ---------------------------------------------------------------------- # +# Pane Element # +# ---------------------------------------------------------------------- # + +# This is for source code compatibility with tkinter version. No Qt equivalent +Pane = ErrorElement + + +# ------------------------------------------------------------------------- # +# Tray CLASS # +# ------------------------------------------------------------------------- # +class SystemTray: + def __init__(self, menu=None, filename=None, data=None, data_base64=None, tooltip=None, metadata=None): + """ + SystemTray - create an icon in the system tray + :param menu: Menu definition. Example - ['UNUSED', ['My', 'Simple', '---', 'Menu', 'Exit']] + :type menu: List[List[List[str] or str]] + :param filename: filename for icon + :type filename: (str) + :param data: in-ram image for icon (same as data_base64 parm) + :type data: (bytes) + :param data_base64: base-64 data for icon + :type data_base64: (bytes) + :param tooltip: tooltip string + :type tooltip: (str) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + self.Menu = menu + self.TrayIcon = None + self.Shown = False + self.MenuItemChosen = TIMEOUT_KEY + self.LastMessage = None + self.LastTitle = None + self.metadata = metadata + + if Window.QTApplication is None: + Window.QTApplication = QApplication(sys.argv) + self.App = Window.QTApplication + self.Widget = self.QWidget = QWidget() # type: QWidget + + if filename is None and data is None and data_base64 is None: + data_base64 = DEFAULT_BASE64_ICON + qicon = None + if filename is not None: + qicon = QIcon(filename) + elif data is not None: + ba = QtCore.QByteArray.fromRawData(data) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qicon = QIcon(pixmap) + elif data_base64 is not None: + ba = QtCore.QByteArray.fromBase64(data_base64) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qicon = QIcon(pixmap) + if qicon is None: + PopupError('ERROR - Tray must have one form of Icon specified') + return + self.TrayIcon = QSystemTrayIcon(qicon) + + if self.Menu is not None: + qmenu = QMenu() + qmenu.setTitle(self.Menu[0]) + AddTrayMenuItem(qmenu, self.Menu[1], self) + self.TrayIcon.setContextMenu(qmenu) + + if tooltip is not None: + self.TrayIcon.setToolTip(str(tooltip)) + + self.TrayIcon.messageClicked.connect(self._message_clicked) + self.TrayIcon.activated.connect(self._double_clicked) + + self.TrayIcon.show() + + def _QT_MenuItemChosenCallback(self, item_chosen): + self.MenuItemChosen = item_chosen.replace('&', '') + self.App.exit() # kick the users out of the mainloop + + # callback function when message is clicked + def _message_clicked(self): + self.MenuItemChosen = EVENT_SYSTEM_TRAY_MESSAGE_CLICKED + self.App.exit() + + def _double_clicked(self, reason): + # print(reason) + if reason == QSystemTrayIcon.DoubleClick: + self.MenuItemChosen = EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED + self.App.exit() + if reason == QSystemTrayIcon.Trigger: + self.MenuItemChosen = EVENT_SYSTEM_TRAY_ICON_ACTIVATED + self.App.exit() + + def Read(self, timeout=None): + """ + Reads the context menu + :param timeout: Optional. Any value other than None indicates a non-blocking read + :return: + """ + if not self.Shown: + self.Shown = True + self.TrayIcon.show() + if timeout is None: + self.App.exec_() + elif timeout == 0: + self.App.processEvents() + else: + self.timer = start_systray_read_timer(self, timeout) + self.App.exec_() + + if self.timer: + stop_timer(self.timer) + + item = self.MenuItemChosen + self.MenuItemChosen = TIMEOUT_KEY + return item + + def _timer_timeout(self): + self.App.exit() # kick the users out of the mainloop + + def Hide(self): + self.TrayIcon.hide() + + def UnHide(self): + self.TrayIcon.show() + + def ShowMessage(self, title, message, filename=None, data=None, data_base64=None, messageicon=None, time=10000): + """ + Shows a balloon above icon in system tray + :param title: Title shown in balloon + :param message: Message to be displayed + :param filename: Optional icon filename + :param data: Optional in-ram icon + :param data_base64: Optional base64 icon + :param time: How long to display message in milliseconds + :return: + """ + qicon = None + if filename is not None: + qicon = QIcon(filename) + elif data is not None: + ba = QtCore.QByteArray.fromRawData(data) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qicon = QIcon(pixmap) + elif data_base64 is not None: + ba = QtCore.QByteArray.fromBase64(data_base64) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qicon = QIcon(pixmap) + + if qicon is not None: + self.TrayIcon.showMessage(title, message, qicon, time) + elif messageicon is not None: + self.TrayIcon.showMessage(title, message, messageicon, time) + else: + self.TrayIcon.showMessage(title, message, QIcon(), time) + + self.LastMessage = message + self.LastTitle = title + return self + + def Close(self): + """ + + :return: + """ + self.Hide() + # Don't close app because windows could be depending on it + # self.App.quit() + + def Update( + self, + menu=None, + tooltip=None, + filename=None, + data=None, + data_base64=None, + ): + """ + Updates the menu, tooltip or icon + :param menu: menu defintion + :param tooltip: string representing tooltip + :param filename: icon filename + :param data: icon raw image + :param data_base64: icon base 64 image + :return: + """ + # Menu + if menu is not None: + self.Menu = menu + qmenu = QMenu() + qmenu.setTitle(self.Menu[0]) + AddTrayMenuItem(qmenu, self.Menu[1], self) + self.TrayIcon.setContextMenu(qmenu) + # Tooltip + if tooltip is not None: + self.TrayIcon.setToolTip(str(tooltip)) + # Icon + qicon = None + if filename is not None: + qicon = QIcon(filename) + elif data is not None: + ba = QtCore.QByteArray.fromRawData(data) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qicon = QIcon(pixmap) + elif data_base64 is not None: + ba = QtCore.QByteArray.fromBase64(data_base64) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qicon = QIcon(pixmap) + if qicon is not None: + self.TrayIcon.setIcon(qicon) + + close = Close + hide = Hide + read = Read + show_message = ShowMessage + un_hide = UnHide + update = Update + + +# ------------------------------------------------------------------------- # +# Window CLASS # +# ------------------------------------------------------------------------- # +class Window: + + NumOpenWindows = 0 + user_defined_icon = None + hidden_master_root = None + QTApplication = None + active_popups = {} + + def __init__( + self, + title, + layout=None, + default_element_size=DEFAULT_ELEMENT_SIZE, + default_button_element_size=(None, None), + auto_size_text=None, + auto_size_buttons=None, + location=(None, None), + size=(None, None), + element_padding=None, + margins=(None, None), + button_color=None, + font=None, + progress_bar_color=(None, None), + background_color=None, + border_depth=None, + auto_close=False, + auto_close_duration=DEFAULT_AUTOCLOSE_TIME, + icon=DEFAULT_WINDOW_ICON, + force_toplevel=False, + alpha_channel=1, + return_keyboard_events=False, + use_default_focus=True, + text_justification=None, + element_justification='float', + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + resizable=True, + disable_close=False, + disable_minimize=False, + background_image=None, + finalize=False, + metadata=None, + ): + """ + :param title: The title that will be displayed in the Titlebar and on the Taskbar + :type title: (str) + :param layout: The layout for the window. Can also be specified in the Layout method + :type layout: List[List[Elements]] + :param default_element_size: size in characters (wide) and rows (high) for all elements in this window + :type default_element_size: Tuple[int, int] - (width, height) + :param default_button_element_size: (width, height) size in characters (wide) and rows (high) for all Button elements in this window + :type default_button_element_size: Tuple[int, int] + :param auto_size_text: True if Elements in Window should be sized to exactly fir the length of text + :type auto_size_text: (bool) + :param auto_size_buttons: True if Buttons in this Window should be sized to exactly fit the text on this. + :type auto_size_buttons: (bool) + :param location: (x,y) location, in pixels, to locate the upper left corner of the window on the screen. Default is to center on screen. + :type location: Tuple[int, int] + :param size: (width, height) size in pixels for this window. Normally the window is autosized to fit contents, not set to an absolute size by the user + :type size: Tuple[int, int] + :param element_padding: Default amount of padding to put around elements in window (left/right, top/bottom) or ((left, right), (top, bottom)) + :type element_padding: Tuple[int, int] or ((int, int),(int,int)) + :param margins: (left/right, top/bottom) Not yet implemented! Parameter here for potability purposes. + :type margins: Tuple[int, int] + :param button_color: Default button colors for all buttons in the window + :type button_color: Tuple[str, str] == (text color, button color) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param progress_bar_color: (bar color, background color) Sets the default colors for all progress bars in the window + :type progress_bar_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param border_depth: Default border depth (width) for all elements in the window + :type border_depth: (int) + :param auto_close: If True, the window will automatically close itself + :type auto_close: (bool) + :param auto_close_duration: Number of seconds to wait before closing the window + :type auto_close_duration: (int) + :param icon: Can be either a filename or Base64 value. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO + :type icon: Union[str, str] + :param force_toplevel: If True will cause this window to skip the normal use of a hidden master window + :type force_toplevel: (bool) + :param alpha_channel: Sets the opacity of the window. 0 = invisible 1 = completely visible. Values bewteen 0 & 1 will produce semi-transparent windows in SOME environments (The Raspberry Pi always has this value at 1 and cannot change. + :type alpha_channel: (float) + :param return_keyboard_events: if True key presses on the keyboard will be returned as Events from Read calls + :type return_keyboard_events: (bool) + :param use_default_focus: If True will use the default focus algorithm to set the focus to the "Correct" element + :type use_default_focus: (bool) + :param text_justification: Default text justification for all Text Elements in window + :type text_justification: Union['left', 'right', 'center'] + :param element_justification: All elements in the Window itself will have this justification 'left', 'right', 'center' are valid values + :type element_justification: (str) + :param no_titlebar: If true, no titlebar nor frame will be shown on window. This means you cannot minimize the window and it will not show up on the taskbar + :type no_titlebar: (bool) + :param grab_anywhere: If True can use mouse to click and drag to move the window. Almost every location of the window will work except input fields on some systems + :type grab_anywhere: (bool) + :param keep_on_top: If True, window will be created on top of all other windows on screen. It can be bumped down if another window created with this parm + :type keep_on_top: (bool) + :param resizable: If True, allows the user to resize the window. Note the not all Elements will change size or location when resizing. + :type resizable: (bool) + :param disable_close: If True, the X button in the top right corner of the window will no work. Use with caution and always give a way out toyour users + :type disable_close: (bool) + :param disable_minimize: if True the user won't be able to minimize window. Good for taking over entire screen and staying that way. + :type disable_minimize: (bool) + :param background_image: ??? + :type background_image: ??? + :param finalize: If True then the Finalize method will be called. Use this rather than chaining .Finalize for cleaner code + :type finalize: (bool) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) + """ + self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT + self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS + self.Title = title + self.Rows = [] # a list of ELEMENTS for this row + self.DefaultElementSize = _convert_tkinter_size_to_Qt(default_element_size) + self.DefaultButtonElementSize = _convert_tkinter_size_to_Qt(default_button_element_size) if default_button_element_size != (None, None) else DEFAULT_BUTTON_ELEMENT_SIZE + self.Location = location + self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR + self.BackgroundColor = background_color if background_color else DEFAULT_BACKGROUND_COLOR + self.ParentWindow = None + self.Font = font if font else DEFAULT_FONT + self.RadioDict = {} + self.BorderDepth = border_depth + self.WindowIcon = icon if icon is not None else Window.user_defined_icon + self.AutoClose = auto_close + self.NonBlocking = False + self.TKroot = None + self.TKrootDestroyed = False + self.CurrentlyRunningMainloop = False + self.FormRemainedOpen = False + self.TKAfterID = None + self.ProgressBarColor = progress_bar_color + self.AutoCloseDuration = auto_close_duration + self.RootNeedsDestroying = False + self.Shown = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.LastButtonClicked = None + self.LastButtonClickedWasRealtime = False + self.UseDictionary = False + self.UseDefaultFocus = use_default_focus + self.ReturnKeyboardEvents = return_keyboard_events + self.LastKeyboardEvent = None + self.TextJustification = text_justification + self.NoTitleBar = no_titlebar + self.GrabAnywhere = grab_anywhere + self.KeepOnTop = keep_on_top + self.ForcefTopLevel = force_toplevel + self.Resizable = resizable + self._AlphaChannel = alpha_channel + self.Timeout = None + self.TimeoutKey = '_timeout_' + self.TimerCancelled = False + self.DisableClose = disable_close + self._Hidden = False + self.QTApplication = None + self.QT_QMainWindow = None + self.QTWindow = None # type Window.QTMainWindow + self._Size = size + self.ElementPadding = element_padding or DEFAULT_ELEMENT_PADDING + self.FocusElement = None + self.BackgroundImage = background_image + self.XFound = False + self.DisableMinimize = disable_minimize + self.UniqueKeyCounter = 0 + self.metadata = metadata + self.ElementJustification = element_justification + self.AllKeysDict = {} + self.margins = margins + + if layout is not None: + self.Layout(layout) + if finalize: + self.Finalize() + + @classmethod + def IncrementOpenCount(self): + self.NumOpenWindows += 1 + # print('+++++ INCREMENTING Num Open Windows = {} ---'.format(Window.NumOpenWindows)) + + @classmethod + def DecrementOpenCount(self): + self.NumOpenWindows -= 1 * (self.NumOpenWindows != 0) # decrement if not 0 + # print('----- DECREMENTING Num Open Windows = {} ---'.format(Window.NumOpenWindows)) + + # ------------------------- Add ONE Row to Form ------------------------- # + def AddRow(self, *args): + """Parms are a variable number of Elements""" + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + if type(element) is list: + PopupError( + 'Error creating layout', + 'Layout has a LIST instead of an ELEMENT', + 'This means you have a badly placed ]', + 'The offensive list is:', + element, + 'This list will be stripped from your layout', + ) + continue + elif callable(element) and not isinstance(element, Element): + PopupError( + 'Error creating layout', + 'Layout has a FUNCTION instead of an ELEMENT', + 'This means you are missing () from your layout', + 'The offensive list is:', + element, + 'This item will be stripped from your layout', + ) + continue + if element.ParentContainer is not None: + warnings.warn( + '*** YOU ARE ATTEMPTING TO RESUSE A LAYOUT! You must not attempt this kind of re-use ***', + UserWarning, + ) + PopupError( + 'Error creating layout', + 'The layout specified has already been used', + 'You MUST start witha "clean", unused layout every time you create a window', + 'The offensive Element = ', + element, + 'and has a key = ', + element.Key, + 'This item will be stripped from your layout', + ) + continue + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + # ------------------------- Add Multiple Rows to Form ------------------------- # + def AddRows(self, rows): + for row in rows: + try: + iter(row) + except TypeError: + PopupError( + 'Error creating layout', + 'Your row is not an iterable (e.g. a list)', + 'The offensive row = ', + row, + 'This item will be stripped from your layout', + ) + continue + self.AddRow(*row) + + def Layout(self, rows): + self.AddRows(rows) + self._BuildKeyDict() + return self + + def LayoutAndRead(self, rows, non_blocking=False): + raise DeprecationWarning('LayoutAndRead is no longer supported... change your call to window.Layout(layout).Read()') + # self.AddRows(rows) + # self.Show(non_blocking=non_blocking) + # return self.ReturnValues + + def LayoutAndShow(self, rows): + raise DeprecationWarning('LayoutAndShow is no longer supported... change your call to LayoutAndRead') + + # ------------------------- ShowForm THIS IS IT! ------------------------- # + def Show(self, non_blocking=False): + self.Shown = True + # Compute num rows & num cols (it'll come in handy debugging) + self.NumRows = len(self.Rows) + self.NumCols = max(len(row) for row in self.Rows) + self.NonBlocking = non_blocking + + # Search through entire form to see if any elements set the focus + # if not, then will set the focus to the first input element + found_focus = False + for row in self.Rows: + for element in row: + try: + if element.Focus: + found_focus = True + except: + pass + try: + if element.Key is not None: + self.UseDictionary = True + except: + pass + + if not found_focus and self.UseDefaultFocus: + self.UseDefaultFocus = True + else: + self.UseDefaultFocus = False + # -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ## + StartupTK(self) + # If a button or keyboard event happened but no results have been built, build the results + if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: + return BuildResults(self, False, self) + return self.ReturnValues + + # ------------------------- SetIcon - set the window's fav icon ------------------------- # + def SetIcon(self, icon=None, pngbase64=None): + pass + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def _GetDefaultElementSize(self): + return self.DefaultElementSize + + def _AutoCloseAlarmCallback(self): + try: + window = self + if window: + if window.NonBlocking: + self.CloseNonBlockingForm() + else: + window._Close() + if self.CurrentlyRunningMainloop: + self.QTApplication.exit() # kick the users out of the mainloop + self.RootNeedsDestroying = True + self.QT_QMainWindow.close() + + except: + pass + + def _timer_timeout(self): + # first, get the results table built + # modify the Results table in the parent FlexForm object + if self.TimerCancelled: + return + self.LastButtonClicked = self.TimeoutKey + self.FormRemainedOpen = True + if self.CurrentlyRunningMainloop: + self.QTApplication.exit() # kick the users out of the mainloop + + def _autoclose_timer_callback(self): + # print('*** TIMEOUT CALLBACK ***') + self.autoclose_timer.stop() + self.QT_QMainWindow.close() + if self.CurrentlyRunningMainloop: + # print("quitting window") + self.QTApplication.exit() # kick the users out of the mainloop + + def Read(self, timeout=None, timeout_key=TIMEOUT_KEY, close=False): + """ + THE biggest deal method in the Window class! This is how you get all of your data from your Window. + Pass in a timeout (in milliseconds) to wait for a maximum of timeout milliseconds. Will return timeout_key + if no other GUI events happen first. + Use the close parameter to close the window after reading + + :param timeout: (int) Milliseconds to wait until the Read will return IF no other GUI events happen first + :param timeout_key: (Any) The value that will be returned from the call if the timer expired + :param close: (bool) if True the window will be closed prior to returning + :return: Tuple[(Any), Union[Dict[Any:Any]], List[Any], None] (event, values) + """ + results = self._read(timeout=timeout, timeout_key=timeout_key) + if close: + self.close() + + return results + + def _read(self, timeout=None, timeout_key=TIMEOUT_KEY): + if timeout == 0: # timeout of zero runs the old readnonblocking + event, values = self._ReadNonBlocking() + if event is None: + event = timeout_key + if values is None: + event = None + return event, values # make event None if values was None and return + # Read with a timeout + self.Timeout = timeout + self.TimeoutKey = timeout_key + self.NonBlocking = False + if not self.Shown: + self.Show() + else: + # if already have a button waiting, the return previously built results + if self.LastButtonClicked is not None and not self.LastButtonClickedWasRealtime: + # print(f'*** Found previous clicked saved {self.LastButtonClicked}') + results = BuildResults(self, False, self) + self.LastButtonClicked = None + return results + InitializeResults(self) + # if the last button clicked was realtime, emulate a read non-blocking + # the idea is to quickly return realtime buttons without any blocks until released + if self.LastButtonClickedWasRealtime: + # print(f'RTime down {self.LastButtonClicked}' ) + try: + rc = self.TKroot.update() + except: + self.TKrootDestroyed = True + Window.DecrementOpenCount() + results = BuildResults(self, False, self) + if results[0] != None and results[0] != timeout_key: + return results + else: + pass + + # else: + # print("** REALTIME PROBLEM FOUND **", results) + + # normal read blocking code.... + if timeout != None: + self.TimerCancelled = False + timer = start_window_read_timer(self, timeout) + else: + timer = None + self.CurrentlyRunningMainloop = True + # print(f'In main {self.Title}') + ################################# CALL GUI MAINLOOP ############################ + + self.QTApplication.exec_() + # self.LastButtonClicked = 'TEST' + self.CurrentlyRunningMainloop = False + self.TimerCancelled = True + if timer: + stop_timer(timer) + if self.RootNeedsDestroying: + self.LastButtonClicked = None + self.QTApplication.exit() + Window.DecrementOpenCount() + # if form was closed with X + if self.LastButtonClicked is None and self.LastKeyboardEvent is None and self.ReturnValues[0] is None: + Window.DecrementOpenCount() + # Determine return values + if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: + results = BuildResults(self, False, self) + if not self.LastButtonClickedWasRealtime: + self.LastButtonClicked = None + return results + else: + if not self.XFound and self.Timeout != 0 and self.Timeout is not None and self.ReturnValues[0] is None: # Special Qt case because returning for no reason so fake timeout + self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout + elif not self.XFound and self.ReturnValues[0] is None: # TODO HIGHLY EXPERIMENTAL... added due to tray icon interaction + # print("*** Faking timeout ***") + self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout + return self.ReturnValues + + def _ReadNonBlocking(self): + if self.TKrootDestroyed: + return None, None + if not self.Shown: + self.Show(non_blocking=True) + else: + self.QTApplication.processEvents() # refresh the window + if 0: # TODO add window closed with X logic + self.TKrootDestroyed = True + _my_windows.Decrement() + # print("read failed") + # return None, None + return BuildResults(self, False, self) + + def Finalize(self): + if self.TKrootDestroyed: + return self + if not self.Shown: + self.Show(non_blocking=True) + else: + try: + self.QTApplication.processEvents() # refresh the window + except: + print('* ERROR FINALIZING *') + self.TKrootDestroyed = True + Window.DecrementOpenCount() + return self + + def Refresh(self): + self.QTApplication.processEvents() # refresh the window + return self + + def VisibilityChanged(self): + self.Refresh() + self.Size = self.Size + self.Refresh() + self.Size = self.Size + self.Refresh() + return self + + def Fill(self, values_dict): + FillFormWithValues(self, values_dict) + return self + + def FindElement(self, key, silent_on_error=False): + try: + element = self.AllKeysDict[key] + except KeyError: + element = None + # element = _FindElementFromKeyInSubForm(self, key) + if element is None: + if not silent_on_error: + print('*** WARNING = FindElement did not find the key. Please check your key\'s spelling ***') + PopupError( + 'Keyword error in FindElement Call', + 'Bad key = {}'.format(key), + 'Your bad line of code may resemble this:', + 'window.FindElement("{}")'.format(key), + ) + return ErrorElement(key=key) + else: + return False + return element + + Element = FindElement # Shortcut function + + def _BuildKeyDict(self): + dict = {} + self.AllKeysDict = self._BuildKeyDictForWindow(self, self, dict) + + def _BuildKeyDictForWindow(self, top_window, window, key_dict): + for row_num, row in enumerate(window.Rows): + for col_num, element in enumerate(row): + if element.Type == ELEM_TYPE_COLUMN: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Type == ELEM_TYPE_FRAME: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Type == ELEM_TYPE_TAB_GROUP: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Type == ELEM_TYPE_TAB: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Key is None: # if no key has been assigned.... create one for input elements + if element.Type == ELEM_TYPE_BUTTON: + element.Key = element.ButtonText + elif element.Type == ELEM_TYPE_TAB: + element.Key = element.Title + if element.Type in ( + ELEM_TYPE_MENUBAR, + ELEM_TYPE_BUTTONMENU, + ELEM_TYPE_CANVAS, + ELEM_TYPE_INPUT_SLIDER, + ELEM_TYPE_GRAPH, + ELEM_TYPE_IMAGE, + ELEM_TYPE_INPUT_CHECKBOX, + ELEM_TYPE_INPUT_LISTBOX, + ELEM_TYPE_INPUT_COMBO, + ELEM_TYPE_INPUT_MULTILINE, + ELEM_TYPE_INPUT_OPTION_MENU, + ELEM_TYPE_INPUT_SPIN, + ELEM_TYPE_TABLE, + ELEM_TYPE_TREE, + ELEM_TYPE_INPUT_TEXT, + ): + element.Key = top_window.DictionaryKeyCounter + top_window.DictionaryKeyCounter += 1 + if element.Key is not None: + if element.Key in key_dict.keys(): + (print('*** Duplicate key found in your layout {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) + element.Key = str(element.Key) + str(self.UniqueKeyCounter) + self.UniqueKeyCounter += 1 + (print('*** Replaced new key with {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) + key_dict[element.Key] = element + return key_dict + + def FindElementWithFocus(self): + return _FindElementWithFocusInSubForm(self) + # return self.FocusElement + + def SaveToDisk(self, filename): + try: + results = BuildResults(self, False, self) + with open(filename, 'wb') as sf: + pickle.dump(results[1], sf) + except: + print('*** Error saving form to disk ***') + + def LoadFromDisk(self, filename): + try: + with open(filename, 'rb') as df: + self.Fill(pickle.load(df)) + except: + print('*** Error loading form to disk ***') + + def GetScreenDimensions(self): + if Window.QTApplication is None: + Window.QTApplication = QApplication(sys.argv) + try: + screen = Window.QTApplication.primaryScreen() + except: + return None, None + size = screen.size() + screen_width = size.width() + screen_height = size.height() + return screen_width, screen_height + + def Move(self, x, y): + if not self._is_window_created(): + return + self.QT_QMainWindow.move(x, y) + + def Minimize(self): + if not self._is_window_created(): + return + self.QT_QMainWindow.setWindowState(Qt.WindowMinimized) + + def Maximize(self): + if not self._is_window_created(): + return + self.QT_QMainWindow.setWindowState(Qt.WindowMaximized) + + def StartMove(self, event): + try: + self.TKroot.x = event.x + self.TKroot.y = event.y + except: + pass + + def StopMove(self, event): + try: + self.TKroot.x = None + self.TKroot.y = None + except: + pass + + def OnMotion(self, event): + try: + deltax = event.x - self.TKroot.x + deltay = event.y - self.TKroot.y + x = self.TKroot.winfo_x() + deltax + y = self.TKroot.winfo_y() + deltay + self.TKroot.geometry('+%s+%s' % (x, y)) + except: + pass + + def _KeyboardCallback(self, event): + self.LastButtonClicked = None + self.FormRemainedOpen = True + if event.char != '': + self.LastKeyboardEvent = event.char + else: + self.LastKeyboardEvent = str(event.keysym) + ':' + str(event.keycode) + if not self.NonBlocking: + BuildResults(self, False, self) + if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! + self.TKroot.quit() + + def _MouseWheelCallback(self, event): + self.LastButtonClicked = None + self.FormRemainedOpen = True + self.LastKeyboardEvent = 'MouseWheel:Down' if event.delta < 0 else 'MouseWheel:Up' + if not self.NonBlocking: + BuildResults(self, False, self) + if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! + self.TKroot.quit() + + def _Close(self): + try: + self.TKroot.update() + except: + pass + if not self.NonBlocking: + BuildResults(self, False, self) + if self.TKrootDestroyed: + return None + self.TKrootDestroyed = True + self.RootNeedsDestroying = True + return None + + def Close(self): + if self.TKrootDestroyed: + return + try: + self.QT_QMainWindow.close() + except: + print('error closing window') + + CloseNonBlockingForm = Close + CloseNonBlocking = Close + + def Disable(self): + if not self._is_window_created(): + return + self.QT_QMainWindow.setEnabled(False) + + def Enable(self): + if not self._is_window_created(): + return + self.QT_QMainWindow.setEnabled(True) + + def Hide(self): + if not self._is_window_created(): + return + self._Hidden = True + self.QT_QMainWindow.hide() + return + + def UnHide(self): + if not self._is_window_created(): + return + if self._Hidden: + self.QT_QMainWindow.show() + self._Hidden = False + + def Disappear(self): + self.AlphaChannel = 0 + + def Reappear(self): + self.AlphaChannel = 255 + + def SetAlpha(self, alpha): + """ + Change the window's transparency + :param alpha: From 0 to 1 with 0 being completely transparent + :return: + """ + if not self._is_window_created(): + return + self._AlphaChannel = alpha + if self._AlphaChannel is not None: + self.QT_QMainWindow.setWindowOpacity(self._AlphaChannel) + + @property + def AlphaChannel(self): + return self._AlphaChannel + + @AlphaChannel.setter + def AlphaChannel(self, alpha): + if not self._is_window_created(): + return + self._AlphaChannel = alpha + if self._AlphaChannel is not None: + self.QT_QMainWindow.setWindowOpacity(self._AlphaChannel) + + def BringToFront(self): + if not self._is_window_created(): + return + self.QTMainWindow.activateWindow(self.QT_QMainWindow) + self.QTMainWindow.raise_(self.QT_QMainWindow) + + def CurrentLocation(self): + if not self._is_window_created(): + return + location = self.QT_QMainWindow.geometry() + return location.left(), location.top() + + def set_title(self, title): + """ + Change the title of the window + + :param title: The string to set the title to + :type title: (str) + """ + if not self._is_window_created(): + return + self.Title = str(title) + self.QT_QMainWindow.setWindowTitle(self.Title) + + class QTMainWindow(QWidget): + def __init__(self, enable_key_events, window): + self.KeyEventsEnabled = enable_key_events + self.Window = window + super().__init__(window.QT_QMainWindow) + + def eventFilter(self, widget, event): + # print(event.type()) + if event.type() == QEvent.MouseButtonPress and self.Window.GrabAnywhere: + self.mouse_offset = event.pos() + if event.type() == QEvent.MouseMove and self.Window.GrabAnywhere: + x = event.globalX() + y = event.globalY() + x_w = self.mouse_offset.x() + y_w = self.mouse_offset.y() + self.move(x - x_w, y - y_w) + + if event.type() == QEvent.KeyRelease and self.KeyEventsEnabled: + # print("got key event") + key = event.key() + try: + self.Window.LastButtonClicked = chr(key).lower() + except: + self.Window.LastButtonClicked = 'special %s' % key + self.Window.FormRemainedOpen = True + if self.Window.CurrentlyRunningMainloop: + self.Window.QTApplication.exit() + return QWidget.eventFilter(self, widget, event) + + class QT_QMainWindowClass(QMainWindow): + def __init__(self, enable_key_events, window): + self.KeyEventsEnabled = enable_key_events + self.Window = window + super().__init__() + + def eventFilter(self, widget, event): + # print(event.type()) + if event.type() == QEvent.MouseButtonPress and self.Window.GrabAnywhere: + self.mouse_offset = event.pos() + if event.type() == QEvent.MouseMove and self.Window.GrabAnywhere: + x = event.globalX() + y = event.globalY() + x_w = self.mouse_offset.x() + y_w = self.mouse_offset.y() + self.move(x - x_w, y - y_w) + + if event.type() == QEvent.KeyRelease and self.KeyEventsEnabled: + # print("got key event") + key = event.key() + try: + self.Window.LastButtonClicked = chr(key).lower() + except: + self.Window.LastButtonClicked = 'special %s' % key + self.Window.FormRemainedOpen = True + if self.Window.CurrentlyRunningMainloop: + self.Window.QTApplication.exit() + return QWidget.eventFilter(self, widget, event) + + def closeEvent(self, event): + if self.Window.DisableClose: + event.ignore() + return + # print('GOT A CLOSE EVENT!', event, self.Window.Title) + self.Window.LastButtonClicked = None + self.Window.XFound = True + if not self.Window.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! + self.Window.RootNeedsDestroying = True + else: + self.Window.RootNeedsDestroying = True + self.Window.QTApplication.exit() # kick the users out of the mainloop + self.Window.QT_QMainWindow.close() + self.Window.TKrootDestroyed = True + self.Window.RootNeedsDestroying = True + + # if self.CurrentlyRunningMainloop: + # print("quitting window") + # self.QTApplication.exit() # kick the users out of the mainloop + + @property + def Size(self): + if not self._is_window_created(): + return + size = self.QT_QMainWindow.sizeHint() + return [size.width(), size.height()] + + @Size.setter + def Size(self, size): + if not self._is_window_created(): + return + self.QT_QMainWindow.resize(QSize(size[0], size[1])) + + def _is_window_created(self): + if self.QT_QMainWindow is None: + warnings.warn( + 'You cannot perform operations on a Window until it is read or finalized. Adding a "finalize=True" parameter to your Window creation will fix this', + UserWarning, + ) + popup_error( + 'You cannot perform operations on a Window until it is read or finalized.', + 'Yea, I know, it\'s a weird thing, but easy to fix.... ', + 'Adding a "finalize=True" parameter to your Window creation will likely fix this', + image=FACE_PALM, + ) + return False + return True + + def __getitem__(self, key): + """ + Returns Element that matches the passed in key. + This is "called" by writing code as thus: + window['element key'].Update + + :param key: The key to find + :type key: Union[str, int, tuple, object] + :return: Union[Element, None] The element found or None if no element was found + :rtype: Element + """ + try: + return self.Element(key) + except Exception as e: + print('The key you passed in is no good. Key = {}*'.format(key)) + return None + + def __call__(self, *args, **kwargs): + """ + Call window.Read but without having to type it out. + window() == window.Read() + window(timeout=50) == window.Read(timeout=50) + + :param args: * + :type args: (any) + :param kwargs: ** + :type kaargs: (any) + :return: Tuple[Any, Dict[Any:Any]] The famous event, values that Read returns. + """ + return self.Read(*args, **kwargs) + + add_row = AddRow + add_rows = AddRows + alpha_channel = AlphaChannel + bring_to_front = BringToFront + close = Close + current_location = CurrentLocation + disable = Disable + disappear = Disappear + element = Element + enable = Enable + fill = Fill + finalize = Finalize + find_element = FindElement + find_element_with_focus = FindElementWithFocus + get_screen_dimensions = GetScreenDimensions + hide = Hide + layout = Layout + load_from_disk = LoadFromDisk + maximize = Maximize + minimize = Minimize + move = Move + read = Read + reappear = Reappear + refresh = Refresh + save_to_disk = SaveToDisk + set_alpha = SetAlpha + set_icon = SetIcon + size = Size + un_hide = UnHide + visibility_changed = VisibilityChanged + + +FlexForm = Window + + +class QtStyle(object): + ''' + API + + # step 1 - make a style + ss = QtStyle(QLabel) + + # step 2 - add fields + ss['font'] = create_style_from_font() + ss['background_color'] = (color, color_default) + ss['color'] = (color, color_default) + # step 2.1 - add additions + ss.append_css_to_end.append(" QScrollBar:vertical { ... some css here ... } ") + # step 2.2 - add anchor + ss.my_anchor = '::chunk' + + # step 3 - build result + css_str = ss.build_css_string() + qt_widget.setStyleSheet(css_str) + + ====== Special fields + - font + - margin + Why they are special? Because of the formatting. + + === === === + made by nngogol + ''' + + def __init__(self, widget_name=''): + self.widget_name = widget_name + self.css_props = {} + self.my_anchor = None + + self.logging = True + self.logging = not True + self.append_css_to_end = [] + + self.make_secure_check = True # Check if "css property is valid, i.e. it's present in default names". + # Make makes development safer, if you have a erro in spelling css-property + # In production: it can be disabled, I guess. + + def __setitem__(self, css_prop_name, css_prop_value): + + css_prop_name = css_prop_name.replace('_', '-') + # validation + if not isinstance(css_prop_value, (tuple, list, str)): + raise Exception('Bad value fro css property -> %s.' % css_prop_value) + if self.make_secure_check: + if not is_valid_css_prop(css_prop_name): + raise Exception('Bad css property name: ' % css_prop_name) + + self.css_props[css_prop_name] = css_prop_value + + def build_css_string(self): + # no css props added -> return empty string + if not self.css_props: # empty case + print(f' final_str # {self.widget_name} = ""') + return '' + + css_props_str_list = [] + for key, value in self.css_props.items(): + # special cases: + if key == 'margin' or key == 'padding': + # validation + if not isinstance(value, (tuple, list)): + raise Exception('Cant handle this TYPE for margin property : %s ' % str(type(value))) + + result_css_string = '' + + if len(value) == 4: + # skip all zeros + if value[0] == value[1] == value[2] == value[3] == 0: + result_css_string = '' + + result_css_string = '{} : {}px {}px {}px {}px;'.format(key, *value) + elif len(value) == 1: + # skip all zeros + if value[0] == 0: + continue + + result_css_string = '{} : {}px;'.format(key, value[0]) + else: + raise Exception('Bad value for margin/padding property: ' % str(value)) + + # # Fix for this case: + # # Wrong: margin: 0px; + # # Right: margin: 0; + # result_css_string = result_css_string.replace(': 0px', ': 0') + + css_props_str_list.append(result_css_string) + + continue + # if key == 'border': ... + + # it's a css string! Format: 'propery: value;'' + if isinstance(value, str): + # is css prop name + css prop value + if ':' in value: + css_props_str_list.append(value) + # is css value + else: + css_props_str_list.append(_to_css_prop(key, value)) + # we continue, because it was 'string type parsing' + continue + + isnot = None + # it's a pair! Format: (val, default_val) + if isinstance(value, (tuple, list)): + user_css_prop_value, isnot = value + + if user_css_prop_value is not None and user_css_prop_value != isnot: + css_props_str_list.append(_to_css_prop(key, user_css_prop_value)) + + # join all props + css_all = ''.join(css_props_str_list) + final_str = css_all + if self.widget_name: + my_anchor = '' if self.my_anchor is None else self.my_anchor + final_str = '%s%s { %s }' % (self.widget_name, my_anchor, css_all) + + # if needed: append some css from self.append_css_to_end + final_str += ' '.join(self.append_css_to_end) + + if self.logging: + print(f'final css string (self.widget_name): {final_str}') + return final_str + + def __repr__(self): + return self.build_css_string() + + +# =========================================================================== # +# Stops the mainloop and sets the event information # +# =========================================================================== # + + +def _element_callback_quit_mainloop(element): + if element.Key is not None: + element.ParentForm.LastButtonClicked = element.Key + else: + element.ParentForm.LastButtonClicked = '' + element.ParentForm.FormRemainedOpen = True + if element.ParentForm.CurrentlyRunningMainloop: + element.ParentForm.QTApplication.exit() # kick the users out of the mainloop + + +# =========================================================================== # +# Convert from characters to pixels # +# =========================================================================== # +def _convert_tkinter_size_to_Qt(size, scaling=DEFAULT_PIXELS_TO_CHARS_SCALING, height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF): + """ + Converts size in characters to size in pixels + :param size: size in characters, rows + :return: size in pixels, pixels + """ + qtsize = size + if size[1] is not None and size[1] < height_cutoff: # change from character based size to pixels (roughly) + qtsize = size[0] * scaling[0], size[1] * scaling[1] + return qtsize + + +# =========================================================================== # +# Stops the mainloop and sets the event information # +# =========================================================================== # +def convert_tkinter_filetypes_to_qt(filetypes): + qt_filetypes = '' + for i, item in enumerate(filetypes): + filetype = item[0] + ' (' + item[1] + ')' + (';;' if i != len(filetypes) - 1 else '') + qt_filetypes += filetype + return qt_filetypes + + +# =========================================================================== # +# Converts a "Font" string or tuple into Qt Style Sheet Entries # +# =========================================================================== # +def create_style_from_font(font): + """ + Convert from font string/tuple into a Qt style sheet string + :param font: "Arial 10 Bold" or ('Arial', 10, 'Bold') + :return: style string that can be combined with other style strings + """ + + if font is None: + return '' + _font = font.split(' ') if type(font) is str else font + + # parsing name + size + font_name, font_size = _font[:2] + # parsing options: + is_bold, is_underline = False, False + if len(_font) > 2: + options = _font[2:] + for some_option in options: + if some_option == 'underline': + is_underline = True + else: + is_bold = True + + # build + is_bold_text = 'font-weight : bold;' if is_bold else '' + is_underline_text = 'text-decoration: underline;' if is_underline else '' + + return textwrap.dedent( + f''' + {is_underline_text} + {is_bold_text} + font-family: "{font_name}"; + font-size: {font_size}pt; + '''.strip() + ).replace('\n', '') + + +def set_widget_visiblity(widget, visible): + if visible is False: + widget.setVisible(False) + elif visible is True: + widget.setVisible(True) + + +# ################################################################################ +# ################################################################################ +# END OF ELEMENT DEFINITIONS +# ################################################################################ +# ################################################################################ + + +# =========================================================================== # +# Button Lazy Functions so the caller doesn't have to define a bunch of stuff # +# =========================================================================== # + + +# ------------------------- FOLDER BROWSE Element lazy function ------------------------- # +def FolderBrowse( + button_text='Browse', + target=(ThisRow, -1), + initial_folder=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + change_submits=False, + enable_events=False, + font=None, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_BROWSE_FOLDER, + target=target, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + disabled=disabled, + button_color=button_color, + change_submits=change_submits, + enable_events=enable_events, + font=font, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- FILE BROWSE Element lazy function ------------------------- # +def FileBrowse( + button_text='Browse', + target=(ThisRow, -1), + file_types=(('ALL Files', '*'),), + initial_folder=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + enable_events=False, + font=None, + disabled=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_BROWSE_FILE, + target=target, + file_types=file_types, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + change_submits=change_submits, + enable_events=enable_events, + disabled=disabled, + button_color=button_color, + font=font, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- FILES BROWSE Element (Multiple file selection) lazy function ------------------------- # +def FilesBrowse( + button_text='Browse', + target=(ThisRow, -1), + file_types=(('ALL Files', '*'),), + disabled=False, + initial_folder=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + enable_events=False, + font=None, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_BROWSE_FILES, + target=target, + file_types=file_types, + initial_folder=initial_folder, + change_submits=change_submits, + enable_events=enable_events, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + disabled=disabled, + button_color=button_color, + font=font, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- FILE BROWSE Element lazy function ------------------------- # +def FileSaveAs( + button_text='Save As...', + target=(ThisRow, -1), + file_types=(('ALL Files', '*'),), + initial_folder=None, + disabled=False, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + enable_events=False, + font=None, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_SAVEAS_FILE, + target=target, + file_types=file_types, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + disabled=disabled, + auto_size_button=auto_size_button, + button_color=button_color, + change_submits=change_submits, + enable_events=enable_events, + font=font, + pad=pad, + key=key, + metadata=metadata, + ) + + +# ------------------------- SAVE AS Element lazy function ------------------------- # +def SaveAs( + button_text='Save As...', + target=(ThisRow, -1), + file_types=(('ALL Files', '*'),), + initial_folder=None, + disabled=False, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + enable_events=False, + font=None, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_SAVEAS_FILE, + target=target, + file_types=file_types, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + disabled=disabled, + auto_size_button=auto_size_button, + button_color=button_color, + change_submits=change_submits, + enable_events=enable_events, + font=font, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- SAVE BUTTON Element lazy function ------------------------- # +def Save( + button_text='Save', + size=(None, None), + auto_size_button=None, + button_color=None, + bind_return_key=True, + disabled=False, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- SUBMIT BUTTON Element lazy function ------------------------- # +def Submit( + button_text='Submit', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- OPEN BUTTON Element lazy function ------------------------- # +# ------------------------- OPEN BUTTON Element lazy function ------------------------- # +def Open( + button_text='Open', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- OK BUTTON Element lazy function ------------------------- # +def OK( + button_text='OK', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- YES BUTTON Element lazy function ------------------------- # +def Ok( + button_text='Ok', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- CANCEL BUTTON Element lazy function ------------------------- # +def Cancel( + button_text='Cancel', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- QUIT BUTTON Element lazy function ------------------------- # +def Quit( + button_text='Quit', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- Exit BUTTON Element lazy function ------------------------- # +def Exit( + button_text='Exit', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- YES BUTTON Element lazy function ------------------------- # +def Yes( + button_text='Yes', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=True, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- NO BUTTON Element lazy function ------------------------- # +def No( + button_text='No', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- NO BUTTON Element lazy function ------------------------- # +def Help( + button_text='Help', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + font=None, + tooltip=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # +def SimpleButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + disabled=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_CLOSES_WIN, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + disabled=disabled, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- CLOSE BUTTON Element lazy function ------------------------- # +def CloseButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + disabled=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_CLOSES_WIN, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + disabled=disabled, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +CButton = CloseButton + + +# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # +def ReadButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + disabled=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + """ + :param button_text: text in the button + :type button_text: (str) + :param image_filename: image filename if there is a button image. GIFs and PNGs only. + :type image_filename: (str) + :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data + :type image_data: Union[bytes, str] + :param image_size: Size of the image in pixels (width, height) + :type image_size: Tuple[int, int] + :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc + :type image_subsample: (int) + :param border_width: width of border around element + :type border_width: (int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param size: (w,h) w=characters-wide, h=rows-high + :type size: Tuple[int, int] + :param auto_size_button: True if button size is determined by button text + :type auto_size_button: (bool) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :type bind_return_key: (bool) + :param disabled: set disable state for element (Default = False) + :type disabled: (bool) + :param focus: if focus should be set to this + :type focus: idk_yetReally + :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: key for uniquely identify this element (for window.FindElement) + :type key: Union[str, int, tuple, object] + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param metadata: Anything you want to store along with this button + :type metadata: (Any) + """ + + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + disabled=disabled, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +ReadFormButton = ReadButton +RButton = ReadFormButton + + +# ------------------------- Realtime BUTTON Element lazy function ------------------------- # +def RealtimeButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + disabled=False, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + """ + :param button_text: text in the button + :type button_text: (str) + :param image_filename: image filename if there is a button image. GIFs and PNGs only. + :type image_filename: (str) + :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data + :type image_data: Union[bytes, str] + :param image_size: Size of the image in pixels (width, height) + :type image_size: Tuple[int, int] + :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc + :type image_subsample: (int) + :param border_width: width of border around element + :type border_width: (int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param size: (w,h) w=characters-wide, h=rows-high + :type size: Tuple[int, int] + :param auto_size_button: True if button size is determined by button text + :type auto_size_button: (bool) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param disabled: set disable state for element (Default = False) + :type disabled: (bool) + :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :type bind_return_key: (bool) + :param focus: if focus should be set to this + :type focus: (bool) + :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: key for uniquely identify this element (for window.FindElement) + :type key: Union[str, int, tuple, object] + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param metadata: Anything you want to store along with this button + :type metadata: (Any) + """ + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_REALTIME, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + disabled=disabled, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- Dummy BUTTON Element lazy function ------------------------- # +def DummyButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + disabled=False, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + """ + :param button_text: text in the button + :type button_text: (str) + :param image_filename: image filename if there is a button image. GIFs and PNGs only. + :type image_filename: (str) + :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data + :type image_data: Union[bytes, str] + :param image_size: Size of the image in pixels (width, height) + :type image_size: Tuple[int, int] + :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc + :type image_subsample: (int) + :param border_width: width of border around element + :type border_width: (int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param size: (w,h) w=characters-wide, h=rows-high + :type size: Tuple[int, int] + :param auto_size_button: True if button size is determined by button text + :type auto_size_button: (bool) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param disabled: set disable state for element (Default = False) + :type disabled: (bool) + :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :type bind_return_key: (bool) + :param focus: if focus should be set to this + :type focus: (bool) + :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param key: key for uniquely identify this element (for window.FindElement) + :type key: Union[str, int, tuple, object] + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: Union[str, int, tuple, object] + :param metadata: Anything you want to store along with this button + :type metadata: (Any) + """ + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_CLOSES_WIN_ONLY, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +# ------------------------- Calendar Chooser Button lazy function ------------------------- # +def CalendarButton( + button_text, + target=(None, None), + close_when_date_chosen=True, + default_date_m_d_y=(None, None, None), + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + tooltip=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + button = Button( + button_text=button_text, + button_type=BUTTON_TYPE_CALENDAR_CHOOSER, + target=target, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + button.CalendarCloseWhenChosen = close_when_date_chosen + button.DefaultDate_M_D_Y = default_date_m_d_y + return button + + +# ------------------------- Calendar Chooser Button lazy function ------------------------- # +def ColorChooserButton( + button_text, + target=(None, None), + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + tooltip=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + k=None, + metadata=None, +): + """ + :param button_text: text in the button + :type button_text: (str) + :param target: key or (row,col) target for the button. Note that -1 for column means 1 element to the left of this one. The constant ThisRow is used to indicate the current row. The Button itself is a valid target for some types of button + :type target: Union[str, Tuple[int, int]] + :param image_filename: image filename if there is a button image. GIFs and PNGs only. + :type image_filename: (str) + :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data + :type image_data: Union[bytes, str] + :param image_size: Size of the image in pixels (width, height) + :type image_size: Tuple[int, int] + :param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc + :type image_subsample: (int) + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param border_width: width of border around element + :type border_width: (int) + :param size: (w,h) w=characters-wide, h=rows-high + :type size: Tuple[int, int] + :param auto_size_button: True if button size is determined by button text + :type auto_size_button: (bool) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param disabled: set disable state for element (Default = False) + :type disabled: (bool) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :type bind_return_key: (bool) + :param focus: if focus should be set to this + :type focus: (bool) + :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) + :param metadata: Anything you want to store along with this button + :type metadata: (Any) + """ + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_COLOR_CHOOSER, + target=target, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + k=k, + metadata=metadata, + ) + + +##################################### ----- RESULTS ------ ################################################## + + +def AddToReturnDictionary(form, element, value): + form.ReturnValuesDictionary[element.Key] = value + return + if element.Key is None: + form.ReturnValuesDictionary[form.DictionaryKeyCounter] = value + element.Key = form.DictionaryKeyCounter + form.DictionaryKeyCounter += 1 + else: + form.ReturnValuesDictionary[element.Key] = value + + +def AddToReturnList(form, value): + form.ReturnValuesList.append(value) + + +# ----------------------------------------------------------------------------# +# ------- FUNCTION InitializeResults. Sets up form results matrix --------# +def InitializeResults(form): + BuildResults(form, True, form) + return + + +# ===== Radio Button RadVar encoding and decoding =====# +# ===== The value is simply the row * 1000 + col =====# +def DecodeRadioRowCol(RadValue): + row = RadValue // 1000 + col = RadValue % 1000 + return row, col + + +def EncodeRadioRowCol(row, col): + RadValue = row * 1000 + col + return RadValue + + +# ------- FUNCTION BuildResults. Form exiting so build the results to pass back ------- # +# format of return values is +# (Button Pressed, input_values) +def BuildResults(form, initialize_only, top_level_form): + # Results for elements are: + # TEXT - Nothing + # INPUT - Read value from TK + # Button - Button Text and position as a Tuple + + # Get the initialized results so we don't have to rebuild + form.DictionaryKeyCounter = 0 + form.ReturnValuesDictionary = {} + form.ReturnValuesList = [] + BuildResultsForSubform(form, initialize_only, top_level_form) + if not top_level_form.LastButtonClickedWasRealtime: + top_level_form.LastButtonClicked = None + return form.ReturnValues + + +def BuildResultsForSubform(form, initialize_only, top_level_form): + button_pressed_text = top_level_form.LastButtonClicked + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + if element.Key is not None and WRITE_ONLY_KEY in str(element.Key): + continue + value = None + if element.Type == ELEM_TYPE_COLUMN: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if element.Type == ELEM_TYPE_FRAME: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if element.Type == ELEM_TYPE_TAB_GROUP: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if element.Type == ELEM_TYPE_TAB: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if not initialize_only: + if element.Type == ELEM_TYPE_INPUT_TEXT: + value = element.QT_QLineEdit.text() + if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: + element.QT_QLineEdit.setText('') + + elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: + value = element.QT_Checkbox.isChecked() + elif element.Type == ELEM_TYPE_INPUT_RADIO: + this_rowcol = EncodeRadioRowCol(row_num, col_num) + value = element.QT_Radio_Button.isChecked() + elif element.Type == ELEM_TYPE_BUTTON: + if top_level_form.LastButtonClicked == element.ButtonText: + button_pressed_text = top_level_form.LastButtonClicked + if element.BType != BUTTON_TYPE_REALTIME: # Do not clear realtime buttons + top_level_form.LastButtonClicked = None + if element.BType == BUTTON_TYPE_CALENDAR_CHOOSER: + try: + value = element.TKCal.selection + except: + value = None + else: + try: + value = element.FileOrFolderName + except: + value = None + elif element.Type == ELEM_TYPE_INPUT_COMBO: + element = element # type: Combo + index = element.QT_ComboBox.currentIndex() # index into the list of values, but can be larger if manual entry + if index < len(element.Values): + value = element.Values[index] + else: # if not a valid index, then get what was typed in + value = element.QT_ComboBox.currentText() + elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: + value = 0 + elif element.Type == ELEM_TYPE_INPUT_LISTBOX: + element = element # type: Listbox + # print(f'selected indexes = {element.QT_ListWidget.selectedIndexes()}') + value = [] + # value = [element.Values[int(i)] for i in element.QT_ListWidget.selectedIndexes()] + # value= [ for i, item in enumerate(element.QT_ListWidget.selectedItems()] + selected_items = [item.text() for item in element.QT_ListWidget.selectedItems()] + for v in element.Values: + if str(v) in selected_items: + value.append(v) + # try: + # value= [item.index() for item in element.QT_ListWidget.selectedItems()] + # # value= [item.text() for item in element.QT_ListWidget.selectedItems()] + # except: + # value = [] + elif element.Type == ELEM_TYPE_INPUT_SPIN: + # value = str(element.QT_Spinner.value()) + # value = str(element.QT_Spinner.textFromValue(element.QT_Spinner.value())) + value = element.Values[element.QT_Spinner.value()] + elif element.Type == ELEM_TYPE_INPUT_DIAL: + value = str(element.QT_Dial.value()) + elif element.Type == ELEM_TYPE_INPUT_SLIDER: + value = element.QT_Slider.value() + elif element.Type == ELEM_TYPE_INPUT_MULTILINE: + if element.WriteOnly: + continue + value = element.QT_TextEdit.toPlainText() + if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: + element.QT_TextEdit.setText('') + elif element.Type == ELEM_TYPE_TAB_GROUP: + element = element # type: TabGroup + cur_index = element.QT_QTabWidget.currentIndex() + tab_element = element.TabList[cur_index] + value = tab_element.Key + elif element.Type == ELEM_TYPE_TABLE: + value = [] + indexes = element.QT_TableWidget.selectionModel().selectedRows() + for index in sorted(indexes): + value.append(index.row()) + elif element.Type == ELEM_TYPE_TREE: + value = [] + indexes = element.QT_QTreeWidget.selectionModel().selectedRows() + for index in sorted(indexes): + value.append(index.row()) + elif element.Type == ELEM_TYPE_BUTTONMENU: + value = element.MenuItemChosen + element.MenuItemChosen = None + elif element.Type == ELEM_TYPE_MENUBAR: + if element.MenuItemChosen is not None: + top_level_form.LastButtonClicked = element.MenuItemChosen + button_pressed_text = top_level_form.LastButtonClicked + value = element.MenuItemChosen + element.MenuItemChosen = None + else: + value = None + + # if an input type element, update the results + if element.Type != ELEM_TYPE_BUTTON and element.Type != ELEM_TYPE_TEXT and element.Type != ELEM_TYPE_IMAGE and element.Type != ELEM_TYPE_OUTPUT and element.Type != ELEM_TYPE_PROGRESS_BAR and element.Type != ELEM_TYPE_COLUMN and element.Type != ELEM_TYPE_FRAME and element.Type != ELEM_TYPE_TAB: + AddToReturnList(form, value) + AddToReturnDictionary(top_level_form, element, value) + elif ( + (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_CALENDAR_CHOOSER and element.Target == (None, None)) + or (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_COLOR_CHOOSER and element.Target == (None, None)) + or ( + element.Type == ELEM_TYPE_BUTTON + and element.Key is not None + and ( + element.BType + in ( + BUTTON_TYPE_SAVEAS_FILE, + BUTTON_TYPE_BROWSE_FILE, + BUTTON_TYPE_BROWSE_FILES, + BUTTON_TYPE_BROWSE_FOLDER, + ) + ) + ) + ): + AddToReturnList(form, value) + AddToReturnDictionary(top_level_form, element, value) + + # if this is a column, then will fail so need to wrap with tr + try: + if form.ReturnKeyboardEvents and form.LastKeyboardEvent is not None: + button_pressed_text = form.LastKeyboardEvent + form.LastKeyboardEvent = None + except: + pass + + try: + form.ReturnValuesDictionary.pop(None, None) # clean up dictionary include None was included + except: + pass + + if not form.UseDictionary: + form.ReturnValues = button_pressed_text, form.ReturnValuesList + else: + form.ReturnValues = button_pressed_text, form.ReturnValuesDictionary + + return form.ReturnValues + + +def FillFormWithValues(form, values_dict): + FillSubformWithValues(form, values_dict) + + +def FillSubformWithValues(form, values_dict): + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + value = None + if element.Type == ELEM_TYPE_COLUMN: + FillSubformWithValues(element, values_dict) + if element.Type == ELEM_TYPE_FRAME: + FillSubformWithValues(element, values_dict) + if element.Type == ELEM_TYPE_TAB_GROUP: + FillSubformWithValues(element, values_dict) + if element.Type == ELEM_TYPE_TAB: + FillSubformWithValues(element, values_dict) + try: + value = values_dict[element.Key] + except: + continue + if element.Type == ELEM_TYPE_INPUT_TEXT: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_RADIO: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_COMBO: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_LISTBOX: + element.SetValue(value) + elif element.Type == ELEM_TYPE_INPUT_SLIDER: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_MULTILINE: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_SPIN: + element.Update(value) + elif element.Type == ELEM_TYPE_BUTTON: + element.Update(value) + + +def _FindElementFromKeyInSubForm(form, key): + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + if element.Type == ELEM_TYPE_COLUMN: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_FRAME: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB_GROUP: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Key == key: + return element + + +def _FindElementWithFocusInSubForm(form): + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + if element.Type == ELEM_TYPE_COLUMN: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_FRAME: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB_GROUP: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + try: + if element.Widget.hasFocus(): + return element + except: + continue + # if element.Type == ELEM_TYPE_INPUT_TEXT: + # if element.QT_QLineEdit is not None: + # if element.QT_QLineEdit is element.TKEntry.focus_get(): + # return element + + +def AddTrayMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): + if type(sub_menu_info) is str: + if not is_sub_menu and not skip: + # print(f'Adding command {sub_menu_info}') + action = QAction(top_menu) + if sub_menu_info == '---': + action.setSeparator(True) + else: + try: + item_without_key = sub_menu_info[: sub_menu_info.index(MENU_KEY_SEPARATOR)] + except: + item_without_key = sub_menu_info + if item_without_key[0] == MENU_DISABLED_CHARACTER: + action.setText(item_without_key[len(MENU_DISABLED_CHARACTER) :]) + action.setDisabled(True) + else: + action.setText(item_without_key) + action.triggered.connect(lambda: SystemTray._QT_MenuItemChosenCallback(element, sub_menu_info)) + top_menu.addAction(action) + else: + i = 0 + while i < (len(sub_menu_info)): + item = sub_menu_info[i] + if i != len(sub_menu_info) - 1: + if type(sub_menu_info[i + 1]) is list: + new_menu = QMenu(top_menu) + item = sub_menu_info[i] + try: + item_without_key = item[: item.index(MENU_KEY_SEPARATOR)] + except: + item_without_key = item + if item_without_key[0] == MENU_DISABLED_CHARACTER: + new_menu.setTitle(item_without_key[len(MENU_DISABLED_CHARACTER) :]) + new_menu.setDisabled(True) + else: + new_menu.setTitle(item_without_key) + top_menu.addAction(new_menu.menuAction()) + # print(f'Adding submenu {sub_menu_info[i]}') + AddTrayMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) + i += 1 # skip the next one + else: + AddTrayMenuItem(top_menu, item, element) + else: + AddTrayMenuItem(top_menu, item, element) + i += 1 + + +def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): + if type(sub_menu_info) is str: + if not is_sub_menu and not skip: + # print(f'Adding command {sub_menu_info}') + action = QAction(top_menu) + + if sub_menu_info == '---': + action.setSeparator(True) + else: + + # Key handling.... strip off key before setting text + try: + item_without_key = sub_menu_info[: sub_menu_info.index(MENU_KEY_SEPARATOR)] + except: + item_without_key = sub_menu_info + if item_without_key[0] == MENU_DISABLED_CHARACTER: + action.setText(item_without_key[len(MENU_DISABLED_CHARACTER) :]) + action.setDisabled(True) + else: + action.setText(item_without_key) + action.triggered.connect(lambda: Menu._QT_MenuItemChosenCallback(element, sub_menu_info)) + top_menu.addAction(action) + else: + i = 0 + while i < (len(sub_menu_info)): + item = sub_menu_info[i] + if i != len(sub_menu_info) - 1: + if type(sub_menu_info[i + 1]) is list: + new_menu = QMenu(top_menu) + # + # # === style === + # menu_style = _Style('QMenu') + # menu_style['font'] = create_style_from_font(element.Font) + # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + # menu_style['color'] = element.TextColor + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # menu_style['background-color'] = element.BackgroundColor + # # style['margin'] = full_element_pad + # new_menu.setStyleSheet(menu_style.build_css_string()) + # print(menu_style) + # # element.qt_styles = (style,) + # # === style === end + # + # Key handling.... strip off key before setting text + item = sub_menu_info[i] + try: + item_without_key = item[: item.index(MENU_KEY_SEPARATOR)] + except: + item_without_key = item + if item_without_key[0] == MENU_DISABLED_CHARACTER: + new_menu.setTitle(item_without_key[len(MENU_DISABLED_CHARACTER) :]) + new_menu.setDisabled(True) + else: + new_menu.setTitle(item_without_key) + top_menu.addAction(new_menu.menuAction()) + # print(f'Adding submenu {sub_menu_info[i]}') + AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) + i += 1 # skip the next one + else: + AddMenuItem(top_menu, item, element) + else: + AddMenuItem(top_menu, item, element) + i += 1 + + +""" + QQQQQQQQQ tttt + QQ:::::::::QQ ttt:::t + QQ:::::::::::::QQ t:::::t +Q:::::::QQQ:::::::Q t:::::t +Q::::::O Q::::::Qttttttt:::::ttttttt +Q:::::O Q:::::Qt:::::::::::::::::t +Q:::::O Q:::::Qt:::::::::::::::::t +Q:::::O Q:::::Qtttttt:::::::tttttt +Q:::::O Q:::::Q t:::::t +Q:::::O Q:::::Q t:::::t +Q:::::O QQQQ:::::Q t:::::t +Q::::::O Q::::::::Q t:::::t tttttt +Q:::::::QQ::::::::Q t::::::tttt:::::t + QQ::::::::::::::Q tt::::::::::::::t + QQ:::::::::::Q tt:::::::::::tt + QQQQQQQQ::::QQ ttttttttttt + Q:::::Q + QQQQQQ +""" + +# My crappy Qt code starts here + +# ░░░░░░░░░░░█▀▀░░█░░░░░░ +# ░░░░░░▄▀▀▀▀░░░░░█▄▄░░░░ +# ░░░░░░█░█░░░░░░░░░░▐░░░ +# ░░░░░░▐▐░░░░░░░░░▄░▐░░░ +# ░░░░░░█░░░░░░░░▄▀▀░▐░░░ +# ░░░░▄▀░░░░░░░░▐░▄▄▀░░░░ +# ░░▄▀░░░▐░░░░░█▄▀░▐░░░░░ +# ░░█░░░▐░░░░░░░░▄░█░░░░░ +# ░░░█▄░░▀▄░░░░▄▀▐░█░░░░░ +# ░░░█▐▀▀▀░▀▀▀▀░░▐░█░░░░░ +# ░░▐█▐▄░░▀░░░░░░▐░█▄▄░░░ +# ░░░▀▀▄░░░░░░░░▄▐▄▄▄▀░░░ +# ░░░░░░░░░░░░░░░░░░░░░░░ + + +# ------------------------------------------------------------------------------------------------------------------ # +# ------------------------------------------------------------------------------------------------------------------ # +# ===================================== Qt CODE STARTS HERE ====================================================== # +# ------------------------------------------------------------------------------------------------------------------ # +# ------------------------------------------------------------------------------------------------------------------ # +# to_css_prop(css_prop.replace('_','-'), value) +def _to_css_prop(key_, val_): + return '{}:{}; '.format(key_.replace('_', '-'), val_) + + +# def style_generate(qt_element_type, css_props_str): +# return '%s {\n %s \n}' % (qt_element_type, css_props_str) +_valid_css_fields = [ + 'align-content', + 'align-items', + 'align-self', + 'background', + 'background-attachment', + 'background-color', + 'background-image', + 'background-position', + 'background-size', + 'border', + 'border-collapse', + 'border-image', + 'border-radius', + 'border-spacing', + 'bottom', + 'box-decoration-break', + 'caret-color', + 'clear', + 'clip-path', + 'color', + 'color-adjust', + 'column-count', + 'column-fill', + 'column-gap', + 'column-rule', + 'column-rule-color', + 'column-rule-style', + 'column-rule-width', + 'column-span', + 'column-width', + 'columns', + 'contain', + 'content', + 'counter-increment', + 'counter-reset', + 'counter-set', + 'cursor', + 'direction', + 'display', + 'empty-cells', + 'fill', + 'filter', + 'flex', + 'flex-basis', + 'flex-direction', + 'flex-flow', + 'flex-grow', + 'flex-shrink', + 'flex-wrap', + 'float', + 'font', + 'font-display', + 'font-family', + 'font-feature-settings', + 'font-kerning', + 'font-optical-sizing', + 'font-size', + 'font-size-adjust', + 'font-stretch', + 'font-style', + 'font-synthesis', + 'font-variant', + 'font-variant-numeric', + 'font-weight', + 'gap', + 'grid-column', + 'grid-row', + 'grid-template-columns', + 'grid-template-rows', + 'hanging-punctuation', + 'height', + 'hyphens', + 'image-rendering', + 'initial-letter', + 'inline-size', + 'inset', + 'inset-block', + 'inset-block-end', + 'inset-block-start', + 'inset-inline', + 'inset-inline-end', + 'inset-inline-start', + 'isolation', + 'justify-content', + 'left', + 'letter-spacing', + 'line-clamp', + 'line-height', + 'list-style', + 'margin', + 'mask-image', + 'mask-position', + 'mask-repeat', + 'mask-size', + 'max-height', + 'max-width', + 'min-height', + 'min-width', + 'mix-blend-mode', + 'object-fit', + 'object-position', + 'offset-anchor', + 'offset-distance', + 'offset-path', + 'offset-rotate', + 'opacity', + 'order', + 'orphans', + 'outline', + 'outline-offset', + 'overflow', + 'overflow-anchor', + 'overflow-wrap', + 'overscroll-behavior', + 'padding', + 'page-break', + 'paint-order', + 'perspective', + 'perspective-origin', + 'place-items', + 'pointer-events', + 'position', + 'quotes', + 'resize', + 'right', + 'row-gap', + 'scroll-behavior', + 'scroll-margin', + 'scroll-padding', + 'scroll-snap-align', + 'scroll-snap-stop', + 'scroll-snap-type', + 'scrollbar', + 'scrollbar-color', + 'scrollbar-gutter', + 'scrollbar-width', + 'shape-image-threshold', + 'shape-margin', + 'shape-outside', + 'speak', + 'stroke', + 'stroke-dasharray', + 'stroke-dashoffset', + 'stroke-linecap', + 'stroke-linejoin', + 'stroke-width', + 'tab-size', + 'table-layout', + 'text-align', + 'text-align-last', + 'text-decoration', + 'text-decoration-color', + 'text-decoration-line', + 'text-decoration-skip', + 'text-decoration-skip-ink', + 'text-decoration-style', + 'text-decoration-thickness', + 'text-indent', + 'text-justify', + 'text-overflow', + 'text-rendering', + 'text-shadow', + 'text-stroke', + 'text-transform', + 'text-underline-offset', + 'text-underline-position', + 'top', + 'touch-action', + 'transform', + 'transform-origin', + 'transform-style', + 'transition', + 'transition-delay', + 'transition-duration', + 'transition-property', + 'transition-timing-function', + 'unicode-bidi', + 'unicode-range', + 'user-select', + 'vertical-align', + 'visibility', + 'white-space', + 'widows', + 'width', + 'will-change', + 'word-break', + 'word-spacing', + 'writing-mode', + 'z-index', + 'zoom', +] + + +def is_valid_css_prop(a_css_prop): + '''Check if a given property EXISTS in qt Spec''' + return True + global _valid_css_fields + norm_ = a_css_prop.replace('_', '-') + return norm_ in _valid_css_fields + + +def PackFormIntoFrame(container_elem, containing_frame, toplevel_win): + """ + :param form: a window class + :type form: (Window) + :param containing_frame: ??? + :type containing_frame: ??? + :param toplevel_form: ??? + :type toplevel_form: (Window) + """ + # align2qt_align + align2qt_align = {'c': Qt.AlignCenter, 'l': Qt.AlignLeft, 'r': Qt.AlignRight} + + border_depth = toplevel_win.BorderDepth if toplevel_win.BorderDepth is not None else DEFAULT_BORDER_WIDTH + # --------------------------------------------------------------------------- # + # **************** Use FlexForm to build the tkinter window ********** ----- # + # Building is done row by row. # + # --------------------------------------------------------------------------- # + focus_set = False + ######################### LOOP THROUGH ROWS ######################### + # *********** ------- Loop through ROWS ------- ***********# + for row_num, flex_row in enumerate(container_elem.Rows): + ######################### LOOP THROUGH ELEMENTS ON ROW ######################### + # *********** ------- Loop through ELEMENTS ------- ***********# + # *********** Make TK Row ***********# + qt_row_layout = QHBoxLayout() + elem_align = container_elem.ElementJustification[0] + if elem_align in align2qt_align: + qt_row_layout.setAlignment(align2qt_align[elem_align]) + for col_num, element in enumerate(flex_row): + element.ParentForm = toplevel_win # save the button's parent form object + element.row_frame = qt_row_layout + if toplevel_win.Font and (element.Font == DEFAULT_FONT or not element.Font): + font = toplevel_win.Font + element.Font = font + elif element.Font is not None: + font = element.Font + else: + font = DEFAULT_FONT + # ------- Determine Auto-Size setting on a cascading basis ------- # + if element.AutoSizeText is not None: # if element overide + auto_size_text = element.AutoSizeText + elif toplevel_win.AutoSizeText is not None: # if form override + auto_size_text = toplevel_win.AutoSizeText + else: + auto_size_text = DEFAULT_AUTOSIZE_TEXT + element_type = element.Type + # Set foreground color + text_color = element.TextColor + # Determine Element size + element_size = element.Size + if element_size == (None, None) and element_type not in ( + ELEM_TYPE_BUTTON, + ELEM_TYPE_BUTTONMENU, + ): # user did not specify a size + element_size = toplevel_win.DefaultElementSize + elif element_size == (None, None) and element_type in (ELEM_TYPE_BUTTON, ELEM_TYPE_BUTTONMENU): + element_size = toplevel_win.DefaultButtonElementSize + else: + auto_size_text = False # if user has specified a size then it shouldn't autosize + full_element_pad = [0, 0, 0, 0] # Top, Right, Bottom, Left + elementpad = element.Pad if element.Pad is not None else toplevel_win.ElementPadding + if type(elementpad[0]) is not tuple: # left and right + full_element_pad[1] = full_element_pad[3] = elementpad[0] + else: + full_element_pad[3], full_element_pad[1] = elementpad[0] + + if type(elementpad[1]) is not tuple: # top and bottom + full_element_pad[0] = full_element_pad[2] = elementpad[1] + else: + full_element_pad[0], full_element_pad[2] = elementpad[1] + + border_depth = toplevel_win.BorderDepth if toplevel_win.BorderDepth is not None else DEFAULT_BORDER_WIDTH + try: + if element.BorderWidth is not None: + border_depth = element.BorderWidth + except: + pass + + # ------------------------- COLUMN placement element ------------------------- # + if element_type == ELEM_TYPE_COLUMN: + element = element # type: Column + # column_widget = QWidget() + column_widget = QGroupBox() + element.Widget = element.QT_QGroupBox = column_widget + # column_widget.setFrameShape(QtWidgets.QFrame.NoFrame) + + # === style === + style = QtStyle('QGroupBox') + style['font'] = create_style_from_font(font) + if element.BackgroundColor is not None: + style['background_color'] = element.BackgroundColor + style['border'] = '0px solid gray' # FIXv2 + column_widget.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + column_layout = QFormLayout() + element.vbox_layout = column_vbox = QVBoxLayout() + PackFormIntoFrame(element, column_layout, toplevel_win) + + scroll = None + if element.Scrollable and (element_size[0] is not None or element_size[1] is not None): + scroll = QtWidgets.QScrollArea() + scroll.setWidget(column_widget) + if element_size[0] is not None: + scroll.setFixedWidth(element_size[0]) + if element_size[1] is not None: + scroll.setFixedHeight(element_size[1]) + scroll.setWidgetResizable(True) + + column_vbox.addLayout(column_layout) + column_widget.setLayout(column_vbox) + + # column_widget.setStyleSheet(style) + if not element.Visible: + column_widget.setVisible(False) + + qt_row_layout.addWidget(scroll if scroll else column_widget, alignment=Qt.AlignVCenter) + # ------------------------- TEXT placement element ------------------------- # + elif element_type == ELEM_TYPE_TEXT: + element.Widget = element.QT_Label = qlabel = QLabel(element.DisplayText, toplevel_win.QTWindow) + if element.Justification is not None: + justification = element.Justification + elif toplevel_win.TextJustification is not None: + justification = toplevel_win.TextJustification + else: + justification = DEFAULT_TEXT_JUSTIFICATION + + if justification[0] in align2qt_align: + element.QT_Label.setAlignment(align2qt_align[justification[0]]) + if not auto_size_text: + if element_size[0] is not None: + element.QT_Label.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_Label.setFixedHeight(element_size[1]) + # element.QT_Label.setWordWrap(True) + + # === style === + style = QtStyle('QLabel') + style['font'] = create_style_from_font(font) + style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) + style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) + style['margin'] = full_element_pad + element.QT_Label.setStyleSheet(str(style)) + element.qt_styles = (style,) + # === style === end + + if element.Relief is not None: + if element.Relief in (RELIEF_RIDGE, RELIEF_RAISED): + qlabel.setFrameStyle(QFrame.Panel | QFrame.Raised) + elif element.Relief in (RELIEF_SUNKEN, RELIEF_GROOVE): + qlabel.setFrameStyle(QFrame.Panel | QFrame.Sunken) + elif element.Relief == RELIEF_FLAT: + qlabel.setFrameStyle(QFrame.Panel | QFrame.NoFrame) + + if element.Margins is not None: + m = element.Margins + qlabel.setContentsMargins(m[0], m[2], m[1], m[3]) # L T B R + if element.Tooltip: + element.QT_Label.setToolTip(element.Tooltip) + if element.ClickSubmits: + element.QT_Label.mousePressEvent = element._QtCallbackTextClicked + if not element.Visible: + element.QT_Label.setVisible(False) + qt_row_layout.addWidget(element.QT_Label, alignment=Qt.AlignVCenter) + # ------------------------- BUTTON placement element ------------------------- # + elif element_type == ELEM_TYPE_BUTTON: + element = element # type: Button + btext = element.ButtonText + btype = element.BType + element.Widget = element.QT_QPushButton = QPushButton(btext) + # === style === + style = QtStyle('QPushButton') + style['font'] = create_style_from_font(font) + style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) + style['background_color'] = element.BackgroundColor + if element.BorderWidth == 0: + style['border'] = 'none' + style['margin'] = full_element_pad + # style['border'] = '{}px solid gray '.format(border_depth) + element.QT_QPushButton.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + # element.QT_QPushButton.setFlat(False) + if (element.AutoSizeButton is False or toplevel_win.AutoSizeButtons is False or element.Size[0] is not None) and element.ImageData is None: + if element_size[0] is not None: + element.QT_QPushButton.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_QPushButton.setFixedHeight(element_size[1]) + + if element.ImageFilename is not None: + element.QT_QPushButton.setIcon(QtGui.QPixmap(element.ImageFilename)) + element.QT_QPushButton.setIconSize(QtGui.QPixmap(element.ImageFilename).rect().size()) + if element.ImageData: + ba = QtCore.QByteArray.fromBase64(element.ImageData) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + element.QT_QPushButton.setIcon(pixmap) + element.QT_QPushButton.setIconSize(pixmap.rect().size()) + + if element.Disabled: + element.QT_QPushButton.setDisabled(True) + + if element.Tooltip: + element.QT_QPushButton.setToolTip(element.Tooltip) + element.QT_QPushButton.clicked.connect(element._ButtonCallBack) + if not element.Visible: + element.QT_QPushButton.setVisible(False) + + qt_row_layout.addWidget(element.QT_QPushButton, alignment=Qt.AlignVCenter) + # ------------------------- INPUT placement element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_TEXT: + element = element # type: InputText + default_text = element.DefaultText + element.Widget = element.QT_QLineEdit = qlineedit = QLineEdit() + + qlineedit.setAcceptDrops(True) + qlineedit.dragEnterEvent = element._dragEnterEvent + qlineedit.dropEvent = element._dropEvent + + if element.Justification[0] in align2qt_align: + element.QT_QLineEdit.setAlignment(align2qt_align[element.Justification[0]]) + element.QT_QLineEdit.setText(str(default_text)) + + # === style === + style = QtStyle('QLineEdit') + style['font'] = create_style_from_font(font) + if element.Disabled or element.ReadOnly: + if element.disabled_readonly_background_color: + style['background_color'] = (element.disabled_readonly_background_color, COLOR_SYSTEM_DEFAULT) + else: + style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) + if element.disabled_readonly_text_color: + style['color'] = (element.disabled_readonly_text_color, COLOR_SYSTEM_DEFAULT) + else: + style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) + else: + style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) + style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) + style['margin'] = full_element_pad + style['border'] = '{}px solid gray '.format(border_depth) + element.QT_QLineEdit.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if element.AutoSizeText is False or toplevel_win.AutoSizeText is False or element.Size[0] is not None: + if element_size[0] is not None: + element.QT_QLineEdit.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_QLineEdit.setFixedHeight(element_size[1]) + + if (element.Focus or toplevel_win.UseDefaultFocus) and not focus_set: + focus_set = True + toplevel_win.FocusElement = element.QT_QLineEdit + + if element.Disabled: + element.QT_QLineEdit.setDisabled(True) + + if element.ReadOnly: + element.QT_QLineEdit.setReadOnly(True) + + if element.ChangeSubmits: + element.QT_QLineEdit.textChanged.connect(element._QtCallbackFocusInEvent) + + element.QT_QLineEdit.returnPressed.connect(element._QtCallbackReturnPressed) + + if element.PasswordCharacter != '': + qlineedit.setEchoMode(QLineEdit.Password) + if element.Tooltip: + element.QT_QLineEdit.setToolTip(element.Tooltip) + + element.InputTextWidget = Input.InputTextWidget(element.QT_QLineEdit, element) + element.QT_QLineEdit.installEventFilter(element.InputTextWidget) + if not element.Visible: + element.QT_QLineEdit.setVisible(False) + qt_row_layout.addWidget(element.QT_QLineEdit, alignment=Qt.AlignVCenter) + # ------------------------- COMBO placement BOX (Drop Down) element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_COMBO: + element = element # type: Combo + element.Widget = element.QT_ComboBox = QComboBox() + + items_as_strings = [str(v) for v in element.Values] + # element.QT_ComboBox.addItems(element.Values) + element.QT_ComboBox.addItems(items_as_strings) + + # === style === + style = QtStyle('QComboBox') + style['font'] = create_style_from_font(font) + style['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) + style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) + style['border'] = '{}px solid gray '.format(border_depth) + style['margin'] = full_element_pad + + style2 = QtStyle('QListView') + style2['color'] = (element.TextColor, COLOR_SYSTEM_DEFAULT) + style2['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) + + element.QT_ComboBox.setStyleSheet(str(style) + str(style2)) + element.qt_styles = (style, style2) + # === style === end + + if element_size[0] is not None: + element.QT_ComboBox.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_ComboBox.setFixedHeight(element_size[1]) + + if element.Disabled: + element.QT_ComboBox.setDisabled(True) + + element.QT_ComboBox.setMaxVisibleItems(element.VisibleItems) + if element.DefaultValue is not None: + for index, v in enumerate(element.Values): + if v == element.DefaultValue: + element.QT_ComboBox.setCurrentIndex(index) + break + + if element.ChangeSubmits: + element.QT_ComboBox.currentIndexChanged.connect(element._QtCurrentItemChanged) + if element.Tooltip: + element.QT_ComboBox.setToolTip(element.Tooltip) + if not element.Readonly: + element.QT_ComboBox.setEditable(True) + if not element.AutoComplete: + element.QT_ComboBox.setAutoCompletion(True) + if not element.Visible: + element.QT_ComboBox.setVisible(False) + qt_row_layout.addWidget(element.QT_ComboBox, alignment=Qt.AlignVCenter) + # ------------------------- OPTION MENU (Like ComboBox but different) element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_OPTION_MENU: + pass + # ------------------------- LISTBOX placement element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_LISTBOX: + element = element # type: Listbox + element.Widget = element.QT_ListWidget = QListWidget() + + # === style === + style = QtStyle('QListWidget') + style['font'] = create_style_from_font(font) + if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + style['color'] = element.TextColor + if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + style['background-color'] = element.BackgroundColor # example for mike here + style['margin'] = full_element_pad + style['border'] = '{}px solid gray; '.format(border_depth) + element.QT_ListWidget.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if not auto_size_text: + if element_size[0] is not None: + element.QT_ListWidget.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_ListWidget.setFixedHeight(element_size[1]) + + if element.SelectMode == SELECT_MODE_MULTIPLE: + element.QT_ListWidget.setSelectionMode(QAbstractItemView.MultiSelection) + elif element.SelectMode == SELECT_MODE_EXTENDED: + element.QT_ListWidget.setSelectionMode(QAbstractItemView.ExtendedSelection) + elif element.SelectMode == SELECT_MODE_CONTIGUOUS: + element.QT_ListWidget.setSelectionMode(QAbstractItemView.ContiguousSelection) + elif element.SelectMode == SELECT_MODE_SINGLE: + element.QT_ListWidget.setSelectionMode(QAbstractItemView.SingleSelection) + if element.Disabled: + element.QT_ListWidget.setDisabled(True) + if element.ChangeSubmits: + element.QT_ListWidget.currentRowChanged.connect(element._QtCurrentRowChanged) + + # add all Values to the ListWidget + element.QT_ListWidget.addItems([str(v) for v in element.Values]) + # select the default items + for index, value in enumerate(element.Values): + item = element.QT_ListWidget.item(index) + if element.DefaultValues is not None and value in element.DefaultValues: + element.QT_ListWidget.setItemSelected(item, True) + + if element.Tooltip: + element.QT_ListWidget.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_ListWidget.setVisible(False) + qt_row_layout.addWidget(element.QT_ListWidget, alignment=Qt.AlignVCenter) + # ------------------------- INPUT MULTILINE placement element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_MULTILINE: + element = element # type: Multiline + default_text = element.DefaultText + width, height = element_size + element.Widget = element.QT_TextEdit = QTextEdit() + + element.QT_TextEdit.setAcceptDrops(True) + element.QT_TextEdit.dragEnterEvent = element._dragEnterEvent + element.QT_TextEdit.dropEvent = element._dropEvent + + # === style === + style = QtStyle('QTextEdit') + style['font'] = create_style_from_font(font) + if element.TextColor is not None: + style['color'] = element.TextColor + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + style['border'] = '{}px solid gray; '.format(border_depth) + element.QT_TextEdit.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if element.AutoSizeText is False or element.Size[0] is not None: + if element_size[0] is not None: + element.QT_TextEdit.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_TextEdit.setFixedHeight(element_size[1]) + + if element.Disabled: + element.QT_TextEdit.setDisabled(True) + + element.MultiQWidget = Multiline.MultiQWidget(element.QT_TextEdit, element) + element.QT_TextEdit.installEventFilter(element.MultiQWidget) + + if element.ChangeSubmits: + element.QT_TextEdit.textChanged.connect(element._QtCallbackFocusInEvent) + + if (element.Focus or toplevel_win.UseDefaultFocus) and not focus_set: + focus_set = True + toplevel_win.FocusElement = element.QT_TextEdit + + element.QT_TextEdit.setText(str(default_text)) + element.QT_TextEdit.moveCursor(QtGui.QTextCursor.End) + if element.Tooltip: + element.QT_TextEdit.setToolTip(element.Tooltip) + # qt_row_layout.setContentsMargins(*full_element_pad) + if not element.Visible: + element.QT_TextEdit.setVisible(False) + qt_row_layout.addWidget(element.QT_TextEdit, alignment=Qt.AlignVCenter) + # ------------------------- OUTPUT MULTILINE placement element ------------------------- # + elif element_type == ELEM_TYPE_MULTILINE_OUTPUT: + element = element # type: MultilineOutput + default_text = element.DefaultText + element.Widget = element.QT_TextBrowser = QTextBrowser() + element.QT_TextBrowser.setDisabled(False) + + # === style === + style = QtStyle('QTextBrowser') + style['font'] = create_style_from_font(font) + if element.TextColor is not None: + style['color'] = element.TextColor + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + style['border'] = '{}px solid gray'.format(border_depth) + element.QT_TextBrowser.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if element.AutoSizeText is False or element.Size[0] is not None: + if element_size[0] is not None: + element.QT_TextBrowser.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_TextBrowser.setFixedHeight(element_size[1]) + + element.QT_TextBrowser.insertPlainText(default_text) + element.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) + if element.Tooltip: + element.QT_TextBrowser.setToolTip(element.Tooltip) + # qt_row_layout.setContentsMargins(*full_element_pad) + if not element.Visible: + element.QT_TextBrowser.setVisible(False) + qt_row_layout.addWidget(element.QT_TextBrowser, alignment=Qt.AlignVCenter) + # ------------------------- INPUT CHECKBOX placement element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_CHECKBOX: + element = element # type: Checkbox + element.QT_Checkbox = QCheckBox(element.Text) + element.QT_Checkbox.setChecked(element.InitialState) + if element.Disabled: + element.QT_Checkbox.setDisabled(True) + + # === style === + style = QtStyle('QCheckBox') + style['font'] = create_style_from_font(font) + if element.TextColor is not None: + style['color'] = element.TextColor + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + element.QT_Checkbox.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if element.AutoSizeText is False or element.Size[0] is not None: + if element_size[0] is not None: + element.QT_Checkbox.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_Checkbox.setFixedHeight(element_size[1]) + if element.ChangeSubmits: + element.QT_Checkbox.stateChanged.connect(element.QtCallbackStateChanged) + # qt_row_layout.setContentsMargins(*full_element_pad) + if element.Tooltip: + element.QT_Checkbox.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_Checkbox.setVisible(False) + qt_row_layout.addWidget(element.QT_Checkbox, alignment=Qt.AlignVCenter) + # ------------------------- PROGRESSBAR placement element ------------------------- # + elif element_type == ELEM_TYPE_PROGRESS_BAR: + element.Widget = element.QT_QProgressBar = QProgressBar() + orientation = element.Orientation.lower()[0] + if element.Size[0] is not None: + if element_size[0] is not None: + element.QT_QProgressBar.setFixedWidth(element_size[orientation != 'h']) + if element_size[1] is not None: + element.QT_QProgressBar.setFixedHeight(element_size[orientation == 'h']) + + element.QT_QProgressBar.setMaximum(element.MaxValue) + element.QT_QProgressBar.setValue(element.StartValue) + if element.Orientation.lower().startswith('v'): + element.QT_QProgressBar.setOrientation(QtCore.Qt.Vertical) + + # === style === + style = QtStyle('QProgressBar') + style_chunk = QtStyle('QProgressBar::chunk') + style['margin'] = full_element_pad + # style += 'margin: {}px {}px {}px {}px;'.format(*full_element_pad) + # style += 'border: {}px solid gray; '.format(border_depth) + if element.BarColor != (None, None): + if element.BarColor[0] is not None: + style_chunk['background-color'] = element.BarColor[0] + + style['border'] = '%spx solid grey' % border_depth + style['border-radius'] = '0px' + style['background-color'] = str(element.BarColor[1] if element.BarColor[1] is not None else DEFAULT_PROGRESS_BAR_COLOR[1]) + + element.QT_QProgressBar.setStyleSheet(style.build_css_string() + style_chunk.build_css_string()) + element.qt_styles = (style, style_chunk) + # === style === end + + element.QT_QProgressBar.setTextVisible(False) + if element.Tooltip: + element.QT_QProgressBar.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_QProgressBar.setVisible(False) + + qt_row_layout.addWidget(element.QT_QProgressBar, alignment=Qt.AlignVCenter) + # ------------------------- INPUT RADIO placement element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_RADIO: + element = element # type: Radio + default_value = element.InitialState + element.Widget = qradio = QRadioButton(element.Text) + element.QT_Radio_Button = qradio + if element.Disabled: + element.QT_Radio_Button.setDisabled(True) + if default_value: + qradio.setChecked(True) + + # === style === + style = QtStyle('QRadioButton') + style['font'] = create_style_from_font(font) + if element.TextColor is not None: + style['color'] = element.TextColor + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + element.QT_Radio_Button.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if element.AutoSizeText is False or element.Size[0] is not None: + if element_size[0] is not None: + element.QT_Radio_Button.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_Radio_Button.setFixedHeight(element_size[1]) + + if element.GroupID in toplevel_win.RadioDict: + element.QT_RadioButtonGroup = toplevel_win.RadioDict[element.GroupID] + else: + element.QT_RadioButtonGroup = QButtonGroup(toplevel_win.QTApplication) + toplevel_win.RadioDict[element.GroupID] = element.QT_RadioButtonGroup + element.QT_RadioButtonGroup.setExclusive(True) + + element.QT_RadioButtonGroup.addButton(element.QT_Radio_Button) + + if element.ChangeSubmits: + element.QT_Radio_Button.toggled.connect(element._QtCallbackValueChanged) + + # qt_row_layout.setContentsMargins(*full_element_pad) + if element.Tooltip: + element.QT_Radio_Button.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_Radio_Button.setVisible(False) + qt_row_layout.addWidget(element.QT_Radio_Button, alignment=Qt.AlignVCenter) + # ------------------------- INPUT SPIN placement element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_SPIN: + # element.QT_Spinner = QSpinBox() + element = element # type: Spin + element.Widget = element.QT_Spinner = Spin.StringBox(element.Values) + if element.DefaultValue is not None: # try to set the default value without crashing on error + try: + element.QT_Spinner.setValue(element.QT_Spinner.valueFromText(element.DefaultValue)) + except: + pass + # === style === + style = QtStyle('QSpinBox') + style['font'] = create_style_from_font(font) + if element.TextColor is not None: + style['color'] = element.TextColor + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + style['border'] = '{}px solid gray'.format(border_depth) + element.QT_Spinner.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + # element.QT_Spinner.setRange(element.Values[0], element.Values[1]) + if not auto_size_text: + if element_size[0] is not None: + element.QT_Spinner.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_Spinner.setFixedHeight(element_size[1]) + + if element.Disabled: + element.QT_Spinner.setDisabled(True) + if element.ChangeSubmits: + element.QT_Spinner.valueChanged.connect(element._QtCallbackValueChanged) + if element.Tooltip: + element.QT_Spinner.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_Spinner.setVisible(False) + qt_row_layout.addWidget(element.QT_Spinner, alignment=Qt.AlignVCenter) + # ------------------------- OUTPUT placement element ------------------------- # + elif element_type == ELEM_TYPE_OUTPUT: + element = element # type: Output + element.Widget = element.QT_TextBrowser = QTextBrowser() + element.QT_TextBrowser.setDisabled(False) + + # === style === + style = QtStyle('QTextBrowser') + style['font'] = create_style_from_font(font) + if element.TextColor is not None: + style['color'] = element.TextColor + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + style['border'] = '{}px solid gray'.format(border_depth) + # style += "QScrollBar:vertical {border: none; background:lightgray; width:12px; margin: 0px 0px 0px 0px; } " + element.QT_TextBrowser.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if element.AutoSizeText is False or element.Size[0] is not None: + if element_size[0] is not None: + element.QT_TextBrowser.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_TextBrowser.setFixedHeight(element_size[1]) + + element.QT_TextBrowser.moveCursor(QtGui.QTextCursor.End) + element._reroute_stdout() + if element.Tooltip: + element.QT_TextBrowser.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_TextBrowser.setVisible(False) + qt_row_layout.addWidget(element.QT_TextBrowser, alignment=Qt.AlignVCenter) + # ------------------------- IMAGE placement element ------------------------- # + elif element_type == ELEM_TYPE_IMAGE: + element = element # type: Image + element.Widget = element.QT_QLabel = qlabel = QLabel() + if element.Filename is not None: + qlabel.setText('') + w = QtGui.QPixmap(element.Filename).width() + h = QtGui.QPixmap(element.Filename).height() + qlabel.setGeometry(QtCore.QRect(0, 0, w, h)) + qlabel.setPixmap(QtGui.QPixmap(element.Filename)) + elif element.Data is not None: + qlabel.setText('') + ba = QtCore.QByteArray.fromRawData(element.Data) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qlabel.setPixmap(pixmap) + elif element.DataBase64: + qlabel.setText('') + ba = QtCore.QByteArray.fromBase64(element.DataBase64) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qlabel.setPixmap(pixmap) + + # === style === + style = QtStyle('QLabel') + style['margin'] = full_element_pad + element.QT_QLabel.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if element.Tooltip: + element.QT_QLabel.setToolTip(element.Tooltip) + if element.ClickSubmits: + element.QT_QLabel.mousePressEvent = element.QtCallbackImageClicked + if not element.Visible: + element.QT_QLabel.setVisible(False) + qt_row_layout.addWidget(element.QT_QLabel, alignment=Qt.AlignVCenter) + # ------------------------- Canvas placement element ------------------------- # + elif element_type == ELEM_TYPE_CANVAS: + width, height = element_size + # ------------------------- Graph placement element ------------------------- # + elif element_type == ELEM_TYPE_GRAPH: + element = element # type: Graph + width, height = element_size + # print(f'Graph element size = {element_size}') + element.Widget = element.QT_QGraphicsView = qgraphicsview = QGraphicsView() + # element.QT_QGraphicsView.setGeometry(0,0,element.CanvasSize[0],element.CanvasSize[1]) + # print(f'Graph Canvas size = {element.CanvasSize}') + + element.QT_QGraphicsScene = QGraphicsScene() + element.QT_QGraphicsScene.setSceneRect(0, 0, element.CanvasSize[0], element.CanvasSize[1]) + element.QT_QGraphicsView.setScene(element.QT_QGraphicsScene) + + # === style === + style = QtStyle('QGraphicsView') + style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) + style['margin'] = full_element_pad + style['border'] = '{}px solid gray '.format(border_depth) + # print(f'style content = {style.content}') + element.QT_QGraphicsView.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + qgraphicsview.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + qgraphicsview.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + # qt_row_layout.setContentsMargins(*full_element_pad) + if element.Tooltip: + element.QT_QGraphicsView.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_QGraphicsView.setVisible(False) + qt_row_layout.addWidget(element.QT_QGraphicsView, alignment=Qt.AlignVCenter) + # ------------------------- MENUBAR placement element ------------------------- # + elif element_type == ELEM_TYPE_MENUBAR: + element = element # type: Menu + menu_def = element.MenuDefinition + element.Widget = element.QT_QMenuBar = QMenuBar(toplevel_win.QT_QMainWindow) + + for menu_entry in menu_def: + # print(f'Adding a Menubar ENTRY {menu_entry}') + baritem = QMenu(element.QT_QMenuBar) + if menu_entry[0][0] == MENU_DISABLED_CHARACTER: + baritem.setDisabled(True) + baritem.setTitle(menu_entry[0][1:]) + else: + baritem.setTitle(menu_entry[0]) + element.QT_QMenuBar.addAction(baritem.menuAction()) + AddMenuItem(baritem, menu_entry[1], element) + # === style === + menu_style = QtStyle('QMenu') + menu_style['font'] = create_style_from_font(font) + if element.MenuItemTextColor is not None and element.MenuItemTextColor != COLOR_SYSTEM_DEFAULT: + menu_style['color'] = element.MenuItemTextColor + if element.MenuItemBackgroundColor is not None and element.MenuItemBackgroundColor != COLOR_SYSTEM_DEFAULT: + menu_style['background-color'] = element.MenuItemBackgroundColor + # style['margin'] = full_element_pad + baritem.setStyleSheet(menu_style.build_css_string()) + # === style === end + + if element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # === style === + style = QtStyle('QMenuBar') + style['background_color'] = (element.BackgroundColor, COLOR_SYSTEM_DEFAULT) + element.QT_QMenuBar.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if not element.Visible: + element.QT_QMenuBar.setVisible(False) + toplevel_win.QT_QMainWindow.setMenuBar(element.QT_QMenuBar) + # ------------------------- BUTTONMENU placement element ------------------------- # + elif element_type == ELEM_TYPE_BUTTONMENU: + btext = element.ButtonText + element.Widget = element.QT_QPushButton = QPushButton(btext) + + # === style === + style = QtStyle('QPushButton') + style['font'] = create_style_from_font(font) + if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + style['color'] = element.TextColor + if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + style['background-color'] = element.BackgroundColor + if element.BorderWidth == 0: + style['border'] = 'none' + style['margin'] = full_element_pad + element.QT_QPushButton.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if (element.AutoSizeButton is False or toplevel_win.AutoSizeButtons is False or element.Size[0] is not None) and element.ImageData is None: + if element_size[0] is not None: + element.QT_QPushButton.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_QPushButton.setFixedHeight(element_size[1]) + + if element.ImageData: + ba = QtCore.QByteArray.fromBase64(element.ImageData) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + element.QT_QPushButton.setIcon(pixmap) + element.QT_QPushButton.setIconSize(pixmap.rect().size()) + + if element.Disabled: + element.QT_QPushButton.setDisabled(True) + + if element.Tooltip: + element.QT_QPushButton.setToolTip(element.Tooltip) + # element.QT_QPushButton.clicked.connect(element._ButtonCallBack) + + menu_def = element.MenuDefinition + + qmenu = QMenu(element.QT_QPushButton) + qmenu.setTitle(menu_def[0]) + AddMenuItem(qmenu, menu_def[1], element) + + # === style === + menu_style = QtStyle('QMenu') + menu_style['font'] = create_style_from_font(font) + if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + menu_style['color'] = element.TextColor + if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + menu_style['background-color'] = element.BackgroundColor + # style['margin'] = full_element_pad + qmenu.setStyleSheet(menu_style.build_css_string()) + # element.qt_styles = (style,) + # === style === end + + element.QT_QPushButton.setMenu(qmenu) + if element.Tooltip: + element.QT_QPushButton.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_QPushButton.setVisible(False) + qt_row_layout.addWidget(element.QT_QPushButton, alignment=Qt.AlignVCenter) + # ------------------------- Frame placement element ------------------------- # + elif element_type == ELEM_TYPE_FRAME: + element = element # type: Frame + element.Widget = column_widget = QGroupBox() + element.QT_QGroupBox = column_widget + + # === style === + style = QtStyle('QGroupBox') + # style['font'] = create_style_from_font(font) + if element.TextColor is not None: + style['color'] = element.TextColor + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + # style['origin'] = 'margin' + style['font'] = create_style_from_font(font) + if element.FrameColor is not None: + style['border'] = '{}px solid {} '.format(border_depth, element.FrameColor) + else: + style['border'] = '{}px solid {} '.format(border_depth, 'gainsboro') # default to a light gray + + # style['padding'] = (10,10,10,10) + # style['margin'] = full_element_pad + # style['padding'] = (0,15,15,15) + # style['padding'] = (10,10,10,10) + style['margin-top'] = '10px' + # style['top'] = '60px' + # style['margin'] = (0,0,0,0) + style['origin'] = 'margin' + + style_title = QtStyle('QGroupBox::title') + # style_title['padding'] = (0,5,0,5) + style_title['margin'] = (0, 0, 0, 0) + + style_title['left'] = '15px' + # style_title['margin-top'] = '-20px' + # style_title['top'] = '20px' + style_title['subcontrol-origin'] = 'margin' + # style_title['subcontrol-origin'] = 'border' + + # style_title['subcontrol-origin'] = 'padding' + style_title['subcontrol-position'] = 'top left' + + column_widget.setStyleSheet(str(style) + str(style_title)) + # column_widget.setStyleSheet(str(style)) + # print(element.Widget.styleSheet()) + element.qt_styles = (style,) + # === style === end + + column_widget.setTitle(element.Title) + + column_layout, column_vbox = QFormLayout(), QVBoxLayout() + PackFormIntoFrame(element, column_layout, toplevel_win) + column_vbox.addLayout(column_layout) + column_widget.setLayout(column_vbox) + + # Add a padding groupbox + pad_layout, pad_vbox = QFormLayout(), QVBoxLayout() + pad_groupbox = QGroupBox() + + pad_vbox.addLayout(pad_layout) + pad_groupbox.setLayout(pad_vbox) + pad_vbox.addWidget(column_widget) + + pad_layout.setSpacing(0) + pad_vbox.setSpacing(0) + # === style === + style = QtStyle('QGroupBox') + # style['font'] = create_style_from_font(font) + style['border'] = '0px' + style['margin'] = (0, 0, 0, 0) + # style['margin'] = full_element_pad + + style['padding'] = (0, 0, 0, 0) + style['margin-top'] = '0px' + style['origin'] = 'content' + style_title = QtStyle('QGroupBox::title') + style_title['subcontrol-origin'] = 'content' + + style_title['padding'] = (0, 0, 0, 0) + style_title['margin'] = (0, 0, 0, 0) + pad_groupbox.setStyleSheet(str(style) + str(style_title)) + # === style === end + + if element.Tooltip: + column_widget.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_QGroupBox.setVisible(False) + + qt_row_layout.addWidget(pad_groupbox) + # qt_row_layout.addWidget(column_widget) + # ------------------------- Tab placement element ------------------------- # + elif element_type == ELEM_TYPE_TAB: + element.Widget = tab_widget = QWidget() + element.QT_QWidget = tab_widget + # tab_widget.setFrameShape(QtWidgets.QFrame.NoFrame) + + # === style === + style = QtStyle('QTabWidget') + style['font'] = create_style_from_font(font) + if element.BackgroundColor is not None: + # style += 'background-color: %s;' % element.BackgroundColor + # style += 'QTabWidget > QWidget > QWidget {background: %s;}'% element.BackgroundColor + style['background-color'] = element.BackgroundColor + style.my_anchor = '::pane' + # style += 'background-color: %s;' % element.BackgroundColor + tab_widget.setAutoFillBackground(True) + palette = tab_widget.palette() + palette.setColor(tab_widget.backgroundRole(), element.BackgroundColor) + tab_widget.setPalette(palette) + + # style += 'border: {}px solid gray; '.format(border_depth) + style['margin'] = full_element_pad + tab_widget.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + column_layout, column_vbox = QFormLayout(), QVBoxLayout() + + PackFormIntoFrame(element, column_layout, toplevel_win) + + column_vbox.addLayout(column_layout) + tab_widget.setLayout(column_vbox) + if element.Tooltip: + tab_widget.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_QWidget.setVisible(False) + container_elem.QT_QTabWidget.addTab(tab_widget, element.Title) + # ------------------------- TabGroup placement element ------------------------- # + elif element_type == ELEM_TYPE_TAB_GROUP: + element = element # type:TabGroup + element.Widget = element.QT_QTabWidget = qtab = QTabWidget() + + # === style === + style = QtStyle('QTabWidget') + # print(f'qtab.styleSheet() -> {qtab.styleSheet()}') + # style = qtab.styleSheet() # FIXv2 + if element.SelectedTitleColor not in (None, COLOR_SYSTEM_DEFAULT): + style.my_anchor = '::tab:selected' + style['background'] = element.SelectedTitleColor + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + style.my_anchor = '::tab' + style['background'] = element.BackgroundColor + if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): + style.my_anchor = '::tab' + style['color'] = element.TextColor + style['margin'] = full_element_pad + qtab.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if element.TabLocation is not None: + position_dict = { + 'left': QtWidgets.QTabWidget.TabPosition.West, + 'right': QtWidgets.QTabWidget.TabPosition.East, + 'top': QtWidgets.QTabWidget.TabPosition.North, + 'bottom': QtWidgets.QTabWidget.TabPosition.South, + 'lefttop': QtWidgets.QTabWidget.TabPosition.North, + 'leftbottom': QtWidgets.QTabWidget.TabPosition.South, + 'righttop': QtWidgets.QTabWidget.TabPosition.North, + 'rightbottom': QtWidgets.QTabWidget.TabPosition.South, + 'bottomleft': QtWidgets.QTabWidget.TabPosition.South, + 'bottomright': QtWidgets.QTabWidget.TabPosition.South, + 'topleft': QtWidgets.QTabWidget.TabPosition.North, + 'topright': QtWidgets.QTabWidget.TabPosition.North, + } + try: + element.Widget.setTabPosition(position_dict[element.TabLocation]) + except: + print('Bad tab position specified {}', element.TabLocation) + PackFormIntoFrame(element, element.ParentForm.QFormLayout, toplevel_win) + + qt_row_layout.addWidget(element.QT_QTabWidget, alignment=Qt.AlignVCenter) + if not element.Visible: + element.QT_QTabWidget.setVisible(False) + + if element.ChangeSubmits: + element.QT_QTabWidget.currentChanged.connect(element.QtCallbackStateChanged) + # ------------------------- SLIDER placement element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_SLIDER: + element = element # type: Slider + element.Widget = element.QT_Slider = QSlider() + element.QT_Slider.setOrientation(Qt.Horizontal if element.Orientation.startswith('h') else Qt.Vertical) + if element.Disabled: + element.QT_Slider.setDisabled(True) + + # === style === + style = QtStyle('QSlider') + style['font'] = create_style_from_font(font) + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + style['border'] = '{}px solid gray'.format(border_depth) + element.QT_Slider.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + element.QT_Slider.setMinimum(element.Range[0]) + element.QT_Slider.setMaximum(element.Range[1]) + position = QSlider.TicksBothSides + if element.Relief == RELIEF_TICK_POSITION_NO_TICKS: + position = QSlider.NoTicks + elif element.Relief == RELIEF_TICK_POSITION_BOTH_SIDES: + position = QSlider.TicksBothSides + elif element.Relief == RELIEF_TICK_POSITION_ABOVE: + position = QSlider.TicksAbove + elif element.Relief == RELIEF_TICK_POSITION_BELOW: + position = QSlider.TicksBelow + elif element.Relief == RELIEF_TICK_POSITION_LEFT: + position = QSlider.TicksLeft + elif element.Relief == RELIEF_TICK_POSITION_RIGHT: + position = QSlider.TicksRight + element.QT_Slider.setTickPosition(position) + + if element.TickInterval is not None: + element.QT_Slider.setTickInterval(element.TickInterval) + if element_size[0] is not None: + element.QT_Slider.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_Slider.setFixedHeight(element_size[1]) + if element.Resolution is not None: + element.QT_Slider.setSingleStep(element.Resolution) + element.QT_Slider.setPageStep(element.Resolution) + element.QT_Slider.setValue(element.DefaultValue) + + if element.ChangeSubmits: + element.QT_Slider.valueChanged.connect(element._QtCallbackValueChanged) + if element.Tooltip: + element.QT_Slider.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_Slider.setVisible(False) + qt_row_layout.addWidget(element.QT_Slider, alignment=Qt.AlignVCenter) + # ------------------------- DIAL placement element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_DIAL: + element.Widget = element.QT_Dial = qdial = QDial() + + # === style === + style = QtStyle('QDial') + style['font'] = create_style_from_font(font) + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + style['border'] = '{}px solid gray'.format(border_depth) + element.QT_Dial.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + # === style === end + + if element.Disabled: + element.QT_Dial.setDisabled(True) + element.QT_Dial.setMinimum(element.Range[0]) + element.QT_Dial.setMaximum(element.Range[1]) + element.QT_Dial.setValue(element.DefaultValue) + qdial.setNotchesVisible(True) + if element.TickInterval is not None: + qdial.setNotchTarget(element.TickInterval) + if element.Resolution is not None: + element.QT_Dial.setSingleStep(element.Resolution) + if element_size[0] is not None: + element.QT_Dial.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_Dial.setFixedHeight(element_size[1]) + if element.ChangeSubmits: + element.QT_Dial.valueChanged.connect(element._QtCallbackValueChanged) + if element.Tooltip: + element.QT_Dial.setToolTip(element.Tooltip) + # qt_row_layout.setContentsMargins(*full_element_pad) + if not element.Visible: + element.QT_Dial.setVisible(False) + qt_row_layout.addWidget(element.QT_Dial, alignment=Qt.AlignVCenter) + # ------------------------- Stretch placement element ------------------------- # + elif element_type == ELEM_TYPE_STRETCH: + element = element # type: Stretch + element.Widget = qt_row_layout.addStretch(1) + # ------------------------- TABLE placement element ------------------------- # + elif element_type == ELEM_TYPE_TABLE: + element = element # type: Table + element.Widget = element.QT_TableWidget = Table.QTTableWidget(toplevel_win.ReturnKeyboardEvents, toplevel_win) + if element.NumRows is not None: + element.QT_TableWidget.setFixedHeight(element.NumRows * 35 + 25) + # element.QT_TableWidget = QTableWidget() + # === style === + + style = QtStyle('QTableWidget') + style['font'] = create_style_from_font(font) + if element.TextColor is not None: + style['color'] = element.TextColor + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + style['border'] = '{}px solid gray'.format(border_depth) + + # more css: ScrollBar + style.append_css_to_end.append(' QScrollBar:vertical {border: none; background:lightgray; width:12px; margin: 0px 0px 0px 0px; } ') + # more css: QHeaderView + header_style = QtStyle('QHeaderView::section') + header_style['font'] = create_style_from_font(element.HeaderFont if element.HeaderFont is not None else font) + header_style['background-color'] = element.HeaderBackgroundColor + header_style['color'] = element.HeaderTextColor + + element.QT_TableWidget.setStyleSheet(style.build_css_string() + header_style.build_css_string()) + element.qt_styles = (style, header_style) + # === style === end + + if element.ChangeSubmits: + element.QT_TableWidget.itemSelectionChanged.connect(element._QtCallbackCellActivated) + element.QT_TableWidget.setRowCount(len(element.Values)) + element.QT_TableWidget.setColumnCount(len(element.Values[0])) + for rownum, rows in enumerate(element.Values): + # element.QT_TableWidget.insertRow(rownum) + for colnum, columns in enumerate(rows): + element.QT_TableWidget.setItem(rownum, colnum, QTableWidgetItem(element.Values[rownum][colnum])) + + if element.ColumnHeadings is not None: + element.QT_TableWidget.setHorizontalHeaderLabels(element.ColumnHeadings) + + element.QT_TableWidget.installEventFilter(element.QT_TableWidget) + element.QT_TableWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + if element.Tooltip: + element.QT_TableWidget.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_TableWidget.setVisible(False) + + qt_row_layout.addWidget(element.QT_TableWidget, alignment=Qt.AlignVCenter) + # ------------------------- Tree placement element ------------------------- # + elif element_type == ELEM_TYPE_TREE: + element = element # type: Tree + element.Widget = element.QT_QTreeWidget = QTreeWidget() + if element_size != (None, None): + element.QT_QTreeWidget.setFixedWidth(element_size[0]) + element.QT_QTreeWidget.setFixedHeight(element_size[1]) + height = element.NumRows + element.QT_QTreeWidget.setFixedHeight(height * 25) # convert num rows into pixels...crude but effective + + if element.ColumnsToDisplay is None: # Which cols to display + displaycolumns = element.ColumnHeadings + else: + displaycolumns = [] + for i, should_display in enumerate(element.ColumnsToDisplay): + if should_display: + displaycolumns.append(element.ColumnHeadings[i]) + column_headings = element.ColumnHeadings + # ------------- GET THE TREEVIEW WIDGET ------------- + for i, heading in enumerate(element.ColumnHeadings): # Configure cols + headings + # QTree.heading(heading, text=heading) + if element.AutoSizeColumns: + width = min(element.MaxColumnWidth, len(heading) + 1) + else: + try: + width = element.ColumnWidths[i] + except: + width = element.DefaultColumnWidth + # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) + + def add_treeview_data(node, widget): + # print(f'Inserting {node.key} under parent {node.parent}') + child = widget + if node != element.TreeData.root_node: + child = QTreeWidgetItem(widget) + child.setText(0, str(node.text)) + # if node.key != '': + # child.setData(0,0,node.values) + if type(node.icon) is bytes: + ba = QtCore.QByteArray.fromBase64(node.icon) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qicon = QIcon(pixmap) + child.setIcon(0, qicon) + elif node.icon is not None: + qicon = QIcon(node.icon) + child.setIcon(0, qicon) + + for node in node.children: + add_treeview_data(node, child) + + # for node in element.TreeData.root_node.children: + # add_treeview_data(node, element.QT_QTreeWidget) + + add_treeview_data(element.TreeData.root_node, element.QT_QTreeWidget) + + # === style === + style = QtStyle('QTreeWidget') + style['font'] = create_style_from_font(font) + if element.TextColor is not None: + style['color'] = element.TextColor + if element.BackgroundColor is not None: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + style['border'] = '{}px solid gray'.format(border_depth) + style.append_css_to_end.append(' QScrollBar:vertical {border: none; background:lightgray; width:12px; margin: 0px 0px 0px 0px; } ') + + header_style = QtStyle('QHeaderView::section') + header_style['font'] = create_style_from_font(element.HeaderFont if element.HeaderFont is not None else font) + header_style['background-color'] = element.HeaderBackgroundColor + header_style['color'] = element.HeaderTextColor + + element.QT_QTreeWidget.setStyleSheet(style.build_css_string() + header_style.build_css_string()) + element.qt_styles = (style, header_style) + # === style === end + + if element.ChangeSubmits: + element.QT_QTreeWidget.itemSelectionChanged.connect(element._QtCallbackCellActivated) + + if element.ShowExpanded: + element.QT_QTreeWidget.expandAll() + element.QT_QTreeWidget.show() + if element.Tooltip: + element.QT_QTreeWidget.setToolTip(element.Tooltip) + if not element.Visible: + element.QT_QTreeWidget.setVisible(False) + qt_row_layout.addWidget(element.QT_QTreeWidget, alignment=Qt.AlignVCenter) + # ------------------------- Separator placement element ------------------------- # + elif element_type == ELEM_TYPE_SEPARATOR: + element = element # type: HorizontalSeparator + element.Widget = element.QT_Label = qlabel = QLabel('', toplevel_win.QTWindow) + if not auto_size_text: + if element_size[0] is not None: + element.QT_Label.setFixedWidth(element_size[0]) + if element_size[1] is not None: + element.QT_Label.setFixedHeight(element_size[1]) + style = QtStyle('QLabel') + if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + style['color'] = element.TextColor + if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + style['background-color'] = element.BackgroundColor + style['margin'] = full_element_pad + element.QT_Label.setStyleSheet(style.build_css_string()) + element.qt_styles = (style,) + + qlabel.setFrameStyle(QFrame.VLine if element.Orientation[0] == 'v' else QFrame.HLine) + + qt_row_layout.addWidget(element.QT_Label, alignment=Qt.AlignVCenter) + + # Align the Element on center in the row + # try: + # element.Widget.setAlignment(element.Widget.alignment() | Qt.AlignVCenter) + # except Exception as e: + # print(f'* Alignment error {e}') + + # ............................DONE WITH ROW pack the row of widgets ..........................# + qt_row_layout.setSpacing(0) + containing_frame.setSpacing(0) + containing_frame.addRow('', qt_row_layout) + + # done with row, pack the row of widgets + # tk_row_frame.grid(row=row_num+2, sticky=tk.NW, padx=DEFAULT_MARGINS[0]) + return + + +def ConvertFlexToTK(window): + InitializeResults(window) + master = 000000 + PackFormIntoFrame(window, window.QFormLayout, window) + # ....................................... DONE creating and laying out window ..........................# + screen_width = 000000 # get window info to move to middle of screen + screen_height = 000000 + if window.Location != (None, None): + window.QT_QMainWindow.move(window.Location[0], window.Location[1]) + x, y = window.Location + elif DEFAULT_WINDOW_LOCATION != (None, None): + x, y = DEFAULT_WINDOW_LOCATION + else: + win_width = 0000000 + win_height = 000000 + x = screen_width / 2 - win_width / 2 + y = screen_height / 2 - win_height / 2 + if y + win_height > screen_height: + y = screen_height - win_height + if x + win_width > screen_width: + x = screen_width - win_width + + return + + +# ----====----====----====----====----==== Start timer ====----====----====----====----====----# + + +def start_window_read_timer(window, amount): + timer = QtCore.QTimer() + timer.timeout.connect(window._timer_timeout) + timer.start(amount) + return timer + + +def start_systray_read_timer(tray, amount): + timer = QtCore.QTimer() + timer.timeout.connect(tray._timer_timeout) + timer.start(amount) + return timer + + +def start_window_autoclose_timer(window, amount): + timer = QtCore.QTimer() + window.autoclose_timer = timer + timer.timeout.connect(window._autoclose_timer_callback) + timer.start(amount) + return timer + + +def stop_timer(timer): + timer.stop() + + +# ----====----====----====----====----==== STARTUP TK ====----====----====----====----====----# +def StartupTK(window): + """ + Does the building of the window with all the widgets + :param window: you window object + :type window: (Window) + """ + + global using_pyqt5 + + ow = Window.NumOpenWindows + + if Window.QTApplication is None: + Window.QTApplication = QApplication(sys.argv) + + window.QTApplication = Window.QTApplication + + Window.IncrementOpenCount() + + # window.QTWindow = QWidget() + + window.QT_QMainWindow = Window.QT_QMainWindowClass(window.ReturnKeyboardEvents, window) + window.QTWindow = Window.QTMainWindow(window.ReturnKeyboardEvents, window) + window.QT_QMainWindow.setCentralWidget(window.QTWindow) + + window.QT_QMainWindow.installEventFilter(window.QT_QMainWindow) + + window.QTApplication.setActiveWindow(window.QT_QMainWindow) + + flags = QtCore.Qt.WindowFlags() + if window.NoTitleBar: + flags |= Qt.FramelessWindowHint + flags |= QtCore.Qt.Tool + if window.KeepOnTop: + flags |= Qt.WindowStaysOnTopHint + + if not using_pyqt5 and flags is not None: + window.QT_QMainWindow.setWindowFlags(flags) + if window.AlphaChannel: + window.QT_QMainWindow.setWindowOpacity(window.AlphaChannel) + if window.WindowIcon is not None: + if type(window.WindowIcon) is bytes: + ba = QtCore.QByteArray.fromBase64(window.WindowIcon) + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ba) + qicon = QIcon(pixmap) + window.QT_QMainWindow.setWindowIcon(qicon) + else: + window.QT_QMainWindow.setWindowIcon(QtGui.QIcon(window.WindowIcon)) + if window.DisableMinimize: + window.QT_QMainWindow.setWindowFlags(window.QT_QMainWindow.windowFlags() & ~Qt.WindowMinimizeButtonHint) + window.QT_QMainWindow.setWindowFlags(window.QT_QMainWindow.windowFlags() & ~Qt.WindowMaximizeButtonHint) + if window.DisableClose: + window.QT_QMainWindow.setWindowFlags(window.QT_QMainWindow.windowFlags() & ~Qt.WindowCloseButtonHint) + + # window.QTWindow.setAttribute(Qt.WA_TranslucentBackground) + # shadow = QtWidgets.QGraphicsDropShadowEffect() + # shadow.setBlurRadius(9.0) + # shadow.setBlurRadius(50) + # window.QTWindow.setGraphicsEffect(shadow) + + # if window.KeepOnTop: + # window.QTWindow.setWindowFlags(Qt.WindowStaysOnTopHint) + + style = QtStyle('QMainWindow') + if window.BackgroundColor is not None and window.BackgroundColor != COLOR_SYSTEM_DEFAULT: + style['background-color'] = window.BackgroundColor + window.QT_QMainWindow.setStyleSheet(str(style)) + + if window.margins != (None, None): + margin_left = margin_right = margin_top = margin_bottom = 0 + if isinstance(window.margins[0], tuple): + margin_left = window.margins[0][0] + margin_right = window.margins[0][1] + elif isinstance(window.margins[0], int): + margin_left = window.margins[0] + margin_right = window.margins[0] + if isinstance(window.margins[1], tuple): + margin_top = window.margins[1][0] + margin_bottom = window.margins[1][1] + elif isinstance(window.margins[1], int): + margin_top = window.margins[1] + margin_bottom = window.margins[1] + window.QT_QMainWindow.setContentsMargins(margin_left, margin_top, margin_right, margin_bottom) + + if window.BackgroundImage is not None: + qlabel = QLabel(window.QTWindow) + qlabel.setText('') + w = QtGui.QPixmap(window.BackgroundImage).width() + h = QtGui.QPixmap(window.BackgroundImage).height() + qlabel.setGeometry(QtCore.QRect(0, 0, w, h)) + # qlabel.setGeometry(window.QTWindow.geometry()) + qlabel.setPixmap(QtGui.QPixmap(window.BackgroundImage)) + # style += 'background-image: url(%s);' % window.BackgroundImage + + window.QT_QMainWindow.setWindowTitle(window.Title) + + if window.GrabAnywhere is not False and not (window.NonBlocking and window.GrabAnywhere is not True): + pass + + window.QFormLayout = QFormLayout() + window.QT_Box_Layout = QVBoxLayout() + ConvertFlexToTK(window) + window.QT_Box_Layout.addLayout(window.QFormLayout) + + # shadow = QtWidgets.QGraphicsDropShadowEffect( window.QFormLayout) + # window.QTWindow.setGraphicsEffect(shadow) + + # Make window visible again + pass + + if window.ReturnKeyboardEvents and not window.NonBlocking: + pass + elif window.ReturnKeyboardEvents: + pass + + # print('..... CALLING MainLoop') + window.CurrentlyRunningMainloop = True + window.QTWindow.setLayout(window.QT_Box_Layout) + + if window.FocusElement is not None: + window.FocusElement.setFocus() + + # Resize the window to the size it should be at... dunno why I need to do this but I do... + # add 5 pixels onto it because stuff was getting cut off + qsize = window.QT_QMainWindow.sizeHint() + size = [qsize.width(), qsize.height()] + size[0] += 10 + window.QT_QMainWindow.resize(*size) + + if window._Size != (None, None): + window.QT_QMainWindow.resize(window._Size[0], window._Size[1]) + + if not window.Resizable: + window.QT_QMainWindow.setFixedSize(*size) + + timer = None + if window.AutoClose: + timer = start_window_autoclose_timer(window, window.AutoCloseDuration * 1000) + + if not window.NonBlocking: + if window.Timeout: + timer = start_window_read_timer(window, window.Timeout) + window.QT_QMainWindow.show() ####### The thing that causes the window to be visible ###### + #### ------------------------------ RUN MAIN LOOP HERE ------------------------------ ##### + window.QTApplication.exec_() + if timer: + stop_timer(timer) + else: # Non-blocking window + window.QT_QMainWindow.show() ####### The thing that causes the window to be visible ###### + window.QTApplication.processEvents() + + window.CurrentlyRunningMainloop = False + window.TimerCancelled = True + # print('..... BACK from MainLoop') + if not window.FormRemainedOpen: + Window.DecrementOpenCount() + if window.RootNeedsDestroying: + # print('** Destroying window **') + window.QT_QMainWindow.close() # destroy the window + window.RootNeedsDestroying = False + return + + +# ==============================_GetNumLinesNeeded ==# +# Helper function for determining how to wrap text # +# ===================================================# +def _GetNumLinesNeeded(text, max_line_width): + if max_line_width == 0: + return 1 + lines = text.split('\n') + num_lines = len(lines) # number of original lines of text + max_line_len = max([len(l) for l in lines]) # longest line + lines_used = [] + for L in lines: + lines_used.append(len(L) // max_line_width + (len(L) % max_line_width > 0)) # fancy math to round up + total_lines_needed = sum(lines_used) + return total_lines_needed + + +# ============================== PROGRESS METER ========================================== # + + +def ConvertArgsToSingleString(*args): + ( + max_line_total, + width_used, + total_lines, + ) = ( + 0, + 0, + 0, + ) + single_line_message = '' + # loop through args and built a SINGLE string from them + for message in args: + # fancy code to check if string and convert if not is not need. Just always convert to string :-) + # if not isinstance(message, str): message = str(message) + message = str(message) + longest_line_len = max([len(l) for l in message.split('\n')]) + width_used = max(longest_line_len, width_used) + max_line_total = max(max_line_total, width_used) + lines_needed = _GetNumLinesNeeded(message, width_used) + total_lines += lines_needed + single_line_message += message + '\n' + return single_line_message, width_used, total_lines + + +METER_REASON_CANCELLED = 'cancelled' +METER_REASON_CLOSED = 'closed' +METER_REASON_REACHED_MAX = 'finished' +METER_OK = True +METER_STOPPED = False + + +class QuickMeter(object): + active_meters = {} + exit_reasons = {} + + def __init__( + self, + title, + current_value, + max_value, + key, + *args, + orientation='v', + bar_color=(None, None), + button_color=(None, None), + size=DEFAULT_PROGRESS_BAR_SIZE, + border_width=None, + grab_anywhere=False, + ): + self.start_time = datetime.datetime.utcnow() + self.key = key + self.orientation = orientation + self.bar_color = bar_color + self.size = size + self.grab_anywhere = grab_anywhere + self.button_color = button_color + self.border_width = border_width + self.title = title + self.current_value = current_value + self.max_value = max_value + self.close_reason = None + self.window = self.BuildWindow(*args) + + def BuildWindow(self, *args): + layout = [] + if self.orientation.lower().startswith('h'): + col = [[T(''.join(map(lambda x: str(x) + '\n', args)), key='_OPTMSG_')]] ### convert all *args into one string that can be updated + col += [ + [T('', size=(25, 5), key='_STATS_')], + [ + ProgressBar( + max_value=self.max_value, + orientation='h', + key='_PROG_', + size=self.size, + bar_color=self.bar_color, + ) + ], + [Cancel(button_color=self.button_color), Stretch()], + ] + layout += [Column(col)] + else: + col = [ + [ + ProgressBar( + max_value=self.max_value, + orientation='v', + key='_PROG_', + size=self.size, + bar_color=self.bar_color, + ) + ] + ] + col2 = [[T(''.join(map(lambda x: str(x) + '\n', args)), key='_OPTMSG_')]] ### convert all *args into one string that can be updated + col2 += [[T('', size=(25, 5), key='_STATS_')], [Cancel(button_color=self.button_color), Stretch()]] + layout += [Column(col), Column(col2)] + self.window = Window(self.title, grab_anywhere=self.grab_anywhere, border_depth=self.border_width) + self.window.Layout([layout]).Finalize() + + return self.window + + def UpdateMeter(self, current_value, max_value, *args): + self.current_value = current_value + self.max_value = max_value + self.window.Element('_PROG_').UpdateBar(self.current_value, self.max_value) + self.window.Element('_STATS_').Update('\n'.join(self.ComputeProgressStats())) + self.window.Element('_OPTMSG_').Update(value=''.join(map(lambda x: str(x) + '\n', args))) ### update the string with the args + event, values = self.window.Read(timeout=0) + if event in ('Cancel', None) or current_value >= max_value: + self.window.Close() + del QuickMeter.active_meters[self.key] + QuickMeter.exit_reasons[self.key] = METER_REASON_CANCELLED if event == 'Cancel' else METER_REASON_CLOSED if event is None else METER_REASON_REACHED_MAX + return QuickMeter.exit_reasons[self.key] + return METER_OK + + def ComputeProgressStats(self): + utc = datetime.datetime.utcnow() + time_delta = utc - self.start_time + total_seconds = time_delta.total_seconds() + if not total_seconds: + total_seconds = 1 + try: + time_per_item = total_seconds / self.current_value + except: + time_per_item = 1 + seconds_remaining = (self.max_value - self.current_value) * time_per_item + time_remaining = str(datetime.timedelta(seconds=seconds_remaining)) + time_remaining_short = (time_remaining).split('.')[0] + time_delta_short = str(time_delta).split('.')[0] + total_time = time_delta + datetime.timedelta(seconds=seconds_remaining) + total_time_short = str(total_time).split('.')[0] + self.stat_messages = [ + '{} of {}'.format(self.current_value, self.max_value), + '{} %'.format(100 * self.current_value // self.max_value), + '', + ' {:6.2f} Iterations per Second'.format(self.current_value / total_seconds), + ' {:6.2f} Seconds per Iteration'.format(total_seconds / (self.current_value if self.current_value else 1)), + '', + '{} Elapsed Time'.format(time_delta_short), + '{} Time Remaining'.format(time_remaining_short), + '{} Estimated Total Time'.format(total_time_short), + ] + return self.stat_messages + + +def OneLineProgressMeter( + title, + current_value, + max_value, + key='OK for 1 meter', + *args, + orientation='v', + bar_color=(None, None), + button_color=None, + size=DEFAULT_PROGRESS_BAR_SIZE, + border_width=None, + grab_anywhere=False, +): + """ + :param orientation: 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical' / 'v') + :type orientation: (str) + :param bar_color: color of a bar line + :type bar_color: Tuple(str, str) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param size: (w,h) w=characters-wide, h=rows-high (Default value = DEFAULT_PROGRESS_BAR_SIZE) + :type size: Tuple[int, int] + :param border_width: width of border around element + :type border_width: (int) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + """ + if key not in QuickMeter.active_meters: + meter = QuickMeter( + title, + current_value, + max_value, + key, + *args, + orientation=orientation, + bar_color=bar_color, + button_color=button_color, + size=size, + border_width=border_width, + grab_anywhere=grab_anywhere, + ) + QuickMeter.active_meters[key] = meter + else: + meter = QuickMeter.active_meters[key] + + rc = meter.UpdateMeter(current_value, max_value, *args) + OneLineProgressMeter.exit_reasons = getattr(OneLineProgressMeter, 'exit_reasons', QuickMeter.exit_reasons) + return rc == METER_OK + + +def OneLineProgressMeterCancel(key='OK for 1 meter'): + try: + meter = QuickMeter.active_meters[key] + meter.window.Close() + del QuickMeter.active_meters[key] + QuickMeter.exit_reasons[key] = METER_REASON_CANCELLED + except: # meter is already deleted + return + + +# input is #RRGGBB +# output is #RRGGBB +def GetComplimentaryHex(color): + """ + :param color: color string, like "#RRGGBB" + :type color: (str) + :return color: color string, like "#RRGGBB" + :type color: (str) + """ + # strip the # from the beginning + color = color[1:] + # convert the string into hex + color = int(color, 16) + # invert the three bytes + # as good as substracting each of RGB component by 255(FF) + comp_color = 0xFFFFFF ^ color + # convert the color back to hex by prefixing a # + comp_color = '#%06X' % comp_color + return comp_color + + +# ======================== EasyPrint =====# +# ===================================================# + + +class DebugWin: + debug_window = None + + def __init__( + self, + size=(None, None), + location=(None, None), + font=None, + no_titlebar=False, + no_button=False, + grab_anywhere=False, + keep_on_top=False, + title=None, + do_not_reroute_stdout=False, + ): + # Show a form that's a running counter + self.size = size + self.location = location + self.font = font + self.no_titlebar = no_titlebar + self.no_button = no_button + self.grab_anywhere = grab_anywhere + self.keep_on_top = keep_on_top + self.do_not_reroute_stdout = do_not_reroute_stdout + + win_size = size if size != (None, None) else DEFAULT_DEBUG_WINDOW_SIZE + self.window = Window( + title=title or 'Debug Window', + no_titlebar=no_titlebar, + auto_size_text=True, + location=location, + font=font or ('Courier New', 10), + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + ) + self.output_element = MultilineOutput(size=win_size, key='_MULTILINE_') if do_not_reroute_stdout else Output(size=win_size) + + if no_button: + self.layout = [[self.output_element]] + else: + self.layout = [[self.output_element], [DummyButton('Quit'), Stretch()]] + self.window.AddRows(self.layout) + self.window.Read(timeout=0) # Show a non-blocking form, returns immediately + Window.active_popups[self.window] = 'debug window' + return + + def Print(self, *args, end=None, sep=None): + sepchar = sep if sep is not None else ' ' + endchar = end if end is not None else '\n' + + if self.window is None: # if window was destroyed already, just print + self.__init__( + size=self.size, + location=self.location, + font=self.font, + no_titlebar=self.no_titlebar, + no_button=self.no_button, + grab_anywhere=self.grab_anywhere, + keep_on_top=self.keep_on_top, + do_not_reroute_stdout=self.do_not_reroute_stdout, + ) + event, values = self.window.Read(timeout=0) + if event == 'Quit' or event is None: + self.Close() + self.__init__( + size=self.size, + location=self.location, + font=self.font, + no_titlebar=self.no_titlebar, + no_button=self.no_button, + grab_anywhere=self.grab_anywhere, + keep_on_top=self.keep_on_top, + do_not_reroute_stdout=self.do_not_reroute_stdout, + ) + if self.do_not_reroute_stdout: + end_str = str(end) if end is not None else '\n' + sep_str = str(sep) if sep is not None else ' ' + + outstring = '' + num_args = len(args) + for i, arg in enumerate(args): + outstring += str(arg) + if i != num_args - 1: + outstring += sep_str + outstring += end_str + self.output_element.Update(outstring, append=True) + else: + print(*args, sep=sepchar, end=endchar) + + def Close(self): + self.window.Close() + self.window = None + + +def PrintClose(): + EasyPrintClose() + + +def EasyPrint( + *args, + size=(None, None), + end=None, + sep=None, + location=(None, None), + font=None, + no_titlebar=False, + no_button=False, + grab_anywhere=False, + keep_on_top=False, + do_not_reroute_stdout=True, +): + """ + :param args: The arguments to display + :type args: List[Any] + :param size: (w,h) w=characters-wide, h=rows-high + :type size: Tuple[int, int] + :param end: The end char to use just like print uses + :type end: (str) + :param sep: end character + :type end: (str) + :param sep: separator character + :type sep: (str) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param no_button: don't show button + :type no_button: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param do_not_reroute_stdout: do not reroute stdout + :type do_not_reroute_stdout: (bool) + :param text_color: color of the text + :type text_color: (str) + :param background_color: color of background + :type background_color: (str) + """ + + if DebugWin.debug_window is None: + DebugWin.debug_window = DebugWin( + size=size, + location=location, + font=font, + no_titlebar=no_titlebar, + no_button=no_button, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + do_not_reroute_stdout=do_not_reroute_stdout, + ) + DebugWin.debug_window.Print(*args, end=end, sep=sep) + + +Print = EasyPrint +eprint = EasyPrint + + +def EasyPrintClose(): + if DebugWin.debug_window is not None: + DebugWin.debug_window.Close() + DebugWin.debug_window = None + + +# d8b 888 +# Y8P 888 +# 888 +# .d8888b 88888b. 888d888 888 88888b. 888888 +# d88P" 888 "88b 888P" 888 888 "88b 888 +# 888 888 888 888 888 888 888 888 +# Y88b. 888 d88P 888 888 888 888 Y88b. +# "Y8888P 88888P" 888 888 888 888 "Y888 +# 888 +# 888 +# 888 + + +CPRINT_DESTINATION_WINDOW = None +CPRINT_DESTINATION_MULTILINE_ELMENT_KEY = None + + +def cprint_set_output_destination(window, multiline_key): + """ + Sets up the color print (cprint) output destination + :param window: The window that the cprint call will route the output to + :type window: (Window) + :param multiline_key: Key for the Multiline Element where output will be sent + :type multiline_key: (Any) + :return: None + :rtype: None + """ + + global CPRINT_DESTINATION_WINDOW, CPRINT_DESTINATION_MULTILINE_ELMENT_KEY + + CPRINT_DESTINATION_WINDOW = window + CPRINT_DESTINATION_MULTILINE_ELMENT_KEY = multiline_key + + +# def cprint(*args, **kwargs): +def cprint( + *args, + end=None, + sep=' ', + text_color=None, + t=None, + background_color=None, + b=None, + colors=None, + c=None, + window=None, + key=None, +): + """ + Color print to a multiline element in a window of your choice. + Must have EITHER called cprint_set_output_destination prior to making this call so that the + window and element key can be saved and used here to route the output, OR used the window + and key parameters to the cprint function to specicy these items. + + args is a variable number of things you want to print. + + end - The end char to use just like print uses + sep - The separation character like print uses + text_color - The color of the text + key - overrides the previously defined Multiline key + window - overrides the previously defined window to output to + background_color - The color of the background + colors -(str, str) or str. A combined text/background color definition in a single parameter + + There are also "aliases" for text_color, background_color and colors (t, b, c) + t - An alias for color of the text (makes for shorter calls) + b - An alias for the background_color parameter + c - Tuple[str, str] - "shorthand" way of specifying color. (foreground, backgrouned) + c - str - can also be a string of the format "foreground on background" ("white on red") + + With the aliases it's possible to write the same print but in more compact ways: + cprint('This will print white text on red background', c=('white', 'red')) + cprint('This will print white text on red background', c='white on red') + cprint('This will print white text on red background', text_color='white', background_color='red') + cprint('This will print white text on red background', t='white', b='red') + + :param *args: stuff to output + :type *args: (Any) + :param text_color: Color of the text + :type text_color: (str) + :param background_color: The background color of the line + :type background_color: (str) + :param colors: Either a tuple or a string that has both the text and background colors + :type colors: (str) or Tuple[str, str] + :param t: Color of the text + :type t: (str) + :param b: The background color of the line + :type b: (str) + :param c: Either a tuple or a string that has both the text and background colors + :type c: (str) or Tuple[str, str] + :param end: end character + :type end: (str) + :param sep: separator character + :type sep: (str) + :param key: key of multiline to output to (if you want to override the one previously set) + :type key: (Any) + :param window: Window containing the multiline to output to (if you want to override the one previously set) + :type window: (Window) + :return: None + :rtype: None + """ + + destination_key = CPRINT_DESTINATION_MULTILINE_ELMENT_KEY if key is None else key + destination_window = window or CPRINT_DESTINATION_WINDOW + + if (destination_window is None and window is None) or (destination_key is None and key is None): + print( + '** Warning ** Attempting to perform a cprint without a valid window & key', + 'Will instead print on Console', + 'You can specify window and key in this cprint call, or set ahead of time using cprint_set_output_destination', + ) + print(*args) + return + + kw_text_color = text_color or t + kw_background_color = background_color or b + dual_color = colors or c + try: + if isinstance(dual_color, tuple): + kw_text_color = dual_color[0] + kw_background_color = dual_color[1] + elif isinstance(dual_color, str): + kw_text_color = dual_color.split(' on ')[0] + kw_background_color = dual_color.split(' on ')[1] + except Exception as e: + print('* cprint warning * you messed up with color formatting', e) + + mline = destination_window.find_element(destination_key, silent_on_error=True) # type: Multiline + try: + # mline = destination_window[destination_key] # type: # Multiline + if end is None: + mline.print(*args, text_color=kw_text_color, background_color=kw_background_color, end='', sep=sep) + mline.print('') + else: + mline.print(*args, text_color=kw_text_color, background_color=kw_background_color, end=end, sep=sep) + except Exception as e: + print('** cprint error trying to print to the multiline. Printing to console instead **', e) + print(*args, end=end, sep=sep) + + +# ------------------------------------------------------------------------------------------------ # +# A print-like call that can be used to output to a multiline element as if it's an Output element # +# ------------------------------------------------------------------------------------------------ # + + +def _print_to_element(multiline_element, *args, end=None, sep=None, text_color=None, background_color=None, autoscroll=True): + """ + Print like Python normally prints except route the output to a multline element and also add colors if desired + + :param multiline_element: The multiline element to be output to + :type multiline_element: (Multiline) + :param args: The arguments to print + :type args: List[Any] + :param end: The end char to use just like print uses + :type end: (str) + :param sep: The separation character like print uses + :type sep: (str) + :param text_color: color of the text + :type text_color: (str) + :param background_color: The background color of the line + :type background_color: (str) + :param autoscroll: If True (the default), the element will scroll to bottom after updating + :type autoscroll: Bool + """ + end_str = str(end) if end is not None else '\n' + sep_str = str(sep) if sep is not None else ' ' + + outstring = '' + num_args = len(args) + for i, arg in enumerate(args): + outstring += str(arg) + if i != num_args - 1: + outstring += sep_str + outstring += end_str + + multiline_element.update( + outstring, + append=True, + text_color_for_value=text_color, + background_color_for_value=background_color, + autoscroll=autoscroll, + ) + + +# ======================== Scrolled Text Box =====# +# ===================================================# +def PopupScrolled( + *args, + button_color=None, + yes_no=False, + auto_close=False, + auto_close_duration=None, + size=(None, None), + location=(None, None), + title=None, + non_blocking=False, +): + """ + :param args: The arguments to display + :type args: List[Any] + :param title: Title to display in the window. + :type title: (str) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param yes_no: If True, displays Yes and No buttons instead of Ok + :type yes_no: (bool) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param size: (w,h) w=characters-wide, h=rows-high + :type size: Tuple[int, int] + :param location: Location on the screen to place the upper left corner of the window + :type location: Tuple[int, int] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + """ + if not args: + return + width, height = size + width = width if width else MESSAGE_BOX_LINE_WIDTH + window = Window( + title=title or args[0], + auto_size_text=True, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + location=location, + ) + max_line_total, max_line_width, total_lines, height_computed = 0, 0, 0, 0 + complete_output = '' + for message in args: + # fancy code to check if string and convert if not is not need. Just always convert to string :-) + # if not isinstance(message, str): message = str(message) - new + message = str(message) + longest_line_len = max([len(l) for l in message.split('\n')]) + width_used = min(longest_line_len, width) + max_line_total = max(max_line_total, width_used) + max_line_width = width + lines_needed = _GetNumLinesNeeded(message, width_used) + height_computed += lines_needed + complete_output += message + '\n' + total_lines += lines_needed + height_computed = MAX_SCROLLED_TEXT_BOX_HEIGHT if height_computed > MAX_SCROLLED_TEXT_BOX_HEIGHT else height_computed + if height: + height_computed = height + computed_size = (max_line_width * 10, height_computed * 16) + window.AddRow(MultilineOutput(complete_output, size=computed_size)) + pad = max_line_total - 15 if max_line_total > 15 else 1 + # show either an OK or Yes/No depending on paramater + button = DummyButton if non_blocking else Button + if yes_no: + window.AddRow(Text('', size=(pad, 1), auto_size_text=False), button('Yes'), button('No')) + else: + window.AddRow(Text('', size=(pad, 1), auto_size_text=False), button('OK', size=(5, 1), button_color=button_color)) + + if non_blocking: + button, values = window.Read(timeout=0) + Window.active_popups[window] = title + else: + button, values = window.Read() + return button + + +ScrolledTextBox = PopupScrolled + + +# ============================== SetGlobalIcon ======# +# Sets the icon to be used by default # +# ===================================================# +def SetGlobalIcon(icon): + """ + :param icon: Either a Base64 byte string or a filename + :type icon: Union[bytes, str] + """ + + try: + with open(icon, 'r') as icon_file: + pass + except: + raise FileNotFoundError + Window.user_defined_icon = icon + return True + + +# ============================== SetOptions =========# +# Sets the icon to be used by default # +# ===================================================# +def SetOptions( + icon=None, + button_color=None, + element_size=(None, None), + button_element_size=(None, None), + margins=(None, None), + element_padding=(None, None), + auto_size_text=None, + auto_size_buttons=None, + font=None, + border_width=None, + slider_border_width=None, + slider_relief=None, + slider_orientation=None, + autoclose_time=None, + message_box_line_width=None, + progress_meter_border_depth=None, + progress_meter_style=None, + progress_meter_relief=None, + progress_meter_color=None, + progress_meter_size=None, + text_justification=None, + background_color=None, + element_background_color=None, + text_element_background_color=None, + input_elements_background_color=None, + input_text_color=None, + scrollbar_color=None, + text_color=None, + element_text_color=None, + debug_win_size=(None, None), + window_location=(None, None), + error_button_color=(None, None), + tooltip_time=None, +): + """ + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param button_color: Color of the button (text, background) + :type button_color: Tuple[str, str] + :param element_size: element size (width, height) in characters + :type element_size: Tuple[int, int] + :param button_element_size: Size of button + :type button_element_size: Tuple[int, int] + :param margins: (left/right, top/bottom) tkinter margins around outsize. Amount of pixels to leave inside the window's frame around the edges before your elements are shown. + :type margins: Tuple[int, int] + :param element_padding: Default amount of padding to put around elements in window (left/right, top/bottom) or ((left, right), (top, bottom)) + :type element_padding: Tuple[int, int] or ((int, int),(int,int)) + :param auto_size_text: True if the Widget should be shrunk to exactly fit the number of chars to show + :type auto_size_text: bool + :param auto_size_buttons: True if Buttons in this Window should be sized to exactly fit the text on this. + :type auto_size_buttons: (bool) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param border_width: width of border around element + :type border_width: (int) + :param slider_border_width: ??? + :type slider_border_width: ??? + :param slider_relief: ??? + :type slider_relief: ??? + :param slider_orientation: ??? + :type slider_orientation: ??? + :param autoclose_time: ??? + :type autoclose_time: ??? + :param message_box_line_width: ??? + :type message_box_line_width: ??? + :param progress_meter_border_depth: ??? + :type progress_meter_border_depth: ??? + :param progress_meter_style: You can no longer set a progress bar style. All ttk styles must be the same for the window + :type progress_meter_style: ??? + :param progress_meter_relief: + :type progress_meter_relief: ??? + :param progress_meter_color: ??? + :type progress_meter_color: ??? + :param progress_meter_size: ??? + :type progress_meter_size: ??? + :param text_justification: Default text justification for all Text Elements in window + :type text_justification: Union['left', 'right', 'center'] + :param background_color: color of background + :type background_color: (str) + :param element_background_color: element background color + :type element_background_color: (str) + :param text_element_background_color: text element background color + :type text_element_background_color: (str) + :param input_elements_background_color: ??? + :type input_elements_background_color: idk_yetReally + :param input_text_color: ??? + :type input_text_color: ??? + :param scrollbar_color: ??? + :type scrollbar_color: ??? + :param text_color: color of the text + :type text_color: (str) + :param element_text_color: ??? + :type element_text_color: ??? + :param debug_win_size: window size + :type debug_win_size: Tuple[int, int] + :param window_location: (Default = (None)) + :type window_location: ??? + :param error_button_color: (Default = (None)) + :type error_button_color: ??? + :param tooltip_time: time in milliseconds to wait before showing a tooltip. Default is 400ms + :type tooltip_time: (int) + :param tooltip_font: font to use for all tooltips + :type tooltip_font: str or Tuple[str, int] or Tuple[str, int, str] + :param use_ttk_buttons: if True will cause all buttons to be ttk buttons + :type use_ttk_buttons: (bool) + :param ttk_theme: Theme to use with ttk widgets. Choices (on Windows) include - 'default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative' + :type ttk_theme: (str) + """ + global DEFAULT_ELEMENT_SIZE + global DEFAULT_BUTTON_ELEMENT_SIZE + global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term + global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels + global DEFAULT_AUTOSIZE_TEXT + global DEFAULT_AUTOSIZE_BUTTONS + global DEFAULT_FONT + global DEFAULT_BORDER_WIDTH + global DEFAULT_AUTOCLOSE_TIME + global DEFAULT_BUTTON_COLOR + global MESSAGE_BOX_LINE_WIDTH + global DEFAULT_PROGRESS_BAR_BORDER_WIDTH + global DEFAULT_PROGRESS_BAR_STYLE + global DEFAULT_PROGRESS_BAR_RELIEF + global DEFAULT_PROGRESS_BAR_COLOR + global DEFAULT_PROGRESS_BAR_SIZE + global DEFAULT_TEXT_JUSTIFICATION + global DEFAULT_DEBUG_WINDOW_SIZE + global DEFAULT_SLIDER_BORDER_WIDTH + global DEFAULT_SLIDER_RELIEF + global DEFAULT_SLIDER_ORIENTATION + global DEFAULT_BACKGROUND_COLOR + global DEFAULT_INPUT_ELEMENTS_COLOR + global DEFAULT_ELEMENT_BACKGROUND_COLOR + global DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR + global DEFAULT_SCROLLBAR_COLOR + global DEFAULT_TEXT_COLOR + global DEFAULT_WINDOW_LOCATION + global DEFAULT_ELEMENT_TEXT_COLOR + global DEFAULT_INPUT_TEXT_COLOR + global DEFAULT_TOOLTIP_TIME + global DEFAULT_ERROR_BUTTON_COLOR + + if icon: + Window.user_defined_icon = icon + + if button_color != None: + DEFAULT_BUTTON_COLOR = button_color + + if element_size != (None, None): + DEFAULT_ELEMENT_SIZE = _convert_tkinter_size_to_Qt(element_size) + + if button_element_size != (None, None): + DEFAULT_BUTTON_ELEMENT_SIZE = _convert_tkinter_size_to_Qt(button_element_size) + + if margins != (None, None): + DEFAULT_MARGINS = margins + + if element_padding != (None, None): + DEFAULT_ELEMENT_PADDING = element_padding + + if auto_size_text != None: + DEFAULT_AUTOSIZE_TEXT = auto_size_text + + if auto_size_buttons != None: + DEFAULT_AUTOSIZE_BUTTONS = auto_size_buttons + + if font != None: + DEFAULT_FONT = font + + if border_width != None: + DEFAULT_BORDER_WIDTH = border_width + + if autoclose_time != None: + DEFAULT_AUTOCLOSE_TIME = autoclose_time + + if message_box_line_width != None: + MESSAGE_BOX_LINE_WIDTH = message_box_line_width + + if progress_meter_border_depth != None: + DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth + + if progress_meter_style != None: + DEFAULT_PROGRESS_BAR_STYLE = progress_meter_style + + if progress_meter_relief != None: + DEFAULT_PROGRESS_BAR_RELIEF = progress_meter_relief + + if progress_meter_color != None: + DEFAULT_PROGRESS_BAR_COLOR = progress_meter_color + + if progress_meter_size != None: + DEFAULT_PROGRESS_BAR_SIZE = progress_meter_size + + if slider_border_width != None: + DEFAULT_SLIDER_BORDER_WIDTH = slider_border_width + + if slider_orientation != None: + DEFAULT_SLIDER_ORIENTATION = slider_orientation + + if slider_relief != None: + DEFAULT_SLIDER_RELIEF = slider_relief + + if text_justification != None: + DEFAULT_TEXT_JUSTIFICATION = text_justification + + if background_color != None: + DEFAULT_BACKGROUND_COLOR = background_color + + if text_element_background_color != None: + DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = text_element_background_color + + if input_elements_background_color != None: + DEFAULT_INPUT_ELEMENTS_COLOR = input_elements_background_color + + if element_background_color != None: + DEFAULT_ELEMENT_BACKGROUND_COLOR = element_background_color + + if window_location != (None, None): + DEFAULT_WINDOW_LOCATION = window_location + + if debug_win_size != (None, None): + DEFAULT_DEBUG_WINDOW_SIZE = debug_win_size + + if text_color != None: + DEFAULT_TEXT_COLOR = text_color + + if scrollbar_color != None: + DEFAULT_SCROLLBAR_COLOR = scrollbar_color + + if element_text_color != None: + DEFAULT_ELEMENT_TEXT_COLOR = element_text_color + + if input_text_color is not None: + DEFAULT_INPUT_TEXT_COLOR = input_text_color + + if tooltip_time is not None: + DEFAULT_TOOLTIP_TIME = tooltip_time + + if error_button_color != (None, None): + print('error button') + DEFAULT_ERROR_BUTTON_COLOR = error_button_color + + return True + + +# ----------------------------------------------------------------- # + +# .########.##.....##.########.##.....##.########..######. +# ....##....##.....##.##.......###...###.##.......##....## +# ....##....##.....##.##.......####.####.##.......##...... +# ....##....#########.######...##.###.##.######....######. +# ....##....##.....##.##.......##.....##.##.............## +# ....##....##.....##.##.......##.....##.##.......##....## +# ....##....##.....##.########.##.....##.########..######. + +# ----------------------------------------------------------------- # + +# The official Theme code + +#################### ChangeLookAndFeel ####################### +# Predefined settings that will change the colors and styles # +# of the elements. # +############################################################## +LOOK_AND_FEEL_TABLE = { + 'SystemDefault': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'SystemDefaultForReal': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': COLOR_SYSTEM_DEFAULT, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'SystemDefault1': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': COLOR_SYSTEM_DEFAULT, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'Material1': { + 'BACKGROUND': '#E3F2FD', + 'TEXT': '#000000', + 'INPUT': '#86A8FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#86A8FF', + 'BUTTON': ('#FFFFFF', '#5079D3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'Material2': { + 'BACKGROUND': '#FAFAFA', + 'TEXT': '#000000', + 'INPUT': '#004EA1', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#5EA7FF', + 'BUTTON': ('#FFFFFF', '#0079D3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'Reddit': { + 'BACKGROUND': '#ffffff', + 'TEXT': '#1a1a1b', + 'INPUT': '#dae0e6', + 'TEXT_INPUT': '#222222', + 'SCROLL': '#a5a4a4', + 'BUTTON': ('#FFFFFF', '#0079d3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#ff5414', + 'ACCENT2': '#33a8ff', + 'ACCENT3': '#dbf0ff', + }, + 'Topanga': { + 'BACKGROUND': '#282923', + 'TEXT': '#E7DB74', + 'INPUT': '#393a32', + 'TEXT_INPUT': '#E7C855', + 'SCROLL': '#E7C855', + 'BUTTON': ('#E7C855', '#284B5A'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#c15226', + 'ACCENT2': '#7a4d5f', + 'ACCENT3': '#889743', + }, + 'GreenTan': { + 'BACKGROUND': '#9FB8AD', + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': '#F7F3EC', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#F7F3EC', + 'BUTTON': ('#FFFFFF', '#475841'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Dark': { + 'BACKGROUND': '#404040', + 'TEXT': '#FFFFFF', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#707070', + 'BUTTON': ('#FFFFFF', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen': { + 'BACKGROUND': '#B7CECE', + 'TEXT': '#000000', + 'INPUT': '#FDFFF7', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#FDFFF7', + 'BUTTON': ('#FFFFFF', '#658268'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'ACCENT1': '#76506d', + 'ACCENT2': '#5148f1', + 'ACCENT3': '#0a1c84', + 'PROGRESS_DEPTH': 0, + }, + 'Dark2': { + 'BACKGROUND': '#404040', + 'TEXT': '#FFFFFF', + 'INPUT': '#FFFFFF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#707070', + 'BUTTON': ('#FFFFFF', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Black': { + 'BACKGROUND': '#000000', + 'TEXT': '#FFFFFF', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#707070', + 'BUTTON': ('#000000', '#FFFFFF'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Tan': { + 'BACKGROUND': '#fdf6e3', + 'TEXT': '#268bd1', + 'INPUT': '#eee8d5', + 'TEXT_INPUT': '#6c71c3', + 'SCROLL': '#eee8d5', + 'BUTTON': ('#FFFFFF', '#063542'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'TanBlue': { + 'BACKGROUND': '#e5dece', + 'TEXT': '#063289', + 'INPUT': '#f9f8f4', + 'TEXT_INPUT': '#242834', + 'SCROLL': '#eee8d5', + 'BUTTON': ('#FFFFFF', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkTanBlue': { + 'BACKGROUND': '#242834', + 'TEXT': '#dfe6f8', + 'INPUT': '#97755c', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#a9afbb', + 'BUTTON': ('#FFFFFF', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkAmber': { + 'BACKGROUND': '#2c2825', + 'TEXT': '#fdcb52', + 'INPUT': '#705e52', + 'TEXT_INPUT': '#fdcb52', + 'SCROLL': '#705e52', + 'BUTTON': ('#000000', '#fdcb52'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue': { + 'BACKGROUND': '#1a2835', + 'TEXT': '#d1ecff', + 'INPUT': '#335267', + 'TEXT_INPUT': '#acc2d0', + 'SCROLL': '#1b6497', + 'BUTTON': ('#000000', '#fafaf8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Reds': { + 'BACKGROUND': '#280001', + 'TEXT': '#FFFFFF', + 'INPUT': '#d8d584', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#763e00', + 'BUTTON': ('#000000', '#daad28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Green': { + 'BACKGROUND': '#82a459', + 'TEXT': '#000000', + 'INPUT': '#d8d584', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e3ecf3', + 'BUTTON': ('#FFFFFF', '#517239'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BluePurple': { + 'BACKGROUND': '#A5CADD', + 'TEXT': '#6E266E', + 'INPUT': '#E0F5FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#E0F5FF', + 'BUTTON': ('#FFFFFF', '#303952'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Purple': { + 'BACKGROUND': '#B0AAC2', + 'TEXT': '#000000', + 'INPUT': '#F2EFE8', + 'SCROLL': '#F2EFE8', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#C2D4D8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BlueMono': { + 'BACKGROUND': '#AAB6D3', + 'TEXT': '#000000', + 'INPUT': '#F1F4FC', + 'SCROLL': '#F1F4FC', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#7186C7'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'GreenMono': { + 'BACKGROUND': '#A8C1B4', + 'TEXT': '#000000', + 'INPUT': '#DDE0DE', + 'SCROLL': '#E3E3E3', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#6D9F85'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BrownBlue': { + 'BACKGROUND': '#64778d', + 'TEXT': '#FFFFFF', + 'INPUT': '#f0f3f7', + 'SCROLL': '#A6B2BE', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#283b5b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BrightColors': { + 'BACKGROUND': '#b4ffb4', + 'TEXT': '#000000', + 'INPUT': '#ffff64', + 'SCROLL': '#ffb482', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#ffa0dc'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'NeutralBlue': { + 'BACKGROUND': '#92aa9d', + 'TEXT': '#000000', + 'INPUT': '#fcfff6', + 'SCROLL': '#fcfff6', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#d0dbbd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Kayak': { + 'BACKGROUND': '#a7ad7f', + 'TEXT': '#000000', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#5d907d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'SandyBeach': { + 'BACKGROUND': '#efeccb', + 'TEXT': '#012f2f', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#012f2f', + 'BUTTON': ('#FFFFFF', '#046380'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'TealMono': { + 'BACKGROUND': '#a8cfdd', + 'TEXT': '#000000', + 'INPUT': '#dfedf2', + 'SCROLL': '#dfedf2', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#183440'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Default': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'Default1': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': COLOR_SYSTEM_DEFAULT, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'DefaultNoMoreNagging': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue': { + 'BACKGROUND': '#E3F2FD', + 'TEXT': '#000000', + 'INPUT': '#86A8FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#86A8FF', + 'BUTTON': ('#FFFFFF', '#5079D3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'LightGrey': { + 'BACKGROUND': '#FAFAFA', + 'TEXT': '#000000', + 'INPUT': '#004EA1', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#5EA7FF', + 'BUTTON': ('#FFFFFF', '#0079D3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'LightGrey1': { + 'BACKGROUND': '#ffffff', + 'TEXT': '#1a1a1b', + 'INPUT': '#dae0e6', + 'TEXT_INPUT': '#222222', + 'SCROLL': '#a5a4a4', + 'BUTTON': ('#FFFFFF', '#0079d3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#ff5414', + 'ACCENT2': '#33a8ff', + 'ACCENT3': '#dbf0ff', + }, + 'DarkBrown': { + 'BACKGROUND': '#282923', + 'TEXT': '#E7DB74', + 'INPUT': '#393a32', + 'TEXT_INPUT': '#E7C855', + 'SCROLL': '#E7C855', + 'BUTTON': ('#E7C855', '#284B5A'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#c15226', + 'ACCENT2': '#7a4d5f', + 'ACCENT3': '#889743', + }, + 'LightGreen1': { + 'BACKGROUND': '#9FB8AD', + 'TEXT': '#000000', + 'INPUT': '#F7F3EC', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#F7F3EC', + 'BUTTON': ('#FFFFFF', '#475841'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey': { + 'BACKGROUND': '#404040', + 'TEXT': '#FFFFFF', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#707070', + 'BUTTON': ('#FFFFFF', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen2': { + 'BACKGROUND': '#B7CECE', + 'TEXT': '#000000', + 'INPUT': '#FDFFF7', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#FDFFF7', + 'BUTTON': ('#FFFFFF', '#658268'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'ACCENT1': '#76506d', + 'ACCENT2': '#5148f1', + 'ACCENT3': '#0a1c84', + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey1': { + 'BACKGROUND': '#404040', + 'TEXT': '#FFFFFF', + 'INPUT': '#FFFFFF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#707070', + 'BUTTON': ('#FFFFFF', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlack': { + 'BACKGROUND': '#000000', + 'TEXT': '#FFFFFF', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#707070', + 'BUTTON': ('#000000', '#FFFFFF'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown': { + 'BACKGROUND': '#fdf6e3', + 'TEXT': '#268bd1', + 'INPUT': '#eee8d5', + 'TEXT_INPUT': '#6c71c3', + 'SCROLL': '#eee8d5', + 'BUTTON': ('#FFFFFF', '#063542'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown1': { + 'BACKGROUND': '#e5dece', + 'TEXT': '#063289', + 'INPUT': '#f9f8f4', + 'TEXT_INPUT': '#242834', + 'SCROLL': '#eee8d5', + 'BUTTON': ('#FFFFFF', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue1': { + 'BACKGROUND': '#242834', + 'TEXT': '#dfe6f8', + 'INPUT': '#97755c', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#a9afbb', + 'BUTTON': ('#FFFFFF', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBrown1': { + 'BACKGROUND': '#2c2825', + 'TEXT': '#fdcb52', + 'INPUT': '#705e52', + 'TEXT_INPUT': '#fdcb52', + 'SCROLL': '#705e52', + 'BUTTON': ('#000000', '#fdcb52'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue2': { + 'BACKGROUND': '#1a2835', + 'TEXT': '#d1ecff', + 'INPUT': '#335267', + 'TEXT_INPUT': '#acc2d0', + 'SCROLL': '#1b6497', + 'BUTTON': ('#000000', '#fafaf8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBrown2': { + 'BACKGROUND': '#280001', + 'TEXT': '#FFFFFF', + 'INPUT': '#d8d584', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#763e00', + 'BUTTON': ('#000000', '#daad28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGreen': { + 'BACKGROUND': '#82a459', + 'TEXT': '#000000', + 'INPUT': '#d8d584', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e3ecf3', + 'BUTTON': ('#FFFFFF', '#517239'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue1': { + 'BACKGROUND': '#A5CADD', + 'TEXT': '#6E266E', + 'INPUT': '#E0F5FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#E0F5FF', + 'BUTTON': ('#FFFFFF', '#303952'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightPurple': { + 'BACKGROUND': '#B0AAC2', + 'TEXT': '#000000', + 'INPUT': '#F2EFE8', + 'SCROLL': '#F2EFE8', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#C2D4D8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue2': { + 'BACKGROUND': '#AAB6D3', + 'TEXT': '#000000', + 'INPUT': '#F1F4FC', + 'SCROLL': '#F1F4FC', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#7186C7'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen3': { + 'BACKGROUND': '#A8C1B4', + 'TEXT': '#000000', + 'INPUT': '#DDE0DE', + 'SCROLL': '#E3E3E3', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#6D9F85'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue3': { + 'BACKGROUND': '#64778d', + 'TEXT': '#FFFFFF', + 'INPUT': '#f0f3f7', + 'SCROLL': '#A6B2BE', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#283b5b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen4': { + 'BACKGROUND': '#b4ffb4', + 'TEXT': '#000000', + 'INPUT': '#ffff64', + 'SCROLL': '#ffb482', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#ffa0dc'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen5': { + 'BACKGROUND': '#92aa9d', + 'TEXT': '#000000', + 'INPUT': '#fcfff6', + 'SCROLL': '#fcfff6', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#d0dbbd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown2': { + 'BACKGROUND': '#a7ad7f', + 'TEXT': '#000000', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#5d907d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown3': { + 'BACKGROUND': '#efeccb', + 'TEXT': '#012f2f', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#012f2f', + 'BUTTON': ('#FFFFFF', '#046380'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue3': { + 'BACKGROUND': '#a8cfdd', + 'TEXT': '#000000', + 'INPUT': '#dfedf2', + 'SCROLL': '#dfedf2', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#183440'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown4': { + 'BACKGROUND': '#d7c79e', + 'TEXT': '#a35638', + 'INPUT': '#9dab86', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#a35638', + 'BUTTON': ('#FFFFFF', '#a35638'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#a35638', '#9dab86', '#e08f62', '#d7c79e'], + }, + 'DarkTeal': { + 'BACKGROUND': '#003f5c', + 'TEXT': '#fb5b5a', + 'INPUT': '#bc4873', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#bc4873', + 'BUTTON': ('#FFFFFF', '#fb5b5a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], + }, + 'DarkPurple': { + 'BACKGROUND': '#472b62', + 'TEXT': '#fb5b5a', + 'INPUT': '#bc4873', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#bc4873', + 'BUTTON': ('#FFFFFF', '#472b62'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], + }, + 'LightGreen6': { + 'BACKGROUND': '#eafbea', + 'TEXT': '#1f6650', + 'INPUT': '#6f9a8d', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#1f6650', + 'BUTTON': ('#FFFFFF', '#1f6650'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#1f6650', '#6f9a8d', '#ea5e5e', '#eafbea'], + }, + 'DarkGrey2': { + 'BACKGROUND': '#2b2b28', + 'TEXT': '#f8f8f8', + 'INPUT': '#f1d6ab', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f1d6ab', + 'BUTTON': ('#2b2b28', '#e3b04b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#2b2b28', '#e3b04b', '#f1d6ab', '#f8f8f8'], + }, + 'LightBrown6': { + 'BACKGROUND': '#f9b282', + 'TEXT': '#8f4426', + 'INPUT': '#de6b35', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#8f4426', + 'BUTTON': ('#FFFFFF', '#8f4426'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#8f4426', '#de6b35', '#64ccda', '#f9b282'], + }, + 'DarkTeal1': { + 'BACKGROUND': '#396362', + 'TEXT': '#ffe7d1', + 'INPUT': '#f6c89f', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f6c89f', + 'BUTTON': ('#ffe7d1', '#4b8e8d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], + }, + 'LightBrown7': { + 'BACKGROUND': '#f6c89f', + 'TEXT': '#396362', + 'INPUT': '#4b8e8d', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#396362', + 'BUTTON': ('#FFFFFF', '#396362'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], + }, + 'DarkPurple1': { + 'BACKGROUND': '#0c093c', + 'TEXT': '#fad6d6', + 'INPUT': '#eea5f6', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#eea5f6', + 'BUTTON': ('#FFFFFF', '#df42d1'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#0c093c', '#df42d1', '#eea5f6', '#fad6d6'], + }, + 'DarkGrey3': { + 'BACKGROUND': '#211717', + 'TEXT': '#dfddc7', + 'INPUT': '#f58b54', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f58b54', + 'BUTTON': ('#dfddc7', '#a34a28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], + }, + 'LightBrown8': { + 'BACKGROUND': '#dfddc7', + 'TEXT': '#211717', + 'INPUT': '#a34a28', + 'TEXT_INPUT': '#dfddc7', + 'SCROLL': '#211717', + 'BUTTON': ('#dfddc7', '#a34a28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], + }, + 'DarkBlue4': { + 'BACKGROUND': '#494ca2', + 'TEXT': '#e3e7f1', + 'INPUT': '#c6cbef', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#c6cbef', + 'BUTTON': ('#FFFFFF', '#8186d5'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#494ca2', '#8186d5', '#c6cbef', '#e3e7f1'], + }, + 'LightBlue4': { + 'BACKGROUND': '#5c94bd', + 'TEXT': '#470938', + 'INPUT': '#1a3e59', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#470938', + 'BUTTON': ('#FFFFFF', '#470938'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#470938', '#1a3e59', '#5c94bd', '#f2d6eb'], + }, + 'DarkTeal2': { + 'BACKGROUND': '#394a6d', + 'TEXT': '#c0ffb3', + 'INPUT': '#52de97', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#52de97', + 'BUTTON': ('#c0ffb3', '#394a6d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], + }, + 'DarkTeal3': { + 'BACKGROUND': '#3c9d9b', + 'TEXT': '#c0ffb3', + 'INPUT': '#52de97', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#52de97', + 'BUTTON': ('#c0ffb3', '#394a6d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], + }, + 'DarkPurple5': { + 'BACKGROUND': '#730068', + 'TEXT': '#f6f078', + 'INPUT': '#01d28e', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#01d28e', + 'BUTTON': ('#f6f078', '#730068'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#730068', '#434982', '#01d28e', '#f6f078'], + }, + 'DarkPurple2': { + 'BACKGROUND': '#202060', + 'TEXT': '#b030b0', + 'INPUT': '#602080', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#602080', + 'BUTTON': ('#FFFFFF', '#202040'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#202040', '#202060', '#602080', '#b030b0'], + }, + 'DarkBlue5': { + 'BACKGROUND': '#000272', + 'TEXT': '#ff6363', + 'INPUT': '#a32f80', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#a32f80', + 'BUTTON': ('#FFFFFF', '#341677'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#000272', '#341677', '#a32f80', '#ff6363'], + }, + 'LightGrey2': { + 'BACKGROUND': '#f6f6f6', + 'TEXT': '#420000', + 'INPUT': '#d4d7dd', + 'TEXT_INPUT': '#420000', + 'SCROLL': '#420000', + 'BUTTON': ('#420000', '#d4d7dd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], + }, + 'LightGrey3': { + 'BACKGROUND': '#eae9e9', + 'TEXT': '#420000', + 'INPUT': '#d4d7dd', + 'TEXT_INPUT': '#420000', + 'SCROLL': '#420000', + 'BUTTON': ('#420000', '#d4d7dd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], + }, + 'DarkBlue6': { + 'BACKGROUND': '#01024e', + 'TEXT': '#ff6464', + 'INPUT': '#8b4367', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#8b4367', + 'BUTTON': ('#FFFFFF', '#543864'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#01024e', '#543864', '#8b4367', '#ff6464'], + }, + 'DarkBlue7': { + 'BACKGROUND': '#241663', + 'TEXT': '#eae7af', + 'INPUT': '#a72693', + 'TEXT_INPUT': '#eae7af', + 'SCROLL': '#a72693', + 'BUTTON': ('#eae7af', '#160f30'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#160f30', '#241663', '#a72693', '#eae7af'], + }, + 'LightBrown9': { + 'BACKGROUND': '#f6d365', + 'TEXT': '#3a1f5d', + 'INPUT': '#c83660', + 'TEXT_INPUT': '#f6d365', + 'SCROLL': '#3a1f5d', + 'BUTTON': ('#f6d365', '#c83660'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3a1f5d', '#c83660', '#e15249', '#f6d365'], + }, + 'DarkPurple3': { + 'BACKGROUND': '#6e2142', + 'TEXT': '#ffd692', + 'INPUT': '#e16363', + 'TEXT_INPUT': '#ffd692', + 'SCROLL': '#e16363', + 'BUTTON': ('#ffd692', '#943855'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], + }, + 'LightBrown10': { + 'BACKGROUND': '#ffd692', + 'TEXT': '#6e2142', + 'INPUT': '#943855', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#6e2142', + 'BUTTON': ('#FFFFFF', '#6e2142'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], + }, + 'DarkPurple4': { + 'BACKGROUND': '#200f21', + 'TEXT': '#f638dc', + 'INPUT': '#5a3d5c', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#5a3d5c', + 'BUTTON': ('#FFFFFF', '#382039'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#200f21', '#382039', '#5a3d5c', '#f638dc'], + }, + 'LightBlue5': { + 'BACKGROUND': '#b2fcff', + 'TEXT': '#3e64ff', + 'INPUT': '#5edfff', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#3e64ff', + 'BUTTON': ('#FFFFFF', '#3e64ff'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3e64ff', '#5edfff', '#b2fcff', '#ecfcff'], + }, + 'DarkTeal4': { + 'BACKGROUND': '#464159', + 'TEXT': '#c7f0db', + 'INPUT': '#8bbabb', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#8bbabb', + 'BUTTON': ('#FFFFFF', '#6c7b95'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], + }, + 'LightTeal': { + 'BACKGROUND': '#c7f0db', + 'TEXT': '#464159', + 'INPUT': '#6c7b95', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#464159', + 'BUTTON': ('#FFFFFF', '#464159'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], + }, + 'DarkTeal5': { + 'BACKGROUND': '#8bbabb', + 'TEXT': '#464159', + 'INPUT': '#6c7b95', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#464159', + 'BUTTON': ('#c7f0db', '#6c7b95'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], + }, + 'LightGrey4': { + 'BACKGROUND': '#faf5ef', + 'TEXT': '#672f2f', + 'INPUT': '#99b19c', + 'TEXT_INPUT': '#672f2f', + 'SCROLL': '#672f2f', + 'BUTTON': ('#672f2f', '#99b19c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], + }, + 'LightGreen7': { + 'BACKGROUND': '#99b19c', + 'TEXT': '#faf5ef', + 'INPUT': '#d7d1c9', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#d7d1c9', + 'BUTTON': ('#FFFFFF', '#99b19c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], + }, + 'LightGrey5': { + 'BACKGROUND': '#d7d1c9', + 'TEXT': '#672f2f', + 'INPUT': '#99b19c', + 'TEXT_INPUT': '#672f2f', + 'SCROLL': '#672f2f', + 'BUTTON': ('#FFFFFF', '#672f2f'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], + }, + 'DarkBrown3': { + 'BACKGROUND': '#a0855b', + 'TEXT': '#f9f6f2', + 'INPUT': '#f1d6ab', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f1d6ab', + 'BUTTON': ('#FFFFFF', '#38470b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], + }, + 'LightBrown11': { + 'BACKGROUND': '#f1d6ab', + 'TEXT': '#38470b', + 'INPUT': '#a0855b', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#38470b', + 'BUTTON': ('#f9f6f2', '#a0855b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], + }, + 'DarkRed': { + 'BACKGROUND': '#83142c', + 'TEXT': '#f9d276', + 'INPUT': '#ad1d45', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#ad1d45', + 'BUTTON': ('#f9d276', '#ad1d45'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#44000d', '#83142c', '#ad1d45', '#f9d276'], + }, + 'DarkTeal6': { + 'BACKGROUND': '#204969', + 'TEXT': '#fff7f7', + 'INPUT': '#dadada', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#dadada', + 'BUTTON': ('#000000', '#fff7f7'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#204969', '#08ffc8', '#dadada', '#fff7f7'], + }, + 'DarkBrown4': { + 'BACKGROUND': '#252525', + 'TEXT': '#ff0000', + 'INPUT': '#af0404', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#af0404', + 'BUTTON': ('#FFFFFF', '#252525'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#252525', '#414141', '#af0404', '#ff0000'], + }, + 'LightYellow': { + 'BACKGROUND': '#f4ff61', + 'TEXT': '#27aa80', + 'INPUT': '#32ff6a', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#27aa80', + 'BUTTON': ('#f4ff61', '#27aa80'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#27aa80', '#32ff6a', '#a8ff3e', '#f4ff61'], + }, + 'DarkGreen1': { + 'BACKGROUND': '#2b580c', + 'TEXT': '#fdef96', + 'INPUT': '#f7b71d', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f7b71d', + 'BUTTON': ('#fdef96', '#2b580c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#2b580c', '#afa939', '#f7b71d', '#fdef96'], + }, + 'LightGreen8': { + 'BACKGROUND': '#c8dad3', + 'TEXT': '#63707e', + 'INPUT': '#93b5b3', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#63707e', + 'BUTTON': ('#FFFFFF', '#63707e'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#63707e', '#93b5b3', '#c8dad3', '#f2f6f5'], + }, + 'DarkTeal7': { + 'BACKGROUND': '#248ea9', + 'TEXT': '#fafdcb', + 'INPUT': '#aee7e8', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#aee7e8', + 'BUTTON': ('#000000', '#fafdcb'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#248ea9', '#28c3d4', '#aee7e8', '#fafdcb'], + }, + 'DarkBlue8': { + 'BACKGROUND': '#454d66', + 'TEXT': '#d9d872', + 'INPUT': '#58b368', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#58b368', + 'BUTTON': ('#000000', '#009975'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#009975', '#454d66', '#58b368', '#d9d872'], + }, + 'DarkBlue9': { + 'BACKGROUND': '#263859', + 'TEXT': '#ff6768', + 'INPUT': '#6b778d', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#6b778d', + 'BUTTON': ('#ff6768', '#263859'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#17223b', '#263859', '#6b778d', '#ff6768'], + }, + 'DarkBlue10': { + 'BACKGROUND': '#0028ff', + 'TEXT': '#f1f4df', + 'INPUT': '#10eaf0', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#10eaf0', + 'BUTTON': ('#f1f4df', '#24009c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#24009c', '#0028ff', '#10eaf0', '#f1f4df'], + }, + 'DarkBlue11': { + 'BACKGROUND': '#6384b3', + 'TEXT': '#e6f0b6', + 'INPUT': '#b8e9c0', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#b8e9c0', + 'BUTTON': ('#e6f0b6', '#684949'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#684949', '#6384b3', '#b8e9c0', '#e6f0b6'], + }, + 'DarkTeal8': { + 'BACKGROUND': '#71a0a5', + 'TEXT': '#212121', + 'INPUT': '#665c84', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#212121', + 'BUTTON': ('#fab95b', '#665c84'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#212121', '#665c84', '#71a0a5', '#fab95b'], + }, + 'DarkRed1': { + 'BACKGROUND': '#c10000', + 'TEXT': '#eeeeee', + 'INPUT': '#dedede', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#dedede', + 'BUTTON': ('#c10000', '#eeeeee'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#c10000', '#ff4949', '#dedede', '#eeeeee'], + }, + 'LightBrown5': { + 'BACKGROUND': '#fff591', + 'TEXT': '#e41749', + 'INPUT': '#f5587b', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e41749', + 'BUTTON': ('#fff591', '#e41749'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#e41749', '#f5587b', '#ff8a5c', '#fff591'], + }, + 'LightGreen9': { + 'BACKGROUND': '#f1edb3', + 'TEXT': '#3b503d', + 'INPUT': '#4a746e', + 'TEXT_INPUT': '#f1edb3', + 'SCROLL': '#3b503d', + 'BUTTON': ('#f1edb3', '#3b503d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], + 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], + }, + 'DarkGreen2': { + 'BACKGROUND': '#3b503d', + 'TEXT': '#f1edb3', + 'INPUT': '#c8cf94', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#c8cf94', + 'BUTTON': ('#f1edb3', '#3b503d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], + 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], + }, + 'LightGray1': { + 'BACKGROUND': '#f2f2f2', + 'TEXT': '#222831', + 'INPUT': '#393e46', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#222831', + 'BUTTON': ('#f2f2f2', '#222831'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#222831', '#393e46', '#f96d00', '#f2f2f2'], + 'DESCRIPTION': ['#000000', 'Grey', 'Orange', 'Grey', 'Autumn'], + }, + 'DarkGrey4': { + 'BACKGROUND': '#52524e', + 'TEXT': '#e9e9e5', + 'INPUT': '#d4d6c8', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#d4d6c8', + 'BUTTON': ('#FFFFFF', '#9a9b94'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#52524e', '#9a9b94', '#d4d6c8', '#e9e9e5'], + 'DESCRIPTION': ['Grey', 'Pastel', 'Winter'], + }, + 'DarkBlue12': { + 'BACKGROUND': '#324e7b', + 'TEXT': '#f8f8f8', + 'INPUT': '#86a6df', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#86a6df', + 'BUTTON': ('#FFFFFF', '#5068a9'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#324e7b', '#5068a9', '#86a6df', '#f8f8f8'], + 'DESCRIPTION': ['Blue', 'Grey', 'Cold', 'Winter'], + }, + 'DarkPurple6': { + 'BACKGROUND': '#070739', + 'TEXT': '#e1e099', + 'INPUT': '#c327ab', + 'TEXT_INPUT': '#e1e099', + 'SCROLL': '#c327ab', + 'BUTTON': ('#e1e099', '#521477'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#070739', '#521477', '#c327ab', '#e1e099'], + 'DESCRIPTION': ['#000000', 'Purple', 'Yellow', 'Dark'], + }, + 'DarkPurple7': { + 'BACKGROUND': '#191930', + 'TEXT': '#B1B7C5', + 'INPUT': '#232B5C', + 'TEXT_INPUT': '#D0E3E7', + 'SCROLL': '#B1B7C5', + 'BUTTON': ('#272D38', '#B1B7C5'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue13': { + 'BACKGROUND': '#203562', + 'TEXT': '#e3e8f8', + 'INPUT': '#c0c5cd', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#c0c5cd', + 'BUTTON': ('#FFFFFF', '#3e588f'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#203562', '#3e588f', '#c0c5cd', '#e3e8f8'], + 'DESCRIPTION': ['Blue', 'Grey', 'Wedding', 'Cold'], + }, + 'DarkBrown5': { + 'BACKGROUND': '#3c1b1f', + 'TEXT': '#f6e1b5', + 'INPUT': '#e2bf81', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e2bf81', + 'BUTTON': ('#3c1b1f', '#f6e1b5'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3c1b1f', '#b21e4b', '#e2bf81', '#f6e1b5'], + 'DESCRIPTION': ['Brown', 'Red', 'Yellow', 'Warm'], + }, + 'DarkGreen3': { + 'BACKGROUND': '#062121', + 'TEXT': '#eeeeee', + 'INPUT': '#e4dcad', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e4dcad', + 'BUTTON': ('#eeeeee', '#181810'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], + 'DESCRIPTION': ['#000000', '#000000', 'Brown', 'Grey'], + }, + 'DarkBlack1': { + 'BACKGROUND': '#181810', + 'TEXT': '#eeeeee', + 'INPUT': '#e4dcad', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e4dcad', + 'BUTTON': ('#FFFFFF', '#062121'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], + 'DESCRIPTION': ['#000000', '#000000', 'Brown', 'Grey'], + }, + 'DarkGrey5': { + 'BACKGROUND': '#343434', + 'TEXT': '#f3f3f3', + 'INPUT': '#e9dcbe', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e9dcbe', + 'BUTTON': ('#FFFFFF', '#8e8b82'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], + 'DESCRIPTION': ['Grey', 'Brown'], + }, + 'LightBrown12': { + 'BACKGROUND': '#8e8b82', + 'TEXT': '#f3f3f3', + 'INPUT': '#e9dcbe', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e9dcbe', + 'BUTTON': ('#f3f3f3', '#8e8b82'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], + 'DESCRIPTION': ['Grey', 'Brown'], + }, + 'DarkTeal9': { + 'BACKGROUND': '#13445a', + 'TEXT': '#fef4e8', + 'INPUT': '#446878', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#446878', + 'BUTTON': ('#fef4e8', '#446878'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#13445a', '#970747', '#446878', '#fef4e8'], + 'DESCRIPTION': ['Red', 'Grey', 'Blue', 'Wedding', 'Retro'], + }, + 'DarkBlue14': { + 'BACKGROUND': '#21273d', + 'TEXT': '#f1f6f8', + 'INPUT': '#b9d4f1', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#b9d4f1', + 'BUTTON': ('#FFFFFF', '#6a759b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], + 'DESCRIPTION': ['Blue', '#000000', 'Grey', 'Cold', 'Winter'], + }, + 'LightBlue6': { + 'BACKGROUND': '#f1f6f8', + 'TEXT': '#21273d', + 'INPUT': '#6a759b', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#21273d', + 'BUTTON': ('#f1f6f8', '#6a759b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], + 'DESCRIPTION': ['Blue', '#000000', 'Grey', 'Cold', 'Winter'], + }, + 'DarkGreen4': { + 'BACKGROUND': '#044343', + 'TEXT': '#e4e4e4', + 'INPUT': '#045757', + 'TEXT_INPUT': '#e4e4e4', + 'SCROLL': '#045757', + 'BUTTON': ('#e4e4e4', '#045757'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#222222', '#044343', '#045757', '#e4e4e4'], + 'DESCRIPTION': ['#000000', 'Turquoise', 'Grey', 'Dark'], + }, + 'DarkGreen5': { + 'BACKGROUND': '#1b4b36', + 'TEXT': '#e0e7f1', + 'INPUT': '#aebd77', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#aebd77', + 'BUTTON': ('#FFFFFF', '#538f6a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#1b4b36', '#538f6a', '#aebd77', '#e0e7f1'], + 'DESCRIPTION': ['Green', 'Grey'], + }, + 'DarkTeal10': { + 'BACKGROUND': '#0d3446', + 'TEXT': '#d8dfe2', + 'INPUT': '#71adb5', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#71adb5', + 'BUTTON': ('#FFFFFF', '#176d81'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#0d3446', '#176d81', '#71adb5', '#d8dfe2'], + 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter', 'Cold'], + }, + 'DarkGrey6': { + 'BACKGROUND': '#3e3e3e', + 'TEXT': '#ededed', + 'INPUT': '#68868c', + 'TEXT_INPUT': '#ededed', + 'SCROLL': '#68868c', + 'BUTTON': ('#FFFFFF', '#405559'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], + 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], + }, + 'DarkTeal11': { + 'BACKGROUND': '#405559', + 'TEXT': '#ededed', + 'INPUT': '#68868c', + 'TEXT_INPUT': '#ededed', + 'SCROLL': '#68868c', + 'BUTTON': ('#ededed', '#68868c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], + 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], + }, + 'LightBlue7': { + 'BACKGROUND': '#9ed0e0', + 'TEXT': '#19483f', + 'INPUT': '#5c868e', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#19483f', + 'BUTTON': ('#FFFFFF', '#19483f'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#19483f', '#5c868e', '#ff6a38', '#9ed0e0'], + 'DESCRIPTION': ['Orange', 'Blue', 'Turquoise'], + }, + 'LightGreen10': { + 'BACKGROUND': '#d8ebb5', + 'TEXT': '#205d67', + 'INPUT': '#639a67', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#205d67', + 'BUTTON': ('#d8ebb5', '#205d67'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#205d67', '#639a67', '#d9bf77', '#d8ebb5'], + 'DESCRIPTION': ['Blue', 'Green', 'Brown', 'Vintage'], + }, + 'DarkBlue15': { + 'BACKGROUND': '#151680', + 'TEXT': '#f1fea4', + 'INPUT': '#375fc0', + 'TEXT_INPUT': '#f1fea4', + 'SCROLL': '#375fc0', + 'BUTTON': ('#f1fea4', '#1c44ac'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], + 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], + }, + 'DarkBlue16': { + 'BACKGROUND': '#1c44ac', + 'TEXT': '#f1fea4', + 'INPUT': '#375fc0', + 'TEXT_INPUT': '#f1fea4', + 'SCROLL': '#375fc0', + 'BUTTON': ('#f1fea4', '#151680'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], + 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], + }, + 'DarkTeal12': { + 'BACKGROUND': '#004a7c', + 'TEXT': '#fafafa', + 'INPUT': '#e8f1f5', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e8f1f5', + 'BUTTON': ('#fafafa', '#005691'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#004a7c', '#005691', '#e8f1f5', '#fafafa'], + 'DESCRIPTION': ['Grey', 'Blue', 'Cold', 'Winter'], + }, + 'LightBrown13': { + 'BACKGROUND': '#ebf5ee', + 'TEXT': '#921224', + 'INPUT': '#bdc6b8', + 'TEXT_INPUT': '#921224', + 'SCROLL': '#921224', + 'BUTTON': ('#FFFFFF', '#921224'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#921224', '#bdc6b8', '#bce0da', '#ebf5ee'], + 'DESCRIPTION': ['Red', 'Blue', 'Grey', 'Vintage', 'Wedding'], + }, + 'DarkBlue17': { + 'BACKGROUND': '#21294c', + 'TEXT': '#f9f2d7', + 'INPUT': '#f2dea8', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f2dea8', + 'BUTTON': ('#f9f2d7', '#141829'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#141829', '#21294c', '#f2dea8', '#f9f2d7'], + 'DESCRIPTION': ['#000000', 'Blue', 'Yellow'], + }, + 'DarkBrown6': { + 'BACKGROUND': '#785e4d', + 'TEXT': '#f2eee3', + 'INPUT': '#baaf92', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#baaf92', + 'BUTTON': ('#FFFFFF', '#785e4d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#785e4d', '#ff8426', '#baaf92', '#f2eee3'], + 'DESCRIPTION': ['Grey', 'Brown', 'Orange', 'Autumn'], + }, + 'DarkGreen6': { + 'BACKGROUND': '#5c715e', + 'TEXT': '#f2f9f1', + 'INPUT': '#ddeedf', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#ddeedf', + 'BUTTON': ('#f2f9f1', '#5c715e'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#5c715e', '#b6cdbd', '#ddeedf', '#f2f9f1'], + 'DESCRIPTION': ['Grey', 'Green', 'Vintage'], + }, + 'DarkGreen7': { + 'BACKGROUND': '#0C231E', + 'TEXT': '#efbe1c', + 'INPUT': '#153C33', + 'TEXT_INPUT': '#efbe1c', + 'SCROLL': '#153C33', + 'BUTTON': ('#efbe1c', '#153C33'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey7': { + 'BACKGROUND': '#4b586e', + 'TEXT': '#dddddd', + 'INPUT': '#574e6d', + 'TEXT_INPUT': '#dddddd', + 'SCROLL': '#574e6d', + 'BUTTON': ('#dddddd', '#43405d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#43405d', '#4b586e', '#574e6d', '#dddddd'], + 'DESCRIPTION': ['Grey', 'Winter', 'Cold'], + }, + 'DarkRed2': { + 'BACKGROUND': '#ab1212', + 'TEXT': '#f6e4b5', + 'INPUT': '#cd3131', + 'TEXT_INPUT': '#f6e4b5', + 'SCROLL': '#cd3131', + 'BUTTON': ('#f6e4b5', '#ab1212'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#ab1212', '#1fad9f', '#cd3131', '#f6e4b5'], + 'DESCRIPTION': ['Turquoise', 'Red', 'Yellow'], + }, + 'LightGrey6': { + 'BACKGROUND': '#e3e3e3', + 'TEXT': '#233142', + 'INPUT': '#455d7a', + 'TEXT_INPUT': '#e3e3e3', + 'SCROLL': '#233142', + 'BUTTON': ('#e3e3e3', '#455d7a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#233142', '#455d7a', '#f95959', '#e3e3e3'], + 'DESCRIPTION': ['#000000', 'Blue', 'Red', 'Grey'], + }, + 'HotDogStand': { + 'BACKGROUND': 'red', + 'TEXT': 'yellow', + 'INPUT': 'yellow', + 'TEXT_INPUT': '#000000', + 'SCROLL': 'yellow', + 'BUTTON': ('red', 'yellow'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey8': { + 'BACKGROUND': '#19232D', + 'TEXT': '#ffffff', + 'INPUT': '#32414B', + 'TEXT_INPUT': '#ffffff', + 'SCROLL': '#505F69', + 'BUTTON': ('#ffffff', '#32414B'), + 'PROGRESS': ('#505F69', '#32414B'), + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey9': { + 'BACKGROUND': '#36393F', + 'TEXT': '#DCDDDE', + 'INPUT': '#40444B', + 'TEXT_INPUT': '#ffffff', + 'SCROLL': '#202225', + 'BUTTON': ('#202225', '#B9BBBE'), + 'PROGRESS': ('#202225', '#40444B'), + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey10': { + 'BACKGROUND': '#1c1e23', + 'TEXT': '#cccdcf', + 'INPUT': '#272a31', + 'TEXT_INPUT': '#8b9fde', + 'SCROLL': '#313641', + 'BUTTON': ('#f5f5f6', '#2e3d5a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey11': { + 'BACKGROUND': '#1c1e23', + 'TEXT': '#cccdcf', + 'INPUT': '#313641', + 'TEXT_INPUT': '#cccdcf', + 'SCROLL': '#313641', + 'BUTTON': ('#f5f5f6', '#313641'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey12': { + 'BACKGROUND': '#1c1e23', + 'TEXT': '#8b9fde', + 'INPUT': '#313641', + 'TEXT_INPUT': '#8b9fde', + 'SCROLL': '#313641', + 'BUTTON': ('#cccdcf', '#2e3d5a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COMPUTE, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey13': { + 'BACKGROUND': '#1c1e23', + 'TEXT': '#cccdcf', + 'INPUT': '#272a31', + 'TEXT_INPUT': '#cccdcf', + 'SCROLL': '#313641', + 'BUTTON': ('#8b9fde', '#313641'), + 'PROGRESS': ('#cccdcf', '#272a31'), + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey14': { + 'BACKGROUND': '#24292e', + 'TEXT': '#fafbfc', + 'INPUT': '#1d2125', + 'TEXT_INPUT': '#fafbfc', + 'SCROLL': '#1d2125', + 'BUTTON': ('#fafbfc', '#155398'), + 'PROGRESS': ('#155398', '#1d2125'), + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBrown7': { + 'BACKGROUND': '#2c2417', + 'TEXT': '#baa379', + 'INPUT': '#baa379', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#392e1c', + 'BUTTON': ('#000000', '#baa379'), + 'PROGRESS': ('#baa379', '#453923'), + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'Python': { + 'BACKGROUND': '#3d7aab', + 'TEXT': '#ffde56', + 'INPUT': '#295273', + 'TEXT_INPUT': '#ffde56', + 'SCROLL': '#295273', + 'BUTTON': ('#ffde56', '#295273'), + 'PROGRESS': ('#ffde56', '#295273'), + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, +} + + +def ListOfLookAndFeelValues(): + """ + Get a list of the valid values to pass into your call to change_look_and_feel + :return: List[str] - list of valid string values + """ + return sorted(list(LOOK_AND_FEEL_TABLE.keys())) + + +def theme(new_theme=None): + """ + Sets / Gets the current Theme. If none is specified then returns the current theme. + This call replaces the ChangeLookAndFeel / change_look_and_feel call which only sets the theme. + + :param new_theme: (str) the new theme name to use + :return: (str) the currently selected theme + """ + if new_theme is not None: + change_look_and_feel(new_theme) + return CURRENT_LOOK_AND_FEEL + + +def theme_background_color(color=None): + """ + Sets/Returns the background color currently in use + Used for Windows and containers (Column, Frame, Tab) and tables + + :return: (str) - color string of the background color currently in use + """ + if color is not None: + set_options(background_color=color) + return DEFAULT_BACKGROUND_COLOR + + +def theme_element_background_color(color=None): + """ + Sets/Returns the background color currently in use for all elements except containers + + :return: (str) - color string of the element background color currently in use + """ + if color is not None: + set_options(element_background_color=color) + return DEFAULT_ELEMENT_BACKGROUND_COLOR + + +def theme_text_color(color=None): + """ + Sets/Returns the text color currently in use + + :return: (str) - color string of the text color currently in use + """ + if color is not None: + set_options(text_color=color) + return DEFAULT_TEXT_COLOR + + +def theme_input_background_color(color=None): + """ + Sets/Returns the input element background color currently in use + + :return: (str) - color string of the input element background color currently in use + """ + if color is not None: + set_options(input_elements_background_color=color) + return DEFAULT_INPUT_ELEMENTS_COLOR + + +def theme_input_text_color(color=None): + """ + Sets/Returns the input element entry color (not the text but the thing that's displaying the text) + + :return: (str) - color string of the input element color currently in use + """ + if color is not None: + set_options(input_text_color=color) + return DEFAULT_INPUT_TEXT_COLOR + + +def theme_button_color(color=None): + """ + Sets/Returns the button color currently in use + + :return: Tuple[str, str] - TUPLE with color strings of the button color currently in use (button text color, button background color) + """ + if color is not None: + set_options(button_color=color) + return DEFAULT_BUTTON_COLOR + + +def theme_progress_bar_color(color=None): + """ + Sets/Returns the progress bar colors by the current color theme + + :return: Tuple[str, str] - TUPLE with color strings of the ProgressBar color currently in use(button text color, button background color) + """ + if color is not None: + set_options(progress_meter_color=color) + return DEFAULT_PROGRESS_BAR_COLOR + + +def theme_slider_color(color=None): + """ + Sets/Returns the slider color (used for sliders) + + :return: (str) - color string of the slider color currently in use + """ + if color is not None: + set_options(scrollbar_color=color) + return DEFAULT_SCROLLBAR_COLOR + + +def theme_border_width(border_width=None): + """ + Sets/Returns the border width currently in use + Used by non ttk elements at the moment + + :return: (int) - border width currently in use + """ + if border_width is not None: + set_options(border_width=border_width) + return DEFAULT_BORDER_WIDTH + + +def theme_slider_border_width(border_width=None): + """ + Sets/Returns the slider border width currently in use + + :return: (int) - border width currently in use + """ + if border_width is not None: + set_options(slider_border_width=border_width) + return DEFAULT_SLIDER_BORDER_WIDTH + + +def theme_progress_bar_border_width(border_width=None): + """ + Sets/Returns the progress meter border width currently in use + + :return: (int) - border width currently in use + """ + if border_width is not None: + set_options(progress_meter_border_depth=border_width) + return DEFAULT_PROGRESS_BAR_BORDER_WIDTH + + +def theme_element_text_color(color=None): + """ + Sets/Returns the text color used by elements that have text as part of their display (Tables, Trees and Sliders) + + :return: (str) - color string currently in use + """ + if color is not None: + set_options(element_text_color=color) + return DEFAULT_ELEMENT_TEXT_COLOR + + +def theme_list(): + """ + Returns a sorted list of the currently available color themes + + :return: List[str] - A sorted list of the currently available color themes + """ + return list_of_look_and_feel_values() + + +def theme_add_new(new_theme_name, new_theme_dict): + """ + Add a new theme to the dictionary of themes + + :param new_theme_name: text to display in element + :type new_theme_name: (str) + :param new_theme_dict: text to display in element + :type new_theme_dict: (dict) + """ + global LOOK_AND_FEEL_TABLE + try: + LOOK_AND_FEEL_TABLE[new_theme_name] = new_theme_dict + except Exception as e: + print('Exception during adding new theme {}'.format(e)) + + +def theme_previewer(columns=12): + """ + Show a window with all of the color themes - takes a while so be patient + + :param columns: (int) number of themes in a single row + """ + preview_all_look_and_feel_themes(columns) + + +def ChangeLookAndFeel(index, force=False): + """ + Change the "color scheme" of all future PySimpleGUI Windows. + The scheme are string names that specify a group of colors. Background colors, text colors, button colors. + There are 13 different color settings that are changed at one time using a single call to ChangeLookAndFeel + The look and feel table itself has these indexes into the dictionary LOOK_AND_FEEL_TABLE. + The original list was (prior to a major rework and renaming)... these names still work... + In Nov 2019 a new Theme Formula was devised to make choosing a theme easier: + The "Formula" is: + ["Dark" or "Light"] Color Number + Colors can be Blue Brown Grey Green Purple Red Teal Yellow Black + The number will vary for each pair. There are more DarkGrey entries than there are LightYellow for example. + Default = The default settings (only button color is different than system default) + Default1 = The full system default including the button (everything's gray... how sad... don't be all gray... please....) + :param index: the name of the index into the Look and Feel table (does not have to be exact, can be "fuzzy") + :type index: (str) + :param force: no longer used + :type force: (bool) + """ + global CURRENT_LOOK_AND_FEEL + + # if sys.platform.startswith('darwin') and not force: + # print('*** Changing look and feel is not supported on Mac platform ***') + # return + + theme = index + # normalize available l&f values + lf_values = [item.lower() for item in list_of_look_and_feel_values()] + # option 1 + opt1 = theme.replace(' ', '').lower() + + # option 2 (reverse lookup) + optx = theme.lower().split(' ') + optx.reverse() + opt2 = ''.join(optx) + + # search for valid l&f name + if opt1 in lf_values: + ix = lf_values.index(opt1) + elif opt2 in lf_values: + ix = lf_values.index(opt2) + else: + ix = random.random.randint(0, len(lf_values) - 1) + print('** Warning - {} Theme is not a valid theme. Change your theme call. **'.format(index)) + print('valid values are', list_of_look_and_feel_values()) + print('Instead, please enjoy a random Theme named {}'.format(list_of_look_and_feel_values()[ix])) + + selection = list_of_look_and_feel_values()[ix] + CURRENT_LOOK_AND_FEEL = selection + try: + colors = LOOK_AND_FEEL_TABLE[selection] + + # Color the progress bar using button background and input colors...unless they're the same + if colors['PROGRESS'] != COLOR_SYSTEM_DEFAULT: + if colors['BUTTON'][1] != colors['INPUT'] and colors['BUTTON'][1] != colors['BACKGROUND']: + colors['PROGRESS'] = colors['BUTTON'][1], colors['INPUT'] + else: # if the same, then use text input on top of input color + colors['PROGRESS'] = (colors['TEXT_INPUT'], colors['INPUT']) + else: + colors['PROGRESS'] = DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL + # call to change all the colors + SetOptions( + background_color=colors['BACKGROUND'], + text_element_background_color=colors['BACKGROUND'], + element_background_color=colors['BACKGROUND'], + text_color=colors['TEXT'], + input_elements_background_color=colors['INPUT'], + # button_color=colors['BUTTON'] if not sys.platform.startswith('darwin') else None, + button_color=colors['BUTTON'], + progress_meter_color=colors['PROGRESS'], + border_width=colors['BORDER'], + slider_border_width=colors['SLIDER_DEPTH'], + progress_meter_border_depth=colors['PROGRESS_DEPTH'], + scrollbar_color=(colors['SCROLL']), + element_text_color=colors['TEXT'], + input_text_color=colors['TEXT_INPUT'], + ) + except: # most likely an index out of range + print('** Warning - Theme value not valid. Change your theme call. **') + print('valid values are', list_of_look_and_feel_values()) + + +def preview_all_look_and_feel_themes(columns=12): + """ + Displays a "Quick Reference Window" showing all of the different Look and Feel settings that are available. + They are sorted alphabetically. The legacy color names are mixed in, but otherwise they are sorted into Dark and Light halves + :param columns: (int) The number of themes to display per row + """ + + # Show a "splash" type message so the user doesn't give up waiting + popup_quick_message( + 'Hang on for a moment, this will take a bit to create....', + background_color='red', + text_color='white', + auto_close=True, + non_blocking=True, + ) + + web = False + + win_bg = 'black' + + def sample_layout(): + return [ + [Text('Text element'), InputText('Input data here', size=(10, 1))], + [Button('Ok'), Button('Cancel'), Slider((1, 10), orientation='h', size=(5, 15))], + ] + + layout = [[Text('Here is a complete list of themes', font='Default 18', background_color=win_bg)]] + + names = list_of_look_and_feel_values() + names.sort() + row = [] + for count, theme in enumerate(names): + change_look_and_feel(theme) + if not count % columns: + layout += [row] + row = [] + row += [Frame(theme, sample_layout() if not web else [[T(theme)]] + sample_layout())] + if row: + layout += [row] + + window = Window('Preview of all Look and Feel choices', layout, background_color=win_bg) + window.read() + window.close() + + +# ============================== sprint ======# +# Is identical to the Scrolled Text Box # +# Provides a crude 'print' mechanism but in a # +# GUI environment # +# ============================================# +sprint = ScrolledTextBox + + +# Converts an object's contents into a nice printable string. Great for dumping debug data +def ObjToStringSingleObj(obj): + """ + Dumps an Object's values as a formatted string. Very nicely done. Great way to display an object's member variables in human form + Returns only the top-most object's variables instead of drilling down to dispolay more + :param obj: The object to display + :type obj: (Any) + :return: Formatted output of the object's values + :rtype: (str) + """ + if obj is None: + return 'None' + return str(obj.__class__) + '\n' + '\n'.join((repr(item) + ' = ' + repr(obj.__dict__[item]) for item in sorted(obj.__dict__))) + + +def ObjToString(obj, extra=' '): + """ + Dumps an Object's values as a formatted string. Very nicely done. Great way to display an object's member variables in human form + :param obj: The object to display + :type obj: (Any) + :param extra: extra stuff (Default value = ' ') + :type extra: (str) + :return: Formatted output of the object's values + :rtype: (str) + """ + if obj is None: + return 'None' + return str(obj.__class__) + '\n' + '\n'.join((extra + (str(item) + ' = ' + (ObjToString(obj.__dict__[item], extra + ' ') if hasattr(obj.__dict__[item], '__dict__') else str(obj.__dict__[item]))) for item in sorted(obj.__dict__))) + + +# ------------------------------------------------------------------------------------------------------------------ # +# ===================================== Upper PySimpleGUI ======================================================== # +# Pre-built dialog boxes for all your needs These are the "high level API calls # +# ------------------------------------------------------------------------------------------------------------------ # + +# ----------------------------------- The mighty Popup! ------------------------------------------------------------ # + + +def Popup( + *args, + title=None, + button_color=None, + background_color=None, + text_color=None, + button_type=POPUP_BUTTONS_OK, + auto_close=False, + auto_close_duration=None, + custom_text=(None, None), + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + any_key_closes=False, + image=None, +): + """ + Popup - Display a popup box with as many parms as you wish to include + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param title: Optional title for the window. If none provided, the first arg will be used instead. + :type title: (str) + :param button_color: Color of the buttons shown (text color, button color) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param button_type: NOT USER SET! Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). There are many Popup functions and they call Popup, changing this parameter to get the desired effect. + :type button_type: (int) + :param auto_close: If True the window will automatically close + :type auto_close: (bool) + :param auto_close_duration: time in seconds to keep window open before closing it automatically + :type auto_close_duration: (int) + :param custom_text: A string or pair of strings that contain the text to display on the buttons + :type custom_text: Union[Tuple[str, str], str] + :param non_blocking: If True then will immediately return from the function without waiting for the user's input. + :type non_blocking: (bool) + :param icon: icon to display on the window. Same format as a Window call + :type icon: Union[str, bytes] + :param line_width: Width of lines in characters. Defaults to MESSAGE_BOX_LINE_WIDTH + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location on screen to display the top left corner of window. Defaults to window centered on screen + :type location: Tuple[int, int] + :param any_key_closes: If True then will turn on return_keyboard_events for the window which will cause window to close as soon as any key is pressed. Normally the return key only will close the window. Default is false. + :type any_key_closes: (bool) + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + :return: Returns text of the button that was pressed. None will be returned if user closed window with X + :rtype: Union[str, None] + """ + + if not args: + args_to_print = [''] + else: + args_to_print = args + if line_width != None: + local_line_width = line_width + else: + local_line_width = MESSAGE_BOX_LINE_WIDTH + + _title = title if title is not None else args_to_print[0] + _title = str(_title) + window = Window( + _title, + auto_size_text=True, + background_color=background_color, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + icon=icon, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + return_keyboard_events=any_key_closes, + ) + max_line_total, total_lines = 0, 0 + layout = [[]] + if image is not None: + if isinstance(image, str): + layout += [[Image(filename=image)]] + else: + layout += [[Image(data_base64=image)]] + + for message in args_to_print: + # fancy code to check if string and convert if not is not need. Just always convert to string :-) + # if not isinstance(message, str): message = str(message) + message = str(message) + if message.count('\n'): # if there are line breaks, then wrap each segment separately + # message_wrapped = message # used to just do this, but now breaking into smaller pieces + message_wrapped = '' + msg_list = message.split('\n') # break into segments that will each be wrapped + message_wrapped = '\n'.join([textwrap.fill(msg, local_line_width) for msg in msg_list]) + else: + message_wrapped = textwrap.fill(message, local_line_width) + message_wrapped_lines = message_wrapped.count('\n') + 1 + longest_line_len = max([len(l) for l in message.split('\n')]) + width_used = min(longest_line_len, local_line_width) + max_line_total = max(max_line_total, width_used) + # height = _GetNumLinesNeeded(message, width_used) + height = message_wrapped_lines + window.AddRow(Text(message_wrapped, auto_size_text=True, text_color=text_color, background_color=background_color)) + total_lines += height + + # if total_lines < 3: + # layout.append([Text('',text_color=text_color, background_color=background_color)]) + if non_blocking: + PopupButton = DummyButton # important to use or else button will close other windows too! + else: + PopupButton = Button + # show either an OK or Yes/No depending on paramater + # show either an OK or Yes/No depending on paramater + if custom_text != (None, None): + if type(custom_text) is not tuple: + layout.append([PopupButton(custom_text, button_color=button_color, focus=True, bind_return_key=True)]) + elif custom_text[1] is None: + layout.append([PopupButton(custom_text[0], button_color=button_color, focus=True, bind_return_key=True)]) + else: + layout.append( + [ + PopupButton(custom_text[0], button_color=button_color, focus=True, bind_return_key=True), + PopupButton(custom_text[1], button_color=button_color), + Stretch(), + ] + ) + elif button_type is POPUP_BUTTONS_YES_NO: + layout.append( + [ + PopupButton('Yes', button_color=button_color, focus=True, bind_return_key=True, size=(60, 20)), + PopupButton('No', button_color=button_color, size=(60, 20)), + ] + ) + elif button_type is POPUP_BUTTONS_CANCELLED: + layout.append([PopupButton('Cancelled', button_color=button_color, focus=True, bind_return_key=True), Stretch()]) + elif button_type is POPUP_BUTTONS_ERROR: + layout.append( + [ + PopupButton('Error', size=(60, 20), button_color=button_color, focus=True, bind_return_key=True), + Stretch(), + ] + ) + elif button_type is POPUP_BUTTONS_OK_CANCEL: + layout.append( + [ + PopupButton('OK', size=(60, 20), button_color=button_color, focus=True, bind_return_key=True), + PopupButton('Cancel', size=(60, 20), button_color=button_color), + Stretch(), + ] + ) + elif button_type is POPUP_BUTTONS_NO_BUTTONS: + pass + else: + layout.append([PopupButton('OK', size=(60, 20), button_color=button_color, focus=True, bind_return_key=True), Stretch()]) + + window.Layout(layout) + if non_blocking: + button, values = window.Read(timeout=0) + Window.active_popups[window] = title + else: + button, values = window.Read() + window.close() + + return button + + +# ============================== MsgBox============# +# Lazy function. Same as calling Popup with parms # +# This function WILL Disappear perhaps today # +# ==================================================# +# MsgBox is the legacy call and should not be used any longer +def MsgBox(*args): + raise DeprecationWarning('MsgBox is no longer supported... change your call to Popup') + + +# --------------------------- PopupNoButtons --------------------------- +def PopupNoButtons( + *args, + title=None, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Show a Popup but without any buttons + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + """ + Popup( + *args, + title=title, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=POPUP_BUTTONS_NO_BUTTONS, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +# --------------------------- PopupNonBlocking --------------------------- +def PopupNonBlocking( + *args, + title=None, + button_type=POPUP_BUTTONS_OK, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=True, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Show Popup box and immediately return (does not block) + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param title: Title to display in the window. + :type title: (str) + :param button_type: + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + """ + Popup( + *args, + title=title, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +PopupNoWait = PopupNonBlocking + + +# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup --------------------------- +def PopupQuick( + *args, + title=None, + button_type=POPUP_BUTTONS_OK, + button_color=None, + background_color=None, + text_color=None, + auto_close=True, + auto_close_duration=2, + non_blocking=True, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Show Popup box that doesn't block and closes itself + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param title: Title to display in the window. + :type title: (str) + :param button_type: Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). + :type button_type: (int) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + """ + Popup( + *args, + title=title, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup with no titlebar and no buttons --------------------------- +def PopupQuickMessage( + *args, + title=None, + button_type=POPUP_BUTTONS_NO_BUTTONS, + button_color=None, + background_color=None, + text_color=None, + auto_close=True, + auto_close_duration=3, + non_blocking=True, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=True, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Show Popup box that doesn't block and closes itself + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param title: Title to display in the window. + :type title: (str) + :param button_type: Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). + :type button_type: (int) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + """ + Popup( + *args, + title=title, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +# --------------------------- PopupNoTitlebar --------------------------- +def PopupNoTitlebar( + *args, + title=None, + button_type=POPUP_BUTTONS_OK, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + grab_anywhere=True, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Display a Popup without a titlebar. Enables grab anywhere so you can move it + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param title: Title to display in the window. + :type title: (str) + :param button_type: Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). + :type button_type: (int) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + """ + Popup( + *args, + title=title, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=True, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +PopupNoFrame = PopupNoTitlebar +PopupNoBorder = PopupNoTitlebar +PopupAnnoying = PopupNoTitlebar + + +# --------------------------- PopupAutoClose --------------------------- +def PopupAutoClose( + *args, + title=None, + button_type=POPUP_BUTTONS_OK, + button_color=None, + background_color=None, + text_color=None, + auto_close=True, + auto_close_duration=DEFAULT_AUTOCLOSE_TIME, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Popup that closes itself after some time period + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param title: Title to display in the window. + :type title: (str) + :param button_type: Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). + :type button_type: (int) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + """ + Popup( + *args, + title=title, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +PopupTimed = PopupAutoClose + + +# --------------------------- PopupError --------------------------- +def PopupError( + *args, + title=None, + button_color=(None, None), + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Popup with colored button and 'Error' as button text + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param title: Title to display in the window. + :type title: (str) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + """ + tbutton_color = DEFAULT_ERROR_BUTTON_COLOR if button_color == (None, None) else button_color + Popup( + *args, + title=title, + button_type=POPUP_BUTTONS_ERROR, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=tbutton_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +# --------------------------- PopupCancel --------------------------- +def PopupCancel( + *args, + title=None, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Display Popup with "cancelled" button text + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param title: Title to display in the window. + :type title: (str) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + """ + Popup( + *args, + title=title, + button_type=POPUP_BUTTONS_CANCELLED, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +# --------------------------- PopupOK --------------------------- +def PopupOK( + *args, + title=None, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Display Popup with OK button only + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + """ + Popup( + *args, + title=title, + button_type=POPUP_BUTTONS_OK, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +# --------------------------- PopupOKCancel --------------------------- +def PopupOKCancel( + *args, + title=None, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Display popup with OK and Cancel buttons + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + :return: OK, Cancel or None + :rtype: Union[str, None] + """ + return Popup( + *args, + title=title, + button_type=POPUP_BUTTONS_OK_CANCEL, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +# --------------------------- PopupYesNo --------------------------- +def PopupYesNo( + *args, + title=None, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Display Popup with Yes and No buttons + + :param *args: Variable number of your arguments. Load up the call with stuff to see! + :type *args: (Any) + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param auto_close: if True window will close itself + :type auto_close: (bool) + :param auto_close_duration: Older versions only accept int. Time in seconds until window will close + :type auto_close_duration: Union[int, float] + :param non_blocking: if True the call will immediately return rather than waiting on user input + :type non_blocking: (bool) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param line_width: Width of lines in characters + :type line_width: (int) + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + :return: Yes, No or None + :rtype: Union[str, None] + """ + return Popup( + *args, + title=title, + button_type=POPUP_BUTTONS_YES_NO, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + image=image, + ) + + +############################################################################## +# The PopupGet_____ functions - Will return user input # +############################################################################## + +# --------------------------- PopupGetFolder --------------------------- + + +def PopupGetFolder( + message, + title=None, + default_path='', + no_window=False, + size=(None, None), + button_color=None, + background_color=None, + text_color=None, + icon=DEFAULT_WINDOW_ICON, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + initial_folder=None, + image=None, +): + """ + Display popup with text entry field and browse button. Browse for folder + :param message: message displayed to user + :type message: (str) + :param title: Window title + :type title: (str) + :param default_path: path to display to user as starting point (filled into the input field) + :type default_path: (str) + :param no_window: if True, no PySimpleGUI window will be shown. Instead just the tkinter dialog is shown + :type no_window: (bool) + :param size: (width, height) of the InputText Element + :type size: Tuple[int, int] + :param button_color: button color (foreground, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param initial_folder: location in filesystem to begin browsing + :type initial_folder: (str) + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + :return: Contents of text field. None if closed using X or cancelled + :rtype: Union[str, None] + """ + + if no_window: + if Window.QTApplication is None: + Window.QTApplication = QApplication(sys.argv) + + folder_name = QFileDialog.getExistingDirectory(dir=initial_folder) + return folder_name + + if image is not None: + if isinstance(image, str): + layout = [[Image(filename=image)]] + else: + layout = [[Image(data_base64=image)]] + else: + layout = [[]] + layout += [ + [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], + [InputText(default_text=default_path, size=size, key='_INPUT_'), FolderBrowse(initial_folder=initial_folder)], + [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], + ] + + _title = title if title is not None else message + window = Window( + title=_title, + layout=layout, + icon=icon, + auto_size_text=True, + button_color=button_color, + background_color=background_color, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + button, values = window.Read() + window.close() + if button != 'Ok': + return None + else: + path = values['_INPUT_'] + return path + + +# --------------------------- PopupGetFile --------------------------- + + +def PopupGetFile( + message, + title=None, + default_path='', + default_extension='', + save_as=False, + file_types=(('ALL Files', '*'),), + no_window=False, + size=(None, None), + button_color=None, + background_color=None, + text_color=None, + icon=DEFAULT_WINDOW_ICON, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + initial_folder=None, + image=None, +): + """ + Display popup with text entry field and browse button. Browse for file + + :param message: message displayed to user + :type message: (str) + :param title: Window title + :type title: (str) + :param default_path: path to display to user as starting point (filled into the input field) + :type default_path: (str) + :param default_extension: If no extension entered by user, add this to filename (only used in saveas dialogs) + :type default_extension: (str) + :param save_as: if True, the "save as" dialog is shown which will verify before overwriting + :type save_as: (bool) + :param multiple_files: if True, then allows multiple files to be selected that are returned with ';' between each filename + :type multiple_files: (bool) + :param file_types: List of extensions to show using wildcards. All files (the default) = (("ALL Files", "*.*"),) + :type file_types: Tuple[Tuple[str,str]] + :param no_window: if True, no PySimpleGUI window will be shown. Instead just the tkinter dialog is shown + :type no_window: (bool) + :param size: (width, height) of the InputText Element + :type size: Tuple[int, int] + :param button_color: Color of the button (text, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: Location of upper left corner of the window + :type location: Tuple[int, int] + :param initial_folder: location in filesystem to begin browsing + :type initial_folder: (str) + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + :return: string representing the path chosen, None if cancelled or window closed with X + :rtype: Union[str, None] + """ + + if no_window: + if Window.QTApplication is None: + Window.QTApplication = QApplication(sys.argv) + + if save_as: + qt_types = convert_tkinter_filetypes_to_qt(file_types) + filename = QFileDialog.getSaveFileName(dir=initial_folder, filter=qt_types) + else: + qt_types = convert_tkinter_filetypes_to_qt(file_types) + filename = QFileDialog.getOpenFileName(dir=initial_folder, filter=qt_types) + return filename[0] + + browse_button = SaveAs(file_types=file_types, initial_folder=initial_folder) if save_as else FileBrowse(file_types=file_types, initial_folder=initial_folder) + + if image is not None: + if isinstance(image, str): + layout = [[Image(filename=image)]] + else: + layout = [[Image(data_base64=image)]] + else: + layout = [[]] + + layout += [ + [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], + [InputText(default_text=default_path, size=(30, 1), key='_INPUT_'), browse_button], + [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], + ] + + _title = title if title is not None else message + + window = Window( + title=_title, + layout=layout, + icon=icon, + auto_size_text=True, + button_color=button_color, + font=font, + background_color=background_color, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + button, values = window.Read() + window.close() + if button != 'Ok': + return None + else: + path = values['_INPUT_'] + return path + + +# --------------------------- PopupGetText --------------------------- + + +def PopupGetText( + message, + title=None, + default_text='', + password_char='', + size=(None, None), + button_color=None, + background_color=None, + text_color=None, + icon=DEFAULT_WINDOW_ICON, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + image=None, +): + """ + Display Popup with text entry field + :param message: message displayed to user + :type message: (str) + :param title: Window title + :type title: (str) + :param default_text: default value to put into input area + :type default_text: (str) + :param password_char: character to be shown instead of actually typed characters + :type password_char: (str) + :param size: (width, height) of the InputText Element + :type size: Tuple[int, int] + :param button_color: Color of the button (text, background) + :type button_color: Tuple[str, str] + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param icon: filename or base64 string to be used for the window's icon + :type icon: Union[bytes, str] + :param font: specifies the font family, size, etc + :type font: Union[str, Tuple[str, int]] + :param no_titlebar: If True no titlebar will be shown + :type no_titlebar: (bool) + :param grab_anywhere: If True, than can grab anywhere to move the window (Default = False) + :type grab_anywhere: (bool) + :param keep_on_top: If True the window will remain above all current windows + :type keep_on_top: (bool) + :param location: (x,y) Location on screen to display the upper left corner of window + :type location: Tuple[int, int] + :param image: Image to include at the top of the popup window + :type image: (str) or (bytes) + :return: Text entered or None if window was closed + :rtype: Union[str, None] + """ + + if image is not None: + if isinstance(image, str): + layout = [[Image(filename=image)]] + else: + layout = [[Image(data_base64=image)]] + else: + layout = [[]] + + layout += [ + [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color, font=font)], + [InputText(default_text=default_text, size=size, password_char=password_char, key='_INPUT_')], + [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], + ] + + _title = title if title is not None else message + + window = Window( + title=_title, + layout=layout, + icon=icon, + auto_size_text=True, + button_color=button_color, + no_titlebar=no_titlebar, + background_color=background_color, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + button, values = window.Read() + window.close() + + if button != 'Ok': + return None + else: + return values['_INPUT_'] + + +# --------------------------------------- a few icons in base64 --------------------------------------- + + +ICON_BASE64_BLOB_PALM = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNjo1ODowNy0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTY6NTg6MDctMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE2OjU4OjA3LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDo4NWFmYzM1My1hMzMyLWFlNGQtOGE0ZC1lYTExOTk1MDU5M2E8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDpiMWM2N2U0Zi1iY2E2LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDphNzVkMDNmMS1lNjE3LWM0NDktYjYxNy0zZjc4YWFjMDBjNTQ8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6YTc1ZDAzZjEtZTYxNy1jNDQ5LWI2MTctM2Y3OGFhYzAwYzU0PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE2OjU4OjA3LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjg1YWZjMzUzLWEzMzItYWU0ZC04YTRkLWVhMTE5OTUwNTkzYTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNjo1ODowNy0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjc4PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjcxPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz62g3pFAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAABdqSURBVHja7Jz3c1xXlt+/5977cucANCKRmINAShoqzYxGOzsu79Ta/g/8L/kvsctll721nl1b3t1ZaShRFClRgkiQIAgih250eOkG/9ANEgRBECABSa7Sq+p6XQQL7+LzTj73XDLG4Jfr6Jf4uSxk+tp1Osr/v3Xz85/0jdNPJXF7QNFL7vtdZr/7jw3yRwe3CxgBYLvuvHff+U57AJreRwHQvc/Od7Pr/qNA/FHA7QOLARCcGcvi2rGFcYUwnmtpx3OUF7jacS3NLaE5Z4YBQCKZjlOWhDFL2xEP45RFiaQokRSnkkVKU6INyd0wTxLgiYPrQdsBZtlC24Gr/Jwvc/lAFfK+rOZ9Wcr6shy4qpBxVTbwlO9Yxu6BEzCQiWRIUpaECQvbIe+0It5sRXy9GfKNRlusNjpic7stGs1QtDoJ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' + +ICON_BASE64_BLOB_PAT = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNjo1OTowNi0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTY6NTk6MDYtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE2OjU5OjA2LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDpiMWNmNDU1YS04Yjk4LTFmNDYtYmJlNi1iNmY1ZGMxMGYwYWE8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkM2FmN2UyNi1iY2E2LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDo0YjE4ZTE1Mi0yNWMzLWY3NDAtOWU4Yi0zYmRjNjFjMjI5Njk8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6NGIxOGUxNTItMjVjMy1mNzQwLTllOGItM2JkYzYxYzIyOTY5PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE2OjU5OjA2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmIxY2Y0NTVhLThiOTgtMWY0Ni1iYmU2LWI2ZjVkYzEwZjBhYTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNjo1OTowNi0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjgxPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjc1PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz6vCsFxAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAABGmSURBVHja7Jx5cJzlfce/z/Meex+SdqWVVpdZWbIlY1lWjIhx42ZIYopT7jFgDCZOAy2QozChSWaShjQpAylkmhCaGHAHN5hjCuWoaaEl0A50SsHGMpaNbQlfyJZk3Vrtvsdz9I93dfpaW7vGJnpmdnTs7rvv+3l/9+/3LJFSYnbNbNFZBLMQZyHOQpxdsxBnIc5CnIU4u46z1PP55Fdf0eiPhXlzUUA0+D2yRFWEX0rCLIbRlEF6+5NKW++w0rbxxdbefJ4HOV8zlrvXNly6amnyPwu8Al5dQFMASiQkCIQAmABMTjGcIjjUp7a2HnQ9+qNf71o/CzGz/uKGhsbLGlNPLptvNuqKhEIlKJl4XgKQEhCSwOZAyqToH6U41Kft3nHItWHHQdf6jf+ybfAPWp3nRO2VF1ZajUG3cCSBnOiV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' + +ICON_BASE64_BLOB_THINK = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNzowNjozMi0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTc6MDY6MzItMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE3OjA2OjMyLTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDpjZGY4YTM3NS05ODVkLTdhNGQtOTU5OS1iOGJmYjZiZGMwYmI8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkZWE3YjA5Yi1iY2E3LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDo5YjY3OTMyNi04MjExLTZjNDgtYjk0NS00NzY2MzI1NDU3M2Y8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6OWI2NzkzMjYtODIxMS02YzQ4LWI5NDUtNDc2NjMyNTQ1NzNmPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE3OjA2OjMyLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmNkZjhhMzc1LTk4NWQtN2E0ZC05NTk5LWI4YmZiNmJkYzBiYjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNzowNjozMi0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjU0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUyPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7BeLFDAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAArWSURBVHja7FppcFRVFv7ue6/3nXQ20kmAkJgQkjSGpKEhQiEIgkZQQBjEQXRwqUHU2YxDUaWjjFVayhQz4zbMoOWCzjC4MCAG2Q0DhBBMgjEGQkJIyEaW3vu9d+/8CGBQknSHBEeLW9U/Xte7957vnnO++91zH2GM4afYOPxE208WmDAUg86eke0cGS2lJ0RK6ZEmOcGip7E6NTMrBaYCgKBEAh4/6Wh3c40tnXxdXYtQUdMkVGwrPFo0WDaQwcqxSc6cJPuowIwpYwOLUm1Bp8XAFAaNDJXAIAgEHLl8HsoIJIkhIBG4fDzaXUSsrFcW7S1XbSo9pSr8oujIyR8U2I3ZudHjkoK3LJvuWpuRGLRp1Qz8AANcpoDXT1BWq6zfuNPw1LGTys9Kjh5uuubApuaNT192s3vtgjxPvlo5uOzq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' + +ICON_BASE64_BLOB_THINK2 = b'iVBORw0KGgoAAAANSUhEUgAAADoAAAA3CAYAAABdJVn2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNzowOTozMy0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTc6MDk6MzMtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE3OjA5OjMzLTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDpiYzcxZTAzMC1hY2UwLWFlNGMtYTI2YS1hNGJlM2E4NDNlMDA8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDo0OWE4ZGNmZC1iY2E4LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDpjMWE2NTcxYS1lNGJlLTczNDMtYjAzNy1lYmE3NmRjMTA3YWI8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6YzFhNjU3MWEtZTRiZS03MzQzLWIwMzctZWJhNzZkYzEwN2FiPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE3OjA5OjMzLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmJjNzFlMDMwLWFjZTAtYWU0Yy1hMjZhLWE0YmUzYTg0M2UwMDwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNzowOTozMy0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjU4PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjU1PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz5uOveQAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAqxSURBVHja7Fp5VJTXFf+9b5kFBoYdDciwiMhe0xhFwYQYTdUTl7hErVWJWYptNGoalwQ92mOiydGqaRajiWarRk1jbIItjUHEiNYYRKKgMiMDiCDrMGyzfN/rH+PgAMMyAybmxHvOd2DuvPvN7/fu/d69776PUErxaxAGvxK5R/Qe0XtE7xG9K4S7Uzde9WxsRKi/OSJsgGmom5y6dTe2vJYtvVHHlmpvcppNu/Iv3wk8pD/z6MqnY8Of+V3jclepqFDIqLuLTJzsiH1zK3OkyUAa8zSSs99ekH29eXf+1buKaMqM3yiWTdWtj1aZhgFI7idsmZdK+NytXyjX7Tl0vvFnJ7r/tYj5MxObFvYjwU6ED5503Tt79eWPfhaiqb+P90md0LA6MsgUzxCMvb28SQH3kSCyIEDiDar7HtD/DxCN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' + +ICON_BASE64_BROW = b'iVBORw0KGgoAAAANSUhEUgAAADUAAAA1CAYAAADh5qNwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNjo1MzowNC0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTY6NTM6MDQtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE2OjUzOjA0LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDo5ZGMyYTFmMC02ZWJhLWQ3NDYtOGIzMC1jYzYwMWM0ZGIzMzE8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDplNjE1MzI2Zi1iY2E1LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDpiMzM5ZDA0Mi1mNGFkLTQ5NDUtOTBiZS1hZTg0ZTE2ZGNiZDI8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6YjMzOWQwNDItZjRhZC00OTQ1LTkwYmUtYWU4NGUxNmRjYmQyPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE2OjUzOjA0LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjlkYzJhMWYwLTZlYmEtZDc0Ni04YjMwLWNjNjAxYzRkYjMzMTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNjo1MzowNC0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjUzPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUzPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz6xTRINAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAeISURBVHja3Jp7UFTXHce/d3fZZR8sIPJQeSwghFeAsTVBIriWphkMGB9kGmOYaXWa1mQm0VJjCGGmDzdoUtT+0w7Q0GbUkNYHhBUz49iGRwKkolVQoKTA7gIqILIsu3ff9/YP0x3JPi+5IPj7a/fc8zt7Pvs753d/v3N+BE3TeNyEg8dQHksoHtsDHnw1NzEznp8pW8WVRYfyogJEhFTsT4gAwGCiyVmS1mkmbSOqO3ZVn9rae/iPLTfZngPBxp46UiLPKtggLEyV8VIIguAA2OqjaiNN09StYWvvhU6T8u3K5s5HDvXRe3k7ijaJdor9CQkDELeARjNFnm8z1e8+dPnviw71wVubN762VbJPLGQFxgmONNFkdZOhav97/2xeFKj++vyjSdF+SQsA4wTXr7b2J+347NCCQe3fkytT7A1U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' + +ICON_BASE64_BLOB_HEADACHE = b'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/GMzLdsAADpCaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNjo1NToyNi0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTY6NTU6MjYtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE2OjU1OjI2LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDoxYzUxOTkzZi05MjNiLTM2NGItYWY5ZS1mNGE3MzViYjgxYTI8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1MDgwYWJhOC1iY2E2LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDo3YWMwMzdjMC03ZmMzLWNhNDgtYmVlYi1lM2Q0ZDJjODJiNTU8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6N2FjMDM3YzAtN2ZjMy1jYTQ4LWJlZWItZTNkNGQyYzgyYjU1PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE2OjU1OjI2LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjFjNTE5OTNmLTkyM2ItMzY0Yi1hZjllLWY0YTczNWJiODFhMjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNjo1NToyNi0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjcxPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjcxPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz5o6m+QAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAABbGSURBVHja7HxZcGTXed73n3Puvb13oxtrYx1gMJjBLMTOWUiKiiRGcuxS2Y7K5bhsObEdOXmIk5TzkJQqSfkpecxTqpyUK3mwrUiqKHbJkkxRIk1KwyFnBrNzFgwGGKyDrbH0fu9Z8nDRBIboBockhpJcc6pQ/dIXOOe7//J93zkHZIzBs1F9sGcQPAPnGTjPwHkGzs/HED/rCXzmxRFyLEBwQ4IbWBzEyIARiAjQxv9RiozSgNRkPAnjSYKnCD89f/GptVv6NFv50PAYBWzDGuNKNCRUMBnTibqwakpETFs4oJtDjmkJOSZhMRMRDEFi4FoDnoJyPSoWPcoVXbaWLdJytsDmt4psPpNlc2ubbHNti7sbeabGL79rfmHAGR4Zo5BjWGeTDHQ2ynRbvRxJRfVnE2E1Gg2ZdNjRkYBtLEsY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' + +ICON_BASE64_LEGO_THINK = b'iVBORw0KGgoAAAANSUhEUgAAAGcAAABwCAYAAAADkm7aAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAADpEaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAyMC0wNy0wMlQxNzoxNTozNC0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMjAtMDctMDJUMTc6MTU6MzQtMDQ6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDIwLTA3LTAyVDE3OjE1OjM0LTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDpkYzQ2ZDAwNS1hY2U0LTkwNDUtODUzMC1iY2Y4Zjc3Y2U2NjA8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDoyM2FiN2M2NS1iY2E5LTExZWEtYTEwYi1iMTlmYTMxNTc1YWQ8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDowNDRhZDA4Zi03ZmQ0LWMwNGItYmY1Yy01MTI4NzQzMjcyNTA8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6MDQ0YWQwOGYtN2ZkNC1jMDRiLWJmNWMtNTEyODc0MzI3MjUwPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE3OjE1OjM0LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmRjNDZkMDA1LWFjZTQtOTA0NS04NTMwLWJjZjhmNzdjZTY2MDwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNzoxNTozNC0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjEwMzwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4xMTI8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PnSBBO0AAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAADbNJREFUeNrsnXuMXFUdxz/nznNn3y92u215s9tSrEXEF1BwqdgVJchDJSKaYKJEjZoYgxhEJSYSefhIJBgSjDS8o/KyJkClLSpt0ZRKlb62tLYWumz3PTu7M/ce//idW/Yuu8PM7szuLT2/ZHP23jn3zp3z+33P73nOVVprLIWTHDsEljmWLHMscyxZ5liyzAkxRcP2QD/65kXOLb943tv6aJcCVG1NBIBkTAEQi4jpH3Pk2FFy7Jjj6cjzNID2tPTLyjFZV44zWTkeGHIV4Dy+cdQF1C2/eN6br7FQYfZztj7adXVtTSQJuMmYcgE3FtHSOsoDco7SHuA5jsqZy1zAm/Tnep7WgOtppQGd9XTOMMcFdCYr9xkYcvWKq9buC8PvDy1z9EufigA7gYVmgPWEwc6BmnisDVP0pL7+OT2B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' + +ICON_BASE64_THINK = b'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAD+paVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMjAtMDctMDJUMTc6MDI6MDUtMDQ6MDA8L3htcDpDcmVhdGVEYXRlPgogICAgICAgICA8eG1wOk1ldGFkYXRhRGF0ZT4yMDIwLTA3LTAyVDE3OjAyOjUxLTA0OjAwPC94bXA6TWV0YWRhdGFEYXRlPgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAyMC0wNy0wMlQxNzowMjo1MS0wNDowMDwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6NGZjMmNkMTQtYjBhZi05ODQ0LTkyNTAtZTk1NTRhYjMzYzJmPC94bXBNTTpJbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwaG90b3Nob3A6NTU3OWQyOWUtYmNhNy0xMWVhLWExMGItYjE5ZmEzMTU3NWFkPC94bXBNTTpEb2N1bWVudElEPgogICAgICAgICA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6OWUzNGE0MWQtOTM2YS1jZTRhLTgzNTMtZWVhYWEyYWQ2NjU1PC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y3JlYXRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjllMzRhNDFkLTkzNmEtY2U0YS04MzUzLWVlYWFhMmFkNjY1NTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0wNy0wMlQxNzowMjowNS0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDoxMGZiMmQwMi04NmUzLTAyNDMtYTc0Ny1hOGVkODM3NDU3NDk8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMjAtMDctMDJUMTc6MDI6NTEtMDQ6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmNvbnZlcnRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6cGFyYW1ldGVycz5mcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvcG5nPC9zdEV2dDpwYXJhbWV0ZXJzPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+ZGVyaXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6cGFyYW1ldGVycz5jb252ZXJ0ZWQgZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZzwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6NGZjMmNkMTQtYjBhZi05ODQ0LTkyNTAtZTk1NTRhYjMzYzJmPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDIwLTA3LTAyVDE3OjAyOjUxLTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICA8L3JkZjpTZXE+CiAgICAgICAgIDwveG1wTU06SGlzdG9yeT4KICAgICAgICAgPHhtcE1NOkRlcml2ZWRGcm9tIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgPHN0UmVmOmluc3RhbmNlSUQ+eG1wLmlpZDoxMGZiMmQwMi04NmUzLTAyNDMtYTc0Ny1hOGVkODM3NDU3NDk8L3N0UmVmOmluc3RhbmNlSUQ+CiAgICAgICAgICAgIDxzdFJlZjpkb2N1bWVudElEPnhtcC5kaWQ6OWUzNGE0MWQtOTM2YS1jZTRhLTgzNTMtZWVhYWEyYWQ2NjU1PC9zdFJlZjpkb2N1bWVudElEPgogICAgICAgICAgICA8c3RSZWY6b3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6OWUzNGE0MWQtOTM2YS1jZTRhLTgzNTMtZWVhYWEyYWQ2NjU1PC9zdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7Z0mgMAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAzySURBVHjatFp7UFzndf+d7959wPLcFQKEQBiEDHoDltB75Ei25WTsNIkdeayJXTudJp04mbT+w/0jM53OONN4kr6cNJ5p6zgPN9NWSWvFiS25lZrI0QPJIPQASQgJEAhYYGHZ991773f6x+6iZbUvUPTNLMtw7/3u+Z3X75zzQcyMB7k2t3UQAPR0d3K264mV6b5c76DFAskm2KFndtHmRktp6xpreWONxVJRQvYCKxUBQDjKkSkfa7fG9FD39Wjg4k09NO4x9UBImpqO5L14KYAWBWRzWwelvuArX9ojtjRbLDvW2ZyNNepWq0qPwZT1AAoBKiRCAQAwQwKsAQhDEZ6owaM37xiXTvdqF89f04euDkXn/CGWSeLwYsDkDSQZRGtbB7W3WGyf211Y2dZkXVtZLrYJxg4ADwEoAWABoAIQAChJ0xKACUAHEAIwy4QR96x5umtA/+17J0PXPrmqz6VIlBegnEBSXWnnjm3iTz9bXPP0TtuBEps4oAg0A6gAUBAHoMSFTwBIBpL4ToAyAIQBzJgS/T5NfvSrU9rRfz7ivx2McHQxrpYVSCqIz31mh/LNgyXrd6+1viQIjwKoYuYiU7LVlCzATCQIiiBShMj2Xk6xUsJCE5Lx+4+vRn/xD//u6xwaNwMcU0ROMDmBJB5+4dldlm9+sWRra4PyCoE6gmF92fhUwD41E1I8s2EKhKLEzLBZVZSV2OAqK0TV8iJUlBdAEOUClQCkAZhhcE/PLePdvz/s/+jSgO7LxzJ5xciXn99te+1Qyd6mKuXPAGwZHPWWf3BiwDox6RPeuTBFdYnkbRQFcBTasHxZEdo3VGPfzodAucMw2To+BvoG3PLtN971vt91TffFt6ee7k6Z7mGRK7d/8emdyqsHi7c3VSrfIGAbiJzdl8dtl69OCPd0kIgEVte7sLujDvt3NWB720pULy9FMKTj5pAHHxzvh5SMPPRFcXmsAMoIWLd6uXj5Lw6WrG1aqaKnu5N7ujtlKu8klporxb7wpGNFc43yJSJsAlAKKa2P7WmgUNighroytG6oRmGBBWCO6ZQIIMKUJ4AzXXdQ4SyAUAiUX3JMJAkVQCkR1jXXKAdf/LTjKgBvNhrI6lqvv/po2avPFn29wEIvAlgBwBZ/hqAIQMaETxcCzAAEAcyZ3YoSPxi4VwwJIApgPKTjzb877H/rW3/7f9qigRzYv8P69l86D60oF98goDFGcAt4YX5FdBOX+twQCmFtUwUKrAooQ4AzEeb8EYxP+jEzG0ZEk1BVQmmxLZYcnA4IAuhuzEQYGBibla98+Tszp47+72lOB0bNpKyvfaGoubqM/oiAGgD2bCC+/845jI55ARA2tlTi5efakC4oZuYi+PmRXgyPzkLXTUgp4zFOICIoikCFy4HdHavQvrGaCiyKAGAjoKa6jF782ueLLgOYS5e90gb7wc/uVPe32x4VRA8DcCSR3D2ucfqTEdwa9iCiGYhoOs52j6LvxhTusTQBPX1uXBtwIxjUYBgmolEDWtSEFjWgRQ1EdQPDIzP46eEL+Jd3u6CbMpEAHIJo+2OP2NdkUnxaIE9uL3BZQTsBOONZJK2fMANDI16wvCu0EMCFy+OxX1KS66aW5djRXodP7WrApvXVUNS799htKvbvacKW1pWorCiC1xeBjO1L8YqhwkK8/sff2a/klbUO7NtO3/5K2SahoDFedohMQFJpOtdylRXguafWQdNNHP7wKnRDzm8cCuuory3FgT0NGHX7IQBYYkATVikUgtZuaLDY4lVAdousqrGoNS7RTjFrWLKBIALqa8sgxN1bTAm0bqwGpEzvAoJgmhKRsLFAA8yAL6DBZhFYXVuGhtqy5IqAAFgJaKlxiaq8XKvWJYpdxaIlKTaymmLHI7VoXOWC3abCbrNge1st1q5eljFrAYDVqqCs1AYSydUAodLlyPY2AaDZVSwef/3P99pSyfse16pdrrpUlarA87GR1a3sFgWvvLQVl666IUQs/UJmIJcEEFXBhocrcfmaGxPuABSFsKGlEjWVxZkUkJDDpaq0tXa5+ksAU8nZawGQXTu30bdeKKmiWE+h5AKRDGbrxhWL6jSb6svx3NMb0DcwhUK7Ba3rquCwq7lY30JASaVTWHft3Ea/P3U2PY+oKpGzWKwEzwf5A1uqIrC2cRmaVpWDiKAqApRbbQIMu7NYqKpKmWNEERCFNnKC5zMVPUgwRIDVosCi5gWC4knBUVAg7L/93RlOjhGRcicJhVSmB2uN+0MPoQhY9j+6nTJaRAKsG6xRrMbJTRFZCk7OfnkpK8GOUtcZMlv6NU02AyE5HO+jZS4QZjgENs0FEjMBLAhSMkwpY4CE+EMBkwBCgaCMmiZnZvbfnTzLR37w+HUmuInRnG1HbeA69PE7IKsN9uZ1UMrKwQDOdI3iyLHrCIU1SMmwWlVUOIvQsmYZNq2rwnJnIQrtaq72NyMQJnim/BwyjBwlypjHnGBGHwHb46U73xP0RJChIEgIwNAR6bsM+8ZWiKJiDI144QuEoQgBoRCiUQPDd2YwODqD/zl5Ew83uLDtkTqsXe1CcaF1MW4lAUSZMTPuMTU9F5DRSTMS1rjHYSN/fEYl0rmVUlYO6ZuLpR5pQr8zAtuaFuzb9RDqakpgtagQgmAYJmZ8GoZGvOi/OY3e/kkMjsyidcMKPLV/DcqLbVmrgBQw4bDGd0YnzYiezbUA4PaEzlNec8BRqY4BqErbszBDlJQuMJUM+AFNQ6XLMd8cJfKBKSWiugmPT8PR4zdw/uIoznSNoK6mFHu31uULwgAwN+U1+29P6JFEvCWY/R5t/+yXp3hkWg4z0BufaMj0HJDyqGlCapFYBqGFXKEqAoV2C1ZWOPAnh9rwxwfbUFtdihWVxYtxqwgDV0amZe/QhGmmNlZpa4LTvdHZbS3WixaBpzJ1hzIUWPgHZrBp5CBAAiRj++YV6Ni8AoIoX8Y1AXh1k//zdG908ONTZ2VejdVrb5zQPQF5jhmT8U14ocwSxsT4wsKQCKSouTmHADZ0sHcW+sgwtIHrMKbc4PRlP8ffH2ZGvyeIztfeOGHm3SECwI9+HehjQccABJOBMEtEB/ohQ8GUtKFCFBTe224RgSXDDPgRvT2I8MVuhLvPQ7t2BfrtIRgTY4jeHozFWPoJpA7AzQLvv/PrwPSSRqYXf/Fk3cZ69S0i2gfAyrpO0eFBGO6xhSQIwNa4BpbK6phmWUJqGqTPC2PGA+n3gQ0dBEpb3pOqwtrUDNVVkUp+MRDM/3VpyPjupmc+HFtUz55YP/zvwIjbJ/8GwBBLk/XRYRju8XsoWlisIFVFdPQ2tBtXEe7pQqTnE0Rv3oD0zgKmGUsOySA4Pj2xWiGcLghHUSoIA8AMgBNun3zrh+8FJu7rWOEHf/Up8dITji9Ypkff0odvuTK1sBAi1t5yhpqZOXZJUSAcRRBFxbFvux2isAhktSa7kwHAA+BYUOM33zkWvPTKX58w0w3mFjXE/uAf9xTvoZ4jxLx3SaW9okBdthxKuROiqBikqgCJWGVw10rJMeEDcFwyf+9YV/TSt3/qk4GwlNmOFdR85BjtHSFeyxpRRn3n7ohKymIxkJ7FE9kpGp/xXoga/HbXgN77+o+9CGq5D3ryAtKyLFwPRj0IS6tfTROmZxKq0wWyWNKRnRmvuCcBnPJH+GfHPomce/OwXw9q85bCfQMpVEynEHDEXyqW5l6W5KEdp1hhCsAFZhz3hPjETz4M3nz/VFj3hTjv0928gBiSwswIEcFczFBiPhBtdlhX1oIUhVMOdGbjAI6CcPL8Lf3W937uDw6NGzjXeXZR1lfzOXabClvdjPAoAZXxod2iYoV1HaZvjoWjSAKIxD+DUuKjkMbv3Zk1+/7teDhy7HSINWNp/zSQl0Uuugs8e1fNnUbseMGebR6cNpClqcugf5yZb+omumaC7J/wmFcG3ebH569FvWcua9IbkHyhq3PJPWRGID3dnZywysnbRcGvtovf2BTZRkARgNI8piyx7pYxrrP47rErtv8YvhQITHs5Ou2XysS0oU9Mm6ZmLCzHs3HFfREiALS1d9DLm6cKX9o4dQjAVwHUx0eqaib6SxAbAx8d6Xc+f+ifhgPZ3DjX8fN9u1ZCQ9u2bAm1VwWPra8IPySI9wGoQ8w6lpRSJ8HMGoAZMOZ21wXsAAIPAsCiYiQG5jw///jGka9vcf/r+orwDBHvoZhlSuNniyIpGwWY4QbwMRGO9ntsZt0DArAo10r222f2baLPN8+WP9E4t7lAlR1Soh5AeTwBmILgJYE7EV1cGfVbT58bKxx7v9+JoTnrgjnZHxJE3kDSBeGz+zcpTzTOOXasDDirHNFyMBVMhCx693hB4OKkw3Njxu73a0rk+KkuflBWWBKQTBklLmDiM19OPCiBM63/HwAQrmGURDhdsAAAAABJRU5ErkJggg==' + + +ICON_BASE64_PALM = b'iVBORw0KGgoAAAANSUhEUgAAAEkAAAA8CAMAAAAdQmecAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAAB8SDx4UEiAUDyAUEiMZFSUbGSsUEy8aFiwbGTsUFTkdICwiHTIjHDQrHzsiGysjIjIlIDMqIzMsKjsgIzotID0uKT01IzszMUkYHE0ZIFkbKEQhHkUqH0sgHUUgI0MtIkUoKEsmIEwqIkwqL00rM0I1I0s1JEswKE04J0w7LUc9OVYpJ1omMVM0JVQ1K1I4JVU7KlszK1k6J1s7KVw4NGEdLmQeMWghNmE5LWQ4NXIiPWs2RHkkQnAzRVRENVNDOF1ENFxEOVpKOWNDM2NHPWNMPGxFOWxKPG9QPnJKPHVQP05HRFJMSltUUWpLQ2lUQ2NcWndNQndIU3JUQ3JUSHRZTHxSRHxVSHxZSnlaVH1YYmZgX39gVmZjYnZran14doFURoNWSYNbTYNeUoleUItbaYRgT4VhU4JiWIVoXIpjVIxlWo1oVo1pWpFiVpFlWZJsXZRxX4JmZYVsY4trY4pzaoN9e5NtYpRtaJltY5ZxYpRyaZpzZZx1ap14a5N4eJx1cJp3eJ15cZp6eqB2a6J6baN8cqJ7eax9cah+epx7hImBf5uBeqWAdKOCe6qCdKqDequJfbKJfY2EgpmIiZqVk52Zk6SBhKaFiKyDgqqFiayJgqyNi6aLkayNlKGQi6uRja+QlqySm6qZlLKMhLKOi7SRi76Th7qTi7KTk7KVm7KYk7SZm7iWk7yZk7ucm7Sco7ScqbqeorOfsK2knrmhnKukoqyop7GrqrqhpLqlq7mup7urqrSosbKuubqjsrymubyps7qqub6xrbSxsLizvbe1wr68ycKYi8OcmsCmo8KmqcGup8Ooq8qqqcersMGvv8yutMKxu8uwtM2zusy+v9G3usCwwsS0yca8xsW7y82/x8m9ysm90Ne8wMjAvMPCxsLAyc3Ax8vAy8/IzMbD0c3F08zK2NPLx9DJztnBydLO09LL2tPS19TS29rU29bU4dnT5NzV6NvZ4+Hf5uDd6+Pi7eXj8ejl9Ozp9/Lw+wAAAAAAAAAAAAAAAErQjXwAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAHU0lEQVRYR5WWf1gbZx3A8bw2y7nJVq00ZM9Eq63t0o3QFNKkrLbjoRjXsWFHCI+T0gNNe2nWm4Ndm9xFTLmj1UFONsUU1s2aZRRxdP7otNZN1Omc1inTdbad06danzlkVoR2/hG/7w8gNG/Y9iE/uPf9vp/7vt/37r0UZPLx34nxiYmJNybp4VuSxzQ9MXn5zempqelLlybfpotteu31ifFzfzp7/sL45PT09MS/aPOCME1jZ8+fGxs9Njw0fGLswuT01D/+SjsWgmU6fnJs7IUTx7410N8/MDx64eKlqV++DRXD9M2BkZOnfjicTvV0d/f0Hx09f/HNf//iL7QzP7mmn0XN9BMjR9PpfhOpkkdHz0z97+9PT9DuvOSa7gvHus2vYEzT6E4MDP10/PKlM0/T7rzkmoLBeyORWMwwzf6UaZowvydPX7z8nzPP0v585JhO7pIkWZbvVXUzlUomzYNmcui7L49fvPDs6zQiDzmmr9UHg7vDu8MRVTcOHESq5KEjz7z8yitnfkwj8pBr2njHNjEo7ZbliGFAQsk+eB155vTpc6f/RkPY5JiOr7i1uh5UYUVROw4ehEolk1193zj2k9+NLpxUbsWXrt5YfdfnpDAUS9UgLaCrq2/g6JETT9IINrmmJStWb9wUCKK6K0h1wDCMDs1IHjr8Tk23F63aeOsdooSXUNU6O7QOTdM7937x0NAbNIRJrumrS4tWb64OiFKrJEshWdnXqWr7QqGO/fuHztIQJrmmzKKiFauc9aLYIkp7xHvkfXtCLSG5Y+TU1w8foRFMGKat7y1asbLaH/CLkNeO5h2trZJk/Oq3I/uHh/9IQ1gwTD+4qnBpkbPK5xf9zTua/c1+fyT1WLLnvi+8MPwoDWHBMEWDjuuFpc5P+Hz3iM3Ndb5mPZVOKfU93xnqj/2GxjDINb3q8PicJYUly2vkutZWURR1PdErtwz8+vloQPk+DWLAyMnjWb58ZUnhrsf3SJ9s8fl1TVWlwODzI/HaxrYFpscw1fscxY7Slc7Yc5LkatDhTpZE3YzH133+1EsP0RgGDNOPqm74cJWrtHRbond9Q0LVdSmsmWa4rf3Ffw4/QmMYMEyZ9rIy502u0hpDlRK6HtNN40BCjI68+IeBtndWp0xmU5VrpWttFdqiensff27QjIuxwf6+/Y13v0YjGDBNr25yrV3rqoL7t7fX1A/0Girs6Eqw7fcPfJlGMGCaMt+rdt7k3Natqwkjpmnm4BNpQ27o+fNLD7yL4xYt+/RxGjYPtimTKSxxqYluEOm63psaTBtB89tDfe2LeY7neW7xnT+ncXPkMT109aoaJaYaugY7CmxSj6UHU12haNPV4EFw3I00cpY8psWLVlcHIxFdVTUtFod9Lm0aO+/adSMVAZy1icZSmKb738fzSzY1yJCRYcBGF48qWpdW11j/AapBQMW203gMw7SVRG4ORBKaqsXjWjSqaRGldkvV5utIF8JqFazWB+kQRI5pK4eLyvPVElQbyqRF92rxDll0rK+pXQRd8LK+/6MbNtzidrvXHKOjgCtMTVTD8++uDet6J1RJaVOie5XglrK6xlrUYf3QOo8HLG6v1+uuOEwHXmHajs45w05Fhx3c0JRQOBRUAi7P+pprIJuPrHHY7cXwZ3eAraJ8Nqss01NLqANzTbumxLSIrijhnTvDQWepq+x6oVAQBAuCh7dgs9sryj10dJbpttmJYT7eDlWOaJEwiO5uuRlJLNDMcSgK3lBOjkeyE3Q8NW1fNk8D1WiD9YJnZ1gOB1sCH0MK2oO/LYIF0gO3RWgkBmJ6cG51yRk5vkbR1IgcDisxWWwoIX0ILBAEm2AjCPx7sIeYbpvLx3odmgLPCaFIREY/WGDnrYOM5sAqUNgJNkEgImRaTGN44YPF0AFYhJsDIXicS/I+EC2n3QBHc7LZYO0q0KXgtttmTdfijDhLMQAiGxxYINK3Ax6ccqcq3jCzVrBaWAKdOKNyckk56N5esJ3UBqLQrAGYHg531CGV5ENtuAEDuSDWVKCMvN5Kb6X7s9R0O8poZvZ4FB0n2MrqGhr8vjLSij5JnYnMDSpIqbLS66amZbOiOSB9PNzmqdtSVVhL2rAcXtCHTeVIVQl4yfQKrs0WoWuNDKLp2dZ5eCvVzkGSmlWRpMCEhtORxIqj8TEBH88ja4IoKfxrqAC2wbmxUG0O7id6NAM+no+9GMuo6hZs+gxZKQQaRTPMFjFMUCuaFlo/Mr2CJkgJReNPfIFfmRM+ZIBVFfhS+BLOCZWb3C7kPofLEkM0CNqQy4zKuwGZ7iTRWIURIPW3mt0MMyo0vYL7s64luCt4Hu6ZeR4EHcgAuaBYcJ0XZPATAC0ampyAJ0fHz0IGZTPXiFTgQiYyJwAnh+pNd4QsZrcjgDYB5Bir7BvAhB5vKCvUZ+F4vMQ4cB54EP0/C2ik9/TDBZmnyG6BGqHuWJR9XvqdD2yCXcq+Dna6JtpIYJ15QWB+5W6v+1P/B0gPXHqaGwimAAAAAElFTkSuQmCC' + + +ICON_BASE64_LIST = [ + ICON_BASE64_BLOB_HEADACHE, + ICON_BASE64_BLOB_PALM, + ICON_BASE64_BLOB_PAT, + ICON_BASE64_BLOB_THINK, + ICON_BASE64_BLOB_THINK2, + ICON_BASE64_BROW, + ICON_BASE64_LEGO_THINK, + ICON_BASE64_PALM, + ICON_BASE64_THINK, +] + + +def _random_error_icon(): + return random.choice(ICON_BASE64_LIST) + + +# d8b +# Y8P +# +# 88888b.d88b. 8888b. 888 88888b. +# 888 "888 "88b "88b 888 888 "88b +# 888 888 888 .d888888 888 888 888 +# 888 888 888 888 888 888 888 888 +# 888 888 888 "Y888888 888 888 888 + + +def main(): + # theme('SystemDefaultForReal') + + # preview_all_look_and_feel_themes() + # ChangeLookAndFeel('Dark Red') + # theme('Dark Red') + # SetOptions(progress_meter_color=(COLOR_SYSTEM_DEFAULT)) + # SetOptions(element_padding=(0,0)) + # ------ Menu Definition ------ # + + ver = version.split('\n')[0] + + menu_def = [ + ['&File', ['!&Open::KeyOpen', '&Save::KeySave', '---', '&Properties::KeyProp', 'E&xit']], + [ + '&Edit', + [ + '&Paste', + [ + 'Special::KeySpecial', + '!Normal', + ], + 'Undo', + ], + ], + ['!&Toolbar', ['Command &1', 'Command &2', 'Command &3', 'Command &4']], + ['&Help', '&About...'], + ] + + treedata = TreeData() + + treedata.Insert( + '', + '_A_', + 'Tree Item 1', + [1, 2, 3], + ) + treedata.Insert( + '', + '_B_', + 'B', + [4, 5, 6], + ) + treedata.Insert( + '_A_', + '_A1_', + 'Sub Item 1', + ['can', 'be', 'anything'], + ) + treedata.Insert( + '', + '_C_', + 'C', + [], + ) + treedata.Insert( + '_C_', + '_C1_', + 'C1', + ['or'], + ) + treedata.Insert('_A_', '_A2_', 'Sub Item 2', [None, None]) + treedata.Insert('_A1_', '_A3_', 'A30', ['getting deep']) + treedata.Insert('_C_', '_C2_', 'C2', ['nothing', 'at', 'all']) + + for i in range(100): + treedata.Insert('_C_', i, i, []) + + frame1 = [ + [Input('Input Text', do_not_clear=True, size=(250, 35), tooltip='Input'), FileBrowse(), Stretch()], + [ + Multiline(size=(250, 75), do_not_clear=True, default_text='Multiline Input', tooltip='Multiline input'), + MultilineOutput(size=(250, 75), default_text='Multiline Output', tooltip='Multiline output', key='-MLINE-'), + ], + ] + + frame2 = [ + [ + Listbox( + ['Listbox 1', 'Listbox 2', 'Listbox 3', 'Item 4', 'Item 5'], + default_values=['Listbox 2', 'Listbox 3'], + size=(200, 85), + tooltip='Listbox', + key='_LISTBOX_', + font='Courier 12', + text_color='red', + ) + ], + [Combo([1, 2, 3], size=(200, 35), tooltip='Combo', visible_items=2, key='_COMBO_')], + [Spin([1, 2, 3], size=(40, 30), tooltip='Spinner', key='_SPIN1_')], + [Spin(['Spin item 1', 'Spin item 2', 'Spin item 3'], size=(240, 30), tooltip='Spinner', key='_SPIN2_')], + ] + + frame3 = [ + [Checkbox('Checkbox1', True, tooltip='Checkbox'), Checkbox('Checkbox1')], + [Radio('Radio Button1', 1, tooltip='Radio'), Radio('Radio Button2', 1, default=True), Stretch()], + ] + + frame4 = [ + [ + Slider(range=(0, 100), tick_interval=None, orientation='v', size=(3, 30), default_value=40, tooltip='Slider'), + Dial(range=(0, 100), tick_interval=1, resolution=1, size=(150, 150), default_value=40, tooltip='Dial'), + Stretch(), + ], + ] + matrix = [[str(x * y) for x in range(4)] for y in range(8)] + + frame5 = [ + [ + Table( + values=matrix, + max_col_width=25, + headings=('aaa', 'bbb', 'ccc', 'ddd'), + auto_size_columns=True, + display_row_numbers=True, + enable_events=True, + bind_return_key=True, + justification='right', + num_rows=6, + alternating_row_color='lightblue', + key='_table_', + tooltip='Table', + ), + Tree( + data=treedata, + headings=['col1', 'col2', 'col3'], + enable_events=True, + auto_size_columns=True, + num_rows=10, + col0_width=10, + key='_TREE_', + show_expanded=True, + size=(200, 150), + tooltip='Tree', + ), + Stretch(), + ], + ] + + graph_elem = Graph((880, 150), (0, 0), (600, 300), key='+GRAPH+', tooltip='Graph') + + frame6 = [ + [graph_elem, Stretch()], + ] + + tab1 = Tab('Graph Number 1', frame6, tooltip='Tab 1') + tab2 = Tab('Graph Number 2', [[]]) + + layout = [ + [Menu(menu_def, key='_REALMENU_', background_color='white')], + [Text('You are running the PySimpleGUI.py file itself', font=('ANY', 15, 'Bold'), text_color='yellow')], + [Text('You should be importing it rather than running it', font='ANY 15')], + [Text('VERSION {}'.format(ver), size=(85, 1), text_color='yellow', font='ANY 18')], + # [Image(data_base64=logo, tooltip='Image', click_submits=True, key='_IMAGE_'), + [ + Frame('Input Text Group', frame1, title_color='yellow', tooltip='Text Group', frame_color='yellow', pad=(0, 0)), + Stretch(), + ], + [ + Frame('Multiple Choice Group', frame2, title_color=theme_text_color(), frame_color='yellow'), + # Column([[Frame('Binary Choice Group', frame3, frame_color='white', title_color='white')]], pad=(0,0)), + Frame('Binary Choice Group', frame3, frame_color='white', title_color='white'), + Frame('Variable Choice Group', frame4, title_color='blue'), + Stretch(), + ], + [ + Frame('Structured Data Group', frame5, title_color='yellow'), + ], + # [Frame('Graphing Group', frame6)], + [TabGroup([[tab1, tab2]], title_color='black')], + [ + ProgressBar(max_value=600, start_value=400, size=(600, 25), key='+PROGRESS+'), + Text('', key='_PROGTEXT_'), + Stretch(), + ButtonMenu('&Menu', ['Menu', ['&Pause Graph', 'Menu item::optional_key']], key='_MENU_', tooltip='Button Menu'), + Button('Button'), + Button('Exit', tooltip='Exit button'), + ], + ] + + window = Window( + 'Window Title', + layout, + font=('Helvetica', 13), + # default_button_element_size=(100, 30), + # auto_size_buttons=False, + default_element_size=(200, 22), + # margins = (40,40), + # border_depth=1, + ) + # graph_elem.DrawCircle((200, 200), 50, 'blue') + i = 0 + graph_paused = False + + # window.Element('_LISTBOX_').SetValue(['Listbox 1','Listbox 3']) + while True: # Event Loop + # TimerStart() + event, values = window.read(timeout=10) + print(event, values) if event != TIMEOUT_KEY else None + window['-MLINE-'].update(value=str(values), append=True) if event != TIMEOUT_KEY else None + if event is None or event == 'Exit': + break + if values['_MENU_'] == 'Pause Graph': + graph_paused = not graph_paused + if event == 'About...': + Popup('You are running PySimpleGUIQt', 'The version number is', version) + if not graph_paused: + + if i < 600: + graph_elem.DrawLine((i, 0), (i, random.randint(0, 300)), width=1, color='#{:06x}'.format(random.randint(0, 0xFFFFFF))) + else: + graph_elem.Move(-1, 0) + graph_elem.DrawLine((i, 0), (i, random.randint(0, 300)), width=1, color='#{:06x}'.format(random.randint(0, 0xFFFFFF))) + + window.FindElement('+PROGRESS+').UpdateBar(i % 600) + window.FindElement('_PROGTEXT_').Update((i % 600) // 6) + i += 1 + + # TimerStop() + window.close() + + # layout = [[Text('You are running the PySimpleGUI.py file itself')], + # [Text('You should be importing it rather than running it')], + # [Text('Here is your sample input window....')], + # [Text('Source File', size=(150, 25), justification='right'), InputText('Source', focus=True), FileBrowse()], + # [Text('Destination Folder', size=(150, 25), justification='right'), InputText('Dest'), FolderBrowse()], + # [Ok(bind_return_key=True), Cancel()]] + # + # window = Window('Demo window..', + # auto_size_buttons=False, + # default_element_size=(280,22), + # auto_size_text=False, + # default_button_element_size=(80,22) + # ).Layout(layout) + # event, values = window.Read() + # print(event, values) + # window.Close() + + +# ------------------------------------------------------------------# +# ------------------------ PEP8-ify The SDK ------------------------# +# ------------------------------------------------------------------# + +change_look_and_feel = ChangeLookAndFeel +easy_print = EasyPrint +easy_print_close = EasyPrintClose +get_complimentary_hex = GetComplimentaryHex +list_of_look_and_feel_values = ListOfLookAndFeelValues +obj_to_string = ObjToString +obj_to_string_single_obj = ObjToStringSingleObj +one_line_progress_meter = OneLineProgressMeter +one_line_progress_meter_cancel = OneLineProgressMeterCancel +popup = Popup +popup_annoying = PopupAnnoying +popup_auto_close = PopupAutoClose +popup_cancel = PopupCancel +popup_error = PopupError +popup_get_file = PopupGetFile +popup_get_folder = PopupGetFolder +popup_get_text = PopupGetText +popup_no_border = PopupNoBorder +popup_no_buttons = PopupNoButtons +popup_no_frame = PopupNoFrame +popup_no_titlebar = PopupNoTitlebar +popup_no_wait = PopupNoWait +popup_non_blocking = PopupNonBlocking +popup_ok = PopupOK +popup_ok_cancel = PopupOKCancel +popup_quick = PopupQuick +popup_quick_message = PopupQuickMessage +popup_scrolled = PopupScrolled +popup_timed = PopupTimed +popup_yes_no = PopupYesNo +rgb = RGB +scrolled_text_box = ScrolledTextBox +set_global_icon = SetGlobalIcon +set_options = SetOptions +timer_start = TimerStart +timer_stop = TimerStop + + +# ------------------------ Set the "Official PySimpleGUI Theme Colors" ------------------------ +theme(CURRENT_LOOK_AND_FEEL) + + +if __name__ == '__main__': + main() + exit(69) diff --git a/FreeSimpleGUIWeb/FreeSimpleGUIWeb/FreeSimpleGUIWeb.py b/FreeSimpleGUIWeb/FreeSimpleGUIWeb/FreeSimpleGUIWeb.py deleted file mode 100644 index c1aabdb8..00000000 --- a/FreeSimpleGUIWeb/FreeSimpleGUIWeb/FreeSimpleGUIWeb.py +++ /dev/null @@ -1,10680 +0,0 @@ -# usr/bin/python3 - -version = __version__ = ( - '0.39.0.6 Unreleased\n , VSeparator added (spelling error), added default key for one_line_progress_meter, auto-add keys to tables & trees, Graph.draw_image now uses image_data property instead of calling set_image, added theme_add_new, changed Remi call menu_item.set_on_click_listener to menu_item.onclick.connect so it can run with latest Remi' -) - -port = 'PySimpleGUIWeb' - -import sys -import datetime -import textwrap -import pickle -import threading -from queue import Queue -import remi -import logging -import traceback -import os -import base64, binascii -import mimetypes -from random import randint -import time -import pkg_resources - - -# from typing import List, Any, Union, Tuple, Dict # For doing types in comments. perhaps not required - - -try: - from io import StringIO -except: - from cStringIO import StringIO - -###### ##### ##### # # ### # # -# # # # # # # # # ##### # ###### # # # # # # # # ###### ##### -# # # # # # ## ## # # # # # # # # # # # # # # -###### # ##### # # ## # # # # ##### # #### # # # # # # ##### ##### -# # # # # # ##### # # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # # # -# # ##### # # # # ###### ###### ##### ##### ### ## ## ###### ##### - -""" - Welcome to the "core" PySimpleGUIWeb code.... - - This special port of the PySimpleGUI SDK to the browser is made possible by the magic of Remi - - https://github.com/dddomodossola/remi - - To be clear, PySimpleGUI would not be able to run in a web browser without this important GUI Framework - It may not be as widely known at tkinter or Qt, but it should be. Just as those are the best of the desktop - GUI frameworks, Remi is THE framework for doing Web Page GUIs in Python. Nothing else like it exists. - - ::::::::: :::::::::: ::: ::: ::::::::::: - :+: :+: :+: :+:+: :+:+: :+: - +:+ +:+ +:+ +:+ +:+:+ +:+ +:+ - +#++:++#: +#++:++# +#+ +:+ +#+ +#+ - +#+ +#+ +#+ +#+ +#+ +#+ - #+# #+# #+# #+# #+# #+# - ### ### ########## ### ### ########### - -""" - -g_time_start = 0 -g_time_end = 0 -g_time_delta = 0 - - -def TimerStart(): - global g_time_start - - g_time_start = time.time() - - -def TimerStop(): - global g_time_delta, g_time_end - - g_time_end = time.time() - g_time_delta = g_time_end - g_time_start - print(g_time_delta * 1000) - - -# Because looks matter... -DEFAULT_BASE64_ICON = b'iVBORw0KGgoAAAANSUhEUgAAACEAAAAgCAMAAACrZuH4AAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAMGmYMGqZMWqaMmubMmycM22dNGuZNm2bNm6bNG2dN26cNG6dNG6eNW+fN3CfOHCeOXGfNXCgNnGhN3KiOHOjOXSjOHSkOnWmOnamOnanPHSiPXakPnalO3eoPnimO3ioPHioPHmpPHmqPXqqPnurPnusPnytP3yuQHimQnurQn2sQH2uQX6uQH6vR32qRn+sSXujSHynTH2mTn+nSX6pQH6wTIGsTYKuTYSvQoCxQoCyRIK0R4S1RYS2Roa4SIe4SIe6SIi7Soq7SYm8SYq8Sou+TY2/UYStUYWvVIWtUYeyVoewUIi0VIizUI6+Vo+8WImxXJG5YI2xZI+xZ5CzZJC0ZpG1b5a3apW4aZm/cZi4dJ2/eJ69fJ+9XZfEZZnCZJzHaZ/Jdp/AeKTI/tM8/9Q7/9Q8/9Q9/9Q+/tQ//9VA/9ZA/9ZB/9ZC/9dD/9ZE/tdJ/9dK/9hF/9hG/9hH/9hI/9hJ/9hK/9lL/9pK/9pL/thO/9pM/9pN/9tO/9tP/9xP/tpR/9xQ/9xR/9xS/9xT/91U/91V/t1W/95W/95X/95Y/95Z/99a/99b/txf/txh/txk/t5l/t1q/t5v/+Bb/+Bc/+Bd/+Be/+Bf/+Bg/+Fh/+Fi/+Jh/+Ji/uJk/uJl/+Jm/+Rm/uJo/+Ro/+Rr/+Zr/+Vs/+Vu/+Zs/+Zu/uF0/uVw/+dw/+dz/+d2/uB5/uB6/uJ9/uR7/uR+/uV//+hx/+hy/+h0/+h2/+l4/+l7/+h8gKXDg6vLgazOhKzMiqrEj6/KhK/Qka/Hk7HJlLHJlLPMmLTLmbbOkLXSmLvXn77XoLrPpr/Tn8DaocLdpcHYrcjdssfZus/g/uOC/uOH/uaB/uWE/uaF/uWK/+qA/uqH/uqI/uuN/uyM/ueS/ueW/ueY/umQ/uqQ/uuS/uuW/uyU/uyX/uqa/uue/uye/uyf/u6f/uyq/u+r/u+t/vCm/vCp/vCu/vCy/vC2/vK2/vO8/vO/wtTjwtXlzdrl/vTA/vPQAAAAiNpY5gAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAAFw8AABcPASe7rwsAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAKUSURBVDhPhdB3WE1xHMdxt5JV0dANoUiyd8kqkey996xclUuTlEKidO3qVnTbhIyMW/bee5NskjJLmR/f3++cK/94vP76Ps/n/Zx7z6mE/6koJowcK154vvHOL/GsKCZXkUgkWlf4vWGWq5tsDz+JWIzSokAiqXGe7nWu3HxhEYof7fhOqp1GtptQuMruVhQdxZ05U5G47tYUHbQ4oah6Fg9Z4ubm7i57JhQjdHS0RSzUPoG17u6zZTKZh8c8XlytqW9YWUOH1LqFOZ6enl5ec+XybFb0rweM1tPTM6yuq6vLs0lYJJfLvb19fHwDWGF0jh5lYNAe4/QFemOwxtfXz8/fPyBgwVMqzAcCF4ybAZ2MRCexJGBhYGBQUHDw4u1UHDG1G2ZqB/Q1MTHmzAE+kpCwL1RghlTaBt/6SaXS2kx9YH1IaOjSZST8vfA9JtoDnSngGgL7wkg4WVkofA9mcF1Sx8zMzBK4v3wFiYiMVLxlEy9u21syFhYNmgN7IyJXEYViNZvEYoCVVWOmUVvgQVSUQqGIjolRFvOAFd8HWVs34VoA+6OjY2JjY5Vxm4BC1UuhGG5jY9OUaQXci1MqlfHx8YmqjyhOViW9ZsUN29akJRmPFwkJCZsTSXIpilJffXiTzorLXYgtcxRJKpUqKTklJQ0oSt9FP/EonxVdNY4jla1kK4q2ZB6mIr+AipvduzFUzMSOtLT09IyMzMxtJKug/F0u/6dTexAWDcXXLGEjapKjfsILOLKEuYiSnTQeYCt3UHhbwEHjGMrETfBJU5zq5dSTcXC8hLJccSWP2cgLXHPu7cQNAcpyxF1dyjehAKb0cSYUAOXCUw6V8OFPgevTXFymC+fPPLU677Nw/1X8A/AbfAKGulaqFlIAAAAASUVORK5CYII=' - - -# ----====----====----==== Constants the user CAN safely change ====----====----====----# -DEFAULT_WINDOW_ICON = 'default_icon.ico' -DEFAULT_ELEMENT_SIZE = (250, 26) # In pixels -DEFAULT_BUTTON_ELEMENT_SIZE = (10, 1) # In CHARACTERS -DEFAULT_MARGINS = (10, 5) # Margins for each LEFT/RIGHT margin is first term -DEFAULT_ELEMENT_PADDING = (5, 3) # Padding between elements (row, col) in pixels -DEFAULT_AUTOSIZE_TEXT = True -DEFAULT_AUTOSIZE_BUTTONS = True -DEFAULT_FONT = ('Helvetica', 15) -DEFAULT_TEXT_JUSTIFICATION = 'left' -DEFAULT_BORDER_WIDTH = 1 -DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form -DEFAULT_DEBUG_WINDOW_SIZE = (80, 20) -DEFAULT_OUTPUT_ELEMENT_SIZE = (40, 10) -DEFAULT_WINDOW_LOCATION = (None, None) -MAX_SCROLLED_TEXT_BOX_HEIGHT = 50 -DEFAULT_TOOLTIP_TIME = 400 - -DEFAULT_PIXELS_TO_CHARS_SCALING = (10, 26) # 1 character represents x by y pixels -DEFAULT_PIXEL_TO_CHARS_CUTOFF = 20 # number of chars that triggers using pixels instead of chars - -#################### COLOR STUFF #################### -BLUES = ('#082567', '#0A37A3', '#00345B') -PURPLES = ('#480656', '#4F2398', '#380474') -GREENS = ('#01826B', '#40A860', '#96D2AB', '#00A949', '#003532') -YELLOWS = ('#F3FB62', '#F0F595') -TANS = ('#FFF9D5', '#F4EFCF', '#DDD8BA') -NICE_BUTTON_COLORS = ( - (GREENS[3], TANS[0]), - ('#000000', '#FFFFFF'), - ('#FFFFFF', '#000000'), - (YELLOWS[0], PURPLES[1]), - (YELLOWS[0], GREENS[3]), - (YELLOWS[0], BLUES[2]), -) - -COLOR_SYSTEM_DEFAULT = '1234567890' # Colors should never be this long - -DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default -OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = ('white', BLUES[0]) # Colors should never be this long - -CURRENT_LOOK_AND_FEEL = 'DarkBlue3' - - -DEFAULT_ERROR_BUTTON_COLOR = ('#FFFFFF', '#FF0000') -DEFAULT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_ELEMENTS_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_SCROLLBAR_COLOR = None -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[0]) # (Text, Background) or (Color "on", Color) as a way to remember -# DEFAULT_BUTTON_COLOR = (GREENS[3], TANS[0]) # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], GREENS[4]) # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = ('white', 'black') # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[2]) # Foreground, Background (None, None) == System Default -# DEFAULT_PROGRESS_BAR_COLOR = (GREENS[2], GREENS[0]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[1], BLUES[1]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[0], BLUES[0]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (PURPLES[1],PURPLES[0]) # a nice purple progress bar - -# A transparent button is simply one that matches the background -TRANSPARENT_BUTTON = 'This constant has been depricated. You must set your button background = background it is on for it to be transparent appearing' -# -------------------------------------------------------------------------------- -# Progress Bar Relief Choices -RELIEF_RAISED = 'raised' -RELIEF_SUNKEN = 'sunken' -RELIEF_FLAT = 'flat' -RELIEF_RIDGE = 'ridge' -RELIEF_GROOVE = 'groove' -RELIEF_SOLID = 'solid' - -DEFAULT_PROGRESS_BAR_COLOR = (GREENS[0], '#D0D0D0') # a nice green progress bar -DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL = (GREENS[0], '#D0D0D0') # a nice green progress bar -DEFAULT_PROGRESS_BAR_SIZE = (25, 20) # Size of Progress Bar (characters for length, pixels for width) -DEFAULT_PROGRESS_BAR_BORDER_WIDTH = 1 -DEFAULT_PROGRESS_BAR_RELIEF = RELIEF_GROOVE -PROGRESS_BAR_STYLES = ('default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative') -DEFAULT_PROGRESS_BAR_STYLE = 'default' -DEFAULT_METER_ORIENTATION = 'horizontal' -DEFAULT_SLIDER_ORIENTATION = 'vertical' -DEFAULT_SLIDER_BORDER_WIDTH = 1 -DEFAULT_SLIDER_RELIEF = 00000 -DEFAULT_FRAME_RELIEF = 00000 - - -DEFAULT_LISTBOX_SELECT_MODE = 'extended' -SELECT_MODE_MULTIPLE = 'multiple' -LISTBOX_SELECT_MODE_MULTIPLE = 'multiple' -SELECT_MODE_BROWSE = 'browse' -LISTBOX_SELECT_MODE_BROWSE = 'browse' -SELECT_MODE_EXTENDED = 'extended' -LISTBOX_SELECT_MODE_EXTENDED = 'extended' -SELECT_MODE_SINGLE = 'single' -LISTBOX_SELECT_MODE_SINGLE = 'single' -SELECT_MODE_CONTIGUOUS = 'contiguous' -LISTBOX_SELECT_MODE_CONTIGUOUS = 'contiguous' - -TABLE_SELECT_MODE_NONE = 00000 -TABLE_SELECT_MODE_BROWSE = 00000 -TABLE_SELECT_MODE_EXTENDED = 00000 -DEFAULT_TABLE_SECECT_MODE = TABLE_SELECT_MODE_EXTENDED - -TITLE_LOCATION_TOP = 00000 -TITLE_LOCATION_BOTTOM = 00000 -TITLE_LOCATION_LEFT = 00000 -TITLE_LOCATION_RIGHT = 00000 -TITLE_LOCATION_TOP_LEFT = 00000 -TITLE_LOCATION_TOP_RIGHT = 00000 -TITLE_LOCATION_BOTTOM_LEFT = 00000 -TITLE_LOCATION_BOTTOM_RIGHT = 00000 - -THEME_DEFAULT = 'default' -THEME_WINNATIVE = 'winnative' -THEME_CLAM = 'clam' -THEME_ALT = 'alt' -THEME_CLASSIC = 'classic' -THEME_VISTA = 'vista' -THEME_XPNATIVE = 'xpnative' - -# DEFAULT_METER_ORIENTATION = 'Vertical' -# ----====----====----==== Constants the user should NOT f-with ====----====----====----# -ThisRow = 555666777 # magic number - -# DEFAULT_WINDOW_ICON = '' -MESSAGE_BOX_LINE_WIDTH = 60 - -# "Special" Key Values.. reserved -# Key representing a Read timeout -EVENT_TIMEOUT = TIMEOUT_EVENT = TIMEOUT_KEY = '__TIMEOUT__' -# Window closed event (user closed with X or destroyed using OS) -WIN_CLOSED = WINDOW_CLOSED = None - -# Key indicating should not create any return values for element -WRITE_ONLY_KEY = '__WRITE ONLY__' - -# MENU Constants, can be changed by user if desired -MENU_DISABLED_CHARACTER = '!' -MENU_KEY_SEPARATOR = '::' - - -# a shameful global variable. This represents the top-level window information. Needed because opening a second window is different than opening the first. -class MyWindows: - def __init__(self): - self._NumOpenWindows = 0 - self.user_defined_icon = None - self.hidden_master_root = None - - def Decrement(self): - self._NumOpenWindows -= 1 * (self._NumOpenWindows != 0) # decrement if not 0 - # print('---- DECREMENTING Num Open Windows = {} ---'.format(self._NumOpenWindows)) - - def Increment(self): - self._NumOpenWindows += 1 - # print('++++ INCREMENTING Num Open Windows = {} ++++'.format(self._NumOpenWindows)) - - -_my_windows = MyWindows() # terrible hack using globals... means need a class for collecing windows - - -# ====================================================================== # -# One-liner functions that are handy as f_ck # -# ====================================================================== # -def RGB(red, green, blue): - return '#%02x%02x%02x' % (red, green, blue) - - -# ====================================================================== # -# Enums for types # -# ====================================================================== # -# ------------------------- Button types ------------------------- # -# todo Consider removing the Submit, Cancel types... they are just 'RETURN' type in reality -# uncomment this line and indent to go back to using Enums -# Was enum previously ButtonType(Enum): -BUTTON_TYPE_BROWSE_FOLDER = 1 -BUTTON_TYPE_BROWSE_FILE = 2 -BUTTON_TYPE_BROWSE_FILES = 21 -BUTTON_TYPE_SAVEAS_FILE = 3 -BUTTON_TYPE_CLOSES_WIN = 5 -BUTTON_TYPE_CLOSES_WIN_ONLY = 6 -BUTTON_TYPE_READ_FORM = 7 -BUTTON_TYPE_REALTIME = 9 -BUTTON_TYPE_CALENDAR_CHOOSER = 30 -BUTTON_TYPE_COLOR_CHOOSER = 40 - -BROWSE_FILES_DELIMITER = ';' # the delimeter to be used between each file in the returned string - -# ------------------------- Element types ------------------------- # -# These used to be enums ElementType(Enum): -ELEM_TYPE_TEXT = 'text' -ELEM_TYPE_INPUT_TEXT = 'input' -ELEM_TYPE_INPUT_COMBO = 'combo' -ELEM_TYPE_INPUT_OPTION_MENU = 'option menu' -ELEM_TYPE_INPUT_RADIO = 'radio' -ELEM_TYPE_INPUT_MULTILINE = 'multiline' -ELEM_TYPE_MULTILINE_OUTPUT = 'multioutput' -ELEM_TYPE_INPUT_CHECKBOX = 'checkbox' -ELEM_TYPE_INPUT_SPIN = 'spin' -ELEM_TYPE_BUTTON = 'button' -ELEM_TYPE_BUTTONMENU = 'buttonmenu' -ELEM_TYPE_IMAGE = 'image' -ELEM_TYPE_CANVAS = 'canvas' -ELEM_TYPE_FRAME = 'frame' -ELEM_TYPE_GRAPH = 'graph' -ELEM_TYPE_TAB = 'tab' -ELEM_TYPE_TAB_GROUP = 'tabgroup' -ELEM_TYPE_INPUT_SLIDER = 'slider' -ELEM_TYPE_INPUT_LISTBOX = 'listbox' -ELEM_TYPE_OUTPUT = 'output' -ELEM_TYPE_COLUMN = 'column' -ELEM_TYPE_MENUBAR = 'menubar' -ELEM_TYPE_PROGRESS_BAR = 'progressbar' -ELEM_TYPE_BLANK = 'blank' -ELEM_TYPE_TABLE = 'table' -ELEM_TYPE_TREE = 'tree' -ELEM_TYPE_ERROR = 'error' -ELEM_TYPE_SEPARATOR = 'separator' - -# ------------------------- Popup Buttons Types ------------------------- # -POPUP_BUTTONS_YES_NO = 1 -POPUP_BUTTONS_CANCELLED = 2 -POPUP_BUTTONS_ERROR = 3 -POPUP_BUTTONS_OK_CANCEL = 4 -POPUP_BUTTONS_OK = 0 -POPUP_BUTTONS_NO_BUTTONS = 5 - - -# ---------------------------------------------------------------------- # -# Cascading structure.... Objects get larger # -# Button # -# Element # -# Row # -# Form # -# ---------------------------------------------------------------------- # -# ------------------------------------------------------------------------- # -# Element CLASS # -# ------------------------------------------------------------------------- # -class Element: - def __init__( - self, - elem_type, - size=(None, None), - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - - if elem_type != ELEM_TYPE_GRAPH: - self.Size = convert_tkinter_size_to_Wx(size) - else: - self.Size = size - if size_px != (None, None): - self.Size = size_px - self.Type = elem_type - self.AutoSizeText = auto_size_text - # self.Pad = DEFAULT_ELEMENT_PADDING if pad is None else pad - self.Pad = pad - if font is not None and type(font) is not str: - self.Font = font - elif font is not None: - self.Font = font.split(' ') - else: - self.Font = font - - self.TKStringVar = None - self.TKIntVar = None - self.TKText = None - self.TKEntry = None - self.TKImage = None - - self.ParentForm = None # type: Window - self.ParentContainer = None # will be a Form, Column, or Frame element - self.TextInputDefault = None - self.Position = (0, 0) # Default position Row 0, Col 0 - self.BackgroundColor = background_color if background_color is not None else DEFAULT_ELEMENT_BACKGROUND_COLOR - self.TextColor = text_color if text_color is not None else DEFAULT_ELEMENT_TEXT_COLOR - self.Key = key # dictionary key for return values - self.Tooltip = tooltip - self.TooltipObject = None - self.Visible = visible - self.metadata = metadata # type: Any - - # ------------------------- REMI CHANGED CALLBACK ----------------------- - # called when a widget has changed and the element has events enabled - def _ChangedCallback(self, widget, *args): - # type: (Element, remi.Widget, Any) -> None - # print(f'Callback {args}') - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - def Update(self, widget, background_color=None, text_color=None, font=None, visible=None, disabled=None, tooltip=None): - if font is not None: - font_info = font_parse_string(font) # family, point size, other - widget.style['font-family'] = font_info[0] - widget.style['font-size'] = '{}px'.format(font_info[1]) - - if background_color not in (None, COLOR_SYSTEM_DEFAULT): - widget.style['background-color'] = background_color - if text_color not in (None, COLOR_SYSTEM_DEFAULT): - widget.style['color'] = text_color - - if disabled: - widget.set_enabled(False) - elif disabled is False: - widget.set_enabled(True) - if visible is False: - widget.attributes['hidden'] = 'true' - elif visible is True: - del widget.attributes['hidden'] - if tooltip is not None: - widget.attributes['title'] = tooltip - - # if font: - # widget.SetFont(font_to_wx_font(font)) - # if text_color not in (None, COLOR_SYSTEM_DEFAULT): - # widget.SetForegroundColour(text_color) - # if background_color not in (None, COLOR_SYSTEM_DEFAULT): - # widget.SetBackgroundColour(background_color) - # if visible is True: - # widget.Show() - # self.ParentForm.VisibilityChanged() - # elif visible is False: - # widget.Hide() - # self.ParentForm.VisibilityChanged() - # if disabled: - # widget.Enable(False) - # elif disabled is False: - # widget.Enable(True) - # if tooltip is not None: - # widget.SetToolTip(tooltip) - if visible is False: - widget.attributes['hidden'] = 'true' - elif visible is True: - del widget.attributes['hidden'] - - def __call__(self, *args, **kwargs): - """ - Makes it possible to "call" an already existing element. When you do make the "call", it actually calls - the Update method for the element. - Example: If this text element was in your layout: - sg.Text('foo', key='T') - Then you can call the Update method for that element by writing: - window.FindElement('T')('new text value') - - :param args: - :param kwargs: - :return: - """ - return self.Update(*args, **kwargs) - - -# ---------------------------------------------------------------------- # -# Input Class # -# ---------------------------------------------------------------------- # -class InputText(Element): - def __init__( - self, - default_text='', - size=(None, None), - disabled=False, - password_char='', - justification=None, - background_color=None, - text_color=None, - font=None, - tooltip=None, - change_submits=False, - enable_events=False, - do_not_clear=True, - key=None, - focus=False, - pad=None, - visible=True, - size_px=(None, None), - ): - ''' - Input a line of text Element - :param default_text: Default value to display - :param size: Size of field in characters - :param password_char: If non-blank, will display this character for every character typed - :param background_color: Color for Element. Text or RGB Hex - ''' - self.DefaultText = default_text - self.PasswordCharacter = password_char - bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - self.Justification = justification or 'left' - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.QT_QLineEdit = None - self.ValueWasChanged = False - self.Widget = None # type: remi.gui.TextInput - super().__init__( - ELEM_TYPE_INPUT_TEXT, - size=size, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - font=font, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - - def _InputTextCallback(self, widget, key, keycode, ctrl, shift, alt): - # print(f'text widget value = {widget.get_value()}') - # widget.set_value('') - # widget.set_value(value) - self.ParentForm.LastButtonClicked = key - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - widget.set_value(widget.get_value() + key) - return (key, keycode, ctrl, shift, alt) - - def Update(self, value=None, disabled=None, select=None, background_color=None, text_color=None, font=None, visible=None): - if value is not None: - self.Widget.set_value(str(value)) - if disabled is True: - self.Widget.set_enabled(False) - elif disabled is False: - self.Widget.set_enabled(True) - - def Get(self): - return self.Widget.get_value() - - get = Get - update = Update - - class TextInput_raw_onkeyup(remi.gui.TextInput): - @remi.gui.decorate_set_on_listener('(self, emitter, key, keycode, ctrl, shift, alt)') - @remi.gui.decorate_event_js( - """var params={};params['key']=event.key; - params['keycode']=(event.which||event.keyCode); - params['ctrl']=event.ctrlKey; - params['shift']=event.shiftKey; - params['alt']=event.altKey; - sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params); - event.stopPropagation();event.preventDefault();return false;""" - ) - def onkeyup(self, key, keycode, ctrl, shift, alt): - return (key, keycode, ctrl, shift, alt) - - @remi.gui.decorate_set_on_listener('(self, emitter, key, keycode, ctrl, shift, alt)') - @remi.gui.decorate_event_js( - """var params={};params['key']=event.key; - params['keycode']=(event.which||event.keyCode); - params['ctrl']=event.ctrlKey; - params['shift']=event.shiftKey; - params['alt']=event.altKey; - sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params); - event.stopPropagation();event.preventDefault();return false;""" - ) - def onkeydown(self, key, keycode, ctrl, shift, alt): - return (key, keycode, ctrl, shift, alt) - - -# ------------------------- INPUT TEXT Element lazy functions ------------------------- # -In = InputText -Input = InputText -I = InputText - - -# ---------------------------------------------------------------------- # -# Combo # -# ---------------------------------------------------------------------- # -class Combo(Element): - def __init__( - self, - values, - default_value=None, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - disabled=False, - key=None, - pad=None, - tooltip=None, - readonly=False, - visible_items=10, - font=None, - auto_complete=True, - visible=True, - size_px=(None, None), - ): - ''' - Input Combo Box Element (also called Dropdown box) - :param values: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex - ''' - self.Values = [str(v) for v in values] - self.DefaultValue = default_value - self.ChangeSubmits = change_submits or enable_events - # self.InitializeAsDisabled = disabled - self.Disabled = disabled - self.Readonly = readonly - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.VisibleItems = visible_items - self.AutoComplete = auto_complete - self.Widget = None # type: remi.gui.DropDown - super().__init__( - ELEM_TYPE_INPUT_COMBO, - size=size, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - font=font or DEFAULT_FONT, - visible=visible, - size_px=size_px, - ) - - def Update( - self, - value=None, - values=None, - set_to_index=None, - disabled=None, - readonly=None, - background_color=None, - text_color=None, - font=None, - visible=None, - ): - if values is not None: - self.Widget.empty() - for i, item in enumerate(values): - self.Widget.append(value=item, key=str(i)) - if value: - self.Widget.select_by_value(value) - if set_to_index is not None: - try: # just in case a bad index is passed in - self.Widget.select_by_key(str(set_to_index)) - except: - pass - - super().Update( - self.Widget, - background_color=background_color, - text_color=text_color, - font=font, - visible=visible, - disabled=disabled, - ) - - update = Update - - -# ------------------------- INPUT COMBO Element lazy functions ------------------------- # -InputCombo = Combo -DropDown = Combo -Drop = Combo - - -# ---------------------------------------------------------------------- # -# Option Menu # -# ---------------------------------------------------------------------- # -class OptionMenu(Element): - def __init__( - self, - values, - default_value=None, - size=(None, None), - disabled=False, - auto_size_text=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None, - ): - ''' - InputOptionMenu - :param values: - :param default_value: - :param size: - :param disabled: - :param auto_size_text: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - ''' - self.Values = values - self.DefaultValue = default_value - self.TKOptionMenu = None - self.Disabled = disabled - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - super().__init__( - ELEM_TYPE_INPUT_OPTION_MENU, - size=size, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - ) - - def Update(self, value=None, values=None, disabled=None): - if values is not None: - self.Values = values - if self.Values is not None: - for index, v in enumerate(self.Values): - if v == value: - try: - self.TKStringVar.set(value) - except: - pass - self.DefaultValue = value - break - if disabled == True: - self.TKOptionMenu['state'] = 'disabled' - elif disabled == False: - self.TKOptionMenu['state'] = 'normal' - - -# ------------------------- OPTION MENU Element lazy functions ------------------------- # -InputOptionMenu = OptionMenu - - -# ---------------------------------------------------------------------- # -# Listbox # -# ---------------------------------------------------------------------- # -class Listbox(Element): - def __init__( - self, - values, - default_values=None, - select_mode=None, - change_submits=False, - enable_events=False, - bind_return_key=False, - size=(None, None), - disabled=False, - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - """ - - :param values: - :param default_values: - :param select_mode: - :param change_submits: - :param enable_events: - :param bind_return_key: - :param size: - :param disabled: - :param auto_size_text: - :param font: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - :param visible: - :param size_px: - """ - self.Values = values - self.DefaultValues = default_values - self.TKListbox = None - self.ChangeSubmits = change_submits or enable_events - self.BindReturnKey = bind_return_key - self.Disabled = disabled - if select_mode == LISTBOX_SELECT_MODE_BROWSE: - self.SelectMode = SELECT_MODE_BROWSE - elif select_mode == LISTBOX_SELECT_MODE_EXTENDED: - self.SelectMode = SELECT_MODE_EXTENDED - elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE: - self.SelectMode = SELECT_MODE_MULTIPLE - elif select_mode == LISTBOX_SELECT_MODE_SINGLE: - self.SelectMode = SELECT_MODE_SINGLE - elif select_mode == LISTBOX_SELECT_MODE_CONTIGUOUS: - self.SelectMode = SELECT_MODE_CONTIGUOUS - else: - self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Widget = None # type: remi.gui.ListView - tsize = size # convert tkinter size to pixels - if size[0] is not None and size[0] < 100: - tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] - - super().__init__( - ELEM_TYPE_INPUT_LISTBOX, - size=tsize, - auto_size_text=auto_size_text, - font=font, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - - def Update( - self, - values=None, - disabled=None, - set_to_index=None, - background_color=None, - text_color=None, - font=None, - visible=None, - ): - if values is not None: - self.Values = values - self.Widget.empty() - for item in values: - self.Widget.append(remi.gui.ListItem(item)) - # if disabled == True: - # self.QT_ListWidget.setDisabled(True) - # elif disabled == False: - # self.QT_ListWidget.setDisabled(False) - # if set_to_index is not None: - # self.QT_ListWidget.setCurrentRow(set_to_index) - super().Update( - self.Widget, - background_color=background_color, - text_color=text_color, - font=font, - visible=visible, - disabled=disabled, - ) - - return - - # def SetValue(self, values): - # # for index, item in enumerate(self.Values): - # for index, value in enumerate(self.Values): - # item = self.QT_ListWidget.item(index) - # if value in values: - # self.QT_ListWidget.setItemSelected(item, True) - - def GetListValues(self): - return self.Values - - get_list_values = GetListValues - update = Update - - -# ---------------------------------------------------------------------- # -# Radio # -# ---------------------------------------------------------------------- # -class Radio(Element): - def __init__( - self, - text, - group_id, - default=False, - disabled=False, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - font=None, - key=None, - pad=None, - tooltip=None, - change_submits=False, - ): - ''' - Radio Button Element - :param text: - :param group_id: - :param default: - :param disabled: - :param size: - :param auto_size_text: - :param background_color: - :param text_color: - :param font: - :param key: - :param pad: - :param tooltip: - :param change_submits: - ''' - self.InitialState = default - self.Text = text - self.TKRadio = None - self.GroupID = group_id - self.Value = None - self.Disabled = disabled - self.TextColor = text_color or DEFAULT_TEXT_COLOR - self.ChangeSubmits = change_submits - - print('*** WARNING - Radio Buttons are not yet available on PySimpleGUIWeb ***') - - super().__init__( - ELEM_TYPE_INPUT_RADIO, - size=size, - auto_size_text=auto_size_text, - font=font, - background_color=background_color, - text_color=self.TextColor, - key=key, - pad=pad, - tooltip=tooltip, - ) - - def Update(self, value=None, disabled=None): - print('*** NOT IMPLEMENTED ***') - location = EncodeRadioRowCol(self.Position[0], self.Position[1]) - if value is not None: - try: - self.TKIntVar.set(location) - except: - pass - self.InitialState = value - if disabled == True: - self.TKRadio['state'] = 'disabled' - elif disabled == False: - self.TKRadio['state'] = 'normal' - - update = Update - - -# ---------------------------------------------------------------------- # -# Checkbox # -# ---------------------------------------------------------------------- # -class Checkbox(Element): - def __init__( - self, - text, - default=False, - size=(None, None), - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - disabled=False, - key=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - ''' - Checkbox Element - :param text: - :param default: - :param size: - :param auto_size_text: - :param font: - :param background_color: - :param text_color: - :param change_submits: - :param disabled: - :param key: - :param pad: - :param tooltip: - ''' - self.Text = text - self.InitialState = default - self.Disabled = disabled - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.ChangeSubmits = change_submits or enable_events - self.Widget = None # type: remi.gui.CheckBox - - super().__init__( - ELEM_TYPE_INPUT_CHECKBOX, - size=size, - auto_size_text=auto_size_text, - font=font, - background_color=background_color, - text_color=self.TextColor, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - - def _ChangedCallback(self, widget, value): - # type: (remi.Widget, Any) -> None - # print(f'text widget value = {widget.get_value()}') - self.ParentForm.LastButtonClicked = self.Key - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - def Get(self): - return self.Widget.get_value() - - def Update(self, value=None, disabled=None): - if value is not None: - self.Widget.set_value(value) - if disabled == True: - self.Widget.set_enabled(False) - elif disabled == False: - self.Widget.set_enabled(True) - - get = Get - update = Update - - -# ------------------------- CHECKBOX Element lazy functions ------------------------- # -CB = Checkbox -CBox = Checkbox -Check = Checkbox - - -# ---------------------------------------------------------------------- # -# Spin # -# ---------------------------------------------------------------------- # - - -class Spin(Element): - # Values = None - # TKSpinBox = None - def __init__( - self, - values, - initial_value=None, - disabled=False, - change_submits=False, - enable_events=False, - size=(None, None), - readonly=True, - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - ''' - Spinner Element - :param values: - :param initial_value: - :param disabled: - :param change_submits: - :param size: - :param auto_size_text: - :param font: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - ''' - self.Values = values - self.DefaultValue = initial_value or values[0] - self.ChangeSubmits = change_submits or enable_events - self.Disabled = disabled - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.CurrentValue = self.DefaultValue - self.ReadOnly = readonly - self.Widget = None # type: remi.gui.SpinBox - super().__init__( - ELEM_TYPE_INPUT_SPIN, - size, - auto_size_text, - font=font, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - return - - def Update(self, value=None, values=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): - if value is not None: - self.Widget.set_value(value) - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def Get(self): - return self.Widget.get_value() - - get = Get - update = Update - - -# ---------------------------------------------------------------------- # -# Multiline # -# ---------------------------------------------------------------------- # -class Multiline(Element): - def __init__( - self, - default_text='', - enter_submits=False, - disabled=False, - autoscroll=False, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - do_not_clear=True, - key=None, - write_only=False, - focus=False, - font=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - ''' - Multiline Element - :param default_text: - :param enter_submits: - :param disabled: - :param autoscroll: - :param size: - :param auto_size_text: - :param background_color: - :param text_color: - :param do_not_clear: - :param key: - :param focus: - :param pad: - :param tooltip: - :param font: - ''' - self.DefaultText = default_text - self.EnterSubmits = enter_submits - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Autoscroll = autoscroll - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.WriteOnly = write_only - if size[0] is not None and size[0] < 100: - size = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] - self.Widget = None # type: remi.gui.TextInput - - super().__init__( - ELEM_TYPE_INPUT_MULTILINE, - size=size, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - font=font or DEFAULT_FONT, - visible=visible, - size_px=size_px, - ) - return - - def _InputTextCallback(self, widget: remi.Widget, value, keycode): - # print(f'text widget value = {widget.get_value()}') - self.ParentForm.LastButtonClicked = chr(int(keycode)) - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - def Update( - self, - value=None, - disabled=None, - append=False, - background_color=None, - text_color=None, - font=None, - visible=None, - autoscroll=None, - ): - if value is not None and not append: - self.Widget.set_value(value) - elif value is not None and append: - text = self.Widget.get_value() + str(value) - self.Widget.set_value(text) - # if background_color is not None: - # self.WxTextCtrl.SetBackgroundColour(background_color) - # if text_color is not None: - # self.WxTextCtrl.SetForegroundColour(text_color) - # if font is not None: - # self.WxTextCtrl.SetFont(font) - # if disabled: - # self.WxTextCtrl.Enable(True) - # elif disabled is False: - # self.WxTextCtrl.Enable(False) - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def print(self, *args, end=None, sep=None, text_color=None, background_color=None): - """ - Print like Python normally prints except route the output to a multline element and also add colors if desired - - :param args: List[Any] The arguments to print - :param end: (str) The end char to use just like print uses - :param sep: (str) The separation character like print uses - :param text_color: The color of the text - :param background_color: The background color of the line - """ - _print_to_element(self, *args, end=end, sep=sep, text_color=text_color, background_color=background_color) - - update = Update - - -ML = Multiline -MLine = Multiline - - -# ---------------------------------------------------------------------- # -# Multiline Output # -# ---------------------------------------------------------------------- # -class MultilineOutput(Element): - def __init__( - self, - default_text='', - enter_submits=False, - disabled=False, - autoscroll=False, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - do_not_clear=True, - key=None, - focus=False, - font=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - ''' - Multiline Element - :param default_text: - :param enter_submits: - :param disabled: - :param autoscroll: - :param size: - :param auto_size_text: - :param background_color: - :param text_color: - :param do_not_clear: - :param key: - :param focus: - :param pad: - :param tooltip: - :param font: - ''' - self.DefaultText = default_text - self.EnterSubmits = enter_submits - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Autoscroll = autoscroll - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - tsize = size # convert tkinter size to pixels - if size[0] is not None and size[0] < 100: - tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] - self.Widget = None # type: remi.gui.TextInput - self.CurrentValue = '' - - super().__init__( - ELEM_TYPE_MULTILINE_OUTPUT, - size=tsize, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - font=font or DEFAULT_FONT, - visible=visible, - size_px=size_px, - ) - return - - def Update( - self, - value=None, - disabled=None, - append=False, - background_color=None, - text_color=None, - font=None, - visible=None, - autoscroll=None, - ): - autoscroll = self.Autoscroll if autoscroll is None else autoscroll - if value is not None and not append: - self.Widget.set_value(str(value)) - self.CurrentValue = str(value) - elif value is not None and append: - self.CurrentValue = self.CurrentValue + str(value) - self.Widget.set_value(self.CurrentValue) - self.Widget._set_updated() - app = self.ParentForm.App - - if hasattr(app, 'websockets'): - app.execute_javascript( - 'element=document.getElementById("%(id)s"); element.innerHTML=`%(content)s`; if(%(autoscroll)s){element.scrollTop=999999;} ' - % { - 'id': self.Widget.identifier, - 'content': self.Widget.get_value(), - 'autoscroll': 'true' if autoscroll else 'false', - } - ) - - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def print(self, *args, end=None, sep=None, text_color=None, background_color=None): - """ - Print like Python normally prints except route the output to a multline element and also add colors if desired - - :param args: List[Any] The arguments to print - :param end: (str) The end char to use just like print uses - :param sep: (str) The separation character like print uses - :param text_color: The color of the text - :param background_color: The background color of the line - """ - _print_to_element(self, *args, end=end, sep=sep, text_color=text_color, background_color=background_color) - - update = Update - - -# ---------------------------------------------------------------------- # -# Text # -# ---------------------------------------------------------------------- # -class Text(Element): - def __init__( - self, - text='', - size=(None, None), - auto_size_text=None, - click_submits=None, - enable_events=False, - relief=None, - border_width=None, - font=None, - text_color=None, - background_color=None, - justification=None, - pad=None, - margins=None, - key=None, - tooltip=None, - visible=True, - size_px=(None, None), - metadata=None, - ): - """ - Text - :param text: - :param size: - :param auto_size_text: - :param click_submits: - :param enable_events: - :param relief: - :param font: - :param text_color: - :param background_color: - :param justification: - :param pad: - :param margins: - :param key: - :param tooltip: - :param visible: - :param size_px: - """ - self.DisplayText = str(text) - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.Justification = justification - self.Relief = relief - self.ClickSubmits = click_submits or enable_events - self.Margins = margins - self.size_px = size_px - if background_color is None: - bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - else: - bg = background_color - pixelsize = size - if size[1] is not None and size[1] < 10: - pixelsize = size[0] * 10, size[1] * 20 - self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH - self.Disabled = False - self.Widget = None # type: remi.gui.Label - - super().__init__( - ELEM_TYPE_TEXT, - pixelsize, - auto_size_text, - background_color=bg, - font=font if font else DEFAULT_FONT, - text_color=self.TextColor, - pad=pad, - key=key, - tooltip=tooltip, - size_px=size_px, - visible=visible, - metadata=metadata, - ) - return - - def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): - if value is not None: - self.Widget.set_text(str(value)) - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - update = Update - - -# ------------------------- Text Element lazy functions ------------------------- # -Txt = Text -T = Text - - -# ---------------------------------------------------------------------- # -# Output # -# Routes stdout, stderr to a scrolled window # -# ---------------------------------------------------------------------- # -class Output(Element): - def __init__( - self, - size=(None, None), - background_color=None, - text_color=None, - pad=None, - font=None, - tooltip=None, - key=None, - visible=True, - size_px=(None, None), - disabled=False, - ): - ''' - Output Element - :param size: - :param background_color: - :param text_color: - :param pad: - :param font: - :param tooltip: - :param key: - ''' - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - # fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - fg = text_color if text_color is not None else 'black' if DEFAULT_INPUT_TEXT_COLOR == COLOR_SYSTEM_DEFAULT else DEFAULT_INPUT_TEXT_COLOR - self.Disabled = disabled - self.Widget = None # type: remi.gui.TextInput - if size_px == (None, None) and size == (None, None): - size = DEFAULT_OUTPUT_ELEMENT_SIZE - if size[0] is not None and size[0] < 100: - size = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] - super().__init__( - ELEM_TYPE_OUTPUT, - size=size, - size_px=size_px, - visible=visible, - background_color=bg, - text_color=fg, - pad=pad, - font=font, - tooltip=tooltip, - key=key, - ) - - def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None): - if value is not None and not append: - self.Widget.set_value(str(value)) - self.CurrentValue = str(value) - elif value is not None and append: - self.CurrentValue = self.CurrentValue + '\n' + str(value) - self.Widget.set_value(self.CurrentValue) - self.Widget._set_updated() - app = self.ParentForm.App - if hasattr(app, 'websockets'): - app.execute_javascript('element=document.getElementById("%(id)s"); element.innerHTML=`%(content)s`; element.scrollTop=999999; ' % {'id': self.Widget.identifier, 'content': self.Widget.get_value()}) - - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - update = Update - - -# ---------------------------------------------------------------------- # -# Button Class # -# ---------------------------------------------------------------------- # -class Button(Element): - def __init__( - self, - button_text='', - button_type=BUTTON_TYPE_READ_FORM, - target=(None, None), - tooltip=None, - file_types=(('ALL Files', '*'),), - initial_folder=None, - disabled=False, - change_submits=False, - enable_events=False, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - visible=True, - size_px=(None, None), - ): - ''' - Button Element - :param button_text: - :param button_type: - :param target: - :param tooltip: - :param file_types: - :param initial_folder: - :param disabled: - :param image_filename: - :param image_size: - :param image_subsample: - :param border_width: - :param size: - :param auto_size_button: - :param button_color: - :param default_value: - :param font: - :param bind_return_key: - :param focus: - :param pad: - :param key: - ''' - self.AutoSizeButton = auto_size_button - self.BType = button_type - self.FileTypes = file_types - self.TKButton = None - self.Target = target - self.ButtonText = str(button_text) - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.TextColor = self.ButtonColor[0] - self.BackgroundColor = self.ButtonColor[1] - self.ImageFilename = image_filename - self.ImageData = image_data - self.ImageSize = image_size - self.ImageSubsample = image_subsample - self.UserData = None - self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH - self.BindReturnKey = bind_return_key - self.Focus = focus - self.TKCal = None - self.CalendarCloseWhenChosen = None - self.DefaultDate_M_D_Y = (None, None, None) - self.InitialFolder = initial_folder - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.QT_QPushButton = None - self.ColorChosen = None - self.Relief = None - # self.temp_size = size if size != (NONE, NONE) else - self.Widget = None # type: remi.gui.Button - super().__init__( - ELEM_TYPE_BUTTON, - size=size, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - text_color=self.TextColor, - background_color=self.BackgroundColor, - visible=visible, - size_px=size_px, - ) - return - - # ------- Button Callback ------- # - def _ButtonCallBack(self, event): - - # print('Button callback') - - # print(f'Parent = {self.ParentForm} Position = {self.Position}') - # Buttons modify targets or return from the form - # If modifying target, get the element object at the target and modify its StrVar - target = self.Target - target_element = None - if target[0] == ThisRow: - target = [self.Position[0], target[1]] - if target[1] < 0: - target[1] = self.Position[1] + target[1] - strvar = None - should_submit_window = False - if target == (None, None): - strvar = self.TKStringVar - else: - if not isinstance(target, str): - if target[0] < 0: - target = [self.Position[0] + target[0], target[1]] - target_element = self.ParentContainer._GetElementAtLocation(target) - else: - target_element = self.ParentForm.FindElement(target) - try: - strvar = target_element.TKStringVar - except: - pass - try: - if target_element.ChangeSubmits: - should_submit_window = True - except: - pass - filetypes = (('ALL Files', '*'),) if self.FileTypes is None else self.FileTypes - if self.BType == BUTTON_TYPE_BROWSE_FOLDER: # Browse Folder - wx_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.DirDialog(self.ParentForm.MasterFrame, style=wx.FD_OPEN) - else: - dialog = wx.DirDialog(self.ParentForm.MasterFrame) - folder_name = '' - if dialog.ShowModal() == wx.ID_OK: - folder_name = dialog.GetPath() - if folder_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = folder_name - else: - target_element.Update(folder_name) - elif self.BType == BUTTON_TYPE_BROWSE_FILE: # Browse File - qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_OPEN) - else: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_OPEN) - file_name = '' - if dialog.ShowModal() == wx.ID_OK: - file_name = dialog.GetPath() - else: - file_name = '' - if file_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_name - else: - target_element.Update(file_name) - elif self.BType == BUTTON_TYPE_BROWSE_FILES: # Browse Files - qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_MULTIPLE) - else: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_MULTIPLE) - file_names = '' - if dialog.ShowModal() == wx.ID_OK: - file_names = dialog.GetPaths() - else: - file_names = '' - if file_names != '': - file_names = BROWSE_FILES_DELIMITER.join(file_names) - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_names - else: - target_element.Update(file_names) - elif self.BType == BUTTON_TYPE_SAVEAS_FILE: # Save As File - qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.FileDialog( - self.ParentForm.MasterFrame, - defaultDir=self.InitialFolder, - wildcard=qt_types, - style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, - ) - else: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) - file_name = '' - if dialog.ShowModal() == wx.ID_OK: - file_name = dialog.GetPath() - else: - file_name = '' - if file_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_name - else: - target_element.Update(file_name) - elif self.BType == BUTTON_TYPE_COLOR_CHOOSER: # Color Chooser - qcolor = QColorDialog.getColor() - rgb_color = qcolor.getRgb() - color = '#' + ''.join('%02x' % i for i in rgb_color[:3]) - if self.Target == (None, None): - self.FileOrFolderName = color - else: - target_element.Update(color) - elif self.BType == BUTTON_TYPE_CLOSES_WIN: # Closes Window - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = False - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.App.ExitMainLoop() - self.ParentForm.IgnoreClose = True - self.ParentForm.MasterFrame.Close() - if self.ParentForm.NonBlocking: - Window._DecrementOpenCount() - self.ParentForm._Close() - elif self.BType == BUTTON_TYPE_READ_FORM: # Read Button - # first, get the results table built - # modify the Results table in the parent FlexForm object - # if self.Key is not None: - # self.ParentForm.LastButtonClicked = self.Key - # else: - # self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = True - element_callback_quit_mainloop(self) - elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop - element_callback_quit_mainloop(self) - self.ParentForm._Close() - Window._DecrementOpenCount() - elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window - should_submit_window = False - - if should_submit_window: - self.ParentForm.LastButtonClicked = target_element.Key - self.ParentForm.FormRemainedOpen = True - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - return - - def Update( - self, - text=None, - button_color=(None, None), - disabled=None, - image_data=None, - image_filename=None, - font=None, - visible=None, - image_subsample=None, - image_size=(None, None), - ): - if text is not None: - self.Widget.set_text(str(text)) - fg, bg = button_color - if image_data: - self.Widget.empty() - simage = SuperImage(image_data) - if image_size is not (None, None): - simage.set_size(image_size[0], image_size[1]) - self.Widget.append(simage) - if image_filename: - self.Widget.empty() - simage = SuperImage(image_filename) - if image_size is not (None, None): - simage.set_size(image_size[0], image_size[1]) - self.Widget.append(simage) - - super().Update(self.Widget, background_color=bg, text_color=fg, disabled=disabled, font=font, visible=visible) - - def GetText(self): - return self.Widget.get_text() - - get_text = GetText - update = Update - - -# ------------------------- Button lazy functions ------------------------- # -B = Button -Btn = Button -Butt = Button - - -def convert_tkinter_filetypes_to_wx(filetypes): - wx_filetypes = '' - for item in filetypes: - filetype = item[0] + ' (' + item[1] + ')|' + item[1] - wx_filetypes += filetype - return wx_filetypes - - -# ---------------------------------------------------------------------- # -# ProgreessBar # -# ---------------------------------------------------------------------- # -class ProgressBar(Element): - def __init__( - self, - max_value, - orientation=None, - size=(None, None), - auto_size_text=None, - bar_color=(None, None), - style=None, - border_width=None, - relief=None, - key=None, - pad=None, - ): - ''' - ProgressBar Element - :param max_value: - :param orientation: - :param size: - :param auto_size_text: - :param bar_color: - :param style: - :param border_width: - :param relief: - :param key: - :param pad: - ''' - self.MaxValue = max_value - self.TKProgressBar = None - self.Cancelled = False - self.NotRunning = True - self.Orientation = orientation if orientation else DEFAULT_METER_ORIENTATION - self.BarColor = bar_color - self.BarStyle = style if style else DEFAULT_PROGRESS_BAR_STYLE - self.BorderWidth = border_width if border_width else DEFAULT_PROGRESS_BAR_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF - self.BarExpired = False - super().__init__(ELEM_TYPE_PROGRESS_BAR, size=size, auto_size_text=auto_size_text, key=key, pad=pad) - - # returns False if update failed - def UpdateBar(self, current_count, max=None): - print('*** NOT IMPLEMENTED ***') - return - if self.ParentForm.TKrootDestroyed: - return False - self.TKProgressBar.Update(current_count, max=max) - try: - self.ParentForm.TKroot.update() - except: - _my_windows.Decrement() - return False - return True - - update_bar = UpdateBar - - -# ---------------------------------------------------------------------- # -# Image # -# ---------------------------------------------------------------------- # -class Image(Element): - def __init__( - self, - filename=None, - data=None, - background_color=None, - size=(None, None), - pad=None, - key=None, - tooltip=None, - right_click_menu=None, - visible=True, - enable_events=False, - ): - ''' - Image Element - :param filename: - :param data: - :param background_color: - :param size: - :param pad: - :param key: - :param tooltip: - ''' - self.Filename = filename if filename else None # note that Remi expects a / at the front of resource files - self.Data = data - self.tktext_label = None - self.BackgroundColor = background_color - self.Disabled = False - self.EnableEvents = enable_events - sz = (0, 0) if size == (None, None) else size - self.Widget = None # type: SuperImage - # if data is None and filename is None: # it is OK to have no image specified when intially creating - # print('* Warning... no image specified in Image Element! *') - super().__init__( - ELEM_TYPE_IMAGE, - size=sz, - background_color=background_color, - pad=pad, - key=key, - tooltip=tooltip, - visible=visible, - ) - return - - def Update(self, filename=None, data=None, size=(None, None), visible=None): - if data is not None: - self.Widget.load(data) - # decoded = base64.b64decode(data) - # with open(r'.\decoded.out', 'wb') as f: - # f.write(decoded) - # filename = r'.\decoded.out' - if filename is not None: - self.Widget.load(filename) - # self.Widget.set_image(filename=filename) - # if size != (None, None): - # self.Widget.style['height'] = '{}px'.format(size[1]) - # self.Widget.style['width'] = '{}px'.format(size[0]) - super().Update(self.Widget, visible=visible) - - update = Update - - -# class SuperImageOld(remi.gui.Image): -# def __init__(self, file_path_name=None, **kwargs): -# image = file_path_name -# super(SuperImage, self).__init__(image, **kwargs) -# -# self.imagedata = None -# self.mimetype = None -# self.encoding = None -# if image is None: -# return -# self.load(image) -# -# def load(self, file_path_name): -# if type(file_path_name) is bytes or len(file_path_name) > 200: -# try: -# self.imagedata = base64.b64decode(file_path_name, validate=True) -# except binascii.Error: -# self.imagedata = file_path_name -# else: -# self.mimetype, self.encoding = mimetypes.guess_type(file_path_name) -# with open(file_path_name, 'rb') as f: -# self.imagedata = f.read() -# self.refresh() -# -# def refresh(self): -# i = int(time.time() * 1e6) -# self.attributes['src'] = "/%s/get_image_data?update_index=%d" % (id(self), i) -# -# def get_image_data(self, update_index): -# headers = {'Content-type': self.mimetype if self.mimetype else 'application/octet-stream'} -# return [self.imagedata, headers] - - -class SuperImage(remi.gui.Image): - def __init__(self, file_path_name=None, **kwargs): - """ - This new app_instance variable is causing lots of problems. I do not know the value of the App - when I create this image. - :param app_instance: - :param file_path_name: - :param kwargs: - """ - # self.app_instance = app_instance - image = file_path_name - super(SuperImage, self).__init__(image, **kwargs) - - self.imagedata = None - self.mimetype = None - self.encoding = None - if not image: - return - self.load(image) - - def load(self, file_path_name): - if type(file_path_name) is bytes: - try: - # here a base64 image is received - self.imagedata = base64.b64decode(file_path_name, validate=True) - self.attributes['src'] = '/%s/get_image_data?update_index=%s' % (id(self), str(time.time())) - except binascii.Error: - # here an image data is received (opencv image) - self.imagedata = file_path_name - self.refresh() - self.refresh() - else: - # here a filename is received - self.attributes['src'] = remi.gui.load_resource(file_path_name) - """print(f'***** Loading file = {file_path_name}') - self.mimetype, self.encoding = mimetypes.guess_type(file_path_name) - with open(file_path_name, 'rb') as f: - self.imagedata = f.read()""" - self.refresh() - - def refresh(self): - i = int(time.time() * 1e6) - # self.app_instance.execute_javascript(""" - if Window.App is not None: - Window.App.execute_javascript( - """ - var url = '/%(id)s/get_image_data?update_index=%(frame_index)s'; - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = 'blob' - xhr.onload = function(e){ - var urlCreator = window.URL || window.webkitURL; - var imageUrl = urlCreator.createObjectURL(this.response); - document.getElementById('%(id)s').src = imageUrl; - } - xhr.send(); - """ - % {'id': id(self), 'frame_index': i} - ) - - def get_image_data(self, update_index): - headers = {'Content-type': self.mimetype if self.mimetype else 'application/octet-stream'} - return [self.imagedata, headers] - - -# ---------------------------------------------------------------------- # -# Graph # -# ---------------------------------------------------------------------- # -class Graph(Element): - def __init__( - self, - canvas_size, - graph_bottom_left, - graph_top_right, - background_color=None, - pad=None, - change_submits=False, - drag_submits=False, - size_px=(None, None), - enable_events=False, - key=None, - visible=True, - disabled=False, - tooltip=None, - ): - ''' - Graph Element - :param canvas_size: - :param graph_bottom_left: - :param graph_top_right: - :param background_color: - :param pad: - :param key: - :param tooltip: - ''' - self.CanvasSize = canvas_size - self.BottomLeft = graph_bottom_left - self.TopRight = graph_top_right - self.ChangeSubmits = change_submits or enable_events - self.DragSubmits = drag_submits - self.ClickPosition = (None, None) - self.MouseButtonDown = False - self.Disabled = disabled - self.Widget = None # type: remi.gui.Svg - self.SvgGroup = None # type: remi.gui.SvgSubcontainer - super().__init__( - ELEM_TYPE_GRAPH, - size=canvas_size, - size_px=size_px, - visible=visible, - background_color=background_color, - pad=pad, - tooltip=tooltip, - key=key, - ) - return - - def _convert_xy_to_canvas_xy(self, x_in, y_in): - if None in (x_in, y_in): - return None, None - scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) - scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) - new_x = 0 + scale_x * (x_in - self.BottomLeft[0]) - new_y = self.CanvasSize[1] + scale_y * (y_in - self.BottomLeft[1]) - return new_x, new_y - - def _convert_canvas_xy_to_xy(self, x_in, y_in): - if None in (x_in, y_in): - return None, None - x_in, y_in = int(x_in), int(y_in) - scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) - scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) - new_x = x_in / scale_x + self.BottomLeft[0] - new_y = (y_in - self.CanvasSize[1]) / scale_y + self.BottomLeft[1] - return int(new_x), int(new_y) - - def DrawLine(self, point_from, point_to, color='black', width=1): - if point_from == (None, None) or color is None: - return - converted_point_from = self._convert_xy_to_canvas_xy(point_from[0], point_from[1]) - converted_point_to = self._convert_xy_to_canvas_xy(point_to[0], point_to[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - line = remi.gui.SvgLine(converted_point_from[0], converted_point_from[1], converted_point_to[0], converted_point_to[1]) - line.set_stroke(width, color) - self.SvgGroup.append( - [ - line, - ] - ) - return line - - def DrawPoint(self, point, size=2, color='black'): - if point == (None, None): - return - converted_point = self._convert_xy_to_canvas_xy(point[0], point[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - rpoint = remi.gui.SvgCircle(converted_point[0], converted_point[1], size) - rpoint.set_stroke(size, color) - rpoint.set_fill(color) - self.SvgGroup.append( - [ - rpoint, - ] - ) - return rpoint - - def DrawCircle(self, center_location, radius, fill_color=None, line_color='black'): - if center_location == (None, None): - return - converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - rpoint = remi.gui.SvgCircle(converted_point[0], converted_point[1], radius=radius) - rpoint.set_fill(fill_color) - rpoint.set_stroke(color=line_color) - self.SvgGroup.append( - [ - rpoint, - ] - ) - return rpoint - - def DrawOval(self, top_left, bottom_right, fill_color=None, line_color=None): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return - - # def DrawArc(self, top_left, bottom_right, extent, start_angle, style=None, arc_color='black'): - # converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - # converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - # tkstyle = tk.PIESLICE if style is None else style - # if self._TKCanvas2 is None: - # print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - # print('Call Window.Finalize() prior to this operation') - # return None - # return - - def DrawRectangle(self, top_left, bottom_right, fill_color=None, line_color='black'): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - - rpoint = remi.gui.SvgRectangle( - converted_top_left[0], - converted_top_left[1], - abs(converted_bottom_right[0] - converted_top_left[0]), - abs(converted_top_left[1] - converted_bottom_right[1]), - ) - rpoint.set_stroke(width=1, color=line_color) - if fill_color is not None: - rpoint.set_fill(fill_color) - else: - rpoint.set_fill('transparent') - self.SvgGroup.append( - [ - rpoint, - ] - ) - return rpoint - - def DrawText(self, text, location, color='black', font=None, angle=0): - text = str(text) - if location == (None, None): - return - converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - - rpoint = remi.gui.SvgText(converted_point[0], converted_point[1], text) - self.SvgGroup.append( - [ - rpoint, - ] - ) - # self.SvgGroup.redraw() - return rpoint - - def DrawImage(self, data=None, image_source=None, location=(None, None), size=(100, 100)): - if location == (None, None): - return - if data is not None: - image_source = data.decode('utf-8') if type(data) is bytes else data - converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - - rpoint = remi.gui.SvgImage('', converted_point[0], converted_point[0], size[0], size[1]) - - if type(image_source) is bytes or len(image_source) > 200: - # rpoint.set_image("data:image/svg;base64,%s"%image_source) - rpoint.image_data = 'data:image/svg;base64,%s' % image_source - else: - mimetype, encoding = mimetypes.guess_type(image_source) - with open(image_source, 'rb') as f: - data = f.read() - b64 = base64.b64encode(data) - b64_str = b64.decode('utf-8') - image_string = 'data:image/svg;base64,%s' % b64_str - # rpoint.set_image(image_string) - rpoint.image_data = image_string - self.SvgGroup.append( - [ - rpoint, - ] - ) - rpoint.redraw() - self.SvgGroup.redraw() - return rpoint - - def Erase(self): - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - self.Widget.empty() - self.SvgGroup = remi.gui.SvgSubcontainer(0, 0, '100%', '100%') - self.Widget.append(self.SvgGroup) - - def Update(self, background_color): - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - if self.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - self.Widget.style['background-color'] = self.BackgroundColor - - def Move(self, x_direction, y_direction): - # TODO - IT's still not working yet! I'm trying!! - - # self.MoveFigure(self.SvgGroup, x_direction,y_direction) - # return - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - print(self.SvgGroup.attributes) - cur_x = float(self.SvgGroup.attributes['x']) - cur_y = float(self.SvgGroup.attributes['y']) - self.SvgGroup.set_position(cur_x - x_direction, cur_y - y_direction) - self.SvgGroup.redraw() - - def Relocate(self, x, y): - shift_converted = self._convert_xy_to_canvas_xy(x, y) - if self.Widget is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - # figure.empty() - self.SvgGroup.set_position(shift_converted[0], shift_converted[1]) - self.SvgGroup.redraw() - - def MoveFigure(self, figure, x_direction, y_direction): - figure = figure # type: remi.gui.SvgCircle - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - if figure is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - print(figure.attributes) - try: - cur_x = float(figure.attributes['x']) - cur_y = float(figure.attributes['y']) - figure.set_position(cur_x - x_direction, cur_y - y_direction) - except: - cur_x1 = float(figure.attributes['x1']) - cur_x2 = float(figure.attributes['x2']) - cur_y1 = float(figure.attributes['y1']) - cur_y2 = float(figure.attributes['y2']) - figure.set_coords(cur_x1 - x_direction, cur_y1 - y_direction, cur_x2 - x_direction, cur_y2 - x_direction) - figure.redraw() - - def RelocateFigure(self, figure, x, y): - figure = figure # type: remi.gui.SvgCircle - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x, y) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - if figure is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - # figure.empty() - figure.set_position(shift_converted[0], shift_converted[1]) - figure.redraw() - - def DeleteFigure(self, figure): - figure = figure # type: remi.gui.SvgCircle - if figure is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - self.SvgGroup.remove_child(figure) - del figure - - def change_coordinates(self, graph_bottom_left, graph_top_right): - """ - Changes the corrdinate system to a new one. The same 2 points in space are used to define the coorinate - system - the bottom left and the top right values of your graph. - - :param graph_bottom_left: Tuple[int, int] (x,y) The bottoms left corner of your coordinate system - :param graph_top_right: Tuple[int, int] (x,y) The top right corner of your coordinate system - """ - self.BottomLeft = graph_bottom_left - self.TopRight = graph_top_right - - def _MouseDownCallback(self, widget, x, y, *args): - # print(f'Mouse down {x,y}') - self.MouseButtonDown = True - - def _MouseUpCallback(self, widget, x, y, *args): - self.ClickPosition = self._convert_canvas_xy_to_xy(int(x), int(y)) - self.MouseButtonDown = False - if self.ChangeSubmits: - # self.ClickPosition = (None, None) - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - # def ClickCallback(self, emitter, x, y): - def ClickCallback(self, widget: remi.gui.Svg, *args): - return - self.ClickPosition = (None, None) - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - def _DragCallback(self, emitter, x, y): - if not self.MouseButtonDown: # only return drag events when mouse is down - return - # print(f'In Drag Callback') - self.ClickPosition = self._convert_canvas_xy_to_xy(x, y) - # print(f'Position {self.ClickPosition}') - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - click_callback = ClickCallback - delete_figure = DeleteFigure - draw_circle = DrawCircle - draw_image = DrawImage - draw_line = DrawLine - draw_oval = DrawOval - draw_point = DrawPoint - draw_rectangle = DrawRectangle - draw_text = DrawText - erase = Erase - move = Move - move_figure = MoveFigure - relocate = Relocate - relocate_figure = RelocateFigure - update = Update - - -# ---------------------------------------------------------------------- # -# Frame # -# ---------------------------------------------------------------------- # - -# First the REMI implementation of a frame - - -class CLASSframe(remi.gui.VBox): - def __init__(self, title, *args, **kwargs): - super(CLASSframe, self).__init__(*args, **kwargs) - self.style.update({'overflow': 'visible', 'border-width': '1px', 'border-style': 'solid', 'border-color': '#7d7d7d'}) - self.frame_label = remi.gui.Label('frame label') - self.frame_label.style.update( - { - 'position': 'relative', - 'overflow': 'auto', - 'background-color': '#ffffff', - 'border-width': '1px', - 'border-style': 'solid', - 'top': '-7px', - 'width': '0px', - 'height': '0px', - 'left': '10px', - } - ) - self.append(self.frame_label, 'frame_label') - self.set_title(title) - - def set_title(self, title): - self.frame_label.set_text(title) - - -class Frame(Element): - def __init__( - self, - title, - layout, - title_color=None, - background_color=None, - title_location=None, - relief=DEFAULT_FRAME_RELIEF, - element_justification='left', - size=(None, None), - font=None, - pad=None, - border_width=None, - key=None, - tooltip=None, - ): - ''' - Frame Element - :param title: - :param layout: - :param title_color: - :param background_color: - :param title_location: - :param relief: - :param size: - :param font: - :param pad: - :param border_width: - :param key: - :param tooltip: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - # self.ParentForm = None - self.TKFrame = None - self.Title = title - self.Relief = relief - self.TitleLocation = title_location - self.BorderWidth = border_width - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.Justification = 'left' - self.ElementJustification = element_justification - self.Widget = None # type: CLASSframe - - self.Layout(layout) - - super().__init__( - ELEM_TYPE_FRAME, - background_color=background_color, - text_color=title_color, - size=size, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - ) - return - - def AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - add_row = AddRow - layout = Layout - - -# ---------------------------------------------------------------------- # -# Separator # -# Routes stdout, stderr to a scrolled window # -# ---------------------------------------------------------------------- # -class VerticalSeparator(Element): - def __init__(self, pad=None): - ''' - VerticalSeperator - A separator that spans only 1 row in a vertical fashion - :param pad: - ''' - self.Orientation = 'vertical' # for now only vertical works - - super().__init__(ELEM_TYPE_SEPARATOR, pad=pad) - - -VSeperator = VerticalSeparator -VSeparator = VerticalSeparator -VSep = VerticalSeparator - - -# ---------------------------------------------------------------------- # -# Tab # -# ---------------------------------------------------------------------- # -class Tab(Element): - def __init__( - self, - title, - layout, - title_color=None, - background_color=None, - font=None, - pad=None, - disabled=False, - element_justification='left', - border_width=None, - key=None, - tooltip=None, - ): - ''' - Tab Element - :param title: - :param layout: - :param title_color: - :param background_color: - :param font: - :param pad: - :param disabled: - :param border_width: - :param key: - :param tooltip: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.TKFrame = None - self.Title = title - self.BorderWidth = border_width - self.Disabled = disabled - self.ParentNotebook = None - self.Justification = 'left' - self.ElementJustification = element_justification - self.TabID = None - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.Widget = None # type: remi.gui.HBox - self._Layout(layout) - - super().__init__( - ELEM_TYPE_TAB, - background_color=background_color, - text_color=title_color, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - ) - return - - def _AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def _Layout(self, rows): - for row in rows: - self._AddRow(*row) - return self - - # def Update(self, disabled=None): # TODO Disable / enable of tabs is not complete - # print('*** Tab.Update is not implemented ***') - # return - # if disabled is None: - # return - # self.Disabled = disabled - # state = 'disabled' if disabled is True else 'normal' - # self.ParentNotebook.tab(self.TabID, state=state) - # return self - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - -# ---------------------------------------------------------------------- # -# TabGroup # -# ---------------------------------------------------------------------- # -class TabGroup(Element): - def __init__( - self, - layout, - tab_location=None, - title_color=None, - selected_title_color=None, - background_color=None, - font=None, - change_submits=False, - enable_events=False, - pad=None, - border_width=None, - theme=None, - key=None, - tooltip=None, - visible=True, - ): - ''' - TabGroup Element - :param layout: - :param tab_location: - :param title_color: - :param selected_title_color: - :param background_color: - :param font: - :param change_submits: - :param pad: - :param border_width: - :param theme: - :param key: - :param tooltip: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.SelectedTitleColor = selected_title_color - self.Rows = [] - self.TKNotebook = None - self.Widget = None # type: remi.gui.TabBox - self.Justification = 'left' - self.TabCount = 0 - self.BorderWidth = border_width - self.Theme = theme - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.ChangeSubmits = enable_events or change_submits - self.TabLocation = tab_location - self.Visible = visible - self.Disabled = False - self._Layout(layout) - - super().__init__( - ELEM_TYPE_TAB_GROUP, - background_color=background_color, - text_color=title_color, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - ) - return - - def _AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def _Layout(self, rows): - for row in rows: - self._AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def FindKeyFromTabName(self, tab_name): - for row in self.Rows: - for element in row: - if element.Title == tab_name: - return element.Key - return None - - find_key_from_tab_name = FindKeyFromTabName - - -# ---------------------------------------------------------------------- # -# Slider # -# ---------------------------------------------------------------------- # -class Slider(Element): - def __init__( - self, - range=(None, None), - default_value=None, - resolution=None, - tick_interval=None, - orientation=None, - border_width=None, - relief=None, - change_submits=False, - enable_events=False, - disabled=False, - size=(None, None), - font=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - """ - - :param range: - :param default_value: - :param resolution: - :param tick_interval: - :param orientation: - :param border_width: - :param relief: - :param change_submits: - :param enable_events: - :param disabled: - :param visible: - :param size_px: - """ - self.TKScale = None - self.Range = (1, 10) if range == (None, None) else range - self.DefaultValue = self.Range[0] if default_value is None else default_value - self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION - self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF - self.Resolution = 1 if resolution is None else resolution - self.ChangeSubmits = change_submits or enable_events - self.Disabled = disabled - self.TickInterval = tick_interval - temp_size = size - if temp_size == (None, None): - temp_size = (200, 20) if self.Orientation.startswith('h') else (200, 20) - elif size[0] is not None and size[0] < 100: - temp_size = size[0] * 10, size[1] * 3 - self.Widget = None # type: remi.gui.Slider - - super().__init__( - ELEM_TYPE_INPUT_SLIDER, - size=temp_size, - font=font, - background_color=background_color, - text_color=text_color, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - return - - def Update(self, value=None, range=(None, None), disabled=None, visible=None): - if value is not None: - self.Widget.set_value(value) - self.DefaultValue = value - if range != (None, None): - self.Widget.attributes['min'] = '{}'.format(range[0]) - self.Widget.attributes['max'] = '{}'.format(range[1]) - super().Update(self.Widget, disabled=disabled, visible=visible) - - def _SliderCallback(self, widget: remi.Widget, value): - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - update = Update - - -# -# ---------------------------------------------------------------------- # -# Column # -# ---------------------------------------------------------------------- # -class Column(Element): - def __init__( - self, - layout, - background_color=None, - size=(None, None), - pad=None, - scrollable=False, - vertical_scroll_only=False, - element_justification='left', - key=None, - ): - ''' - Column Element - :param layout: - :param background_color: - :param size: - :param pad: - :param scrollable: - :param key: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.TKFrame = None - self.Scrollable = scrollable - self.VerticalScrollOnly = vertical_scroll_only - self.ElementJustification = element_justification - # self.ImageFilename = image_filename - # self.ImageData = image_data - # self.ImageSize = image_size - # self.ImageSubsample = image_subsample - # bg = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - - self.Layout(layout) - - super().__init__(ELEM_TYPE_COLUMN, background_color=background_color, size=size, pad=pad, key=key) - return - - def AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - add_row = AddRow - layout = Layout - - -Col = Column - - -# ---------------------------------------------------------------------- # -# Menu # -# ---------------------------------------------------------------------- # -class Menu(Element): - def __init__( - self, - menu_definition, - background_color=COLOR_SYSTEM_DEFAULT, - text_color=None, - size=(None, None), - tearoff=False, - pad=None, - key=None, - disabled=False, - font=None, - ): - ''' - Menu Element - :param menu_definition: - :param background_color: - :param size: - :param tearoff: - :param pad: - :param key: - ''' - back_color = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.MenuDefinition = menu_definition - self.TKMenu = None - self.Tearoff = tearoff - self.Widget = None # type: remi.gui.MenuBar - self.MenuItemChosen = None - self.Disabled = disabled - - super().__init__( - ELEM_TYPE_MENUBAR, - background_color=back_color, - text_color=text_color, - size=size, - pad=pad, - key=key, - font=font, - ) - return - - def _ChangedCallbackMenu(self, widget, *user_data): - widget = widget # type: remi.gui.MenuItem - chosen = user_data[0] - self.MenuItemChosen = chosen - self.ParentForm.LastButtonClicked = chosen - self.ParentForm.MessageQueue.put(chosen) - - -# ---------------------------------------------------------------------- # -# Table # -# ---------------------------------------------------------------------- # -class Table(Element): - def __init__( - self, - values, - headings=None, - visible_column_map=None, - col_widths=None, - def_col_width=10, - auto_size_columns=True, - max_col_width=20, - select_mode=None, - display_row_numbers=False, - row_header_text='Row', - starting_row_num=0, - num_rows=None, - row_height=None, - font=None, - justification='right', - text_color=None, - background_color=None, - alternating_row_color=None, - row_colors=None, - vertical_scroll_only=True, - disabled=False, - size=(None, None), - change_submits=False, - enable_events=False, - bind_return_key=False, - pad=None, - key=None, - tooltip=None, - right_click_menu=None, - visible=True, - size_px=(None, None), - ): - ''' - Table - :param values: - :param headings: - :param visible_column_map: - :param col_widths: - :param def_col_width: - :param auto_size_columns: - :param max_col_width: - :param select_mode: - :param display_row_numbers: - :param num_rows: - :param row_height: - :param font: - :param justification: - :param text_color: - :param background_color: - :param alternating_row_color: - :param size: - :param change_submits: - :param enable_events: - :param bind_return_key: - :param pad: - :param key: - :param tooltip: - :param right_click_menu: - :param visible: - ''' - self.Values = values - self.ColumnHeadings = headings - self.ColumnsToDisplay = visible_column_map - self.ColumnWidths = col_widths - self.MaxColumnWidth = max_col_width - self.DefaultColumnWidth = def_col_width - self.AutoSizeColumns = auto_size_columns - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.TextColor = text_color - self.Justification = justification - self.InitialState = None - self.SelectMode = select_mode - self.DisplayRowNumbers = display_row_numbers - self.NumRows = num_rows if num_rows is not None else size[1] - self.RowHeight = row_height - self.TKTreeview = None - self.AlternatingRowColor = alternating_row_color - self.VerticalScrollOnly = vertical_scroll_only - self.SelectedRows = [] - self.ChangeSubmits = change_submits or enable_events - self.BindReturnKey = bind_return_key - self.StartingRowNumber = starting_row_num # When displaying row numbers, where to start - self.RowHeaderText = row_header_text - self.RightClickMenu = right_click_menu - self.RowColors = row_colors - self.Disabled = disabled - self.SelectedItem = None - self.SelectedRow = None - self.Widget = None # type: remi.Table - - super().__init__( - ELEM_TYPE_TABLE, - text_color=text_color, - background_color=background_color, - font=font, - size=size, - pad=pad, - key=key, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - return - - def Update(self, values=None): - print('*** Table Update not yet supported ***') - return - if values is not None: - children = self.TKTreeview.get_children() - for i in children: - self.TKTreeview.detach(i) - self.TKTreeview.delete(i) - children = self.TKTreeview.get_children() - # self.TKTreeview.delete(*self.TKTreeview.get_children()) - for i, value in enumerate(values): - if self.DisplayRowNumbers: - value = [i + self.StartingRowNumber] + value - id = self.TKTreeview.insert('', 'end', text=i, iid=i + 1, values=value, tag=i % 2) - if self.AlternatingRowColor is not None: - self.TKTreeview.tag_configure(1, background=self.AlternatingRowColor) - self.Values = values - self.SelectedRows = [] - - def _on_table_row_click(self, table, row, item): - # self.SelectedRow = row # type: remi.gui.TableRow - self.SelectedItem = item.get_text() - index = -1 - # each widget (and specifically in this case the table) has a _render_children_list attribute that - # is an ordered list of the children keys - # first, we search for the row in the children dictionary - for key, value in table.children.items(): - if value == row: - # if the row is found, we get the index in the ordered list - index = table._render_children_list.index(key) - break - self.SelectedRow = index - if self.ChangeSubmits: - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - else: - self.ParentForm.LastButtonClicked = '' - - -# ---------------------------------------------------------------------- # -# Tree # -# ---------------------------------------------------------------------- # -class Tree(Element): - def __init__( - self, - data=None, - headings=None, - visible_column_map=None, - col_widths=None, - col0_width=10, - def_col_width=10, - auto_size_columns=True, - max_col_width=20, - select_mode=None, - show_expanded=False, - change_submits=False, - font=None, - justification='right', - text_color=None, - background_color=None, - num_rows=None, - pad=None, - key=None, - tooltip=None, - ): - ''' - Tree Element - :param headings: - :param visible_column_map: - :param col_widths: - :param def_col_width: - :param auto_size_columns: - :param max_col_width: - :param select_mode: - :param font: - :param justification: - :param text_color: - :param background_color: - :param num_rows: - :param pad: - :param key: - :param tooltip: - ''' - self.TreeData = data - self.ColumnHeadings = headings - self.ColumnsToDisplay = visible_column_map - self.ColumnWidths = col_widths - self.MaxColumnWidth = max_col_width - self.DefaultColumnWidth = def_col_width - self.AutoSizeColumns = auto_size_columns - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.TextColor = text_color - self.Justification = justification - self.InitialState = None - self.SelectMode = select_mode - self.ShowExpanded = show_expanded - self.NumRows = num_rows - self.Col0Width = col0_width - self.TKTreeview = None - self.SelectedRows = [] - self.ChangeSubmits = change_submits - - print('*** Tree Element not yet supported ***') - - super().__init__( - ELEM_TYPE_TREE, - text_color=text_color, - background_color=background_color, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - ) - - def add_treeview_data(self, node): - # print(f'Inserting {node.key} under parent {node.parent}') - if node.key != '': - self.TKTreeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, open=self.ShowExpanded) - for node in node.children: - self.add_treeview_data(node) - - def Update(self, values=None, key=None, value=None, text=None): - print('*** Tree Element not yet supported ***') - if values is not None: - children = self.TKTreeview.get_children() - for i in children: - self.TKTreeview.detach(i) - self.TKTreeview.delete(i) - children = self.TKTreeview.get_children() - self.TreeData = values - self.add_treeview_data(self.TreeData.root_node) - self.SelectedRows = [] - if key is not None: - item = self.TKTreeview.item(key) - if value is not None: - self.TKTreeview.item(key, values=value) - if text is not None: - self.TKTreeview.item(key, text=text) - item = self.TKTreeview.item(key) - return self - - update = Update - - -class TreeData(object): - class Node(object): - def __init__(self, parent, key, text, values): - self.parent = parent - self.children = [] - self.key = key - self.text = text - self.values = values - - def _Add(self, node): - self.children.append(node) - - def __init__(self): - self.tree_dict = {} - self.root_node = self.Node('', '', 'root', []) - self.tree_dict[''] = self.root_node - - def _AddNode(self, key, node): - self.tree_dict[key] = node - - def Insert(self, parent, key, text, values): - node = self.Node(parent, key, text, values) - self.tree_dict[key] = node - parent_node = self.tree_dict[parent] - parent_node._Add(node) - - def __repr__(self): - return self._NodeStr(self.root_node, 1) - - def _NodeStr(self, node, level): - return '\n'.join([str(node.key) + ' : ' + str(node.text)] + [' ' * 4 * level + self._NodeStr(child, level + 1) for child in node.children]) - - insert = Insert - - -# ---------------------------------------------------------------------- # -# Error Element # -# ---------------------------------------------------------------------- # -class ErrorElement(Element): - def __init__(self, key=None): - ''' - Error Element - :param key: - ''' - self.Key = key - - super().__init__(ELEM_TYPE_ERROR, key=key) - return - - def Update(self, *args, **kwargs): - PopupError( - 'Keyword error in Update', - 'You need to stop this madness and check your spelling', - 'Bad key = {}'.format(self.Key), - 'Your bad line of code may resemble this:', - 'window.FindElement("{}")'.format(self.Key), - ) - return self - - def Get(self): - return 'This is NOT a valid Element!\nSTOP trying to do things with it or I will have to crash at some point!' - - get = Get - update = Update - - -# ------------------------------------------------------------------------- # -# Window CLASS # -# ------------------------------------------------------------------------- # -class Window: - - _NumOpenWindows = 0 - user_defined_icon = None - hidden_master_root = None - QTApplication = None - active_popups = {} - highest_level_app = None - stdout_is_rerouted = False - stdout_string_io = None - stdout_location = None - port_number = 6900 - active_windows = [] # type: [Window] - App = None # type: remi.App - - def __init__( - self, - title, - layout=None, - default_element_size=DEFAULT_ELEMENT_SIZE, - default_button_element_size=(None, None), - auto_size_text=None, - auto_size_buttons=None, - location=(None, None), - size=(None, None), - element_padding=None, - button_color=None, - font=None, - progress_bar_color=(None, None), - background_color=None, - border_depth=None, - auto_close=False, - auto_close_duration=None, - icon=DEFAULT_BASE64_ICON, - force_toplevel=False, - alpha_channel=1, - return_keyboard_events=False, - return_key_down_events=False, - use_default_focus=True, - text_justification=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - resizable=True, - disable_close=False, - margins=(None, None), - element_justification='left', - disable_minimize=False, - background_image=None, - finalize=False, - web_debug=False, - web_ip='0.0.0.0', - web_port=0, - web_start_browser=True, - web_update_interval=0.0000001, - web_multiple_instance=False, - ): - ''' - - :param title: - :param default_element_size: - :param default_button_element_size: - :param auto_size_text: - :param auto_size_buttons: - :param location: - :param size: - :param element_padding: - :param button_color: - :param font: - :param progress_bar_color: - :param background_color: - :param border_depth: - :param auto_close: - :param auto_close_duration: - :param icon: - :param force_toplevel: - :param alpha_channel: - :param return_keyboard_events: - :param use_default_focus: - :param text_justification: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param resizable: - :param disable_close: - :param background_image: - ''' - self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT - self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS - self.Title = title - self.Rows = [] # a list of ELEMENTS for this row - self.DefaultElementSize = convert_tkinter_size_to_Wx(default_element_size) - self.DefaultButtonElementSize = convert_tkinter_size_to_Wx(default_button_element_size) if default_button_element_size != (None, None) else DEFAULT_BUTTON_ELEMENT_SIZE - self.Location = location - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.BackgroundColor = background_color if background_color else DEFAULT_BACKGROUND_COLOR - self.ParentWindow = None - self.Font = font if font else DEFAULT_FONT - self.RadioDict = {} - self.BorderDepth = border_depth - self.WindowIcon = icon if icon is not None else Window.user_defined_icon - self.AutoClose = auto_close - self.NonBlocking = False - self.TKroot = None - self.TKrootDestroyed = False - self.CurrentlyRunningMainloop = False - self.FormRemainedOpen = False - self.TKAfterID = None - self.ProgressBarColor = progress_bar_color - self.AutoCloseDuration = auto_close_duration - self.RootNeedsDestroying = False - self.Shown = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.AllKeysDict = {} - self.LastButtonClicked = None - self.LastButtonClickedWasRealtime = False - self.UseDictionary = False - self.UseDefaultFocus = use_default_focus - self.ReturnKeyboardEvents = return_keyboard_events - self.ReturnKeyDownEvents = return_key_down_events - self.KeyInfoDict = {} - self.LastKeyboardEvent = None - self.TextJustification = text_justification - self.NoTitleBar = no_titlebar - self.GrabAnywhere = grab_anywhere - self.KeepOnTop = keep_on_top - self.ForcefTopLevel = force_toplevel - self.Resizable = resizable - self._AlphaChannel = alpha_channel - self.Timeout = None - self.TimeoutKey = TIMEOUT_KEY - self.TimerCancelled = False - self.DisableClose = disable_close - self._Hidden = False - # self.QTApplication = None - # self.QT_QMainWindow = None - self._Size = size - self.ElementPadding = element_padding or DEFAULT_ELEMENT_PADDING - self.FocusElement = None - self.BackgroundImage = background_image - self.XFound = False - self.DisableMinimize = disable_minimize - self.OutputElementForStdOut = None # type: Output - self.Justification = 'left' - self.ElementJustification = element_justification - self.IgnoreClose = False - self.thread_id = None - self.App = None # type: Window.MyApp - self.web_debug = web_debug - self.web_ip = web_ip - self.web_port = web_port - self.web_start_browser = web_start_browser - self.web_update_interval = web_update_interval - self.web_multiple_instance = web_multiple_instance - self.MessageQueue = Queue() - self.master_widget = None # type: remi.gui.VBox - self.UniqueKeyCounter = 0 - - if layout is not None: - self.Layout(layout) - if finalize: - self.Finalize() - - @classmethod - def IncrementOpenCount(self): - self._NumOpenWindows += 1 - # print('+++++ INCREMENTING Num Open Windows = {} ---'.format(Window._NumOpenWindows)) - - @classmethod - def _DecrementOpenCount(self): - self._NumOpenWindows -= 1 * (self._NumOpenWindows != 0) # decrement if not 0 - # print('----- DECREMENTING Num Open Windows = {} ---'.format(Window._NumOpenWindows)) - - # ------------------------- Add ONE Row to Form ------------------------- # - def AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - # ------------------------- Add Multiple Rows to Form ------------------------- # - def AddRows(self, rows): - for row in rows: - self.AddRow(*row) - - def Layout(self, rows): - self.AddRows(rows) - self._BuildKeyDict() - return self - - def LayoutAndRead(self, rows, non_blocking=False): - raise DeprecationWarning('LayoutAndRead is no longer supported... change your call to window.Layout(layout).Read()') - # self.AddRows(rows) - # self.Show(non_blocking=non_blocking) - # return self.ReturnValues - - def LayoutAndShow(self, rows): - raise DeprecationWarning('LayoutAndShow is no longer supported... change your call to LayoutAndRead') - - # ------------------------- ShowForm THIS IS IT! ------------------------- # - def Show(self, non_blocking=False): - self.Shown = True - # Compute num rows & num cols (it'll come in handy debugging) - self.NumRows = len(self.Rows) - self.NumCols = max(len(row) for row in self.Rows) - self.NonBlocking = non_blocking - - # Search through entire form to see if any elements set the focus - # if not, then will set the focus to the first input element - found_focus = False - for row in self.Rows: - for element in row: - try: - if element.Focus: - found_focus = True - except: - pass - try: - if element.Key is not None: - self.UseDictionary = True - except: - pass - - if not found_focus and self.UseDefaultFocus: - self.UseDefaultFocus = True - else: - self.UseDefaultFocus = False - # -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ## - StartupTK(self) - - def Read(self, timeout=None, timeout_key=TIMEOUT_KEY, close=False): - """ - THE biggest deal method in the Window class! This is how you get all of your data from your Window. - Pass in a timeout (in milliseconds) to wait for a maximum of timeout milliseconds. Will return timeout_key - if no other GUI events happen first. - Use the close parameter to close the window after reading - - :param timeout: (int) Milliseconds to wait until the Read will return IF no other GUI events happen first - :param timeout_key: (Any) The value that will be returned from the call if the timer expired - :param close: (bool) if True the window will be closed prior to returning - :return: Tuple[(Any), Union[Dict[Any:Any]], List[Any], None] (event, values) - """ - results = self._read(timeout=timeout, timeout_key=timeout_key) - if close: - self.close() - - return results - - def _read(self, timeout=None, timeout_key=TIMEOUT_KEY): - # if timeout == 0: # timeout of zero runs the old readnonblocking - # event, values = self._ReadNonBlocking() - # if event is None: - # event = timeout_key - # if values is None: - # event = None - # return event, values # make event None if values was None and return - # Read with a timeout - self.Timeout = timeout - self.TimeoutKey = timeout_key - self.NonBlocking = False - if not self.Shown: - self.Show() - # if already have a button waiting, the return previously built results - if self.LastButtonClicked is not None and not self.LastButtonClickedWasRealtime: - # print(f'*** Found previous clicked saved {self.LastButtonClicked}') - results = BuildResults(self, False, self) - self.LastButtonClicked = None - return results - InitializeResults(self) - # if the last button clicked was realtime, emulate a read non-blocking - # the idea is to quickly return realtime buttons without any blocks until released - if self.LastButtonClickedWasRealtime: - # print(f'RTime down {self.LastButtonClicked}' ) - try: - rc = self.TKroot.update() - except: - self.TKrootDestroyed = True - Window._DecrementOpenCount() - results = BuildResults(self, False, self) - if results[0] != None and results[0] != timeout_key: - return results - else: - pass - - # else: - # print("** REALTIME PROBLEM FOUND **", results) - # print('****************** CALLING MESSAGE QUEUE GET ***********************') - self.CurrentlyRunningMainloop = True - if timeout is not None: - try: - self.LastButtonClicked = self.MessageQueue.get(timeout=(timeout if timeout else 0.001) / 1000) - # print(f'Got event {self.LastButtonClicked}') - except: # timeout - self.LastButtonClicked = timeout_key - else: - self.LastButtonClicked = self.MessageQueue.get() - # print(f'Got event {self.LastButtonClicked}') - # print('--------------------- BACK FROM MESSAGE QUEUE GET ----------------------') - - results = BuildResults(self, False, self) - return results - # print(f'In main {self.Title}') - ################################# CALL GUWxTextCtrlI MAINLOOP ############################ - # self.App.MainLoop() - # self.CurrentlyRunningMainloop = False - # self.TimerCancelled = True - # if timer: - # timer.Stop() - # if Window.stdout_is_rerouted: - # sys.stdout = Window.stdout_location - # if self.RootNeedsDestroying: - # self.LastButtonClicked = None - # self.App.Close() - # try: - # self.MasterFrame.Close() - # except: - # pass - # Window._DecrementOpenCount() - # if form was closed with X - # if self.LastButtonClicked is None and self.LastKeyboardEvent is None and self.ReturnValues[0] is None: - # Window._DecrementOpenCount() - # Determine return values - # if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: - # results = BuildResults(self, False, self) - # if not self.LastButtonClickedWasRealtime: - # self.LastButtonClicked = None - # return results - # else: - # if not self.XFound and self.Timeout != 0 and self.Timeout is not None and self.ReturnValues[ - # 0] is None: # Special Qt case because returning for no reason so fake timeout - # self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout - # elif not self.XFound and self.ReturnValues[ - # 0] is None: # TODO HIGHLY EXPERIMENTAL... added due to tray icon interaction - # print("*** Faking timeout ***") - # self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout - # return self.ReturnValues - - def _ReadNonBlocking(self): - if self.TKrootDestroyed: - return None, None - if not self.Shown: - self.Show(non_blocking=True) - # event = wx.Event() - # self.App.QueueEvent(event) - timer = wx.Timer(self.App) - self.App.Bind(wx.EVT_TIMER, self.timer_timeout) - timer.Start(milliseconds=0, oneShot=wx.TIMER_ONE_SHOT) - self.CurrentlyRunningMainloop = True - # print(f'In main {self.Title}') - ################################# CALL GUWxTextCtrlI MAINLOOP ############################ - - self.App.MainLoop() - if Window.stdout_is_rerouted: - sys.stdout = Window.stdout_location - # self.LastButtonClicked = 'TEST' - self.CurrentlyRunningMainloop = False - timer.Stop() - # while self.App.HasPendingEvents(): - # self.App.ProcessPendingEvents() - return BuildResults(self, False, self) - - # ------------------------- SetIcon - set the window's fav icon ------------------------- # - def SetIcon(self, icon=None, pngbase64=None): - pass - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def _GetDefaultElementSize(self): - return self.DefaultElementSize - - def _AutoCloseAlarmCallback(self): - try: - window = self - if window: - if window.NonBlocking: - self.CloseNonBlockingForm() - else: - window._Close() - if self.CurrentlyRunningMainloop: - self.QTApplication.exit() # kick the users out of the mainloop - self.RootNeedsDestroying = True - self.QT_QMainWindow.close() - - except: - pass - - def timer_timeout(self, event): - # first, get the results table built - # modify the Results table in the parent FlexForm object - # print('timer timeout') - if self.TimerCancelled: - return - self.LastButtonClicked = self.TimeoutKey - self.FormRemainedOpen = True - if self.CurrentlyRunningMainloop: - self.App.ExitMainLoop() - - def non_block_timer_timeout(self, event): - # print('non-blocking timer timeout') - self.App.ExitMainLoop() - - def autoclose_timer_callback(self, frame): - # print('*** AUTOCLOSE TIMEOUT CALLBACK ***', frame) - try: - frame.Close() - except: - pass # if user has already closed the frame will get an error - - if self.CurrentlyRunningMainloop: - self.App.ExitMainLoop() - - def on_key_down(self, emitter, key, keycode, ctrl, shift, alt): - self.LastButtonClicked = 'DOWN' + key - self.MessageQueue.put(self.LastButtonClicked) - self.KeyInfoDict = {'key': key, 'keycode': keycode, 'ctrl': ctrl, 'shift': shift, 'alt': alt} - - def on_key_up(self, emitter, key, keycode, ctrl, shift, alt): - self.LastButtonClicked = key - self.MessageQueue.put(self.LastButtonClicked) - self.KeyInfoDict = {'key': key, 'keycode': keycode, 'ctrl': ctrl, 'shift': shift, 'alt': alt} - - def callback_keyboard_char(self, event): - self.LastButtonClicked = None - self.FormRemainedOpen = True - if event.ClassName == 'wxMouseEvent': - if event.WheelRotation < 0: - self.LastKeyboardEvent = 'MouseWheel:Down' - else: - self.LastKeyboardEvent = 'MouseWheel:Up' - else: - self.LastKeyboardEvent = event.GetKeyCode() - if not self.NonBlocking: - BuildResults(self, False, self) - if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! - self.App.ExitMainLoop() # kick the users out of the mainloop - if event.ClassName != 'wxMouseEvent': - event.DoAllowNextEvent() - - def Finalize(self): - if self.TKrootDestroyed: - return self - if not self.Shown: - self.Show(non_blocking=True) - # else: - # try: - # self.QTApplication.processEvents() # refresh the window - # except: - # print('* ERROR FINALIZING *') - # self.TKrootDestroyed = True - # Window._DecrementOpenCount() - return self - - def Refresh(self): - # self.QTApplication.processEvents() # refresh the window - return self - - def VisibilityChanged(self): - self.SizeChanged() - return self - - def Fill(self, values_dict): - _FillFormWithValues(self, values_dict) - return self - - def FindElement(self, key, silent_on_error=False): - try: - element = self.AllKeysDict[key] - except KeyError: - element = None - if element is None: - if not silent_on_error: - print('*** WARNING = FindElement did not find the key. Please check your key\'s spelling ***') - PopupError( - 'Keyword error in FindElement Call', - 'Bad key = {}'.format(key), - 'Your bad line of code may resemble this:', - 'window.FindElement("{}")'.format(key), - ) - return ErrorElement(key=key) - else: - return False - return element - - Element = FindElement # shortcut function definition - - def _BuildKeyDict(self): - dict = {} - self.AllKeysDict = self._BuildKeyDictForWindow(self, self, dict) - # print(f'keys built = {self.AllKeysDict}') - - def _BuildKeyDictForWindow(self, top_window, window, key_dict): - for row_num, row in enumerate(window.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Type == ELEM_TYPE_FRAME: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Type == ELEM_TYPE_TAB_GROUP: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Type == ELEM_TYPE_TAB: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Key is None: # if no key has been assigned.... create one for input elements - if element.Type == ELEM_TYPE_BUTTON: - element.Key = element.ButtonText - if element.Type in ( - ELEM_TYPE_MENUBAR, - ELEM_TYPE_BUTTONMENU, - ELEM_TYPE_CANVAS, - ELEM_TYPE_INPUT_SLIDER, - ELEM_TYPE_GRAPH, - ELEM_TYPE_IMAGE, - ELEM_TYPE_INPUT_CHECKBOX, - ELEM_TYPE_INPUT_LISTBOX, - ELEM_TYPE_INPUT_COMBO, - ELEM_TYPE_INPUT_MULTILINE, - ELEM_TYPE_INPUT_OPTION_MENU, - ELEM_TYPE_INPUT_SPIN, - ELEM_TYPE_TABLE, - ELEM_TYPE_TREE, - ELEM_TYPE_INPUT_TEXT, - ): - element.Key = top_window.DictionaryKeyCounter - top_window.DictionaryKeyCounter += 1 - if element.Key is not None: - if element.Key in key_dict.keys(): - (print('*** Duplicate key found in your layout {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) - element.Key = element.Key + str(self.UniqueKeyCounter) - self.UniqueKeyCounter += 1 - (print('*** Replaced new key with {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) - key_dict[element.Key] = element - return key_dict - - def FindElementWithFocus(self): - return self.FocusElement - # element = _FindElementWithFocusInSubForm(self) - # return element - - def SaveToDisk(self, filename): - try: - results = BuildResults(self, False, self) - with open(filename, 'wb') as sf: - pickle.dump(results[1], sf) - except: - print('*** Error saving form to disk ***') - - def LoadFromDisk(self, filename): - try: - with open(filename, 'rb') as df: - self.Fill(pickle.load(df)) - except: - print('*** Error loading form to disk ***') - - def GetScreenDimensions(self): # TODO - Not sure what to return so (0,0) for now - size = (0, 0) - return size - - def Move(self, x, y): - self.MasterFrame.SetPosition((x, y)) - - def Minimize(self): - self.MasterFrame.Iconize() - - def Maximize(self): - self.MasterFrame.Maximize() - - def _Close(self): - if not self.NonBlocking: - BuildResults(self, False, self) - if self.TKrootDestroyed: - return None - self.TKrootDestroyed = True - self.RootNeedsDestroying = True - self.Close() - - def Close(self): - if len(Window.active_windows) != 0: - del Window.active_windows[-1] # delete current window from active windows - if len(Window.active_windows) != 0: - window = Window.active_windows[-1] # get prior window to change to - Window.App.set_root_widget(window.master_widget) - else: - self.App.close() - self.App.server.server_starter_instance._alive = False - self.App.server.server_starter_instance._sserver.shutdown() - return - - self.App.close() - self.App.server.server_starter_instance._alive = False - self.App.server.server_starter_instance._sserver.shutdown() - - CloseNonBlockingForm = Close - CloseNonBlocking = Close - - def Disable(self): - self.MasterFrame.Enable(False) - - def Enable(self): - self.MasterFrame.Enable(True) - - def Hide(self): - self._Hidden = True - self.master_widget.attributes['hidden'] = 'true' - # self.MasterFrame.Hide() - return - - def UnHide(self): - if self._Hidden: - del self.master_widget.attributes['hidden'] - self._Hidden = False - - def Disappear(self): - self.MasterFrame.SetTransparent(0) - - def Reappear(self): - self.MasterFrame.SetTransparent(255) - - def SetAlpha(self, alpha): - ''' - Change the window's transparency - :param alpha: From 0 to 1 with 0 being completely transparent - :return: - ''' - self._AlphaChannel = alpha * 255 - if self._AlphaChannel is not None: - self.MasterFrame.SetTransparent(self._AlphaChannel) - - @property - def AlphaChannel(self): - return self._AlphaChannel - - @AlphaChannel.setter - def AlphaChannel(self, alpha): - self.SetAlpha(alpha) - - def BringToFront(self): - self.MasterFrame.ToggleWindowStyle(wx.STAY_ON_TOP) - - def CurrentLocation(self): - location = self.MasterFrame.GetPosition() - return location - - @property - def Size(self): - size = self.MasterFrame.GetSize() - return size - - @Size.setter - def Size(self, size): - self.MasterFrame.SetSize(size[0], size[1]) - - def SizeChanged(self): - size = self.Size - self.Size = size[0] + 1, size[1] + 1 - self.Size = size - self.MasterFrame.SetSizer(self.OuterSizer) - self.OuterSizer.Fit(self.MasterFrame) - - def __getitem__(self, key): - """ - Returns Element that matches the passed in key. - This is "called" by writing code as thus: - window['element key'].Update - - :param key: (Any) The key to find - :return: Union[Element, None] The element found or None if no element was found - """ - try: - return self.Element(key) - except Exception as e: - print('The key you passed in is no good. Key = {}*'.format(key)) - return None - - def __call__(self, *args, **kwargs): - """ - Call window.Read but without having to type it out. - window() == window.Read() - window(timeout=50) == window.Read(timeout=50) - - :param args: - :param kwargs: - :return: Tuple[Any, Dict[Any:Any]] The famous event, values that Read returns. - """ - return self.Read(*args, **kwargs) - - add_row = AddRow - add_rows = AddRows - alpha_channel = AlphaChannel - bring_to_front = BringToFront - close = Close - current_location = CurrentLocation - disable = Disable - disappear = Disappear - element = Element - enable = Enable - fill = Fill - finalize = Finalize - find_element = FindElement - find_element_with_focus = FindElementWithFocus - get_screen_dimensions = GetScreenDimensions - hide = Hide - increment_open_count = IncrementOpenCount - layout = Layout - layout_and_read = LayoutAndRead - layout_and_show = LayoutAndShow - load_from_disk = LoadFromDisk - maximize = Maximize - minimize = Minimize - move = Move - num_open_windows = _NumOpenWindows - read = Read - reappear = Reappear - refresh = Refresh - save_to_disk = SaveToDisk - set_alpha = SetAlpha - set_icon = SetIcon - show = Show - size = Size - size_changed = SizeChanged - un_hide = UnHide - visibility_changed = VisibilityChanged - - def remi_thread(self): - # print('Remi Thread started') - logging.getLogger('remi').setLevel(logging.CRITICAL) - logging.getLogger('remi').disabled = True - logging.getLogger('remi.server.ws').disabled = True - logging.getLogger('remi.server').disabled = True - logging.getLogger('remi.request').disabled = True - # use this code to start the application instead of the **start** call - # s = remi.Server(self.MyApp, start=True, title=self.Title, address='0.0.0.0', port=8081, start_browser=True, userdata=(self,), multiple_instance=False, update_interval=.001) - - # logging.getLogger('remi').setLevel(level=logging.CRITICAL) - # logging.getLogger('remi').disabled = True - # logging.disable(logging.CRITICAL) - # s = remi.server.StandaloneServer(self.MyApp, width=1100, height=600) - # s.start() - Window.port_number += 1 - try: - remi.start( - self.MyApp, - title=self.Title, - debug=self.web_debug, - address=self.web_ip, - port=self.web_port, - multiple_instance=self.web_multiple_instance, - start_browser=self.web_start_browser, - update_interval=self.web_update_interval, - userdata=(self,), - ) - - except: - print('*** ERROR Caught inside Remi ***') - print(traceback.format_exc()) - # remi.start(self.MyApp, title=self.Title ,debug=False, userdata=(self,), standalone=True) # standalone=True) - - # remi.start(self.MyApp, standalone=True, debug=True, userdata=(self,) ) # Can't do this because of a threading problem - print('Returned from Remi Start command... now sending None event') - - self.MessageQueue.put(None) # if returned from start call, then the window has been destroyed and a None event should be generated - - class MyApp(remi.App): - def __init__(self, *args, userdata2=None): - # self.window = window # type: Window - # print(args[-1]) - if userdata2 is None: - userdata = args[-1].userdata - self.window = userdata[0] # type: Window - else: - self.window = userdata2 # type: Window - self.master_widget = None - # print("new App instance %s" % str(id(self))) - # self.window.App = self - # Window.App = self - self.lines_shown = [] - - if userdata2 is None: - # res_path = os.path.dirname(os.path.abspath(__file__)) - # print('res path', res_path) - super(Window.MyApp, self).__init__( - *args, - static_file_path={ - 'C': 'c:', - 'c': 'c:', - 'D': 'd:', - 'd': 'd:', - 'E': 'e:', - 'e': 'e:', - 'dot': '.', - '.': '.', - } - ) - - def _instance(self): - remi.App._instance(self) - self.window.App = remi.server.clients[self.session] - - def log_message(self, *args, **kwargs): - pass - - def idle(self): - if Window.stdout_is_rerouted: - Window.stdout_string_io.seek(0) - lines = Window.stdout_string_io.readlines() - # lines.reverse() - # self.window.OutputElementForStdOut.Widget.set_text("".join(lines)) - # self.window.OutputElementForStdOut.Update("".join(lines)) - if lines != self.lines_shown: - self.window.OutputElementForStdOut.Update(''.join(lines)) - self.lines_shown = lines - - def main(self, name='world'): - # margin 0px auto allows to center the app to the screen - # self.master_widget = remi.gui.VBox() - # self.master_widget.style['justify-content'] = 'flex-start' - # self.master_widget.style['align-items'] = 'baseline' - # if self.window.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - # self.master_widget.style['background-color'] = self.window.BackgroundColor - # try: - # PackFormIntoFrame(self.window, self.master_widget, self.window) - # except: - # print('* ERROR PACKING FORM *') - # print(traceback.format_exc()) - # - # if self.window.BackgroundImage: - # self.master_widget.style['background-image'] = "url('{}')".format('/'+self.window.BackgroundImage) - # # print(f'background info',self.master_widget.attributes['background-image'] ) - # - # if not self.window.DisableClose: - # # add the following 3 lines to your app and the on_window_close method to make the console close automatically - # tag = remi.gui.Tag(_type='script') - # tag.add_child("javascript", """window.onunload=function(e){sendCallback('%s','%s');return "close?";};""" % ( - # str(id(self)), "on_window_close")) - # self.master_widget.add_child("onunloadevent", tag) - - self.master_widget = setup_remi_window(self, self.window) - self.window.master_widget = self.master_widget - # if self.window.WindowIcon: - # print('placing icon') - # self.page.children['head'].set_icon_file("/res:logo.png") - # self.page.children['head'].set_icon_data( base64_data=self.window.WindowIcon, mimetype="image/png" ) - - self.window.MessageQueue.put('Layout complete') # signal the main code that the layout is all done - return self.master_widget # returning the root widget - - def on_window_close(self): - # here you can handle the unload - print('app closing') - self.close() - self.server.server_starter_instance._alive = False - self.server.server_starter_instance._sserver.shutdown() - # self.window.MessageQueue.put(None) - print('server stopped') - - -FlexForm = Window - - -# =========================================================================== # -# Stops the mainloop and sets the event information # -# =========================================================================== # - - -def element_callback_quit_mainloop(element): - if element.Key is not None: - element.ParentForm.LastButtonClicked = element.Key - else: - element.ParentForm.LastButtonClicked = '' - try: - element.ParentForm.LastButtonClicked = element.Key if element.Key is not None else element.ButtonText - except: - element.ParentForm.LastButtonClicked = element.Key - # print(f'Putting into message queue {element.ParentForm.LastButtonClicked}') - element.ParentForm.MessageQueue.put(element.ParentForm.LastButtonClicked) - - -def quit_mainloop(window): - window.App.ExitMainLoop() - - -# =========================================================================== # -# Stops the mainloop and sets the event information # -# =========================================================================== # -def convert_tkinter_size_to_Wx(size): - """ - Converts size in characters to size in pixels - :param size: size in characters, rows - :return: size in pixels, pixels - """ - qtsize = size - if size[1] is not None and size[1] < DEFAULT_PIXEL_TO_CHARS_CUTOFF: # change from character based size to pixels (roughly) - qtsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] - return qtsize - - -def base64_to_style_image(base64_image): - x = "url('data:image/png;base64," - x += str(base64_image) - x += "')" - # print(x) - return x - - -def font_parse_string(font): - """ - Convert from font string/tyuple into a Qt style sheet string - :param font: "Arial 10 Bold" or ('Arial', 10, 'Bold) - :return: style string that can be combined with other style strings - """ - - if font is None: - return '' - - if type(font) is str: - _font = font.split(' ') - else: - _font = font - family = _font[0] - point_size = int(_font[1]) - - style = _font[2:] if len(_font) > 1 else None - - # underline = 'underline' in _font[2:] - # bold = 'bold' in _font - - return family, point_size, style - - -# ################################################################################ -# ################################################################################ -# END OF ELEMENT DEFINITIONS -# ################################################################################ -# ################################################################################ - - -# =========================================================================== # -# Button Lazy Functions so the caller doesn't have to define a bunch of stuff # -# =========================================================================== # - - -# ------------------------- FOLDER BROWSE Element lazy function ------------------------- # -def FolderBrowse( - button_text='Browse', - target=(ThisRow, -1), - initial_folder=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - change_submits=False, - font=None, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_BROWSE_FOLDER, - target=target, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - disabled=disabled, - button_color=button_color, - change_submits=change_submits, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- FILE BROWSE Element lazy function ------------------------- # -def FileBrowse( - button_text='Browse', - target=(ThisRow, -1), - file_types=(('ALL Files', '*.*'),), - initial_folder=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - font=None, - disabled=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_BROWSE_FILE, - target=target, - file_types=file_types, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - change_submits=change_submits, - disabled=disabled, - button_color=button_color, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- FILES BROWSE Element (Multiple file selection) lazy function ------------------------- # -def FilesBrowse( - button_text='Browse', - target=(ThisRow, -1), - file_types=(('ALL Files', '*.*'),), - disabled=False, - initial_folder=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - font=None, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_BROWSE_FILES, - target=target, - file_types=file_types, - initial_folder=initial_folder, - change_submits=change_submits, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - disabled=disabled, - button_color=button_color, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- FILE BROWSE Element lazy function ------------------------- # -def FileSaveAs( - button_text='Save As...', - target=(ThisRow, -1), - file_types=(('ALL Files', '*.*'),), - initial_folder=None, - disabled=False, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - font=None, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_SAVEAS_FILE, - target=target, - file_types=file_types, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - disabled=disabled, - auto_size_button=auto_size_button, - button_color=button_color, - change_submits=change_submits, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- SAVE AS Element lazy function ------------------------- # -def SaveAs( - button_text='Save As...', - target=(ThisRow, -1), - file_types=(('ALL Files', '*.*'),), - initial_folder=None, - disabled=False, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - font=None, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_SAVEAS_FILE, - target=target, - file_types=file_types, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - disabled=disabled, - auto_size_button=auto_size_button, - button_color=button_color, - change_submits=change_submits, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- SAVE BUTTON Element lazy function ------------------------- # -def Save( - button_text='Save', - size=(None, None), - auto_size_button=None, - button_color=None, - bind_return_key=True, - disabled=False, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- SUBMIT BUTTON Element lazy function ------------------------- # -def Submit( - button_text='Submit', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- OPEN BUTTON Element lazy function ------------------------- # -def Open( - button_text='Open', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- OK BUTTON Element lazy function ------------------------- # -def OK( - button_text='OK', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- YES BUTTON Element lazy function ------------------------- # -def Ok( - button_text='Ok', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- CANCEL BUTTON Element lazy function ------------------------- # -def Cancel( - button_text='Cancel', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- QUIT BUTTON Element lazy function ------------------------- # -def Quit( - button_text='Quit', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Exit BUTTON Element lazy function ------------------------- # -def Exit( - button_text='Exit', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Up arrow BUTTON Element lazy function ------------------------- # -def Up( - button_text='▲', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=True, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Down arrow BUTTON Element lazy function ------------------------- # -def Down( - button_text='▼', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=True, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Left arrow BUTTON Element lazy function ------------------------- # -def Left( - button_text='◄', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=True, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Right arrow BUTTON Element lazy function ------------------------- # -def Right( - button_text='►', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=True, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- YES BUTTON Element lazy function ------------------------- # -def Yes( - button_text='Yes', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=True, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- NO BUTTON Element lazy function ------------------------- # -def No( - button_text='No', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- NO BUTTON Element lazy function ------------------------- # -def Help( - button_text='Help', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - font=None, - tooltip=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # -def SimpleButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - disabled=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_CLOSES_WIN, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - disabled=disabled, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- CLOSE BUTTON Element lazy function ------------------------- # -def CloseButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - disabled=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_CLOSES_WIN, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - disabled=disabled, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -CButton = CloseButton - - -# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # -def ReadButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - disabled=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - disabled=disabled, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -ReadFormButton = ReadButton -RButton = ReadFormButton - - -# ------------------------- Realtime BUTTON Element lazy function ------------------------- # -def RealtimeButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - disabled=False, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_REALTIME, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - disabled=disabled, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Dummy BUTTON Element lazy function ------------------------- # -def DummyButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - disabled=False, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_CLOSES_WIN_ONLY, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Calendar Chooser Button lazy function ------------------------- # -def CalendarButton( - button_text, - target=(None, None), - close_when_date_chosen=True, - default_date_m_d_y=(None, None, None), - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - tooltip=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - button = Button( - button_text=button_text, - button_type=BUTTON_TYPE_CALENDAR_CHOOSER, - target=target, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - button.CalendarCloseWhenChosen = close_when_date_chosen - button.DefaultDate_M_D_Y = default_date_m_d_y - return button - - -# ------------------------- Calendar Chooser Button lazy function ------------------------- # -def ColorChooserButton( - button_text, - target=(None, None), - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - tooltip=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_COLOR_CHOOSER, - target=target, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -##################################### ----- RESULTS ------ ################################################## - - -def AddToReturnDictionary(form, element, value): - form.ReturnValuesDictionary[element.Key] = value - return - if element.Key is None: - form.ReturnValuesDictionary[form.DictionaryKeyCounter] = value - element.Key = form.DictionaryKeyCounter - form.DictionaryKeyCounter += 1 - else: - form.ReturnValuesDictionary[element.Key] = value - - -def AddToReturnList(form, value): - form.ReturnValuesList.append(value) - - -# ----------------------------------------------------------------------------# -# ------- FUNCTION InitializeResults. Sets up form results matrix --------# -def InitializeResults(form): - BuildResults(form, True, form) - return - - -# ===== Radio Button RadVar encoding and decoding =====# -# ===== The value is simply the row * 1000 + col =====# -def DecodeRadioRowCol(RadValue): - row = RadValue // 1000 - col = RadValue % 1000 - return row, col - - -def EncodeRadioRowCol(row, col): - RadValue = row * 1000 + col - return RadValue - - -# ------- FUNCTION BuildResults. Form exiting so build the results to pass back ------- # -# format of return values is -# (Button Pressed, input_values) -def BuildResults(form, initialize_only, top_level_form): - # Results for elements are: - # TEXT - Nothing - # INPUT - Read value from TK - # Button - Button Text and position as a Tuple - - # Get the initialized results so we don't have to rebuild - form.DictionaryKeyCounter = 0 - form.ReturnValuesDictionary = {} - form.ReturnValuesList = [] - BuildResultsForSubform(form, initialize_only, top_level_form) - if not top_level_form.LastButtonClickedWasRealtime: - top_level_form.LastButtonClicked = None - return form.ReturnValues - - -def BuildResultsForSubform(form, initialize_only, top_level_form): - button_pressed_text = top_level_form.LastButtonClicked - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Key is not None and WRITE_ONLY_KEY in str(element.Key): - continue - value = None - if element.Type == ELEM_TYPE_COLUMN: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_FRAME: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_TAB_GROUP: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_TAB: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if not initialize_only: - if element.Type == ELEM_TYPE_INPUT_TEXT: - element = element # type: InputText - value = element.Widget.get_value() - if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: - element.Widget.set_value('') - elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: - element = element # type: Checkbox - value = element.Widget.get_value() - elif element.Type == ELEM_TYPE_INPUT_RADIO: - # RadVar = element.TKIntVar.get() - # this_rowcol = EncodeRadioRowCol(row_num, col_num) - value = False - elif element.Type == ELEM_TYPE_BUTTON: - if top_level_form.LastButtonClicked == element.ButtonText: - button_pressed_text = top_level_form.LastButtonClicked - if element.BType != BUTTON_TYPE_REALTIME: # Do not clear realtime buttons - top_level_form.LastButtonClicked = None - if element.BType == BUTTON_TYPE_CALENDAR_CHOOSER: - try: - value = element.TKCal.selection - except: - value = None - else: - try: - value = element.TKStringVar.get() - except: - value = None - elif element.Type == ELEM_TYPE_INPUT_COMBO: - element = element # type: Combo - value = element.Widget.get_value() - elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: - # value = element.TKStringVar.get() - value = None - elif element.Type == ELEM_TYPE_INPUT_LISTBOX: - element = element # type: Listbox - value = element.Widget.get_value() - value = [ - value, - ] - # items = element.TKListbox.curselection() - # value = [element.Values[int(item)] for item in items] - elif element.Type == ELEM_TYPE_INPUT_SPIN: - element = element # type: Spin - value = element.Widget.get_value() - elif element.Type == ELEM_TYPE_INPUT_SLIDER: - element = element # type: Slider - value = element.Widget.get_value() - elif element.Type == ELEM_TYPE_INPUT_MULTILINE: - element = element # type: Multiline - if element.WriteOnly: - continue - value = element.Widget.get_value() - elif element.Type == ELEM_TYPE_TAB_GROUP: - try: - value = element.TKNotebook.tab(element.TKNotebook.index('current'))['text'] - tab_key = element.FindKeyFromTabName(value) - if tab_key is not None: - value = tab_key - except: - value = None - elif element.Type == ELEM_TYPE_TABLE: - element = element # type:Table - value = [ - element.SelectedRow, - ] - elif element.Type == ELEM_TYPE_TREE: - value = element.SelectedRows - elif element.Type == ELEM_TYPE_GRAPH: - value = element.ClickPosition - elif element.Type == ELEM_TYPE_MENUBAR: - value = element.MenuItemChosen - else: - value = None - - # if an input type element, update the results - if element.Type != ELEM_TYPE_BUTTON and element.Type != ELEM_TYPE_TEXT and element.Type != ELEM_TYPE_IMAGE and element.Type != ELEM_TYPE_OUTPUT and element.Type != ELEM_TYPE_PROGRESS_BAR and element.Type != ELEM_TYPE_COLUMN and element.Type != ELEM_TYPE_FRAME and element.Type != ELEM_TYPE_TAB: - AddToReturnList(form, value) - AddToReturnDictionary(top_level_form, element, value) - elif ( - (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_CALENDAR_CHOOSER and element.Target == (None, None)) - or (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_COLOR_CHOOSER and element.Target == (None, None)) - or ( - element.Type == ELEM_TYPE_BUTTON - and element.Key is not None - and ( - element.BType - in ( - BUTTON_TYPE_SAVEAS_FILE, - BUTTON_TYPE_BROWSE_FILE, - BUTTON_TYPE_BROWSE_FILES, - BUTTON_TYPE_BROWSE_FOLDER, - ) - ) - ) - ): - AddToReturnList(form, value) - AddToReturnDictionary(top_level_form, element, value) - - # if this is a column, then will fail so need to wrap with tr - try: - if form.ReturnKeyboardEvents and form.LastKeyboardEvent is not None: - button_pressed_text = form.LastKeyboardEvent - form.LastKeyboardEvent = None - except: - pass - - try: - form.ReturnValuesDictionary.pop(None, None) # clean up dictionary include None was included - except: - pass - - if not form.UseDictionary: - form.ReturnValues = button_pressed_text, form.ReturnValuesList - else: - form.ReturnValues = button_pressed_text, form.ReturnValuesDictionary - - return form.ReturnValues - - -def _FillFormWithValues(form, values_dict): - _FillSubformWithValues(form, values_dict) - - -def _FillSubformWithValues(form, values_dict): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - value = None - if element.Type == ELEM_TYPE_COLUMN: - _FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_FRAME: - _FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_TAB_GROUP: - _FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_TAB: - _FillSubformWithValues(element, values_dict) - try: - value = values_dict[element.Key] - except: - continue - if element.Type == ELEM_TYPE_INPUT_TEXT: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_RADIO: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_COMBO: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_LISTBOX: - element.SetValue(value) - elif element.Type == ELEM_TYPE_INPUT_SLIDER: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_MULTILINE: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_SPIN: - element.Update(value) - elif element.Type == ELEM_TYPE_BUTTON: - element.Update(value) - - -def _FindElementFromKeyInSubForm(form, key): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_FRAME: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB_GROUP: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Key == key: - return element - - -def _FindElementWithFocusInSubForm(form): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_FRAME: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB_GROUP: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_INPUT_TEXT: - if element.TKEntry is not None: - if element.TKEntry is element.TKEntry.focus_get(): - return element - if element.Type == ELEM_TYPE_INPUT_MULTILINE: - if element.TKText is not None: - if element.TKText is element.TKText.focus_get(): - return element - - -def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): - # m3 = gui.MenuItem('Dialog', width=100, height=30) - # m3.onclick.connect(self.menu_dialog_clicked) - # menu.append([m1, m2, m3]) - - return_val = None - if type(sub_menu_info) is str: - if not is_sub_menu and not skip: - # print(f'Adding command {sub_menu_info}') - pos = sub_menu_info.find('&') - if pos != -1: - if pos == 0 or sub_menu_info[pos - 1] != '\\': - sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos + 1 :] - if sub_menu_info == '---': - # top_menu.add('separator') - pass - else: - try: - item_without_key = sub_menu_info[: sub_menu_info.index(MENU_KEY_SEPARATOR)] - except: - item_without_key = sub_menu_info - if item_without_key[0] == MENU_DISABLED_CHARACTER: - menu_item = remi.gui.MenuItem(item_without_key[1:], width=100, height=30) - menu_item.set_enabled(False) - top_menu.append( - [ - menu_item, - ] - ) - - # TODO add callback here! - # TODO disable entry - else: - menu_item = remi.gui.MenuItem(item_without_key, width=100, height=30) - top_menu.append( - [ - menu_item, - ] - ) - # menu_item.set_on_click_listener(element._ChangedCallbackMenu, sub_menu_info) - menu_item.onclick.connect(element._ChangedCallbackMenu, sub_menu_info) - else: - i = 0 - while i < (len(sub_menu_info)): - item = sub_menu_info[i] - if i != len(sub_menu_info) - 1: - if type(sub_menu_info[i + 1]) == list: - pos = sub_menu_info[i].find('&') - if pos != -1: - if pos == 0 or sub_menu_info[i][pos - 1] != '\\': - sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1 :] - if sub_menu_info[i][0] == MENU_DISABLED_CHARACTER: - new_menu = remi.gui.MenuItem(sub_menu_info[i][len(MENU_DISABLED_CHARACTER) :], width=100, height=30) - new_menu.set_enabled(False) - - # TODO Disable Entry - else: - new_menu = remi.gui.MenuItem(sub_menu_info[i], width=100, height=30) - - top_menu.append( - [ - new_menu, - ] - ) - return_val = new_menu - AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) - i += 1 # skip the next one - else: - AddMenuItem(top_menu, item, element) - else: - AddMenuItem(top_menu, item, element) - i += 1 - return return_val - - -""" - ::::::::: :::::::::: ::: ::: ::::::::::: - :+: :+: :+: :+:+: :+:+: :+: - +:+ +:+ +:+ +:+ +:+:+ +:+ +:+ - +#++:++#: +#++:++# +#+ +:+ +#+ +#+ - +#+ +#+ +#+ +#+ +#+ +#+ - #+# #+# #+# #+# #+# #+# - ### ### ########## ### ### ########### -""" -# ------------------------------------------------------------------------------------------------------------ # -# ===================================== REMI CODE STARTS HERE ================================================ # -# ------------------------------------------------------------------------------------------------------------ # - - -def PackFormIntoFrame(form, containing_frame, toplevel_form): - def CharWidthInPixels(): - return tkinter.font.Font().measure('A') # single character width - - def pad_widget(widget): - lrsizer = wx.BoxSizer(wx.HORIZONTAL) - if full_element_pad[1] == full_element_pad[3]: # if right = left - lrsizer.Add(widget, 0, wx.LEFT | wx.RIGHT, border=full_element_pad[1]) - else: - sizer = wx.BoxSizer(wx.HORIZONTAL) - sizer.Add(widget, 0, wx.LEFT, border=full_element_pad[3]) - lrsizer.Add(sizer, 0, wx.RIGHT, border=full_element_pad[1]) - - top_bottom_sizer = wx.BoxSizer(wx.HORIZONTAL) - if full_element_pad[0] == full_element_pad[2]: # if top = bottom - top_bottom_sizer.Add(lrsizer, 0, wx.TOP | wx.BOTTOM, border=full_element_pad[0]) - else: - sizer = wx.BoxSizer(wx.HORIZONTAL) - sizer.Add(lrsizer, 0, wx.TOP, border=full_element_pad[0]) - top_bottom_sizer.Add(sizer, 0, wx.BOTTOM, border=full_element_pad[2]) - return top_bottom_sizer - - # - # font, text color, background color, size, disabled, visible, tooltip - # - def do_font_and_color(widget): - font_info = font_parse_string(font) # family, point size, other - widget.style['font-family'] = font_info[0] - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - widget.style['background-color'] = element.BackgroundColor - if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): - widget.style['color'] = element.TextColor - widget.style['font-size'] = '{}px'.format(font_info[1]) - if element_size[0]: # if size is zero, don't set any sizes - size = convert_tkinter_size_to_Wx(element_size) - widget.style['height'] = '{}px'.format(size[1]) - widget.style['width'] = '{}px'.format(size[0]) - widget.style['margin'] = '{}px {}px {}px {}px'.format(*full_element_pad) - if element.Disabled: - widget.set_enabled(False) - if not element.Visible: - widget.attributes['hidden'] = 'true' - if element.Tooltip is not None: - widget.attributes['title'] = element.Tooltip - - border_depth = toplevel_form.BorderDepth if toplevel_form.BorderDepth is not None else DEFAULT_BORDER_WIDTH - # --------------------------------------------------------------------------- # - # **************** Use FlexForm to build the tkinter window ********** ----- # - # Building is done row by row. # - # --------------------------------------------------------------------------- # - focus_set = False - ######################### LOOP THROUGH ROWS ######################### - # *********** ------- Loop through ROWS ------- ***********# - for row_num, flex_row in enumerate(form.Rows): - ######################### LOOP THROUGH ELEMENTS ON ROW ######################### - # *********** ------- Loop through ELEMENTS ------- ***********# - # *********** Make TK Row ***********# - tk_row_frame = remi.gui.HBox() - tk_row_frame.style['align-items'] = 'flex-start' - if form.ElementJustification.startswith('c'): - tk_row_frame.style['margin-left'] = 'auto' - tk_row_frame.style['margin-right'] = 'auto' - # tk_row_frame.style['justify-content'] = 'center' - elif form.ElementJustification.startswith('r'): - # tk_row_frame.style['justify-content'] = 'flex-end' - tk_row_frame.style['margin-left'] = 'auto' - else: # everything else is left justified - # tk_row_frame.style['justify-content'] = 'flex-flexstart' - tk_row_frame.style['margin-right'] = 'auto' - - if form.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - tk_row_frame.style['background-color'] = form.BackgroundColor - - for col_num, element in enumerate(flex_row): - element.ParentForm = toplevel_form # save the button's parent form object - if toplevel_form.Font and (element.Font == DEFAULT_FONT or not element.Font): - font = toplevel_form.Font - elif element.Font is not None: - font = element.Font - else: - font = DEFAULT_FONT - # ------- Determine Auto-Size setting on a cascading basis ------- # - if element.AutoSizeText is not None: # if element overide - auto_size_text = element.AutoSizeText - elif toplevel_form.AutoSizeText is not None: # if form override - auto_size_text = toplevel_form.AutoSizeText - else: - auto_size_text = DEFAULT_AUTOSIZE_TEXT - element_type = element.Type - # Set foreground color - text_color = element.TextColor - # Determine Element size - element_size = element.Size - if element_size == (None, None) and element_type != ELEM_TYPE_BUTTON: # user did not specify a size - element_size = toplevel_form.DefaultElementSize - elif element_size == (None, None) and element_type == ELEM_TYPE_BUTTON: - element_size = toplevel_form.DefaultButtonElementSize - else: - auto_size_text = False # if user has specified a size then it shouldn't autosize - - full_element_pad = [0, 0, 0, 0] # Top, Right, Bottom, Left - elementpad = element.Pad if element.Pad is not None else toplevel_form.ElementPadding - if type(elementpad[0]) != tuple: # left and right - full_element_pad[1] = full_element_pad[3] = elementpad[0] - else: - full_element_pad[3], full_element_pad[1] = elementpad[0] - if type(elementpad[1]) != tuple: # top and bottom - full_element_pad[0] = full_element_pad[2] = elementpad[1] - else: - full_element_pad[0], full_element_pad[2] = elementpad[1] - - # ------------------------- COLUMN element ------------------------- # - if element_type == ELEM_TYPE_COLUMN: - element = element # type: Column - element.Widget = column_widget = remi.gui.VBox() - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - column_widget.style['background-color'] = element.BackgroundColor - PackFormIntoFrame(element, column_widget, toplevel_form) - tk_row_frame.append(element.Widget) - - # ------------------------- TEXT element ------------------------- # - elif element_type == ELEM_TYPE_TEXT: - element = element # type: Text - element.Widget = remi.gui.Label(element.DisplayText) - do_font_and_color(element.Widget) - if auto_size_text and element.Size == (None, None): - del element.Widget.style['width'] - if element.Justification: - if element.Justification.startswith('c'): - element.Widget.style['text-align'] = 'center' - elif element.Justification.startswith('r'): - element.Widget.style['text-align'] = 'right' - if element.ClickSubmits: - element.Widget.onclick.connect(element._ChangedCallback) - tk_row_frame.append(element.Widget) - - # ------------------------- BUTTON element ------------------------- # - elif element_type == ELEM_TYPE_BUTTON: - element = element # type: Button - size = convert_tkinter_size_to_Wx(element_size) - element.Widget = remi.gui.Button(element.ButtonText, width=size[0], height=size[1], margin='10px') - element.Widget.onclick.connect(element._ButtonCallBack) - do_font_and_color(element.Widget) - if element.AutoSizeButton or (toplevel_form.AutoSizeButtons and element.AutoSizeButton is not False) and element.Size == (None, None): - del element.Widget.style['width'] - if element.ImageFilename: - element.ImageWidget = SuperImage(element.ImageFilename if element.ImageFilename is not None else element.ImageData) - element.Widget.append(element.ImageWidget) - tk_row_frame.append(element.Widget) - - # stringvar = tk.StringVar() - # element.TKStringVar = stringvar - # element.Location = (row_num, col_num) - # btext = element.ButtonText - # btype = element.BType - # if element.AutoSizeButton is not None: - # auto_size = element.AutoSizeButton - # else: - # auto_size = toplevel_form.AutoSizeButtons - # if auto_size is False or element.Size[0] is not None: - # width, height = element_size - # else: - # width = 0 - # height = toplevel_form.DefaultButtonElementSize[1] - # if element.ButtonColor != (None, None) and element.ButtonColor != DEFAULT_BUTTON_COLOR: - # bc = element.ButtonColor - # elif toplevel_form.ButtonColor != (None, None) and toplevel_form.ButtonColor != DEFAULT_BUTTON_COLOR: - # bc = toplevel_form.ButtonColor - # else: - # bc = DEFAULT_BUTTON_COLOR - # border_depth = element.BorderWidth - # if btype != BUTTON_TYPE_REALTIME: - # tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height, - # command=element.ButtonCallBack, justify=tk.LEFT, bd=border_depth, font=font) - # else: - # tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height, justify=tk.LEFT, - # bd=border_depth, font=font) - # tkbutton.bind('', element.ButtonReleaseCallBack) - # tkbutton.bind('', element.ButtonPressCallBack) - # if bc != (None, None) and bc != COLOR_SYSTEM_DEFAULT and bc[1] != COLOR_SYSTEM_DEFAULT: - # tkbutton.config(foreground=bc[0], background=bc[1], activebackground=bc[1]) - # elif bc[1] == COLOR_SYSTEM_DEFAULT: - # tkbutton.config(foreground=bc[0]) - # - # element.TKButton = tkbutton # not used yet but save the TK button in case - # wraplen = tkbutton.winfo_reqwidth() # width of widget in Pixels - # if element.ImageFilename: # if button has an image on it - # tkbutton.config(highlightthickness=0) - # photo = tk.PhotoImage(file=element.ImageFilename) - # if element.ImageSize != (None, None): - # width, height = element.ImageSize - # if element.ImageSubsample: - # photo = photo.subsample(element.ImageSubsample) - # else: - # width, height = photo.width(), photo.height() - # tkbutton.config(image=photo, compound=tk.CENTER, width=width, height=height) - # tkbutton.image = photo - # if element.ImageData: # if button has an image on it - # tkbutton.config(highlightthickness=0) - # photo = tk.PhotoImage(data=element.ImageData) - # if element.ImageSize != (None, None): - # width, height = element.ImageSize - # if element.ImageSubsample: - # photo = photo.subsample(element.ImageSubsample) - # else: - # width, height = photo.width(), photo.height() - # tkbutton.config(image=photo, compound=tk.CENTER, width=width, height=height) - # tkbutton.image = photo - # if width != 0: - # tkbutton.configure(wraplength=wraplen + 10) # set wrap to width of widget - # tkbutton.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.BindReturnKey: - # element.TKButton.bind('', element.ReturnKeyHandler) - # if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - # focus_set = True - # element.TKButton.bind('', element.ReturnKeyHandler) - # element.TKButton.focus_set() - # toplevel_form.TKroot.focus_force() - # if element.Disabled == True: - # element.TKButton['state'] = 'disabled' - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKButton, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # # ------------------------- INPUT element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_TEXT: - element = element # type: InputText - element.Widget = InputText.TextInput_raw_onkeyup(hint=element.DefaultText) - # element.Widget = remi.gui.TextInput(hint=element.DefaultText) - do_font_and_color(element.Widget) - if element.ChangeSubmits: - element.Widget.onkeyup.connect(element._InputTextCallback) - # element.Widget.onkeydown.connect(element._InputTextCallback) - tk_row_frame.append(element.Widget) - - # show = element.PasswordCharacter if element.PasswordCharacter else "" - # if element.Justification is not None: - # justification = element.Justification - # else: - # justification = DEFAULT_TEXT_JUSTIFICATION - # justify = tk.LEFT if justification == 'left' else tk.CENTER if justification == 'center' else tk.RIGHT - # # anchor = tk.NW if justification == 'left' else tk.N if justification == 'center' else tk.NE - # element.TKEntry = tk.Entry(tk_row_frame, width=element_size[0], textvariable=element.TKStringVar, - # bd=border_depth, font=font, show=show, justify=justify) - # if element.ChangeSubmits: - # element.TKEntry.bind('', element.KeyboardHandler) - # element.TKEntry.bind('', element.ReturnKeyHandler) - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element.TKEntry.configure(background=element.BackgroundColor) - # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - # element.TKEntry.configure(fg=text_color) - # element.TKEntry.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='x') - # if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - # focus_set = True - # element.TKEntry.focus_set() - # if element.Disabled: - # element.TKEntry['state'] = 'disabled' - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKEntry, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- COMBO element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_COMBO: - element = element # type: Combo - element.Widget = remi.gui.DropDown.new_from_list(element.Values) - if element.DefaultValue is not None: - element.Widget.select_by_value(element.DefaultValue) - do_font_and_color(element.Widget) - if element.ChangeSubmits: - element.Widget.onchange.connect(element._ChangedCallback) - tk_row_frame.append(element.Widget) - - # ------------------------- OPTION MENU (Like ComboBox but different) element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_OPTION_MENU: - element.Widget = remi.gui.FileUploader('./', width=200, height=30, margin='10px') - - # element.Widget = remi.gui.FileFolderNavigator(False, r'a:\TEMP', True, False) - tk_row_frame.append(element.Widget) - pass - # ------------------------- LISTBOX element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_LISTBOX: - element = element # type: Listbox - element.Widget = remi.gui.ListView.new_from_list(element.Values) - do_font_and_color(element.Widget) - if element.ChangeSubmits: - element.Widget.onselection.connect(element._ChangedCallback) - tk_row_frame.append(element.Widget) - # max_line_len = max([len(str(l)) for l in element.Values]) if len(element.Values) != 0 else 0 - # if auto_size_text is False: - # width = element_size[0] - # else: - # width = max_line_len - # listbox_frame = tk.Frame(tk_row_frame) - # element.TKStringVar = tk.StringVar() - # element.TKListbox = tk.Listbox(listbox_frame, height=element_size[1], width=width, - # selectmode=element.SelectMode, font=font) - # for index, item in enumerate(element.Values): - # element.TKListbox.insert(tk.END, item) - # if element.DefaultValues is not None and item in element.DefaultValues: - # element.TKListbox.selection_set(index) - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element.TKListbox.configure(background=element.BackgroundColor) - # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - # element.TKListbox.configure(fg=text_color) - # if element.ChangeSubmits: - # element.TKListbox.bind('<>', element.ListboxSelectHandler) - # vsb = tk.Scrollbar(listbox_frame, orient="vertical", command=element.TKListbox.yview) - # element.TKListbox.configure(yscrollcommand=vsb.set) - # element.TKListbox.pack(side=tk.LEFT) - # vsb.pack(side=tk.LEFT, fill='y') - # listbox_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.BindReturnKey: - # element.TKListbox.bind('', element.ListboxSelectHandler) - # element.TKListbox.bind('', element.ListboxSelectHandler) - # if element.Disabled == True: - # element.TKListbox['state'] = 'disabled' - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKListbox, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- INPUT MULTILINE element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_MULTILINE: - element = element # type: Multiline - element.Widget = remi.gui.TextInput(single_line=False, hint=element.DefaultText) - do_font_and_color(element.Widget) - if element.ChangeSubmits: - element.Widget.onkeydown.connect(element._InputTextCallback) - tk_row_frame.append(element.Widget) - # default_text = element.DefaultText - # width, height = element_size - # element.TKText = tk.scrolledtext.ScrolledText(tk_row_frame, width=width, height=height, wrap='word', - # bd=border_depth, font=font) - # element.TKText.insert(1.0, default_text) # set the default text - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element.TKText.configure(background=element.BackgroundColor) - # element.TKText.vbar.config(troughcolor=DEFAULT_SCROLLBAR_COLOR) - # element.TKText.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') - # if element.ChangeSubmits: - # element.TKText.bind('', element.KeyboardHandler) - # if element.EnterSubmits: - # element.TKText.bind('', element.ReturnKeyHandler) - # if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - # focus_set = True - # element.TKText.focus_set() - # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - # element.TKText.configure(fg=text_color) - # if element.Disabled == True: - # element.TKText['state'] = 'disabled' - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKText, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - - # ------------------------- INPUT CHECKBOX element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_CHECKBOX: - element = element # type: Checkbox - element.Widget = remi.gui.CheckBoxLabel(element.Text) - if element.InitialState: - element.Widget.set_value(element.InitialState) - if element.ChangeSubmits: - element.Widget.onchange.connect(element._ChangedCallback) - do_font_and_color(element.Widget) - tk_row_frame.append(element.Widget) - - # width = 0 if auto_size_text else element_size[0] - # default_value = element.InitialState - # element.TKIntVar = tk.IntVar() - # element.TKIntVar.set(default_value if default_value is not None else 0) - # if element.ChangeSubmits: - # element.TKCheckbutton = tk.Checkbutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, - # variable=element.TKIntVar, bd=border_depth, font=font, - # command=element.CheckboxHandler) - # else: - # element.TKCheckbutton = tk.Checkbutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, - # variable=element.TKIntVar, bd=border_depth, font=font) - # if default_value is None or element.Disabled: - # element.TKCheckbutton.configure(state='disable') - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element.TKCheckbutton.configure(background=element.BackgroundColor) - # element.TKCheckbutton.configure(selectcolor=element.BackgroundColor) - # element.TKCheckbutton.configure(activebackground=element.BackgroundColor) - # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - # element.TKCheckbutton.configure(fg=text_color) - # element.TKCheckbutton.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKCheckbutton, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # # ------------------------- PROGRESS BAR element ------------------------- # - elif element_type == ELEM_TYPE_PROGRESS_BAR: - pass - # # save this form because it must be 'updated' (refreshed) solely for the purpose of updating bar - # width = element_size[0] - # fnt = tkinter.font.Font() - # char_width = fnt.measure('A') # single character width - # progress_length = width * char_width - # progress_width = element_size[1] - # direction = element.Orientation - # if element.BarColor != (None, None): # if element has a bar color, use it - # bar_color = element.BarColor - # else: - # bar_color = DEFAULT_PROGRESS_BAR_COLOR - # element.TKProgressBar = TKProgressBar(tk_row_frame, element.MaxValue, progress_length, progress_width, - # orientation=direction, BarColor=bar_color, - # border_width=element.BorderWidth, relief=element.Relief, - # style=element.BarStyle, key=element.Key) - # element.TKProgressBar.TKProgressBarForReal.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # ------------------------- INPUT RADIO BUTTON element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_RADIO: - pass - # width = 0 if auto_size_text else element_size[0] - # default_value = element.InitialState - # ID = element.GroupID - # # see if ID has already been placed - # value = EncodeRadioRowCol(row_num, col_num) # value to set intvar to if this radio is selected - # if ID in toplevel_form.RadioDict: - # RadVar = toplevel_form.RadioDict[ID] - # else: - # RadVar = tk.IntVar() - # toplevel_form.RadioDict[ID] = RadVar - # element.TKIntVar = RadVar # store the RadVar in Radio object - # if default_value: # if this radio is the one selected, set RadVar to match - # element.TKIntVar.set(value) - # if element.ChangeSubmits: - # element.TKRadio = tk.Radiobutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, - # variable=element.TKIntVar, value=value, bd=border_depth, font=font, - # command=element.RadioHandler) - # else: - # element.TKRadio = tk.Radiobutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, - # variable=element.TKIntVar, value=value, bd=border_depth, font=font) - # if not element.BackgroundColor in (None, COLOR_SYSTEM_DEFAULT): - # element.TKRadio.configure(background=element.BackgroundColor) - # element.TKRadio.configure(selectcolor=element.BackgroundColor) - # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - # element.TKRadio.configure(fg=text_color) - # if element.Disabled: - # element.TKRadio['state'] = 'disabled' - # element.TKRadio.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKRadio, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- INPUT SPIN element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_SPIN: - element = element # type: Spin - element.Widget = remi.gui.SpinBox(50, 0, 100) - if element.DefaultValue is not None: - element.Widget.set_value(element.DefaultValue) - do_font_and_color(element.Widget) - if element.ChangeSubmits: - element.Widget.onchange.connect(element._ChangedCallback) - tk_row_frame.append(element.Widget) - # width, height = element_size - # width = 0 if auto_size_text else element_size[0] - # element.TKStringVar = tk.StringVar() - # element.TKSpinBox = tk.Spinbox(tk_row_frame, values=element.Values, textvariable=element.TKStringVar, - # width=width, bd=border_depth) - # element.TKStringVar.set(element.DefaultValue) - # element.TKSpinBox.configure(font=font) # set wrap to width of widget - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element.TKSpinBox.configure(background=element.BackgroundColor) - # element.TKSpinBox.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - # element.TKSpinBox.configure(fg=text_color) - # if element.ChangeSubmits: - # element.TKSpinBox.bind('', element.SpinChangedHandler) - # if element.Disabled == True: - # element.TKSpinBox['state'] = 'disabled' - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKSpinBox, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- OUTPUT element ------------------------- # - elif element_type == ELEM_TYPE_OUTPUT: - element = element # type: Output - element.Widget = remi.gui.TextInput(single_line=False) - element.Disabled = True - do_font_and_color(element.Widget) - tk_row_frame.append(element.Widget) - toplevel_form.OutputElementForStdOut = element - Window.stdout_is_rerouted = True - Window.stdout_string_io = StringIO() - sys.stdout = Window.stdout_string_io - - # width, height = element_size - # element._TKOut = TKOutput(tk_row_frame, width=width, height=height, bd=border_depth, - # background_color=element.BackgroundColor, text_color=text_color, font=font, - # pad=element.Pad) - # element._TKOut.pack(side=tk.LEFT, expand=True, fill='both') - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element._TKOut, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- OUTPUT MULTILINE element ------------------------- # - elif element_type == ELEM_TYPE_MULTILINE_OUTPUT: - element = element # type: MultilineOutput - element.Widget = remi.gui.TextInput(single_line=False) - element.Disabled = True - do_font_and_color(element.Widget) - tk_row_frame.append(element.Widget) - if element.DefaultText: - element.Widget.set_value(element.DefaultText) - # ------------------------- IMAGE element ------------------------- # - elif element_type == ELEM_TYPE_IMAGE: - element = element # type: Image - # element.Widget = remi.gui.Image(element.Filename) - element.Widget = SuperImage(element.Filename if element.Filename is not None else element.Data) - if element.Filename is not None: - # print(f'loading image filename in pack frame {element.Filename}') - element.Widget.load(element.Filename) - do_font_and_color(element.Widget) - if element.EnableEvents: - element.Widget.onclick.connect(element._ChangedCallback) - tk_row_frame.append(element.Widget) - # if element.Filename is not None: - # photo = tk.PhotoImage(file=element.Filename) - # elif element.Data is not None: - # photo = tk.PhotoImage(data=element.Data) - # else: - # photo = None - # print('*ERROR laying out form.... Image Element has no image specified*') - # - # if photo is not None: - # if element_size == ( - # None, None) or element_size == None or element_size == toplevel_form.DefaultElementSize: - # width, height = photo.width(), photo.height() - # else: - # width, height = element_size - # if photo is not None: - # element.tktext_label = tk.Label(tk_row_frame, image=photo, width=width, height=height, - # bd=border_depth) - # else: - # element.tktext_label = tk.Label(tk_row_frame, width=width, height=height, bd=border_depth) - # if element.BackgroundColor is not None: - # element.tktext_label.config(background=element.BackgroundColor); - # - # element.tktext_label.image = photo - # # tktext_label.configure(anchor=tk.NW, image=photo) - # element.tktext_label.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.tktext_label, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Canvas element ------------------------- # - elif element_type == ELEM_TYPE_CANVAS: - pass - # width, height = element_size - # if element._TKCanvas is None: - # element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) - # else: - # element._TKCanvas.master = tk_row_frame - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) - # element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - - # ------------------------- Graph element ------------------------- # - elif element_type == ELEM_TYPE_GRAPH: - element = element # type: Graph - element.Widget = remi.gui.Svg(width=element.CanvasSize[0], height=element.CanvasSize[1]) - element.SvgGroup = remi.gui.SvgSubcontainer(0, 0, '100%', '100%') - element.Widget.append( - [ - element.SvgGroup, - ] - ) - do_font_and_color(element.Widget) - if element.ChangeSubmits: - element.Widget.onmouseup.connect(element._MouseUpCallback) - # element.Widget.onclick.connect(element.ClickCallback) - if element.DragSubmits: - element.Widget.onmousedown.connect(element._MouseDownCallback) - element.Widget.onmouseup.connect(element._MouseUpCallback) - element.Widget.onmousemove.connect(element._DragCallback) - - tk_row_frame.append(element.Widget) - # width, height = element_size - # if element._TKCanvas is None: - # element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) - # else: - # element._TKCanvas.master = tk_row_frame - # element._TKCanvas2 = tk.Canvas(element._TKCanvas, width=width, height=height, bd=border_depth) - # element._TKCanvas2.pack(side=tk.LEFT) - # element._TKCanvas2.addtag_all('mytag') - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element._TKCanvas2.configure(background=element.BackgroundColor, highlightthickness=0) - # element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) - # element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # if element.ChangeSubmits: - # element._TKCanvas2.bind('', element.ButtonReleaseCallBack) - # element._TKCanvas2.bind('', element.ButtonPressCallBack) - # if element.DragSubmits: - # element._TKCanvas2.bind('', element.MotionCallBack) - # ------------------------- MENUBAR element ------------------------- # - elif element_type == ELEM_TYPE_MENUBAR: - element = element # type: Menu - menu = remi.gui.Menu(width='100%', height=str(element_size[1])) - element_size = (0, 0) # makes the menu span across the top - do_font_and_color(menu) - - menu_def = element.MenuDefinition - for menu_entry in menu_def: - # print(f'Adding a Menubar ENTRY {menu_entry}') - pos = menu_entry[0].find('&') - # print(pos) - if pos != -1: - if pos == 0 or menu_entry[0][pos - 1] != '\\': - menu_entry[0] = menu_entry[0][:pos] + menu_entry[0][pos + 1 :] - if menu_entry[0][0] == MENU_DISABLED_CHARACTER: - item = remi.gui.MenuItem(menu_entry[0][1:], width=100, height=element_size[1]) - item.set_enabled(False) - else: - item = remi.gui.MenuItem(menu_entry[0], width=100, height=element_size[1]) - do_font_and_color(item) - menu.append( - [ - item, - ] - ) - if len(menu_entry) > 1: - AddMenuItem(item, menu_entry[1], element) - - element.Widget = menubar = remi.gui.MenuBar(width='100%', height='30px') - element.Widget.style['z-index'] = '1' - menubar.append(menu) - # tk_row_frame.append(element.Widget) - containing_frame.append(element.Widget) - - # ------------------------- Frame element ------------------------- # - elif element_type == ELEM_TYPE_FRAME: - element = element # type: Frame - # element.Widget = column_widget = remi.gui.VBox() - element.Widget = column_widget = CLASSframe(element.Title) - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - column_widget.style['background-color'] = element.BackgroundColor - PackFormIntoFrame(element, column_widget, toplevel_form) - tk_row_frame.append(element.Widget) - - # - # element = element # type: Frame - # element.Widget = column_widget = remi.gui.VBox() - # if element.Justification.startswith('c'): - # column_widget.style['align-items'] = 'center' - # column_widget.style['justify-content'] = 'center' - # else: - # column_widget.style['justify-content'] = 'flex-start' - # column_widget.style['align-items'] = 'baseline' - # if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - # column_widget.style['background-color'] = element.BackgroundColor - # PackFormIntoFrame(element, column_widget, toplevel_form) - # tk_row_frame.append(element.Widget) - - # labeled_frame = tk.LabelFrame(tk_row_frame, text=element.Title, relief=element.Relief) - # PackFormIntoFrame(element, labeled_frame, toplevel_form) - # labeled_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: - # labeled_frame.configure(background=element.BackgroundColor, - # highlightbackground=element.BackgroundColor, - # highlightcolor=element.BackgroundColor) - # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: - # labeled_frame.configure(foreground=element.TextColor) - # if font is not None: - # labeled_frame.configure(font=font) - # if element.TitleLocation is not None: - # labeled_frame.configure(labelanchor=element.TitleLocation) - # if element.BorderWidth is not None: - # labeled_frame.configure(borderwidth=element.BorderWidth) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(labeled_frame, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Tab element ------------------------- # - elif element_type == ELEM_TYPE_TAB: - element = element # type: Tab - element.Widget = remi.gui.VBox() - if element.Justification.startswith('c'): - # print('CENTERING') - element.Widget.style['align-items'] = 'center' - element.Widget.style['justify-content'] = 'center' - else: - element.Widget.style['justify-content'] = 'flex-start' - element.Widget.style['align-items'] = 'baseline' - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - element.Widget.style['background-color'] = element.BackgroundColor - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - element.Widget.style['background-color'] = element.BackgroundColor - PackFormIntoFrame(element, element.Widget, toplevel_form) - # tk_row_frame.append(element.Widget) - containing_frame.add_tab(element.Widget, element.Title, None) - - # element.TKFrame = tk.Frame(form.TKNotebook) - # PackFormIntoFrame(element, element.TKFrame, toplevel_form) - # if element.Disabled: - # form.TKNotebook.add(element.TKFrame, text=element.Title, state='disabled') - # else: - # form.TKNotebook.add(element.TKFrame, text=element.Title) - # form.TKNotebook.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # element.ParentNotebook = form.TKNotebook - # element.TabID = form.TabCount - # form.TabCount += 1 - # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: - # element.TKFrame.configure(background=element.BackgroundColor, - # highlightbackground=element.BackgroundColor, - # highlightcolor=element.BackgroundColor) - # # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: - # # element.TKFrame.configure(foreground=element.TextColor) - # - # # ttk.Style().configure("TNotebook", background='red') - # # ttk.Style().map("TNotebook.Tab", background=[("selected", 'orange')], - # # foreground=[("selected", 'green')]) - # # ttk.Style().configure("TNotebook.Tab", background='blue', foreground='yellow') - # - # if element.BorderWidth is not None: - # element.TKFrame.configure(borderwidth=element.BorderWidth) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKFrame, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- TabGroup element ------------------------- # - elif element_type == ELEM_TYPE_TAB_GROUP: - element = element # type: TabGroup - element.Widget = remi.gui.TabBox() - # do_font_and_color(element.Widget) - PackFormIntoFrame(element, element.Widget, toplevel_form) - tk_row_frame.append(element.Widget) - - # custom_style = str(element.Key) + 'customtab.TNotebook' - # style = ttk.Style(tk_row_frame) - # if element.Theme is not None: - # style.theme_use(element.Theme) - # if element.TabLocation is not None: - # position_dict = {'left': 'w', 'right': 'e', 'top': 'n', 'bottom': 's', 'lefttop': 'wn', - # 'leftbottom': 'ws', 'righttop': 'en', 'rightbottom': 'es', 'bottomleft': 'sw', - # 'bottomright': 'se', 'topleft': 'nw', 'topright': 'ne'} - # try: - # tab_position = position_dict[element.TabLocation] - # except: - # tab_position = position_dict['top'] - # style.configure(custom_style, tabposition=tab_position) - # - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # style.configure(custom_style, background=element.BackgroundColor, foreground='purple') - # - # # style.theme_create("yummy", parent="alt", settings={ - # # "TNotebook": {"configure": {"tabmargins": [2, 5, 2, 0]}}, - # # "TNotebook.Tab": { - # # "configure": {"padding": [5, 1], "background": mygreen}, - # # "map": {"background": [("selected", myred)], - # # "expand": [("selected", [1, 1, 1, 0])]}}}) - # - # # style.configure(custom_style+'.Tab', background='red') - # if element.SelectedTitleColor != None: - # style.map(custom_style + '.Tab', foreground=[("selected", element.SelectedTitleColor)]) - # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - # style.configure(custom_style + '.Tab', foreground=element.TextColor) - # # style.configure(custom_style, background='blue', foreground='yellow') - # - # element.TKNotebook = ttk.Notebook(tk_row_frame, style=custom_style) - # - # PackFormIntoFrame(element, toplevel_form.TKroot, toplevel_form) - # - # if element.ChangeSubmits: - # element.TKNotebook.bind('<>', element.TabGroupSelectHandler) - # if element.BorderWidth is not None: - # element.TKNotebook.configure(borderwidth=element.BorderWidth) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKNotebook, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- SLIDER element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_SLIDER: - element = element # type: Slider - orient = remi.gui.Container.LAYOUT_HORIZONTAL if element.Orientation.lower().startswith('h') else remi.gui.Container.LAYOUT_VERTICAL - # print(f'slider orient = {orient}') - element.Widget = remi.gui.Slider( - layout_orientation=orient, - default_value=element.DefaultValue, - min=element.Range[0], - max=element.Range[1], - step=element.Resolution, - ) - if element.DefaultValue: - element.Widget.set_value(element.DefaultValue) - # if element.Orientation.startswith('v'): - # element.Container.LAYOUT_orientation = remi.gui.Container.LAYOUT_VERTICAL - do_font_and_color(element.Widget) - if element.ChangeSubmits: - element.Widget.onchange.connect(element._SliderCallback) - element.Widget.style['orientation'] = 'vertical' - element.Widget.attributes['orientation'] = 'vertical' - # print(f'slider = {element.Widget.style, element.Widget.attributes}') - tk_row_frame.append(element.Widget) # slider_length = element_size[0] * CharWidthInPixels() - - # ------------------------- TABLE element ------------------------- # - elif element_type == ELEM_TYPE_TABLE: - element = element # type: Table - new_table = [] - for row_num, row in enumerate(element.Values): # convert entire table to strings - new_row = [str(item) for item in row] - if element.DisplayRowNumbers: - new_row = [ - element.RowHeaderText if row_num == 0 else str(row_num + element.StartingRowNumber), - ] + new_row - new_table.append(new_row) - element.Widget = remi.gui.Table.new_from_list(new_table) - do_font_and_color(element.Widget) - tk_row_frame.append(element.Widget) - element.Widget.on_table_row_click.connect(element._on_table_row_click) - # frame = tk.Frame(tk_row_frame) - # - # height = element.NumRows - # if element.Justification == 'left': - # anchor = tk.W - # elif element.Justification == 'right': - # anchor = tk.E - # else: - # anchor = tk.CENTER - # column_widths = {} - # for row in element.Values: - # for i, col in enumerate(row): - # col_width = min(len(str(col)), element.MaxColumnWidth) - # try: - # if col_width > column_widths[i]: - # column_widths[i] = col_width - # except: - # column_widths[i] = col_width - # if element.ColumnsToDisplay is None: - # displaycolumns = element.ColumnHeadings if element.ColumnHeadings is not None else element.Values[0] - # else: - # displaycolumns = [] - # for i, should_display in enumerate(element.ColumnsToDisplay): - # if should_display: - # displaycolumns.append(element.ColumnHeadings[i]) - # column_headings = element.ColumnHeadings - # if element.DisplayRowNumbers: # if display row number, tack on the numbers to front of columns - # displaycolumns = [element.RowHeaderText, ] + displaycolumns - # column_headings = [element.RowHeaderText, ] + element.ColumnHeadings - # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, - # displaycolumns=displaycolumns, show='headings', height=height, - # selectmode=element.SelectMode,) - # treeview = element.TKTreeview - # if element.DisplayRowNumbers: - # treeview.heading(element.RowHeaderText, text=element.RowHeaderText) # make a dummy heading - # treeview.column(element.RowHeaderText, width=50, anchor=anchor) - # - # headings = element.ColumnHeadings if element.ColumnHeadings is not None else element.Values[0] - # for i, heading in enumerate(headings): - # treeview.heading(heading, text=heading) - # if element.AutoSizeColumns: - # width = max(column_widths[i], len(heading)) - # else: - # try: - # width = element.ColumnWidths[i] - # except: - # width = element.DefaultColumnWidth - # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) - # - # # Insert values into the tree - # for i, value in enumerate(element.Values): - # if element.DisplayRowNumbers: - # value = [i+element.StartingRowNumber] + value - # id = treeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i) - # if element.AlternatingRowColor is not None: # alternating colors - # for row in range(0, len(element.Values), 2): - # treeview.tag_configure(row, background=element.AlternatingRowColor) - # if element.RowColors is not None: # individual row colors - # for row_def in element.RowColors: - # if len(row_def) == 2: # only background is specified - # treeview.tag_configure(row_def[0], background=row_def[1]) - # else: - # treeview.tag_configure(row_def[0], background=row_def[2], foreground=row_def[1]) - # - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", background=element.BackgroundColor, - # fieldbackground=element.BackgroundColor) - # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", foreground=element.TextColor) - # if element.RowHeight is not None: - # ttk.Style().configure("Treeview", rowheight=element.RowHeight) - # ttk.Style().configure("Treeview", font=font) - # # scrollable_frame.pack(side=tk.LEFT, padx=elementpad[0], pady=elementpad[1], expand=True, fill='both') - # treeview.bind("<>", element.treeview_selected) - # if element.BindReturnKey: - # treeview.bind('', element.treeview_double_click) - # treeview.bind('', element.treeview_double_click) - # - # scrollbar = tk.Scrollbar(frame) - # scrollbar.pack(side=tk.RIGHT, fill='y') - # scrollbar.config(command=treeview.yview) - # - # if not element.VerticalScrollOnly: - # hscrollbar = tk.Scrollbar(frame, orient=tk.HORIZONTAL) - # hscrollbar.pack(side=tk.BOTTOM, fill='x') - # hscrollbar.config(command=treeview.xview) - # treeview.configure(xscrollcommand=hscrollbar.set) - # - # treeview.configure(yscrollcommand=scrollbar.set) - # - # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') - # if element.Visible is False: - # element.TKTreeview.pack_forget() - # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # if element.RightClickMenu or toplevel_form.RightClickMenu: - # menu = element.RightClickMenu or toplevel_form.RightClickMenu - # top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) - # AddMenuItem(top_menu, menu[1], element) - # element.TKRightClickMenu = top_menu - # element.TKTreeview.bind('', element.RightClickMenuCallback) - pass - # frame = tk.Frame(tk_row_frame) - # - # height = element.NumRows - # if element.Justification == 'left': - # anchor = tk.W - # elif element.Justification == 'right': - # anchor = tk.E - # else: - # anchor = tk.CENTER - # column_widths = {} - # for row in element.Values: - # for i, col in enumerate(row): - # col_width = min(len(str(col)), element.MaxColumnWidth) - # try: - # if col_width > column_widths[i]: - # column_widths[i] = col_width - # except: - # column_widths[i] = col_width - # if element.ColumnsToDisplay is None: - # displaycolumns = element.ColumnHeadings - # else: - # displaycolumns = [] - # for i, should_display in enumerate(element.ColumnsToDisplay): - # if should_display: - # displaycolumns.append(element.ColumnHeadings[i]) - # column_headings = element.ColumnHeadings - # if element.DisplayRowNumbers: # if display row number, tack on the numbers to front of columns - # displaycolumns = [element.RowHeaderText, ] + displaycolumns - # column_headings = [element.RowHeaderText, ] + element.ColumnHeadings - # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, - # displaycolumns=displaycolumns, show='headings', height=height, - # selectmode=element.SelectMode) - # treeview = element.TKTreeview - # if element.DisplayRowNumbers: - # treeview.heading(element.RowHeaderText, text=element.RowHeaderText) # make a dummy heading - # treeview.column(element.RowHeaderText, width=50, anchor=anchor) - # for i, heading in enumerate(element.ColumnHeadings): - # treeview.heading(heading, text=heading) - # if element.AutoSizeColumns: - # width = max(column_widths[i], len(heading)) - # else: - # try: - # width = element.ColumnWidths[i] - # except: - # width = element.DefaultColumnWidth - # - # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) - # # Insert values into the tree - # for i, value in enumerate(element.Values): - # if element.DisplayRowNumbers: - # value = [i + element.StartingRowNumber] + value - # id = treeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i % 2) - # if element.AlternatingRowColor is not None: - # treeview.tag_configure(1, background=element.AlternatingRowColor) - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", background=element.BackgroundColor, - # fieldbackground=element.BackgroundColor) - # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", foreground=element.TextColor) - # # scrollable_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') - # treeview.bind("<>", element.treeview_selected) - # if element.BindReturnKey: - # treeview.bind('', element.treeview_double_click) - # treeview.bind('', element.treeview_double_click) - # scrollbar = tk.Scrollbar(frame) - # scrollbar.pack(side=tk.RIGHT, fill='y') - # scrollbar.config(command=treeview.yview) - # treeview.configure(yscrollcommand=scrollbar.set) - # - # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') - # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Tree element ------------------------- # - elif element_type == ELEM_TYPE_TREE: - pass - # frame = tk.Frame(tk_row_frame) - # - # height = element.NumRows - # if element.Justification == 'left': # justification - # anchor = tk.W - # elif element.Justification == 'right': - # anchor = tk.E - # else: - # anchor = tk.CENTER - # - # if element.ColumnsToDisplay is None: # Which cols to display - # displaycolumns = element.ColumnHeadings - # else: - # displaycolumns = [] - # for i, should_display in enumerate(element.ColumnsToDisplay): - # if should_display: - # displaycolumns.append(element.ColumnHeadings[i]) - # column_headings = element.ColumnHeadings - # # ------------- GET THE TREEVIEW WIDGET ------------- - # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, - # displaycolumns=displaycolumns, show='tree headings', height=height, - # selectmode=element.SelectMode, ) - # treeview = element.TKTreeview - # for i, heading in enumerate(element.ColumnHeadings): # Configure cols + headings - # treeview.heading(heading, text=heading) - # if element.AutoSizeColumns: - # width = min(element.MaxColumnWidth, len(heading) + 1) - # else: - # try: - # width = element.ColumnWidths[i] - # except: - # width = element.DefaultColumnWidth - # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) - # - # def add_treeview_data(node): - # # print(f'Inserting {node.key} under parent {node.parent}') - # if node.key != '': - # treeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, - # open=element.ShowExpanded) - # for node in node.children: - # add_treeview_data(node) - # - # add_treeview_data(element.TreeData.root_node) - # treeview.column('#0', width=element.Col0Width * CharWidthInPixels(), anchor=anchor) - # # ----- configure colors ----- - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", background=element.BackgroundColor, - # fieldbackground=element.BackgroundColor) - # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", foreground=element.TextColor) - # - # scrollbar = tk.Scrollbar(frame) - # scrollbar.pack(side=tk.RIGHT, fill='y') - # scrollbar.config(command=treeview.yview) - # treeview.configure(yscrollcommand=scrollbar.set) - # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') - # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) - # treeview.bind("<>", element.treeview_selected) - # if element.Tooltip is not None: # tooltip - # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Separator element ------------------------- # - elif element_type == ELEM_TYPE_SEPARATOR: - pass - # separator = ttk.Separator(tk_row_frame, orient=element.Orientation, ) - # separator.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], fill='both', expand=True) - # - # # ............................DONE WITH ROW pack the row of widgets ..........................# - # done with row, pack the row of widgets - # tk_row_frame.grid(row=row_num+2, sticky=tk.NW, padx=DEFAULT_MARGINS[0]) - # tk_row_frame.pack(side=tk.TOP, anchor='nw', padx=DEFAULT_MARGINS[0], expand=False) - # if form.BackgroundColor is not None and form.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # tk_row_frame.configure(background=form.BackgroundColor) - # toplevel_form.TKroot.configure(padx=DEFAULT_MARGINS[0], pady=DEFAULT_MARGINS[1]) - if not type(containing_frame) == remi.gui.TabBox: - containing_frame.append(tk_row_frame) - return - - -def setup_remi_window(app: Window.MyApp, window: Window): - master_widget = remi.gui.VBox() - master_widget.style['justify-content'] = 'flex-start' - master_widget.style['align-items'] = 'baseline' - if window.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - master_widget.style['background-color'] = window.BackgroundColor - try: - PackFormIntoFrame(window, master_widget, window) - except: - print('* ERROR PACKING FORM *') - print(traceback.format_exc()) - - if window.BackgroundImage: - master_widget.style['background-image'] = "url('{}')".format('/' + window.BackgroundImage) - # print(f'background info',self.master_widget.attributes['background-image'] ) - - if not window.DisableClose: - # add the following 3 lines to your app and the on_window_close method to make the console close automatically - tag = remi.gui.Tag(_type='script') - tag.add_child( - 'javascript', - """window.onunload=function(e){sendCallback('%s','%s');return "close?";};""" % (str(id(app)), 'on_window_close'), - ) - master_widget.add_child('onunloadevent', tag) - - if window.ReturnKeyboardEvents: - app.page.children['body'].onkeyup.connect(window.on_key_up) - if window.ReturnKeyDownEvents: - app.page.children['body'].onkeydown.connect(window.on_key_down) - - # if window.WindowIcon: - # if type(window.WindowIcon) is bytes or len(window.WindowIcon) > 200: - # app.page.children['head'].set_icon_data( base64_data=str(window.WindowIcon), mimetype="image/gif" ) - # else: - # app.page.children['head'].set_icon_file("/res:{}".format(window.WindowIcon)) - # pass - # mimetype, encoding = mimetypes.guess_type(image_source) - # with open(image_source, 'rb') as f: - # data = f.read() - # b64 = base64.b64encode(data) - # b64_str = b64.decode("utf-8") - # image_string = "data:image/svg;base64,%s"%b64_str - # rpoint.set_image(image_string) - - return master_widget - - -# ----====----====----====----====----==== STARTUP TK ====----====----====----====----====----# -def StartupTK(window: Window): - global _my_windows - - # print('Starting TK open Windows = {}'.format(ow)) - - _my_windows.Increment() - - # if not my_flex_form.Resizable: - # root.resizable(False, False) - - # if my_flex_form.KeepOnTop: - # root.wm_attributes("-topmost", 1) - # master = window.TKroot - # Set Title - # master.title(MyFlexForm.Title) - # master = 00000 - - InitializeResults(window) - - # Does all of the window setup, starting up Remi - # if no windows exist, start Remi thread which will call same setup_remi_window call as shown below - if len(Window.active_windows) == 0: - window.thread_id = threading.Thread(target=window.remi_thread, daemon=True) - window.thread_id.daemon = True - window.thread_id.start() - item = window.MessageQueue.get() # Get the layout complete message - Window.active_windows.append(window) - Window.App = window.App - else: - # print('Starting second page') - # margin 0px auto allows to center the app to the screen - # master_widget = remi.gui.VBox() - # master_widget.style['justify-content'] = 'flex-start' - # master_widget.style['align-items'] = 'baseline' - # if window.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - # master_widget.style['background-color'] = window.BackgroundColor - # PackFormIntoFrame(window, master_widget, window) - master_widget = setup_remi_window(Window.App, window) - window.master_widget = master_widget - Window.active_windows.append(window) - Window.App.set_root_widget(master_widget) - - return - - -# ==============================_GetNumLinesNeeded ==# -# Helper function for determining how to wrap text # -# ===================================================# -def _GetNumLinesNeeded(text, max_line_width): - if max_line_width == 0: - return 1 - lines = text.split('\n') - num_lines = len(lines) # number of original lines of text - max_line_len = max([len(l) for l in lines]) # longest line - lines_used = [] - for L in lines: - lines_used.append(len(L) // max_line_width + (len(L) % max_line_width > 0)) # fancy math to round up - total_lines_needed = sum(lines_used) - return total_lines_needed - - -# ============================== PROGRESS METER ========================================== # - - -def ConvertArgsToSingleString(*args): - ( - max_line_total, - width_used, - total_lines, - ) = ( - 0, - 0, - 0, - ) - single_line_message = '' - # loop through args and built a SINGLE string from them - for message in args: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = max(longest_line_len, width_used) - max_line_total = max(max_line_total, width_used) - lines_needed = _GetNumLinesNeeded(message, width_used) - total_lines += lines_needed - single_line_message += message + '\n' - return single_line_message, width_used, total_lines - - -# ============================== ProgressMeter =====# -# ===================================================# -def _ProgressMeter(title, max_value, *args, orientation=None, bar_color=(None, None), button_color=None, size=DEFAULT_PROGRESS_BAR_SIZE, border_width=None, grab_anywhere=False): - ''' - Create and show a form on tbe caller's behalf. - :param title: - :param max_value: - :param args: ANY number of arguments the caller wants to display - :param orientation: - :param bar_color: - :param size: - :param Style: - :param StyleOffset: - :return: ProgressBar object that is in the form - ''' - local_orientation = DEFAULT_METER_ORIENTATION if orientation is None else orientation - local_border_width = DEFAULT_PROGRESS_BAR_BORDER_WIDTH if border_width is None else border_width - bar2 = ProgressBar( - max_value, - orientation=local_orientation, - size=size, - bar_color=bar_color, - border_width=local_border_width, - relief=DEFAULT_PROGRESS_BAR_RELIEF, - ) - form = Window(title, auto_size_text=True, grab_anywhere=grab_anywhere) - - # Form using a horizontal bar - if local_orientation[0].lower() == 'h': - single_line_message, width, height = ConvertArgsToSingleString(*args) - bar2.TextToDisplay = single_line_message - bar2.TextToDisplay = single_line_message - bar2.MaxValue = max_value - bar2.CurrentValue = 0 - bar_text = Text(single_line_message, size=(width, height + 3), auto_size_text=True) - form.AddRow(bar_text) - form.AddRow((bar2)) - form.AddRow((CloseButton('Cancel', button_color=button_color))) - else: - single_line_message, width, height = ConvertArgsToSingleString(*args) - bar2.TextToDisplay = single_line_message - bar2.MaxValue = max_value - bar2.CurrentValue = 0 - bar_text = Text(single_line_message, size=(width, height + 3), auto_size_text=True) - form.AddRow(bar2, bar_text) - form.AddRow((CloseButton('Cancel', button_color=button_color))) - - form.NonBlocking = True - form.Show(non_blocking=True) - return bar2, bar_text - - -# ============================== ProgressMeterUpdate =====# -def _ProgressMeterUpdate(bar, value, text_elem, *args): - ''' - Update the progress meter for a form - :param form: class ProgressBar - :param value: int - :return: True if not cancelled, OK....False if Error - ''' - global _my_windows - if bar == None: - return False - if bar.BarExpired: - return False - message, w, h = ConvertArgsToSingleString(*args) - text_elem.Update(message) - # bar.TextToDisplay = message - bar.CurrentValue = value - rc = bar.UpdateBar(value) - if value >= bar.MaxValue or not rc: - bar.BarExpired = True - bar.ParentForm._Close() - if rc: # if update was OK but bar expired, decrement num windows - _my_windows.Decrement() - if bar.ParentForm.RootNeedsDestroying: - try: - bar.ParentForm.TKroot.destroy() - # there is a bug with progress meters not decrementing the number of windows - # correctly when the X is used to close the window - # uncommenting this line fixes that problem, but causes a double-decrement when - # the cancel button is used... damned if you do, damned if you don't, so I'm choosing - # don't, as in don't decrement too many times. It's OK now to have a mismatch in - # number of windows because of the "hidden" master window. This ensures all windows - # will be toplevel. Sorry about the bug, but the user never sees any problems as a result - # _my_windows.Decrement() - except: - pass - bar.ParentForm.RootNeedsDestroying = False - return False - - return rc - - -# ============================== EASY PROGRESS METER ========================================== # -# class to hold the easy meter info (a global variable essentialy) -class EasyProgressMeterDataClass: - def __init__(self, title='', current_value=1, max_value=10, start_time=None, stat_messages=()): - self.Title = title - self.CurrentValue = current_value - self.MaxValue = max_value - self.StartTime = start_time - self.StatMessages = stat_messages - self.ParentForm = None - self.MeterID = None - self.MeterText = None - - # =========================== COMPUTE PROGRESS STATS ======================# - def ComputeProgressStats(self): - utc = datetime.datetime.utcnow() - time_delta = utc - self.StartTime - total_seconds = time_delta.total_seconds() - if not total_seconds: - total_seconds = 1 - try: - time_per_item = total_seconds / self.CurrentValue - except: - time_per_item = 1 - seconds_remaining = (self.MaxValue - self.CurrentValue) * time_per_item - time_remaining = str(datetime.timedelta(seconds=seconds_remaining)) - time_remaining_short = (time_remaining).split('.')[0] - time_delta_short = str(time_delta).split('.')[0] - total_time = time_delta + datetime.timedelta(seconds=seconds_remaining) - total_time_short = str(total_time).split('.')[0] - self.StatMessages = [ - '{} of {}'.format(self.CurrentValue, self.MaxValue), - '{} %'.format(100 * self.CurrentValue // self.MaxValue), - '', - ' {:6.2f} Iterations per Second'.format(self.CurrentValue / total_seconds), - ' {:6.2f} Seconds per Iteration'.format(total_seconds / (self.CurrentValue if self.CurrentValue else 1)), - '', - '{} Elapsed Time'.format(time_delta_short), - '{} Time Remaining'.format(time_remaining_short), - '{} Estimated Total Time'.format(total_time_short), - ] - return - - -# ============================== EasyProgressMeter =====# -def EasyProgressMeter(title, current_value, max_value, *args, orientation=None, bar_color=(None, None), button_color=None, size=DEFAULT_PROGRESS_BAR_SIZE, border_width=None): - ''' - A ONE-LINE progress meter. Add to your code where ever you need a meter. No need for a second - function call before your loop. You've got enough code to write! - :param title: Title will be shown on the window - :param current_value: Current count of your items - :param max_value: Max value your count will ever reach. This indicates it should be closed - :param args: VARIABLE number of arguements... you request it, we'll print it no matter what the item! - :param orientation: - :param bar_color: - :param size: - :param Style: - :param StyleOffset: - :return: False if should stop the meter - ''' - local_border_width = DEFAULT_PROGRESS_BAR_BORDER_WIDTH if not border_width else border_width - # STATIC VARIABLE! - # This is a very clever form of static variable using a function attribute - # If the variable doesn't yet exist, then it will create it and initialize with the 3rd parameter - EasyProgressMeter.Data = getattr(EasyProgressMeter, 'Data', EasyProgressMeterDataClass()) - # if no meter currently running - if EasyProgressMeter.Data.MeterID is None: # Starting a new meter - print('Please change your call of EasyProgressMeter to use OneLineProgressMeter. EasyProgressMeter will be removed soon') - if int(current_value) >= int(max_value): - return False - del EasyProgressMeter.Data - EasyProgressMeter.Data = EasyProgressMeterDataClass(title, 1, int(max_value), datetime.datetime.utcnow(), []) - EasyProgressMeter.Data.ComputeProgressStats() - message = '\n'.join([line for line in EasyProgressMeter.Data.StatMessages]) - EasyProgressMeter.Data.MeterID, EasyProgressMeter.Data.MeterText = _ProgressMeter(title, int(max_value), message, *args, orientation=orientation, bar_color=bar_color, size=size, button_color=button_color, border_width=local_border_width) - EasyProgressMeter.Data.ParentForm = EasyProgressMeter.Data.MeterID.ParentForm - return True - # if exactly the same values as before, then ignore. - if EasyProgressMeter.Data.MaxValue == max_value and EasyProgressMeter.Data.CurrentValue == current_value: - return True - if EasyProgressMeter.Data.MaxValue != int(max_value): - EasyProgressMeter.Data.MeterID = None - EasyProgressMeter.Data.ParentForm = None - del EasyProgressMeter.Data - EasyProgressMeter.Data = EasyProgressMeterDataClass() # setup a new progress meter - return True # HAVE to return TRUE or else the new meter will thing IT is failing when it hasn't - EasyProgressMeter.Data.CurrentValue = int(current_value) - EasyProgressMeter.Data.MaxValue = int(max_value) - EasyProgressMeter.Data.ComputeProgressStats() - message = '' - for line in EasyProgressMeter.Data.StatMessages: - message = message + str(line) + '\n' - message = '\n'.join(EasyProgressMeter.Data.StatMessages) - args = args + (message,) - rc = _ProgressMeterUpdate(EasyProgressMeter.Data.MeterID, current_value, EasyProgressMeter.Data.MeterText, *args) - # if counter >= max then the progress meter is all done. Indicate none running - if current_value >= EasyProgressMeter.Data.MaxValue or not rc: - EasyProgressMeter.Data.MeterID = None - del EasyProgressMeter.Data - EasyProgressMeter.Data = EasyProgressMeterDataClass() # setup a new progress meter - return False # even though at the end, return True so don't cause error with the app - return rc # return whatever the update told us - - -def EasyProgressMeterCancel(title, *args): - EasyProgressMeter.EasyProgressMeterData = getattr(EasyProgressMeter, 'EasyProgressMeterData', EasyProgressMeterDataClass()) - if EasyProgressMeter.EasyProgressMeterData.MeterID is not None: - # tell the normal meter update that we're at max value which will close the meter - rc = EasyProgressMeter(title, EasyProgressMeter.EasyProgressMeterData.MaxValue, EasyProgressMeter.EasyProgressMeterData.MaxValue, ' *** CANCELLING ***', 'Caller requested a cancel', *args) - return rc - return True - - -# global variable containing dictionary will all currently running one-line progress meters. -_one_line_progress_meters = {} - - -# ============================== OneLineProgressMeter =====# -def OneLineProgressMeter(title, current_value, max_value, key='OK for 1 meter', *args, orientation=None, bar_color=(None, None), button_color=None, size=DEFAULT_PROGRESS_BAR_SIZE, border_width=None, grab_anywhere=False): - global _one_line_progress_meters - - local_border_width = DEFAULT_PROGRESS_BAR_BORDER_WIDTH if border_width is not None else border_width - try: - meter_data = _one_line_progress_meters[key] - except: # a new meater is starting - if int(current_value) >= int(max_value): # if already expired then it's an old meter, ignore - return False - meter_data = EasyProgressMeterDataClass(title, 1, int(max_value), datetime.datetime.utcnow(), []) - _one_line_progress_meters[key] = meter_data - meter_data.ComputeProgressStats() - message = '\n'.join([line for line in meter_data.StatMessages]) - meter_data.MeterID, meter_data.MeterText = _ProgressMeter(title, int(max_value), message, *args, orientation=orientation, bar_color=bar_color, size=size, button_color=button_color, border_width=local_border_width, grab_anywhere=grab_anywhere) - meter_data.ParentForm = meter_data.MeterID.ParentForm - return True - - # if exactly the same values as before, then ignore, return success. - if meter_data.MaxValue == max_value and meter_data.CurrentValue == current_value: - return True - meter_data.CurrentValue = int(current_value) - meter_data.MaxValue = int(max_value) - meter_data.ComputeProgressStats() - message = '' - for line in meter_data.StatMessages: - message = message + str(line) + '\n' - message = '\n'.join(meter_data.StatMessages) - args = args + (message,) - rc = _ProgressMeterUpdate(meter_data.MeterID, current_value, meter_data.MeterText, *args) - # if counter >= max then the progress meter is all done. Indicate none running - if current_value >= meter_data.MaxValue or not rc: - del _one_line_progress_meters[key] - return False - return rc # return whatever the update told us - - -def OneLineProgressMeterCancel(key='OK for 1 meter'): - global _one_line_progress_meters - - try: - meter_data = _one_line_progress_meters[key] - except: # meter is already deleted - return - OneLineProgressMeter('', meter_data.MaxValue, meter_data.MaxValue, key=key) - - -# input is #RRGGBB -# output is #RRGGBB -def GetComplimentaryHex(color): - # strip the # from the beginning - color = color[1:] - # convert the string into hex - color = int(color, 16) - # invert the three bytes - # as good as substracting each of RGB component by 255(FF) - comp_color = 0xFFFFFF ^ color - # convert the color back to hex by prefixing a # - comp_color = '#%06X' % comp_color - return comp_color - - -# ======================== EasyPrint =====# -# ===================================================# -_easy_print_data = None # global variable... I'm cheating - - -class DebugWin: - def __init__( - self, - size=(None, None), - location=(None, None), - font=None, - no_titlebar=False, - no_button=False, - grab_anywhere=False, - keep_on_top=False, - ): - # Show a form that's a running counter - win_size = size if size != (None, None) else DEFAULT_DEBUG_WINDOW_SIZE - self.window = Window( - 'Debug Window', - no_titlebar=no_titlebar, - auto_size_text=True, - location=location, - font=font or ('Courier New', 10), - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - ) - self.output_element = Output(size=win_size) - if no_button: - self.layout = [[self.output_element]] - else: - self.layout = [[self.output_element], [DummyButton('Quit')]] - self.window.AddRows(self.layout) - self.window.Read(timeout=0) # Show a non-blocking form, returns immediately - return - - def Print(self, *args, end=None, sep=None): - sepchar = sep if sep is not None else ' ' - endchar = end if end is not None else '\n' - - if self.window is None: # if window was destroyed already, just print - print(*args, sep=sepchar, end=endchar) - return - - event, values = self.window.Read(timeout=0) - if event == 'Quit' or event is None: - self.Close() - print(*args, sep=sepchar, end=endchar) - # Add extra check to see if the window was closed... if closed by X sometimes am not told - try: - state = self.window.TKroot.state() - except: - self.Close() - - def Close(self): - self.window.Close() - self.window = None - - -def PrintClose(): - EasyPrintClose() - - -def EasyPrint(*args, size=(None, None), end=None, sep=None, location=(None, None), font=None, no_titlebar=False, no_button=False, grab_anywhere=False, keep_on_top=False): - global _easy_print_data - - if _easy_print_data is None: - _easy_print_data = DebugWin( - size=size, - location=location, - font=font, - no_titlebar=no_titlebar, - no_button=no_button, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - ) - _easy_print_data.Print(*args, end=end, sep=sep) - - -Print = EasyPrint -eprint = EasyPrint - - -def EasyPrintClose(): - global _easy_print_data - if _easy_print_data is not None: - _easy_print_data.Close() - _easy_print_data = None - - -# d8b 888 -# Y8P 888 -# 888 -# .d8888b 88888b. 888d888 888 88888b. 888888 -# d88P" 888 "88b 888P" 888 888 "88b 888 -# 888 888 888 888 888 888 888 888 -# Y88b. 888 d88P 888 888 888 888 Y88b. -# "Y8888P 88888P" 888 888 888 888 "Y888 -# 888 -# 888 -# 888 - - -CPRINT_DESTINATION_WINDOW = None -CPRINT_DESTINATION_MULTILINE_ELMENT_KEY = None - - -def cprint_set_output_destination(window, multiline_key): - """ - Sets up the color print (cprint) output destination - :param window: The window that the cprint call will route the output to - :type window: (Window) - :param multiline_key: Key for the Multiline Element where output will be sent - :type multiline_key: (Any) - :return: None - :rtype: None - """ - - global CPRINT_DESTINATION_WINDOW, CPRINT_DESTINATION_MULTILINE_ELMENT_KEY - - CPRINT_DESTINATION_WINDOW = window - CPRINT_DESTINATION_MULTILINE_ELMENT_KEY = multiline_key - - -# def cprint(*args, **kwargs): -def cprint(*args, end=None, sep=' ', text_color=None, t=None, background_color=None, b=None, colors=None, c=None, window=None, key=None): - """ - Color print to a multiline element in a window of your choice. - Must have EITHER called cprint_set_output_destination prior to making this call so that the - window and element key can be saved and used here to route the output, OR used the window - and key parameters to the cprint function to specicy these items. - - args is a variable number of things you want to print. - - end - The end char to use just like print uses - sep - The separation character like print uses - text_color - The color of the text - key - overrides the previously defined Multiline key - window - overrides the previously defined window to output to - background_color - The color of the background - colors -(str, str) or str. A combined text/background color definition in a single parameter - - There are also "aliases" for text_color, background_color and colors (t, b, c) - t - An alias for color of the text (makes for shorter calls) - b - An alias for the background_color parameter - c - Tuple[str, str] - "shorthand" way of specifying color. (foreground, backgrouned) - c - str - can also be a string of the format "foreground on background" ("white on red") - - With the aliases it's possible to write the same print but in more compact ways: - cprint('This will print white text on red background', c=('white', 'red')) - cprint('This will print white text on red background', c='white on red') - cprint('This will print white text on red background', text_color='white', background_color='red') - cprint('This will print white text on red background', t='white', b='red') - - :param *args: stuff to output - :type *args: (Any) - :param text_color: Color of the text - :type text_color: (str) - :param background_color: The background color of the line - :type background_color: (str) - :param colors: Either a tuple or a string that has both the text and background colors - :type colors: (str) or Tuple[str, str] - :param t: Color of the text - :type t: (str) - :param b: The background color of the line - :type b: (str) - :param c: Either a tuple or a string that has both the text and background colors - :type c: (str) or Tuple[str, str] - :param end: end character - :type end: (str) - :param sep: separator character - :type sep: (str) - :param key: key of multiline to output to (if you want to override the one previously set) - :type key: (Any) - :param window: Window containing the multiline to output to (if you want to override the one previously set) - :type window: (Window) - :return: None - :rtype: None - """ - - destination_key = CPRINT_DESTINATION_MULTILINE_ELMENT_KEY if key is None else key - destination_window = window or CPRINT_DESTINATION_WINDOW - - if (destination_window is None and window is None) or (destination_key is None and key is None): - print( - '** Warning ** Attempting to perform a cprint without a valid window & key', - 'Will instead print on Console', - 'You can specify window and key in this cprint call, or set ahead of time using cprint_set_output_destination', - ) - print(*args) - return - - kw_text_color = text_color or t - kw_background_color = background_color or b - dual_color = colors or c - try: - if isinstance(dual_color, tuple): - kw_text_color = dual_color[0] - kw_background_color = dual_color[1] - elif isinstance(dual_color, str): - kw_text_color = dual_color.split(' on ')[0] - kw_background_color = dual_color.split(' on ')[1] - except Exception as e: - print('* cprint warning * you messed up with color formatting', e) - - mline = destination_window.find_element(destination_key, silent_on_error=True) # type: Multiline - try: - # mline = destination_window[destination_key] # type: Multiline - if end is None: - mline.print(*args, text_color=kw_text_color, background_color=kw_background_color, end='', sep=sep) - mline.print('') - else: - mline.print(*args, text_color=kw_text_color, background_color=kw_background_color, end=end, sep=sep) - except Exception as e: - print('** cprint error trying to print to the multiline. Printing to console instead **', e) - print(*args, end=end, sep=sep) - - -# ------------------------------------------------------------------------------------------------ # -# A print-like call that can be used to output to a multiline element as if it's an Output element # -# ------------------------------------------------------------------------------------------------ # -def _print_to_element(multiline_element, *args, end=None, sep=None, text_color=None, background_color=None, autoscroll=True): - """ - Print like Python normally prints except route the output to a multline element and also add colors if desired - - :param multiline_element: The multiline element to be output to - :type multiline_element: Multiline or MultilineOutput - :param args: The arguments to print - :type args: List[Any] - :param end: The end char to use just like print uses - :type end: (str) - :param sep: The separation character like print uses - :type sep: (str) - :param text_color: color of the text - :type text_color: (str) - :param background_color: The background color of the line - :type background_color: (str) - :param autoscroll: If True (the default), the element will scroll to bottom after updating - :type autoscroll: Bool - """ - end_str = str(end) if end is not None else '\n' - sep_str = str(sep) if sep is not None else ' ' - - outstring = '' - num_args = len(args) - for i, arg in enumerate(args): - outstring += str(arg) - if i != num_args - 1: - outstring += sep_str - outstring += end_str - - multiline_element.update(outstring, append=True, text_color=text_color, background_color=background_color, autoscroll=autoscroll) - - -# ======================== Scrolled Text Box =====# -# ===================================================# -def PopupScrolled(*args, button_color=None, yes_no=False, auto_close=False, auto_close_duration=None, size=(None, None)): - if not args: - return - width, height = size - width = width if width else MESSAGE_BOX_LINE_WIDTH - form = Window( - args[0], - auto_size_text=True, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - ) - max_line_total, max_line_width, total_lines, height_computed = 0, 0, 0, 0 - complete_output = '' - for message in args: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = min(longest_line_len, width) - max_line_total = max(max_line_total, width_used) - max_line_width = width - lines_needed = _GetNumLinesNeeded(message, width_used) - height_computed += lines_needed - complete_output += message + '\n' - total_lines += lines_needed - height_computed = MAX_SCROLLED_TEXT_BOX_HEIGHT if height_computed > MAX_SCROLLED_TEXT_BOX_HEIGHT else height_computed - if height: - height_computed = height - form.AddRow(Multiline(complete_output, size=(max_line_width, height_computed))) - pad = max_line_total - 15 if max_line_total > 15 else 1 - # show either an OK or Yes/No depending on paramater - if yes_no: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Yes(), No()) - button, values = form.Read() - return button - else: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Button('OK', size=(5, 1), button_color=button_color)) - button, values = form.Read() - form.Close() - return button - - -ScrolledTextBox = PopupScrolled - - -# ============================== SetGlobalIcon ======# -# Sets the icon to be used by default # -# ===================================================# -def SetGlobalIcon(icon): - global _my_windows - - try: - with open(icon, 'r') as icon_file: - pass - except: - raise FileNotFoundError - _my_windows.user_defined_icon = icon - return True - - -# ============================== SetOptions =========# -# Sets the icon to be used by default # -# ===================================================# -def SetOptions( - icon=None, - button_color=None, - element_size=(None, None), - button_element_size=(None, None), - margins=(None, None), - element_padding=(None, None), - auto_size_text=None, - auto_size_buttons=None, - font=None, - border_width=None, - slider_border_width=None, - slider_relief=None, - slider_orientation=None, - autoclose_time=None, - message_box_line_width=None, - progress_meter_border_depth=None, - progress_meter_style=None, - progress_meter_relief=None, - progress_meter_color=None, - progress_meter_size=None, - text_justification=None, - background_color=None, - element_background_color=None, - text_element_background_color=None, - input_elements_background_color=None, - input_text_color=None, - scrollbar_color=None, - text_color=None, - element_text_color=None, - debug_win_size=(None, None), - window_location=(None, None), - tooltip_time=None, -): - global DEFAULT_ELEMENT_SIZE - global DEFAULT_BUTTON_ELEMENT_SIZE - global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term - global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels - global DEFAULT_AUTOSIZE_TEXT - global DEFAULT_AUTOSIZE_BUTTONS - global DEFAULT_FONT - global DEFAULT_BORDER_WIDTH - global DEFAULT_AUTOCLOSE_TIME - global DEFAULT_BUTTON_COLOR - global MESSAGE_BOX_LINE_WIDTH - global DEFAULT_PROGRESS_BAR_BORDER_WIDTH - global DEFAULT_PROGRESS_BAR_STYLE - global DEFAULT_PROGRESS_BAR_RELIEF - global DEFAULT_PROGRESS_BAR_COLOR - global DEFAULT_PROGRESS_BAR_SIZE - global DEFAULT_TEXT_JUSTIFICATION - global DEFAULT_DEBUG_WINDOW_SIZE - global DEFAULT_SLIDER_BORDER_WIDTH - global DEFAULT_SLIDER_RELIEF - global DEFAULT_SLIDER_ORIENTATION - global DEFAULT_BACKGROUND_COLOR - global DEFAULT_INPUT_ELEMENTS_COLOR - global DEFAULT_ELEMENT_BACKGROUND_COLOR - global DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - global DEFAULT_SCROLLBAR_COLOR - global DEFAULT_TEXT_COLOR - global DEFAULT_WINDOW_LOCATION - global DEFAULT_ELEMENT_TEXT_COLOR - global DEFAULT_INPUT_TEXT_COLOR - global DEFAULT_TOOLTIP_TIME - global _my_windows - - if icon: - try: - with open(icon, 'r') as icon_file: - pass - except: - raise FileNotFoundError - _my_windows.user_defined_icon = icon - - if button_color != None: - DEFAULT_BUTTON_COLOR = button_color - - if element_size != (None, None): - DEFAULT_ELEMENT_SIZE = element_size - - if button_element_size != (None, None): - DEFAULT_BUTTON_ELEMENT_SIZE = button_element_size - - if margins != (None, None): - DEFAULT_MARGINS = margins - - if element_padding != (None, None): - DEFAULT_ELEMENT_PADDING = element_padding - - if auto_size_text != None: - DEFAULT_AUTOSIZE_TEXT = auto_size_text - - if auto_size_buttons != None: - DEFAULT_AUTOSIZE_BUTTONS = auto_size_buttons - - if font != None: - DEFAULT_FONT = font - - if border_width != None: - DEFAULT_BORDER_WIDTH = border_width - - if autoclose_time != None: - DEFAULT_AUTOCLOSE_TIME = autoclose_time - - if message_box_line_width != None: - MESSAGE_BOX_LINE_WIDTH = message_box_line_width - - if progress_meter_border_depth != None: - DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth - - if progress_meter_style != None: - DEFAULT_PROGRESS_BAR_STYLE = progress_meter_style - - if progress_meter_relief != None: - DEFAULT_PROGRESS_BAR_RELIEF = progress_meter_relief - - if progress_meter_color != None: - DEFAULT_PROGRESS_BAR_COLOR = progress_meter_color - - if progress_meter_size != None: - DEFAULT_PROGRESS_BAR_SIZE = progress_meter_size - - if slider_border_width != None: - DEFAULT_SLIDER_BORDER_WIDTH = slider_border_width - - if slider_orientation != None: - DEFAULT_SLIDER_ORIENTATION = slider_orientation - - if slider_relief != None: - DEFAULT_SLIDER_RELIEF = slider_relief - - if text_justification != None: - DEFAULT_TEXT_JUSTIFICATION = text_justification - - if background_color != None: - DEFAULT_BACKGROUND_COLOR = background_color - - if text_element_background_color != None: - DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = text_element_background_color - - if input_elements_background_color != None: - DEFAULT_INPUT_ELEMENTS_COLOR = input_elements_background_color - - if element_background_color != None: - DEFAULT_ELEMENT_BACKGROUND_COLOR = element_background_color - - if window_location != (None, None): - DEFAULT_WINDOW_LOCATION = window_location - - if debug_win_size != (None, None): - DEFAULT_DEBUG_WINDOW_SIZE = debug_win_size - - if text_color != None: - DEFAULT_TEXT_COLOR = text_color - - if scrollbar_color != None: - DEFAULT_SCROLLBAR_COLOR = scrollbar_color - - if element_text_color != None: - DEFAULT_ELEMENT_TEXT_COLOR = element_text_color - - if input_text_color is not None: - DEFAULT_INPUT_TEXT_COLOR = input_text_color - - if tooltip_time is not None: - DEFAULT_TOOLTIP_TIME = tooltip_time - - return True - - -# ----------------------------------------------------------------- # - -# .########.##.....##.########.##.....##.########..######. -# ....##....##.....##.##.......###...###.##.......##....## -# ....##....##.....##.##.......####.####.##.......##...... -# ....##....#########.######...##.###.##.######....######. -# ....##....##.....##.##.......##.....##.##.............## -# ....##....##.....##.##.......##.....##.##.......##....## -# ....##....##.....##.########.##.....##.########..######. - -# ----------------------------------------------------------------- # - -# The official Theme code - -#################### ChangeLookAndFeel ####################### -# Predefined settings that will change the colors and styles # -# of the elements. # -############################################################## -LOOK_AND_FEEL_TABLE = { - 'SystemDefault': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'SystemDefaultForReal': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': COLOR_SYSTEM_DEFAULT, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'SystemDefault1': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': COLOR_SYSTEM_DEFAULT, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'Material1': { - 'BACKGROUND': '#E3F2FD', - 'TEXT': '#000000', - 'INPUT': '#86A8FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#86A8FF', - 'BUTTON': ('#FFFFFF', '#5079D3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'Material2': { - 'BACKGROUND': '#FAFAFA', - 'TEXT': '#000000', - 'INPUT': '#004EA1', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#5EA7FF', - 'BUTTON': ('#FFFFFF', '#0079D3'), # based on Reddit color - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'Reddit': { - 'BACKGROUND': '#ffffff', - 'TEXT': '#1a1a1b', - 'INPUT': '#dae0e6', - 'TEXT_INPUT': '#222222', - 'SCROLL': '#a5a4a4', - 'BUTTON': ('#FFFFFF', '#0079d3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#ff5414', - 'ACCENT2': '#33a8ff', - 'ACCENT3': '#dbf0ff', - }, - 'Topanga': { - 'BACKGROUND': '#282923', - 'TEXT': '#E7DB74', - 'INPUT': '#393a32', - 'TEXT_INPUT': '#E7C855', - 'SCROLL': '#E7C855', - 'BUTTON': ('#E7C855', '#284B5A'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#c15226', - 'ACCENT2': '#7a4d5f', - 'ACCENT3': '#889743', - }, - 'GreenTan': { - 'BACKGROUND': '#9FB8AD', - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': '#F7F3EC', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#F7F3EC', - 'BUTTON': ('#FFFFFF', '#475841'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Dark': { - 'BACKGROUND': '#404040', - 'TEXT': '#FFFFFF', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#707070', - 'BUTTON': ('#FFFFFF', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen': { - 'BACKGROUND': '#B7CECE', - 'TEXT': '#000000', - 'INPUT': '#FDFFF7', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#FDFFF7', - 'BUTTON': ('#FFFFFF', '#658268'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'ACCENT1': '#76506d', - 'ACCENT2': '#5148f1', - 'ACCENT3': '#0a1c84', - 'PROGRESS_DEPTH': 0, - }, - 'Dark2': { - 'BACKGROUND': '#404040', - 'TEXT': '#FFFFFF', - 'INPUT': '#FFFFFF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#707070', - 'BUTTON': ('#FFFFFF', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Black': { - 'BACKGROUND': '#000000', - 'TEXT': '#FFFFFF', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#707070', - 'BUTTON': ('#000000', '#FFFFFF'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Tan': { - 'BACKGROUND': '#fdf6e3', - 'TEXT': '#268bd1', - 'INPUT': '#eee8d5', - 'TEXT_INPUT': '#6c71c3', - 'SCROLL': '#eee8d5', - 'BUTTON': ('#FFFFFF', '#063542'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'TanBlue': { - 'BACKGROUND': '#e5dece', - 'TEXT': '#063289', - 'INPUT': '#f9f8f4', - 'TEXT_INPUT': '#242834', - 'SCROLL': '#eee8d5', - 'BUTTON': ('#FFFFFF', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkTanBlue': { - 'BACKGROUND': '#242834', - 'TEXT': '#dfe6f8', - 'INPUT': '#97755c', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#a9afbb', - 'BUTTON': ('#FFFFFF', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkAmber': { - 'BACKGROUND': '#2c2825', - 'TEXT': '#fdcb52', - 'INPUT': '#705e52', - 'TEXT_INPUT': '#fdcb52', - 'SCROLL': '#705e52', - 'BUTTON': ('#000000', '#fdcb52'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue': { - 'BACKGROUND': '#1a2835', - 'TEXT': '#d1ecff', - 'INPUT': '#335267', - 'TEXT_INPUT': '#acc2d0', - 'SCROLL': '#1b6497', - 'BUTTON': ('#000000', '#fafaf8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Reds': { - 'BACKGROUND': '#280001', - 'TEXT': '#FFFFFF', - 'INPUT': '#d8d584', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#763e00', - 'BUTTON': ('#000000', '#daad28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Green': { - 'BACKGROUND': '#82a459', - 'TEXT': '#000000', - 'INPUT': '#d8d584', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e3ecf3', - 'BUTTON': ('#FFFFFF', '#517239'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BluePurple': { - 'BACKGROUND': '#A5CADD', - 'TEXT': '#6E266E', - 'INPUT': '#E0F5FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#E0F5FF', - 'BUTTON': ('#FFFFFF', '#303952'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Purple': { - 'BACKGROUND': '#B0AAC2', - 'TEXT': '#000000', - 'INPUT': '#F2EFE8', - 'SCROLL': '#F2EFE8', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#C2D4D8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BlueMono': { - 'BACKGROUND': '#AAB6D3', - 'TEXT': '#000000', - 'INPUT': '#F1F4FC', - 'SCROLL': '#F1F4FC', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#7186C7'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'GreenMono': { - 'BACKGROUND': '#A8C1B4', - 'TEXT': '#000000', - 'INPUT': '#DDE0DE', - 'SCROLL': '#E3E3E3', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#6D9F85'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BrownBlue': { - 'BACKGROUND': '#64778d', - 'TEXT': '#FFFFFF', - 'INPUT': '#f0f3f7', - 'SCROLL': '#A6B2BE', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#283b5b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BrightColors': { - 'BACKGROUND': '#b4ffb4', - 'TEXT': '#000000', - 'INPUT': '#ffff64', - 'SCROLL': '#ffb482', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#ffa0dc'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'NeutralBlue': { - 'BACKGROUND': '#92aa9d', - 'TEXT': '#000000', - 'INPUT': '#fcfff6', - 'SCROLL': '#fcfff6', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#d0dbbd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Kayak': { - 'BACKGROUND': '#a7ad7f', - 'TEXT': '#000000', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#5d907d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'SandyBeach': { - 'BACKGROUND': '#efeccb', - 'TEXT': '#012f2f', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#012f2f', - 'BUTTON': ('#FFFFFF', '#046380'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'TealMono': { - 'BACKGROUND': '#a8cfdd', - 'TEXT': '#000000', - 'INPUT': '#dfedf2', - 'SCROLL': '#dfedf2', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#183440'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - ################################## Renamed Original Themes ################################## - 'Default': { # plain gray but blue buttons - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'Default1': { # everything is gray - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': COLOR_SYSTEM_DEFAULT, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'DefaultNoMoreNagging': { # a duplicate of "Default" for users that are tired of the nag screen - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue': { - 'BACKGROUND': '#E3F2FD', - 'TEXT': '#000000', - 'INPUT': '#86A8FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#86A8FF', - 'BUTTON': ('#FFFFFF', '#5079D3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'LightGrey': { - 'BACKGROUND': '#FAFAFA', - 'TEXT': '#000000', - 'INPUT': '#004EA1', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#5EA7FF', - 'BUTTON': ('#FFFFFF', '#0079D3'), # based on Reddit color - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'LightGrey1': { - 'BACKGROUND': '#ffffff', - 'TEXT': '#1a1a1b', - 'INPUT': '#dae0e6', - 'TEXT_INPUT': '#222222', - 'SCROLL': '#a5a4a4', - 'BUTTON': ('#FFFFFF', '#0079d3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#ff5414', - 'ACCENT2': '#33a8ff', - 'ACCENT3': '#dbf0ff', - }, - 'DarkBrown': { - 'BACKGROUND': '#282923', - 'TEXT': '#E7DB74', - 'INPUT': '#393a32', - 'TEXT_INPUT': '#E7C855', - 'SCROLL': '#E7C855', - 'BUTTON': ('#E7C855', '#284B5A'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#c15226', - 'ACCENT2': '#7a4d5f', - 'ACCENT3': '#889743', - }, - 'LightGreen1': { - 'BACKGROUND': '#9FB8AD', - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': '#F7F3EC', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#F7F3EC', - 'BUTTON': ('#FFFFFF', '#475841'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey': { - 'BACKGROUND': '#404040', - 'TEXT': '#FFFFFF', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#707070', - 'BUTTON': ('#FFFFFF', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen2': { - 'BACKGROUND': '#B7CECE', - 'TEXT': '#000000', - 'INPUT': '#FDFFF7', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#FDFFF7', - 'BUTTON': ('#FFFFFF', '#658268'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'ACCENT1': '#76506d', - 'ACCENT2': '#5148f1', - 'ACCENT3': '#0a1c84', - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey1': { - 'BACKGROUND': '#404040', - 'TEXT': '#FFFFFF', - 'INPUT': '#FFFFFF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#707070', - 'BUTTON': ('#FFFFFF', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlack': { - 'BACKGROUND': '#000000', - 'TEXT': '#FFFFFF', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#707070', - 'BUTTON': ('#000000', '#FFFFFF'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown': { - 'BACKGROUND': '#fdf6e3', - 'TEXT': '#268bd1', - 'INPUT': '#eee8d5', - 'TEXT_INPUT': '#6c71c3', - 'SCROLL': '#eee8d5', - 'BUTTON': ('#FFFFFF', '#063542'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown1': { - 'BACKGROUND': '#e5dece', - 'TEXT': '#063289', - 'INPUT': '#f9f8f4', - 'TEXT_INPUT': '#242834', - 'SCROLL': '#eee8d5', - 'BUTTON': ('#FFFFFF', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue1': { - 'BACKGROUND': '#242834', - 'TEXT': '#dfe6f8', - 'INPUT': '#97755c', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#a9afbb', - 'BUTTON': ('#FFFFFF', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBrown1': { - 'BACKGROUND': '#2c2825', - 'TEXT': '#fdcb52', - 'INPUT': '#705e52', - 'TEXT_INPUT': '#fdcb52', - 'SCROLL': '#705e52', - 'BUTTON': ('#000000', '#fdcb52'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue2': { - 'BACKGROUND': '#1a2835', - 'TEXT': '#d1ecff', - 'INPUT': '#335267', - 'TEXT_INPUT': '#acc2d0', - 'SCROLL': '#1b6497', - 'BUTTON': ('#000000', '#fafaf8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBrown2': { - 'BACKGROUND': '#280001', - 'TEXT': '#FFFFFF', - 'INPUT': '#d8d584', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#763e00', - 'BUTTON': ('#000000', '#daad28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGreen': { - 'BACKGROUND': '#82a459', - 'TEXT': '#000000', - 'INPUT': '#d8d584', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e3ecf3', - 'BUTTON': ('#FFFFFF', '#517239'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue1': { - 'BACKGROUND': '#A5CADD', - 'TEXT': '#6E266E', - 'INPUT': '#E0F5FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#E0F5FF', - 'BUTTON': ('#FFFFFF', '#303952'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightPurple': { - 'BACKGROUND': '#B0AAC2', - 'TEXT': '#000000', - 'INPUT': '#F2EFE8', - 'SCROLL': '#F2EFE8', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#C2D4D8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue2': { - 'BACKGROUND': '#AAB6D3', - 'TEXT': '#000000', - 'INPUT': '#F1F4FC', - 'SCROLL': '#F1F4FC', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#7186C7'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen3': { - 'BACKGROUND': '#A8C1B4', - 'TEXT': '#000000', - 'INPUT': '#DDE0DE', - 'SCROLL': '#E3E3E3', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#6D9F85'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue3': { - 'BACKGROUND': '#64778d', - 'TEXT': '#FFFFFF', - 'INPUT': '#f0f3f7', - 'SCROLL': '#A6B2BE', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#283b5b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen4': { - 'BACKGROUND': '#b4ffb4', - 'TEXT': '#000000', - 'INPUT': '#ffff64', - 'SCROLL': '#ffb482', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#ffa0dc'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen5': { - 'BACKGROUND': '#92aa9d', - 'TEXT': '#000000', - 'INPUT': '#fcfff6', - 'SCROLL': '#fcfff6', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#000000', '#d0dbbd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown2': { - 'BACKGROUND': '#a7ad7f', - 'TEXT': '#000000', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#5d907d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown3': { - 'BACKGROUND': '#efeccb', - 'TEXT': '#012f2f', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#012f2f', - 'BUTTON': ('#FFFFFF', '#046380'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue3': { - 'BACKGROUND': '#a8cfdd', - 'TEXT': '#000000', - 'INPUT': '#dfedf2', - 'SCROLL': '#dfedf2', - 'TEXT_INPUT': '#000000', - 'BUTTON': ('#FFFFFF', '#183440'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - ################################## End Renamed Original Themes ################################## - # - 'LightBrown4': { - 'BACKGROUND': '#d7c79e', - 'TEXT': '#a35638', - 'INPUT': '#9dab86', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#a35638', - 'BUTTON': ('#FFFFFF', '#a35638'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#a35638', '#9dab86', '#e08f62', '#d7c79e'], - }, - 'DarkTeal': { - 'BACKGROUND': '#003f5c', - 'TEXT': '#fb5b5a', - 'INPUT': '#bc4873', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#bc4873', - 'BUTTON': ('#FFFFFF', '#fb5b5a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], - }, - 'DarkPurple': { - 'BACKGROUND': '#472b62', - 'TEXT': '#fb5b5a', - 'INPUT': '#bc4873', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#bc4873', - 'BUTTON': ('#FFFFFF', '#472b62'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], - }, - 'LightGreen6': { - 'BACKGROUND': '#eafbea', - 'TEXT': '#1f6650', - 'INPUT': '#6f9a8d', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#1f6650', - 'BUTTON': ('#FFFFFF', '#1f6650'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#1f6650', '#6f9a8d', '#ea5e5e', '#eafbea'], - }, - 'DarkGrey2': { - 'BACKGROUND': '#2b2b28', - 'TEXT': '#f8f8f8', - 'INPUT': '#f1d6ab', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f1d6ab', - 'BUTTON': ('#2b2b28', '#e3b04b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#2b2b28', '#e3b04b', '#f1d6ab', '#f8f8f8'], - }, - 'LightBrown6': { - 'BACKGROUND': '#f9b282', - 'TEXT': '#8f4426', - 'INPUT': '#de6b35', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#8f4426', - 'BUTTON': ('#FFFFFF', '#8f4426'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#8f4426', '#de6b35', '#64ccda', '#f9b282'], - }, - 'DarkTeal1': { - 'BACKGROUND': '#396362', - 'TEXT': '#ffe7d1', - 'INPUT': '#f6c89f', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f6c89f', - 'BUTTON': ('#ffe7d1', '#4b8e8d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], - }, - 'LightBrown7': { - 'BACKGROUND': '#f6c89f', - 'TEXT': '#396362', - 'INPUT': '#4b8e8d', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#396362', - 'BUTTON': ('#FFFFFF', '#396362'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], - }, - 'DarkPurple1': { - 'BACKGROUND': '#0c093c', - 'TEXT': '#fad6d6', - 'INPUT': '#eea5f6', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#eea5f6', - 'BUTTON': ('#FFFFFF', '#df42d1'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#0c093c', '#df42d1', '#eea5f6', '#fad6d6'], - }, - 'DarkGrey3': { - 'BACKGROUND': '#211717', - 'TEXT': '#dfddc7', - 'INPUT': '#f58b54', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f58b54', - 'BUTTON': ('#dfddc7', '#a34a28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], - }, - 'LightBrown8': { - 'BACKGROUND': '#dfddc7', - 'TEXT': '#211717', - 'INPUT': '#a34a28', - 'TEXT_INPUT': '#dfddc7', - 'SCROLL': '#211717', - 'BUTTON': ('#dfddc7', '#a34a28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], - }, - 'DarkBlue4': { - 'BACKGROUND': '#494ca2', - 'TEXT': '#e3e7f1', - 'INPUT': '#c6cbef', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#c6cbef', - 'BUTTON': ('#FFFFFF', '#8186d5'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#494ca2', '#8186d5', '#c6cbef', '#e3e7f1'], - }, - 'LightBlue4': { - 'BACKGROUND': '#5c94bd', - 'TEXT': '#470938', - 'INPUT': '#1a3e59', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#470938', - 'BUTTON': ('#FFFFFF', '#470938'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#470938', '#1a3e59', '#5c94bd', '#f2d6eb'], - }, - 'DarkTeal2': { - 'BACKGROUND': '#394a6d', - 'TEXT': '#c0ffb3', - 'INPUT': '#52de97', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#52de97', - 'BUTTON': ('#c0ffb3', '#394a6d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], - }, - 'DarkTeal3': { - 'BACKGROUND': '#3c9d9b', - 'TEXT': '#c0ffb3', - 'INPUT': '#52de97', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#52de97', - 'BUTTON': ('#c0ffb3', '#394a6d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], - }, - 'DarkPurple5': { - 'BACKGROUND': '#730068', - 'TEXT': '#f6f078', - 'INPUT': '#01d28e', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#01d28e', - 'BUTTON': ('#f6f078', '#730068'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#730068', '#434982', '#01d28e', '#f6f078'], - }, - 'DarkPurple2': { - 'BACKGROUND': '#202060', - 'TEXT': '#b030b0', - 'INPUT': '#602080', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#602080', - 'BUTTON': ('#FFFFFF', '#202040'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#202040', '#202060', '#602080', '#b030b0'], - }, - 'DarkBlue5': { - 'BACKGROUND': '#000272', - 'TEXT': '#ff6363', - 'INPUT': '#a32f80', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#a32f80', - 'BUTTON': ('#FFFFFF', '#341677'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#000272', '#341677', '#a32f80', '#ff6363'], - }, - 'LightGrey2': { - 'BACKGROUND': '#f6f6f6', - 'TEXT': '#420000', - 'INPUT': '#d4d7dd', - 'TEXT_INPUT': '#420000', - 'SCROLL': '#420000', - 'BUTTON': ('#420000', '#d4d7dd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], - }, - 'LightGrey3': { - 'BACKGROUND': '#eae9e9', - 'TEXT': '#420000', - 'INPUT': '#d4d7dd', - 'TEXT_INPUT': '#420000', - 'SCROLL': '#420000', - 'BUTTON': ('#420000', '#d4d7dd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], - }, - 'DarkBlue6': { - 'BACKGROUND': '#01024e', - 'TEXT': '#ff6464', - 'INPUT': '#8b4367', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#8b4367', - 'BUTTON': ('#FFFFFF', '#543864'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#01024e', '#543864', '#8b4367', '#ff6464'], - }, - 'DarkBlue7': { - 'BACKGROUND': '#241663', - 'TEXT': '#eae7af', - 'INPUT': '#a72693', - 'TEXT_INPUT': '#eae7af', - 'SCROLL': '#a72693', - 'BUTTON': ('#eae7af', '#160f30'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#160f30', '#241663', '#a72693', '#eae7af'], - }, - 'LightBrown9': { - 'BACKGROUND': '#f6d365', - 'TEXT': '#3a1f5d', - 'INPUT': '#c83660', - 'TEXT_INPUT': '#f6d365', - 'SCROLL': '#3a1f5d', - 'BUTTON': ('#f6d365', '#c83660'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3a1f5d', '#c83660', '#e15249', '#f6d365'], - }, - 'DarkPurple3': { - 'BACKGROUND': '#6e2142', - 'TEXT': '#ffd692', - 'INPUT': '#e16363', - 'TEXT_INPUT': '#ffd692', - 'SCROLL': '#e16363', - 'BUTTON': ('#ffd692', '#943855'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], - }, - 'LightBrown10': { - 'BACKGROUND': '#ffd692', - 'TEXT': '#6e2142', - 'INPUT': '#943855', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#6e2142', - 'BUTTON': ('#FFFFFF', '#6e2142'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], - }, - 'DarkPurple4': { - 'BACKGROUND': '#200f21', - 'TEXT': '#f638dc', - 'INPUT': '#5a3d5c', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#5a3d5c', - 'BUTTON': ('#FFFFFF', '#382039'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#200f21', '#382039', '#5a3d5c', '#f638dc'], - }, - 'LightBlue5': { - 'BACKGROUND': '#b2fcff', - 'TEXT': '#3e64ff', - 'INPUT': '#5edfff', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#3e64ff', - 'BUTTON': ('#FFFFFF', '#3e64ff'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3e64ff', '#5edfff', '#b2fcff', '#ecfcff'], - }, - 'DarkTeal4': { - 'BACKGROUND': '#464159', - 'TEXT': '#c7f0db', - 'INPUT': '#8bbabb', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#8bbabb', - 'BUTTON': ('#FFFFFF', '#6c7b95'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], - }, - 'LightTeal': { - 'BACKGROUND': '#c7f0db', - 'TEXT': '#464159', - 'INPUT': '#6c7b95', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#464159', - 'BUTTON': ('#FFFFFF', '#464159'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], - }, - 'DarkTeal5': { - 'BACKGROUND': '#8bbabb', - 'TEXT': '#464159', - 'INPUT': '#6c7b95', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#464159', - 'BUTTON': ('#c7f0db', '#6c7b95'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], - }, - 'LightGrey4': { - 'BACKGROUND': '#faf5ef', - 'TEXT': '#672f2f', - 'INPUT': '#99b19c', - 'TEXT_INPUT': '#672f2f', - 'SCROLL': '#672f2f', - 'BUTTON': ('#672f2f', '#99b19c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], - }, - 'LightGreen7': { - 'BACKGROUND': '#99b19c', - 'TEXT': '#faf5ef', - 'INPUT': '#d7d1c9', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#d7d1c9', - 'BUTTON': ('#FFFFFF', '#99b19c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], - }, - 'LightGrey5': { - 'BACKGROUND': '#d7d1c9', - 'TEXT': '#672f2f', - 'INPUT': '#99b19c', - 'TEXT_INPUT': '#672f2f', - 'SCROLL': '#672f2f', - 'BUTTON': ('#FFFFFF', '#672f2f'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], - }, - 'DarkBrown3': { - 'BACKGROUND': '#a0855b', - 'TEXT': '#f9f6f2', - 'INPUT': '#f1d6ab', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f1d6ab', - 'BUTTON': ('#FFFFFF', '#38470b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], - }, - 'LightBrown11': { - 'BACKGROUND': '#f1d6ab', - 'TEXT': '#38470b', - 'INPUT': '#a0855b', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#38470b', - 'BUTTON': ('#f9f6f2', '#a0855b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], - }, - 'DarkRed': { - 'BACKGROUND': '#83142c', - 'TEXT': '#f9d276', - 'INPUT': '#ad1d45', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#ad1d45', - 'BUTTON': ('#f9d276', '#ad1d45'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#44000d', '#83142c', '#ad1d45', '#f9d276'], - }, - 'DarkTeal6': { - 'BACKGROUND': '#204969', - 'TEXT': '#fff7f7', - 'INPUT': '#dadada', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#dadada', - 'BUTTON': ('#000000', '#fff7f7'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#204969', '#08ffc8', '#dadada', '#fff7f7'], - }, - 'DarkBrown4': { - 'BACKGROUND': '#252525', - 'TEXT': '#ff0000', - 'INPUT': '#af0404', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#af0404', - 'BUTTON': ('#FFFFFF', '#252525'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#252525', '#414141', '#af0404', '#ff0000'], - }, - 'LightYellow': { - 'BACKGROUND': '#f4ff61', - 'TEXT': '#27aa80', - 'INPUT': '#32ff6a', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#27aa80', - 'BUTTON': ('#f4ff61', '#27aa80'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#27aa80', '#32ff6a', '#a8ff3e', '#f4ff61'], - }, - 'DarkGreen1': { - 'BACKGROUND': '#2b580c', - 'TEXT': '#fdef96', - 'INPUT': '#f7b71d', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f7b71d', - 'BUTTON': ('#fdef96', '#2b580c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#2b580c', '#afa939', '#f7b71d', '#fdef96'], - }, - 'LightGreen8': { - 'BACKGROUND': '#c8dad3', - 'TEXT': '#63707e', - 'INPUT': '#93b5b3', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#63707e', - 'BUTTON': ('#FFFFFF', '#63707e'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#63707e', '#93b5b3', '#c8dad3', '#f2f6f5'], - }, - 'DarkTeal7': { - 'BACKGROUND': '#248ea9', - 'TEXT': '#fafdcb', - 'INPUT': '#aee7e8', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#aee7e8', - 'BUTTON': ('#000000', '#fafdcb'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#248ea9', '#28c3d4', '#aee7e8', '#fafdcb'], - }, - 'DarkBlue8': { - 'BACKGROUND': '#454d66', - 'TEXT': '#d9d872', - 'INPUT': '#58b368', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#58b368', - 'BUTTON': ('#000000', '#009975'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#009975', '#454d66', '#58b368', '#d9d872'], - }, - 'DarkBlue9': { - 'BACKGROUND': '#263859', - 'TEXT': '#ff6768', - 'INPUT': '#6b778d', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#6b778d', - 'BUTTON': ('#ff6768', '#263859'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#17223b', '#263859', '#6b778d', '#ff6768'], - }, - 'DarkBlue10': { - 'BACKGROUND': '#0028ff', - 'TEXT': '#f1f4df', - 'INPUT': '#10eaf0', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#10eaf0', - 'BUTTON': ('#f1f4df', '#24009c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#24009c', '#0028ff', '#10eaf0', '#f1f4df'], - }, - 'DarkBlue11': { - 'BACKGROUND': '#6384b3', - 'TEXT': '#e6f0b6', - 'INPUT': '#b8e9c0', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#b8e9c0', - 'BUTTON': ('#e6f0b6', '#684949'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#684949', '#6384b3', '#b8e9c0', '#e6f0b6'], - }, - 'DarkTeal8': { - 'BACKGROUND': '#71a0a5', - 'TEXT': '#212121', - 'INPUT': '#665c84', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#212121', - 'BUTTON': ('#fab95b', '#665c84'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#212121', '#665c84', '#71a0a5', '#fab95b'], - }, - 'DarkRed1': { - 'BACKGROUND': '#c10000', - 'TEXT': '#eeeeee', - 'INPUT': '#dedede', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#dedede', - 'BUTTON': ('#c10000', '#eeeeee'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#c10000', '#ff4949', '#dedede', '#eeeeee'], - }, - 'LightBrown5': { - 'BACKGROUND': '#fff591', - 'TEXT': '#e41749', - 'INPUT': '#f5587b', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e41749', - 'BUTTON': ('#fff591', '#e41749'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#e41749', '#f5587b', '#ff8a5c', '#fff591'], - }, - 'LightGreen9': { - 'BACKGROUND': '#f1edb3', - 'TEXT': '#3b503d', - 'INPUT': '#4a746e', - 'TEXT_INPUT': '#f1edb3', - 'SCROLL': '#3b503d', - 'BUTTON': ('#f1edb3', '#3b503d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], - 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], - }, - 'DarkGreen2': { - 'BACKGROUND': '#3b503d', - 'TEXT': '#f1edb3', - 'INPUT': '#c8cf94', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#c8cf94', - 'BUTTON': ('#f1edb3', '#3b503d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], - 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], - }, - 'LightGray1': { - 'BACKGROUND': '#f2f2f2', - 'TEXT': '#222831', - 'INPUT': '#393e46', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#222831', - 'BUTTON': ('#f2f2f2', '#222831'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#222831', '#393e46', '#f96d00', '#f2f2f2'], - 'DESCRIPTION': ['#000000', 'Grey', 'Orange', 'Grey', 'Autumn'], - }, - 'DarkGrey4': { - 'BACKGROUND': '#52524e', - 'TEXT': '#e9e9e5', - 'INPUT': '#d4d6c8', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#d4d6c8', - 'BUTTON': ('#FFFFFF', '#9a9b94'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#52524e', '#9a9b94', '#d4d6c8', '#e9e9e5'], - 'DESCRIPTION': ['Grey', 'Pastel', 'Winter'], - }, - 'DarkBlue12': { - 'BACKGROUND': '#324e7b', - 'TEXT': '#f8f8f8', - 'INPUT': '#86a6df', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#86a6df', - 'BUTTON': ('#FFFFFF', '#5068a9'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#324e7b', '#5068a9', '#86a6df', '#f8f8f8'], - 'DESCRIPTION': ['Blue', 'Grey', 'Cold', 'Winter'], - }, - 'DarkPurple6': { - 'BACKGROUND': '#070739', - 'TEXT': '#e1e099', - 'INPUT': '#c327ab', - 'TEXT_INPUT': '#e1e099', - 'SCROLL': '#c327ab', - 'BUTTON': ('#e1e099', '#521477'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#070739', '#521477', '#c327ab', '#e1e099'], - 'DESCRIPTION': ['#000000', 'Purple', 'Yellow', 'Dark'], - }, - 'DarkBlue13': { - 'BACKGROUND': '#203562', - 'TEXT': '#e3e8f8', - 'INPUT': '#c0c5cd', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#c0c5cd', - 'BUTTON': ('#FFFFFF', '#3e588f'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#203562', '#3e588f', '#c0c5cd', '#e3e8f8'], - 'DESCRIPTION': ['Blue', 'Grey', 'Wedding', 'Cold'], - }, - 'DarkBrown5': { - 'BACKGROUND': '#3c1b1f', - 'TEXT': '#f6e1b5', - 'INPUT': '#e2bf81', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e2bf81', - 'BUTTON': ('#3c1b1f', '#f6e1b5'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3c1b1f', '#b21e4b', '#e2bf81', '#f6e1b5'], - 'DESCRIPTION': ['Brown', 'Red', 'Yellow', 'Warm'], - }, - 'DarkGreen3': { - 'BACKGROUND': '#062121', - 'TEXT': '#eeeeee', - 'INPUT': '#e4dcad', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e4dcad', - 'BUTTON': ('#eeeeee', '#181810'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], - 'DESCRIPTION': ['#000000', '#000000', 'Brown', 'Grey'], - }, - 'DarkBlack1': { - 'BACKGROUND': '#181810', - 'TEXT': '#eeeeee', - 'INPUT': '#e4dcad', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e4dcad', - 'BUTTON': ('#FFFFFF', '#062121'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], - 'DESCRIPTION': ['#000000', '#000000', 'Brown', 'Grey'], - }, - 'DarkGrey5': { - 'BACKGROUND': '#343434', - 'TEXT': '#f3f3f3', - 'INPUT': '#e9dcbe', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e9dcbe', - 'BUTTON': ('#FFFFFF', '#8e8b82'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], - 'DESCRIPTION': ['Grey', 'Brown'], - }, - 'LightBrown12': { - 'BACKGROUND': '#8e8b82', - 'TEXT': '#f3f3f3', - 'INPUT': '#e9dcbe', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e9dcbe', - 'BUTTON': ('#f3f3f3', '#8e8b82'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], - 'DESCRIPTION': ['Grey', 'Brown'], - }, - 'DarkTeal9': { - 'BACKGROUND': '#13445a', - 'TEXT': '#fef4e8', - 'INPUT': '#446878', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#446878', - 'BUTTON': ('#fef4e8', '#446878'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#13445a', '#970747', '#446878', '#fef4e8'], - 'DESCRIPTION': ['Red', 'Grey', 'Blue', 'Wedding', 'Retro'], - }, - 'DarkBlue14': { - 'BACKGROUND': '#21273d', - 'TEXT': '#f1f6f8', - 'INPUT': '#b9d4f1', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#b9d4f1', - 'BUTTON': ('#FFFFFF', '#6a759b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], - 'DESCRIPTION': ['Blue', '#000000', 'Grey', 'Cold', 'Winter'], - }, - 'LightBlue6': { - 'BACKGROUND': '#f1f6f8', - 'TEXT': '#21273d', - 'INPUT': '#6a759b', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#21273d', - 'BUTTON': ('#f1f6f8', '#6a759b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], - 'DESCRIPTION': ['Blue', '#000000', 'Grey', 'Cold', 'Winter'], - }, - 'DarkGreen4': { - 'BACKGROUND': '#044343', - 'TEXT': '#e4e4e4', - 'INPUT': '#045757', - 'TEXT_INPUT': '#e4e4e4', - 'SCROLL': '#045757', - 'BUTTON': ('#e4e4e4', '#045757'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#222222', '#044343', '#045757', '#e4e4e4'], - 'DESCRIPTION': ['#000000', 'Turquoise', 'Grey', 'Dark'], - }, - 'DarkGreen5': { - 'BACKGROUND': '#1b4b36', - 'TEXT': '#e0e7f1', - 'INPUT': '#aebd77', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#aebd77', - 'BUTTON': ('#FFFFFF', '#538f6a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#1b4b36', '#538f6a', '#aebd77', '#e0e7f1'], - 'DESCRIPTION': ['Green', 'Grey'], - }, - 'DarkTeal10': { - 'BACKGROUND': '#0d3446', - 'TEXT': '#d8dfe2', - 'INPUT': '#71adb5', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#71adb5', - 'BUTTON': ('#FFFFFF', '#176d81'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#0d3446', '#176d81', '#71adb5', '#d8dfe2'], - 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter', 'Cold'], - }, - 'DarkGrey6': { - 'BACKGROUND': '#3e3e3e', - 'TEXT': '#ededed', - 'INPUT': '#68868c', - 'TEXT_INPUT': '#ededed', - 'SCROLL': '#68868c', - 'BUTTON': ('#FFFFFF', '#405559'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], - 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], - }, - 'DarkTeal11': { - 'BACKGROUND': '#405559', - 'TEXT': '#ededed', - 'INPUT': '#68868c', - 'TEXT_INPUT': '#ededed', - 'SCROLL': '#68868c', - 'BUTTON': ('#ededed', '#68868c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], - 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], - }, - 'LightBlue7': { - 'BACKGROUND': '#9ed0e0', - 'TEXT': '#19483f', - 'INPUT': '#5c868e', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#19483f', - 'BUTTON': ('#FFFFFF', '#19483f'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#19483f', '#5c868e', '#ff6a38', '#9ed0e0'], - 'DESCRIPTION': ['Orange', 'Blue', 'Turquoise'], - }, - 'LightGreen10': { - 'BACKGROUND': '#d8ebb5', - 'TEXT': '#205d67', - 'INPUT': '#639a67', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#205d67', - 'BUTTON': ('#d8ebb5', '#205d67'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#205d67', '#639a67', '#d9bf77', '#d8ebb5'], - 'DESCRIPTION': ['Blue', 'Green', 'Brown', 'Vintage'], - }, - 'DarkBlue15': { - 'BACKGROUND': '#151680', - 'TEXT': '#f1fea4', - 'INPUT': '#375fc0', - 'TEXT_INPUT': '#f1fea4', - 'SCROLL': '#375fc0', - 'BUTTON': ('#f1fea4', '#1c44ac'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], - 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], - }, - 'DarkBlue16': { - 'BACKGROUND': '#1c44ac', - 'TEXT': '#f1fea4', - 'INPUT': '#375fc0', - 'TEXT_INPUT': '#f1fea4', - 'SCROLL': '#375fc0', - 'BUTTON': ('#f1fea4', '#151680'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], - 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], - }, - 'DarkTeal12': { - 'BACKGROUND': '#004a7c', - 'TEXT': '#fafafa', - 'INPUT': '#e8f1f5', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e8f1f5', - 'BUTTON': ('#fafafa', '#005691'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#004a7c', '#005691', '#e8f1f5', '#fafafa'], - 'DESCRIPTION': ['Grey', 'Blue', 'Cold', 'Winter'], - }, - 'LightBrown13': { - 'BACKGROUND': '#ebf5ee', - 'TEXT': '#921224', - 'INPUT': '#bdc6b8', - 'TEXT_INPUT': '#921224', - 'SCROLL': '#921224', - 'BUTTON': ('#FFFFFF', '#921224'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#921224', '#bdc6b8', '#bce0da', '#ebf5ee'], - 'DESCRIPTION': ['Red', 'Blue', 'Grey', 'Vintage', 'Wedding'], - }, - 'DarkBlue17': { - 'BACKGROUND': '#21294c', - 'TEXT': '#f9f2d7', - 'INPUT': '#f2dea8', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f2dea8', - 'BUTTON': ('#f9f2d7', '#141829'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#141829', '#21294c', '#f2dea8', '#f9f2d7'], - 'DESCRIPTION': ['#000000', 'Blue', 'Yellow'], - }, - 'DarkBrown6': { - 'BACKGROUND': '#785e4d', - 'TEXT': '#f2eee3', - 'INPUT': '#baaf92', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#baaf92', - 'BUTTON': ('#FFFFFF', '#785e4d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#785e4d', '#ff8426', '#baaf92', '#f2eee3'], - 'DESCRIPTION': ['Grey', 'Brown', 'Orange', 'Autumn'], - }, - 'DarkGreen6': { - 'BACKGROUND': '#5c715e', - 'TEXT': '#f2f9f1', - 'INPUT': '#ddeedf', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#ddeedf', - 'BUTTON': ('#f2f9f1', '#5c715e'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#5c715e', '#b6cdbd', '#ddeedf', '#f2f9f1'], - 'DESCRIPTION': ['Grey', 'Green', 'Vintage'], - }, - 'DarkGrey7': { - 'BACKGROUND': '#4b586e', - 'TEXT': '#dddddd', - 'INPUT': '#574e6d', - 'TEXT_INPUT': '#dddddd', - 'SCROLL': '#574e6d', - 'BUTTON': ('#dddddd', '#43405d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#43405d', '#4b586e', '#574e6d', '#dddddd'], - 'DESCRIPTION': ['Grey', 'Winter', 'Cold'], - }, - 'DarkRed2': { - 'BACKGROUND': '#ab1212', - 'TEXT': '#f6e4b5', - 'INPUT': '#cd3131', - 'TEXT_INPUT': '#f6e4b5', - 'SCROLL': '#cd3131', - 'BUTTON': ('#f6e4b5', '#ab1212'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#ab1212', '#1fad9f', '#cd3131', '#f6e4b5'], - 'DESCRIPTION': ['Turquoise', 'Red', 'Yellow'], - }, - 'LightGrey6': { - 'BACKGROUND': '#e3e3e3', - 'TEXT': '#233142', - 'INPUT': '#455d7a', - 'TEXT_INPUT': '#e3e3e3', - 'SCROLL': '#233142', - 'BUTTON': ('#e3e3e3', '#455d7a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#233142', '#455d7a', '#f95959', '#e3e3e3'], - 'DESCRIPTION': ['#000000', 'Blue', 'Red', 'Grey'], - }, - 'HotDogStand': { - 'BACKGROUND': 'red', - 'TEXT': 'yellow', - 'INPUT': 'yellow', - 'TEXT_INPUT': '#000000', - 'SCROLL': 'yellow', - 'BUTTON': ('red', 'yellow'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, -} - - -def ListOfLookAndFeelValues(): - """ - Get a list of the valid values to pass into your call to change_look_and_feel - :return: List[str] - list of valid string values - """ - return sorted(list(LOOK_AND_FEEL_TABLE.keys())) - - -def theme(new_theme=None): - """ - Sets / Gets the current Theme. If none is specified then returns the current theme. - This call replaces the ChangeLookAndFeel / change_look_and_feel call which only sets the theme. - - :param new_theme: (str) the new theme name to use - :return: (str) the currently selected theme - """ - if new_theme is not None: - change_look_and_feel(new_theme) - return CURRENT_LOOK_AND_FEEL - - -def theme_background_color(color=None): - """ - Sets/Returns the background color currently in use - Used for Windows and containers (Column, Frame, Tab) and tables - - :return: (str) - color string of the background color currently in use - """ - if color is not None: - set_options(background_color=color) - return DEFAULT_BACKGROUND_COLOR - - -def theme_element_background_color(color=None): - """ - Sets/Returns the background color currently in use for all elements except containers - - :return: (str) - color string of the element background color currently in use - """ - if color is not None: - set_options(element_background_color=color) - return DEFAULT_ELEMENT_BACKGROUND_COLOR - - -def theme_text_color(color=None): - """ - Sets/Returns the text color currently in use - - :return: (str) - color string of the text color currently in use - """ - if color is not None: - set_options(text_color=color) - return DEFAULT_TEXT_COLOR - - -def theme_text_element_background_color(color=None): - """ - Sets/Returns the background color for text elements - - :return: (str) - color string of the text background color currently in use - """ - if color is not None: - set_options(text_element_background_color=color) - return DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - - -def theme_input_background_color(color=None): - """ - Sets/Returns the input element background color currently in use - - :return: (str) - color string of the input element background color currently in use - """ - if color is not None: - set_options(input_elements_background_color=color) - return DEFAULT_INPUT_ELEMENTS_COLOR - - -def theme_input_text_color(color=None): - """ - Sets/Returns the input element entry color (not the text but the thing that's displaying the text) - - :return: (str) - color string of the input element color currently in use - """ - if color is not None: - set_options(input_text_color=color) - return DEFAULT_INPUT_TEXT_COLOR - - -def theme_button_color(color=None): - """ - Sets/Returns the button color currently in use - - :return: Tuple[str, str] - TUPLE with color strings of the button color currently in use (button text color, button background color) - """ - if color is not None: - set_options(button_color=color) - return DEFAULT_BUTTON_COLOR - - -def theme_progress_bar_color(color=None): - """ - Sets/Returns the progress bar colors by the current color theme - - :return: Tuple[str, str] - TUPLE with color strings of the ProgressBar color currently in use(button text color, button background color) - """ - if color is not None: - set_options(progress_meter_color=color) - return DEFAULT_PROGRESS_BAR_COLOR - - -def theme_slider_color(color=None): - """ - Sets/Returns the slider color (used for sliders) - - :return: (str) - color string of the slider color currently in use - """ - if color is not None: - set_options(scrollbar_color=color) - return DEFAULT_SCROLLBAR_COLOR - - -def theme_border_width(border_width=None): - """ - Sets/Returns the border width currently in use - Used by non ttk elements at the moment - - :return: (int) - border width currently in use - """ - if border_width is not None: - set_options(border_width=border_width) - return DEFAULT_BORDER_WIDTH - - -def theme_slider_border_width(border_width=None): - """ - Sets/Returns the slider border width currently in use - - :return: (int) - border width currently in use - """ - if border_width is not None: - set_options(slider_border_width=border_width) - return DEFAULT_SLIDER_BORDER_WIDTH - - -def theme_progress_bar_border_width(border_width=None): - """ - Sets/Returns the progress meter border width currently in use - - :return: (int) - border width currently in use - """ - if border_width is not None: - set_options(progress_meter_border_depth=border_width) - return DEFAULT_PROGRESS_BAR_BORDER_WIDTH - - -def theme_element_text_color(color=None): - """ - Sets/Returns the text color used by elements that have text as part of their display (Tables, Trees and Sliders) - - :return: (str) - color string currently in use - """ - if color is not None: - set_options(element_text_color=color) - return DEFAULT_ELEMENT_TEXT_COLOR - - -def theme_list(): - """ - Returns a sorted list of the currently available color themes - - :return: List[str] - A sorted list of the currently available color themes - """ - return list_of_look_and_feel_values() - - -def theme_add_new(new_theme_name, new_theme_dict): - """ - Add a new theme to the dictionary of themes - - :param new_theme_name: text to display in element - :type new_theme_name: (str) - :param new_theme_dict: text to display in element - :type new_theme_dict: (dict) - """ - global LOOK_AND_FEEL_TABLE - try: - LOOK_AND_FEEL_TABLE[new_theme_name] = new_theme_dict - except Exception as e: - print('Exception during adding new theme {}'.format(e)) - - -def theme_previewer(columns=12): - """ - Show a window with all of the color themes - takes a while so be patient - - :param columns: (int) number of themes in a single row - """ - preview_all_look_and_feel_themes(columns) - - -def ChangeLookAndFeel(index, force=False): - """ - Change the "color scheme" of all future PySimpleGUI Windows. - The scheme are string names that specify a group of colors. Background colors, text colors, button colors. - There are 13 different color settings that are changed at one time using a single call to ChangeLookAndFeel - The look and feel table itself has these indexes into the dictionary LOOK_AND_FEEL_TABLE. - The original list was (prior to a major rework and renaming)... these names still work... - In Nov 2019 a new Theme Formula was devised to make choosing a theme easier: - The "Formula" is: - ["Dark" or "Light"] Color Number - Colors can be Blue Brown Grey Green Purple Red Teal Yellow Black - The number will vary for each pair. There are more DarkGrey entries than there are LightYellow for example. - Default = The default settings (only button color is different than system default) - Default1 = The full system default including the button (everything's gray... how sad... don't be all gray... please....) - :param index: (str) the name of the index into the Look and Feel table (does not have to be exact, can be "fuzzy") - :param force: (bool) no longer used - """ - - global CURRENT_LOOK_AND_FEEL - - # if sys.platform.startswith('darwin') and not force: - # print('*** Changing look and feel is not supported on Mac platform ***') - # return - - theme = index - # normalize available l&f values - lf_values = [item.lower() for item in list_of_look_and_feel_values()] - - # option 1 - opt1 = theme.replace(' ', '').lower() - - # option 2 (reverse lookup) - optx = theme.lower().split(' ') - optx.reverse() - opt2 = ''.join(optx) - - # search for valid l&f name - if opt1 in lf_values: - ix = lf_values.index(opt1) - elif opt2 in lf_values: - ix = lf_values.index(opt2) - else: - ix = randint(0, len(lf_values) - 1) - print('** Warning - {} Theme is not a valid theme. Change your theme call. **'.format(index)) - print('valid values are', list_of_look_and_feel_values()) - print('Instead, please enjoy a random Theme named {}'.format(list_of_look_and_feel_values()[ix])) - - selection = list_of_look_and_feel_values()[ix] - CURRENT_LOOK_AND_FEEL = selection - try: - colors = LOOK_AND_FEEL_TABLE[selection] - - # Color the progress bar using button background and input colors...unless they're the same - if colors['PROGRESS'] != COLOR_SYSTEM_DEFAULT: - if colors['BUTTON'][1] != colors['INPUT'] and colors['BUTTON'][1] != colors['BACKGROUND']: - colors['PROGRESS'] = colors['BUTTON'][1], colors['INPUT'] - else: # if the same, then use text input on top of input color - colors['PROGRESS'] = (colors['TEXT_INPUT'], colors['INPUT']) - else: - colors['PROGRESS'] = DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL - # call to change all the colors - SetOptions( - background_color=colors['BACKGROUND'], - text_element_background_color=colors['BACKGROUND'], - element_background_color=colors['BACKGROUND'], - text_color=colors['TEXT'], - input_elements_background_color=colors['INPUT'], - # button_color=colors['BUTTON'] if not sys.platform.startswith('darwin') else None, - button_color=colors['BUTTON'], - progress_meter_color=colors['PROGRESS'], - border_width=colors['BORDER'], - slider_border_width=colors['SLIDER_DEPTH'], - progress_meter_border_depth=colors['PROGRESS_DEPTH'], - scrollbar_color=(colors['SCROLL']), - element_text_color=colors['TEXT'], - input_text_color=colors['TEXT_INPUT'], - ) - except: # most likely an index out of range - print('** Warning - Theme value not valid. Change your theme call. **') - print('valid values are', list_of_look_and_feel_values()) - - -def preview_all_look_and_feel_themes(columns=12): - """ - Displays a "Quick Reference Window" showing all of the different Look and Feel settings that are available. - They are sorted alphabetically. The legacy color names are mixed in, but otherwise they are sorted into Dark and Light halves - :param columns: (int) The number of themes to display per row - """ - - # Show a "splash" type message so the user doesn't give up waiting - popup_quick_message( - 'Hang on for a moment, this will take a bit to create....', - background_color='red', - text_color='#FFFFFF', - auto_close=True, - non_blocking=True, - ) - - web = False - - win_bg = 'black' - - def sample_layout(): - return [ - [Text('Text element'), InputText('Input data here', size=(10, 1))], - [Button('Ok'), Button('Cancel'), Slider((1, 10), orientation='h', size=(5, 15))], - ] - - layout = [[Text('Here is a complete list of themes', font='Default 18', background_color=win_bg)]] - - names = list_of_look_and_feel_values() - names.sort() - row = [] - for count, theme in enumerate(names): - change_look_and_feel(theme) - if not count % columns: - layout += [row] - row = [] - row += [Frame(theme, sample_layout() if not web else [[T(theme)]] + sample_layout())] - if row: - layout += [row] - - window = Window('Preview of all Look and Feel choices', layout, background_color=win_bg) - window.read() - window.close() - - -# ============================== sprint ======# -# Is identical to the Scrolled Text Box # -# Provides a crude 'print' mechanism but in a # -# GUI environment # -# ============================================# -sprint = ScrolledTextBox - - -# Converts an object's contents into a nice printable string. Great for dumping debug data -def ObjToStringSingleObj(obj): - if obj is None: - return 'None' - return str(obj.__class__) + '\n' + '\n'.join((repr(item) + ' = ' + repr(obj.__dict__[item]) for item in sorted(obj.__dict__))) - - -def ObjToString(obj, extra=' '): - if obj is None: - return 'None' - return str(obj.__class__) + '\n' + '\n'.join((extra + (str(item) + ' = ' + (ObjToString(obj.__dict__[item], extra + ' ') if hasattr(obj.__dict__[item], '__dict__') else str(obj.__dict__[item]))) for item in sorted(obj.__dict__))) - - -# ------------------------------------------------------------------------------------------------------------------ # -# ===================================== Upper PySimpleGUI ======================================================== # -# Pre-built dialog boxes for all your needs These are the "high level API calls # -# ------------------------------------------------------------------------------------------------------------------ # - -# ----------------------------------- The mighty Popup! ------------------------------------------------------------ # - - -def Popup(*args, button_color=None, background_color=None, text_color=None, button_type=POPUP_BUTTONS_OK, auto_close=False, auto_close_duration=None, custom_text=(None, None), non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Popup - Display a popup box with as many parms as you wish to include - :param args: - :param button_color: - :param background_color: - :param text_color: - :param button_type: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - if not args: - args_to_print = [''] - else: - args_to_print = args - if line_width != None: - local_line_width = line_width - else: - local_line_width = MESSAGE_BOX_LINE_WIDTH - title = args_to_print[0] if args_to_print[0] is not None else 'None' - window = Window( - title, - auto_size_text=True, - background_color=background_color, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - icon=icon, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - max_line_total, total_lines = 0, 0 - for message in args_to_print: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - if message.count('\n'): - message_wrapped = message - else: - message_wrapped = textwrap.fill(message, local_line_width) - message_wrapped_lines = message_wrapped.count('\n') + 1 - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = min(longest_line_len, local_line_width) - max_line_total = max(max_line_total, width_used) - # height = _GetNumLinesNeeded(message, width_used) - height = message_wrapped_lines - window.AddRow(Text(message_wrapped, auto_size_text=True, text_color=text_color, background_color=background_color)) - total_lines += height - - if non_blocking: - PopupButton = DummyButton # important to use or else button will close other windows too! - else: - PopupButton = Button - # show either an OK or Yes/No depending on paramater - if custom_text != (None, None): - if type(custom_text) is not tuple: - window.AddRow(PopupButton(custom_text, size=(len(custom_text), 1), button_color=button_color, focus=True, bind_return_key=True)) - elif custom_text[1] is None: - window.AddRow( - PopupButton( - custom_text[0], - size=(len(custom_text[0]), 1), - button_color=button_color, - focus=True, - bind_return_key=True, - ) - ) - else: - window.AddRow( - PopupButton( - custom_text[0], - button_color=button_color, - focus=True, - bind_return_key=True, - size=(len(custom_text[0]), 1), - ), - PopupButton(custom_text[1], button_color=button_color, size=(len(custom_text[0]), 1)), - ) - elif button_type is POPUP_BUTTONS_YES_NO: - window.AddRow( - PopupButton('Yes', button_color=button_color, focus=True, bind_return_key=True, pad=((20, 5), 3), size=(5, 1)), - PopupButton('No', button_color=button_color, size=(5, 1)), - ) - elif button_type is POPUP_BUTTONS_CANCELLED: - window.AddRow(PopupButton('Cancelled', button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) - elif button_type is POPUP_BUTTONS_ERROR: - window.AddRow(PopupButton('Error', size=(6, 1), button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) - elif button_type is POPUP_BUTTONS_OK_CANCEL: - window.AddRow( - PopupButton('OK', size=(6, 1), button_color=button_color, focus=True, bind_return_key=True), - PopupButton('Cancel', size=(6, 1), button_color=button_color), - ) - elif button_type is POPUP_BUTTONS_NO_BUTTONS: - pass - else: - window.AddRow(PopupButton('OK', size=(5, 1), button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) - - if non_blocking: - button, values = window.Read(timeout=0) - else: - button, values = window.Read() - window.Close() - return button - - -# ============================== MsgBox============# -# Lazy function. Same as calling Popup with parms # -# This function WILL Disappear perhaps today # -# ==================================================# -# MsgBox is the legacy call and should not be used any longer -def MsgBox(*args): - raise DeprecationWarning('MsgBox is no longer supported... change your call to Popup') - - -# --------------------------- PopupNoButtons --------------------------- -def PopupNoButtons(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Show a Popup but without any buttons - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=POPUP_BUTTONS_NO_BUTTONS, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location - ) - - -# --------------------------- PopupNonBlocking --------------------------- -def PopupNonBlocking(*args, button_type=POPUP_BUTTONS_OK, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=True, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Show Popup box and immediately return (does not block) - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - -PopupNoWait = PopupNonBlocking - - -# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup --------------------------- -def PopupQuick(*args, button_type=POPUP_BUTTONS_OK, button_color=None, background_color=None, text_color=None, auto_close=True, auto_close_duration=2, non_blocking=True, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Show Popup box that doesn't block and closes itself - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - -# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup with no titlebar and no buttons --------------------------- -def PopupQuickMessage(*args, button_type=POPUP_BUTTONS_NO_BUTTONS, button_color=None, background_color=None, text_color=None, auto_close=True, auto_close_duration=2, non_blocking=True, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=True, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Show Popup box that doesn't block and closes itself - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - -# --------------------------- PopupNoTitlebar --------------------------- -def PopupNoTitlebar(*args, button_type=POPUP_BUTTONS_OK, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, grab_anywhere=True, keep_on_top=False, location=(None, None)): - """ - Display a Popup without a titlebar. Enables grab anywhere so you can move it - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=True, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - -PopupNoFrame = PopupNoTitlebar -PopupNoBorder = PopupNoTitlebar -PopupAnnoying = PopupNoTitlebar - - -# --------------------------- PopupAutoClose --------------------------- -def PopupAutoClose(*args, button_type=POPUP_BUTTONS_OK, button_color=None, background_color=None, text_color=None, auto_close=True, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Popup that closes itself after some time period - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - -PopupTimed = PopupAutoClose - - -# --------------------------- PopupError --------------------------- -def PopupError(*args, button_color=DEFAULT_ERROR_BUTTON_COLOR, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Popup with colored button and 'Error' as button text - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_type=POPUP_BUTTONS_ERROR, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location - ) - - -# --------------------------- PopupCancel --------------------------- -def PopupCancel(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Display Popup with "cancelled" button text - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_type=POPUP_BUTTONS_CANCELLED, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location - ) - - -# --------------------------- PopupOK --------------------------- -def PopupOK(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Display Popup with OK button only - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_type=POPUP_BUTTONS_OK, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location - ) - - -# --------------------------- PopupOKCancel --------------------------- -def PopupOKCancel(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Display popup with OK and Cancel buttons - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: OK, Cancel or None - """ - return Popup( - *args, - button_type=POPUP_BUTTONS_OK_CANCEL, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location - ) - - -# --------------------------- PopupYesNo --------------------------- -def PopupYesNo(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Display Popup with Yes and No buttons - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: Yes, No or None - """ - return Popup( - *args, - button_type=POPUP_BUTTONS_YES_NO, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location - ) - - -############################################################################## -# The PopupGet_____ functions - Will return user input # -############################################################################## - -# --------------------------- PopupGetFolder --------------------------- - - -def PopupGetFolder( - message, - default_path='', - no_window=False, - size=(None, None), - button_color=None, - background_color=None, - text_color=None, - icon=DEFAULT_WINDOW_ICON, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - initial_folder=None, -): - """ - Display popup with text entry field and browse button. Browse for folder - :param message: - :param default_path: - :param no_window: - :param size: - :param button_color: - :param background_color: - :param text_color: - :param icon: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: Contents of text field. None if closed using X or cancelled - """ - - global _my_windows - - if no_window: - if _my_windows._NumOpenWindows: - root = tk.Toplevel() - else: - root = tk.Tk() - try: - root.attributes('-alpha', 0) # hide window while building it. makes for smoother 'paint' - except: - pass - folder_name = tk.filedialog.askdirectory() # show the 'get folder' dialog box - root.destroy() - return folder_name - - layout = [ - [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], - [InputText(default_text=default_path, size=size, key='_INPUT_'), FolderBrowse(initial_folder=initial_folder)], - [Button('Ok', size=(5, 1), bind_return_key=True), Button('Cancel', size=(5, 1))], - ] - - window = Window( - title=message, - layout=layout, - icon=icon, - auto_size_text=True, - button_color=button_color, - background_color=background_color, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - button, values = window.Read() - window.Close() - if button != 'Ok': - return None - else: - path = values['_INPUT_'] - return path - - -# --------------------------- PopupGetFile --------------------------- - - -def PopupGetFile( - message, - default_path='', - default_extension='', - save_as=False, - file_types=(('ALL Files', '*.*'),), - no_window=False, - size=(None, None), - button_color=None, - background_color=None, - text_color=None, - icon=DEFAULT_WINDOW_ICON, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - initial_folder=None, -): - """ - Display popup with text entry field and browse button. Browse for file - :param message: - :param default_path: - :param default_extension: - :param save_as: - :param file_types: - :param no_window: - :param size: - :param button_color: - :param background_color: - :param text_color: - :param icon: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: string representing the path chosen, None if cancelled or window closed with X - """ - - global _my_windows - - if no_window: - if _my_windows._NumOpenWindows: - root = tk.Toplevel() - else: - root = tk.Tk() - try: - root.attributes('-alpha', 0) # hide window while building it. makes for smoother 'paint' - except: - pass - if save_as: - filename = tk.filedialog.asksaveasfilename(filetypes=file_types, defaultextension=default_extension) # show the 'get file' dialog box - else: - filename = tk.filedialog.askopenfilename(filetypes=file_types, defaultextension=default_extension) # show the 'get file' dialog box - root.destroy() - return filename - - browse_button = SaveAs(file_types=file_types, initial_folder=initial_folder) if save_as else FileBrowse(file_types=file_types, initial_folder=initial_folder) - - layout = [ - [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], - [InputText(default_text=default_path, size=size, key='_INPUT_'), browse_button], - [Button('Ok', size=(6, 1), bind_return_key=True), Button('Cancel', size=(6, 1))], - ] - - window = Window( - title=message, - layout=layout, - icon=icon, - auto_size_text=True, - button_color=button_color, - font=font, - background_color=background_color, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - button, values = window.Read() - window.Close() - if button != 'Ok': - return None - else: - path = values['_INPUT_'] - return path - - -# --------------------------- PopupGetText --------------------------- - - -def PopupGetText( - message, - default_text='', - password_char='', - size=(None, None), - button_color=None, - background_color=None, - text_color=None, - icon=DEFAULT_WINDOW_ICON, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Display Popup with text entry field - :param message: - :param default_text: - :param password_char: - :param size: - :param button_color: - :param background_color: - :param text_color: - :param icon: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: Text entered or None if window was closed - """ - - layout = [ - [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color, font=font)], - [InputText(default_text=default_text, size=size, key='_INPUT_', password_char=password_char)], - [Button('Ok', size=(5, 1), bind_return_key=True), Button('Cancel', size=(5, 1))], - ] - - window = Window( - title=message, - layout=layout, - icon=icon, - auto_size_text=True, - button_color=button_color, - no_titlebar=no_titlebar, - background_color=background_color, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - button, values = window.Read() - window.Close() - if button != 'Ok': - return None - else: - path = values['_INPUT_'] - return path - - -change_look_and_feel = ChangeLookAndFeel -easy_print = EasyPrint -easy_print_close = EasyPrintClose -get_complimentary_hex = GetComplimentaryHex -list_of_look_and_feel_values = ListOfLookAndFeelValues -obj_to_string = ObjToString -obj_to_string_single_obj = ObjToStringSingleObj -one_line_progress_meter = OneLineProgressMeter -one_line_progress_meter_cancel = OneLineProgressMeterCancel -popup = Popup -popup_annoying = PopupAnnoying -popup_auto_close = PopupAutoClose -popup_cancel = PopupCancel -popup_error = PopupError -popup_get_file = PopupGetFile -popup_get_folder = PopupGetFolder -popup_get_text = PopupGetText -popup_no_border = PopupNoBorder -popup_no_buttons = PopupNoButtons -popup_no_frame = PopupNoFrame -popup_no_titlebar = PopupNoTitlebar -popup_no_wait = PopupNoWait -popup_non_blocking = PopupNonBlocking -popup_ok = PopupOK -popup_ok_cancel = PopupOKCancel -popup_quick = PopupQuick -popup_quick_message = PopupQuickMessage -popup_scrolled = PopupScrolled -popup_timed = PopupTimed -popup_yes_no = PopupYesNo -print_close = PrintClose -rgb = RGB -scrolled_text_box = ScrolledTextBox -set_global_icon = SetGlobalIcon -set_options = SetOptions -timer_start = TimerStart -timer_stop = TimerStop -sprint = sprint - -# ------------------------ Set the "Official PySimpleGUI Theme Colors" ------------------------ -theme(CURRENT_LOOK_AND_FEEL) -# theme_previewer() -# -------------------------------- ENTRY POINT IF RUN STANDALONE -------------------------------- # - - -def main(): - # ChangeLookAndFeel('light green 6' ) - - # Popup('Popup Test') - - # SetOptions(background_color='blue', text_element_background_color='blue', text_color='white') - # layout = [[Text('You are running the PySimpleGUI.py file itself', font='Any 25', size=(60,1), tooltip='My tooltip!')], - # [Text('You should be importing it rather than running it', size=(60, 1))], - # [Text('Here is your sample window....')], - # [Text('Source Folder', justification='right', size=(40,1)), InputText('Source', focus=True, disabled=True), - # FolderBrowse()], - # [Text('Destination Folder', justification='right', size=(40,1)), InputText('Dest'), FolderBrowse()], - # [Ok(), Cancel(disabled=True), Exit(tooltip='Exit button'), Button('Hidden Button', visible=False)]] - - ver = version.split('\n')[0] - - def VerLine(version, description, size=(30, 1)): - return [Column([[T(description, font='Courier 18', text_color='yellow')], [T(version, font='Courier 18', size=size)]])] - - menu_def = [ - ['&File', ['&Open', '&Save', 'E&xit', 'Properties']], - [ - '&Edit', - [ - 'Paste', - [ - 'Special', - 'Normal', - ], - '!Undo', - ], - ], - [ - '!&Disabled', - [ - 'Paste', - [ - 'Special', - 'Normal', - ], - '!Undo', - ], - ], - ['&Help', '&About...'], - ] - - menu_def = [ - ['File', ['&Open::mykey', '&Save', 'E&xit', 'Properties']], - [ - 'Edit', - [ - '!Paste', - [ - 'Special', - 'Normal', - ], - '!Undo', - ], - ], - [ - '!Disabled', - [ - 'Has Sub', - [ - 'Item1', - 'Item2', - ], - 'No Sub', - ], - ], - ['Help', 'About...'], - ] - - col1 = [[Text('Column 1 line 1', background_color='red')], [Text('Column 1 line 2')]] - - layout = [ - [Menu(menu_def, key='_MENU_', text_color='yellow', background_color='#475841', font='Courier 14')], - # [T('123435', size=(1,8))], - [ - Text( - 'PySimpleGUIWeb Welcomes You...', - tooltip='text', - font=('Comic sans ms', 20), - size=(40, 1), - text_color='yellow', - enable_events=False, - key='_PySimpleGUIWeb_', - ) - ], - # [OptionMenu([])], - [T('System platform = %s' % sys.platform)], - [Image(data=DEFAULT_BASE64_ICON, enable_events=False)], - # [Image(filename=r'C:\Python\PycharmProjects\PSG\logo500.png', key='-IMAGE-')], - VerLine(ver, 'PySimpleGUI Version'), - VerLine(os.path.dirname(os.path.abspath(__file__)), 'PySimpleGUI Location'), - VerLine(sys.version, 'Python Version', size=(60, 2)), - VerLine(pkg_resources.get_distribution('remi').version, 'Remi Version'), - # [Text('VERSION {}'.format(version), text_color='red', font='Courier 24')], - [ - T('Current Time '), - Text('Text', key='_TEXT_', font='Arial 18', text_color='black', size=(30, 1)), - Column(col1, background_color='red'), - ], - [T('Up Time'), Text('Text', key='_TEXT_UPTIME_', font='Arial 18', text_color='black', size=(30, 1))], - [Input('Single Line Input', do_not_clear=True, enable_events=False, size=(30, 1), text_color='red', key='_IN_')], - [Multiline('Multiline Input', do_not_clear=True, size=(40, 4), enable_events=False, key='_MULTI_IN_')], - # [Output(size=(60,10))], - [ - MultilineOutput( - 'Multiline Output', - size=(80, 8), - text_color='blue', - font='Courier 12', - key='_MULTIOUT_', - autoscroll=True, - ) - ], - [ - Checkbox('Checkbox 1', enable_events=True, key='_CB1_'), - Checkbox('Checkbox 2', default=True, key='_CB2_', enable_events=True), - ], - [ - Combo( - values=['Combo 1', 'Combo 2', 'Combo 3'], - default_value='Combo 2', - key='_COMBO_', - enable_events=True, - readonly=False, - tooltip='Combo box', - disabled=False, - size=(12, 1), - ) - ], - [Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), enable_events=True, size=(10, 3), key='_LIST_')], - # [Image(filename=r'C:\Python\PycharmProjects\PSG\logo200.png', enable_events=False)], - [Slider((1, 100), default_value=80, key='_SLIDER_', visible=True, enable_events=True, orientation='v')], - [Spin(values=(1, 2, 3), initial_value='2', size=(4, 1), key='_SPIN_', enable_events=True)], - [ - OK(), - Button('Hidden', visible=False, key='_HIDDEN_'), - Button('Values'), - Button('Exit', button_color=('white', 'red')), - Button('UnHide'), - B('Popup'), - ], - ] - - window = Window( - 'PySimpleGUIWeb Test Harness Window', - layout, - font='Arial 18', - icon=DEFAULT_BASE64_ICON, - default_element_size=(12, 1), - auto_size_buttons=False, - ) - - start_time = datetime.datetime.now() - while True: - event, values = window.Read(timeout=100) - window.Element('_TEXT_').Update(str(datetime.datetime.now())) - window.Element('_TEXT_UPTIME_').Update(str(datetime.datetime.now() - start_time)) - print(event, values) if event != TIMEOUT_KEY else None - if event in (None, 'Exit'): - break - elif event == 'OK': - - window.Element('_MULTIOUT_').print('You clicked the OK button') - # window.Element('_MULTIOUT_').Update('You clicked the OK button', append=True, autoscroll=True) - window.Element('_PySimpleGUIWeb_').Widget.style['background-image'] = "url('/my_resources:mine.png')" - - elif event == 'Values': - window.Element('_MULTIOUT_').Update(str(values) + '\n', append=True) - # nav = remi.gui.FileFolderNavigator(False,r'a:\TEMP', True, False) - # here is returned the Input Dialog widget, and it will be shown - # fileselectionDialog.show(window.Element('_IN_').Widget) - - elif event != TIMEOUT_KEY: - window.Element('_MULTIOUT_').print('EVENT: ' + str(event)) - if event == 'Popup': - Popup('This is a popup!') - if event == 'UnHide': - print('Unhiding...') - window.Element('_HIDDEN_').Update(visible=True) - - window.Close() - - -if __name__ == '__main__': - main() - exit(0) diff --git a/FreeSimpleGUIWeb/FreeSimpleGUIWeb/__init__.py b/FreeSimpleGUIWeb/FreeSimpleGUIWeb/__init__.py index 208f79b8..c1aabdb8 100644 --- a/FreeSimpleGUIWeb/FreeSimpleGUIWeb/__init__.py +++ b/FreeSimpleGUIWeb/FreeSimpleGUIWeb/__init__.py @@ -1,2 +1,10680 @@ -from .FreeSimpleGUIWeb import * -from .FreeSimpleGUIWeb import __version__ +# usr/bin/python3 + +version = __version__ = ( + '0.39.0.6 Unreleased\n , VSeparator added (spelling error), added default key for one_line_progress_meter, auto-add keys to tables & trees, Graph.draw_image now uses image_data property instead of calling set_image, added theme_add_new, changed Remi call menu_item.set_on_click_listener to menu_item.onclick.connect so it can run with latest Remi' +) + +port = 'PySimpleGUIWeb' + +import sys +import datetime +import textwrap +import pickle +import threading +from queue import Queue +import remi +import logging +import traceback +import os +import base64, binascii +import mimetypes +from random import randint +import time +import pkg_resources + + +# from typing import List, Any, Union, Tuple, Dict # For doing types in comments. perhaps not required + + +try: + from io import StringIO +except: + from cStringIO import StringIO + +###### ##### ##### # # ### # # +# # # # # # # # # ##### # ###### # # # # # # # # ###### ##### +# # # # # # ## ## # # # # # # # # # # # # # # +###### # ##### # # ## # # # # ##### # #### # # # # # # ##### ##### +# # # # # # ##### # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # +# # ##### # # # # ###### ###### ##### ##### ### ## ## ###### ##### + +""" + Welcome to the "core" PySimpleGUIWeb code.... + + This special port of the PySimpleGUI SDK to the browser is made possible by the magic of Remi + + https://github.com/dddomodossola/remi + + To be clear, PySimpleGUI would not be able to run in a web browser without this important GUI Framework + It may not be as widely known at tkinter or Qt, but it should be. Just as those are the best of the desktop + GUI frameworks, Remi is THE framework for doing Web Page GUIs in Python. Nothing else like it exists. + + ::::::::: :::::::::: ::: ::: ::::::::::: + :+: :+: :+: :+:+: :+:+: :+: + +:+ +:+ +:+ +:+ +:+:+ +:+ +:+ + +#++:++#: +#++:++# +#+ +:+ +#+ +#+ + +#+ +#+ +#+ +#+ +#+ +#+ + #+# #+# #+# #+# #+# #+# + ### ### ########## ### ### ########### + +""" + +g_time_start = 0 +g_time_end = 0 +g_time_delta = 0 + + +def TimerStart(): + global g_time_start + + g_time_start = time.time() + + +def TimerStop(): + global g_time_delta, g_time_end + + g_time_end = time.time() + g_time_delta = g_time_end - g_time_start + print(g_time_delta * 1000) + + +# Because looks matter... +DEFAULT_BASE64_ICON = b'iVBORw0KGgoAAAANSUhEUgAAACEAAAAgCAMAAACrZuH4AAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAMGmYMGqZMWqaMmubMmycM22dNGuZNm2bNm6bNG2dN26cNG6dNG6eNW+fN3CfOHCeOXGfNXCgNnGhN3KiOHOjOXSjOHSkOnWmOnamOnanPHSiPXakPnalO3eoPnimO3ioPHioPHmpPHmqPXqqPnurPnusPnytP3yuQHimQnurQn2sQH2uQX6uQH6vR32qRn+sSXujSHynTH2mTn+nSX6pQH6wTIGsTYKuTYSvQoCxQoCyRIK0R4S1RYS2Roa4SIe4SIe6SIi7Soq7SYm8SYq8Sou+TY2/UYStUYWvVIWtUYeyVoewUIi0VIizUI6+Vo+8WImxXJG5YI2xZI+xZ5CzZJC0ZpG1b5a3apW4aZm/cZi4dJ2/eJ69fJ+9XZfEZZnCZJzHaZ/Jdp/AeKTI/tM8/9Q7/9Q8/9Q9/9Q+/tQ//9VA/9ZA/9ZB/9ZC/9dD/9ZE/tdJ/9dK/9hF/9hG/9hH/9hI/9hJ/9hK/9lL/9pK/9pL/thO/9pM/9pN/9tO/9tP/9xP/tpR/9xQ/9xR/9xS/9xT/91U/91V/t1W/95W/95X/95Y/95Z/99a/99b/txf/txh/txk/t5l/t1q/t5v/+Bb/+Bc/+Bd/+Be/+Bf/+Bg/+Fh/+Fi/+Jh/+Ji/uJk/uJl/+Jm/+Rm/uJo/+Ro/+Rr/+Zr/+Vs/+Vu/+Zs/+Zu/uF0/uVw/+dw/+dz/+d2/uB5/uB6/uJ9/uR7/uR+/uV//+hx/+hy/+h0/+h2/+l4/+l7/+h8gKXDg6vLgazOhKzMiqrEj6/KhK/Qka/Hk7HJlLHJlLPMmLTLmbbOkLXSmLvXn77XoLrPpr/Tn8DaocLdpcHYrcjdssfZus/g/uOC/uOH/uaB/uWE/uaF/uWK/+qA/uqH/uqI/uuN/uyM/ueS/ueW/ueY/umQ/uqQ/uuS/uuW/uyU/uyX/uqa/uue/uye/uyf/u6f/uyq/u+r/u+t/vCm/vCp/vCu/vCy/vC2/vK2/vO8/vO/wtTjwtXlzdrl/vTA/vPQAAAAiNpY5gAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAAFw8AABcPASe7rwsAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAKUSURBVDhPhdB3WE1xHMdxt5JV0dANoUiyd8kqkey996xclUuTlEKidO3qVnTbhIyMW/bee5NskjJLmR/f3++cK/94vP76Ps/n/Zx7z6mE/6koJowcK154vvHOL/GsKCZXkUgkWlf4vWGWq5tsDz+JWIzSokAiqXGe7nWu3HxhEYof7fhOqp1GtptQuMruVhQdxZ05U5G47tYUHbQ4oah6Fg9Z4ubm7i57JhQjdHS0RSzUPoG17u6zZTKZh8c8XlytqW9YWUOH1LqFOZ6enl5ec+XybFb0rweM1tPTM6yuq6vLs0lYJJfLvb19fHwDWGF0jh5lYNAe4/QFemOwxtfXz8/fPyBgwVMqzAcCF4ybAZ2MRCexJGBhYGBQUHDw4u1UHDG1G2ZqB/Q1MTHmzAE+kpCwL1RghlTaBt/6SaXS2kx9YH1IaOjSZST8vfA9JtoDnSngGgL7wkg4WVkofA9mcF1Sx8zMzBK4v3wFiYiMVLxlEy9u21syFhYNmgN7IyJXEYViNZvEYoCVVWOmUVvgQVSUQqGIjolRFvOAFd8HWVs34VoA+6OjY2JjY5Vxm4BC1UuhGG5jY9OUaQXci1MqlfHx8YmqjyhOViW9ZsUN29akJRmPFwkJCZsTSXIpilJffXiTzorLXYgtcxRJKpUqKTklJQ0oSt9FP/EonxVdNY4jla1kK4q2ZB6mIr+AipvduzFUzMSOtLT09IyMzMxtJKug/F0u/6dTexAWDcXXLGEjapKjfsILOLKEuYiSnTQeYCt3UHhbwEHjGMrETfBJU5zq5dSTcXC8hLJccSWP2cgLXHPu7cQNAcpyxF1dyjehAKb0cSYUAOXCUw6V8OFPgevTXFymC+fPPLU677Nw/1X8A/AbfAKGulaqFlIAAAAASUVORK5CYII=' + + +# ----====----====----==== Constants the user CAN safely change ====----====----====----# +DEFAULT_WINDOW_ICON = 'default_icon.ico' +DEFAULT_ELEMENT_SIZE = (250, 26) # In pixels +DEFAULT_BUTTON_ELEMENT_SIZE = (10, 1) # In CHARACTERS +DEFAULT_MARGINS = (10, 5) # Margins for each LEFT/RIGHT margin is first term +DEFAULT_ELEMENT_PADDING = (5, 3) # Padding between elements (row, col) in pixels +DEFAULT_AUTOSIZE_TEXT = True +DEFAULT_AUTOSIZE_BUTTONS = True +DEFAULT_FONT = ('Helvetica', 15) +DEFAULT_TEXT_JUSTIFICATION = 'left' +DEFAULT_BORDER_WIDTH = 1 +DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form +DEFAULT_DEBUG_WINDOW_SIZE = (80, 20) +DEFAULT_OUTPUT_ELEMENT_SIZE = (40, 10) +DEFAULT_WINDOW_LOCATION = (None, None) +MAX_SCROLLED_TEXT_BOX_HEIGHT = 50 +DEFAULT_TOOLTIP_TIME = 400 + +DEFAULT_PIXELS_TO_CHARS_SCALING = (10, 26) # 1 character represents x by y pixels +DEFAULT_PIXEL_TO_CHARS_CUTOFF = 20 # number of chars that triggers using pixels instead of chars + +#################### COLOR STUFF #################### +BLUES = ('#082567', '#0A37A3', '#00345B') +PURPLES = ('#480656', '#4F2398', '#380474') +GREENS = ('#01826B', '#40A860', '#96D2AB', '#00A949', '#003532') +YELLOWS = ('#F3FB62', '#F0F595') +TANS = ('#FFF9D5', '#F4EFCF', '#DDD8BA') +NICE_BUTTON_COLORS = ( + (GREENS[3], TANS[0]), + ('#000000', '#FFFFFF'), + ('#FFFFFF', '#000000'), + (YELLOWS[0], PURPLES[1]), + (YELLOWS[0], GREENS[3]), + (YELLOWS[0], BLUES[2]), +) + +COLOR_SYSTEM_DEFAULT = '1234567890' # Colors should never be this long + +DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default +OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = ('white', BLUES[0]) # Colors should never be this long + +CURRENT_LOOK_AND_FEEL = 'DarkBlue3' + + +DEFAULT_ERROR_BUTTON_COLOR = ('#FFFFFF', '#FF0000') +DEFAULT_BACKGROUND_COLOR = None +DEFAULT_ELEMENT_BACKGROUND_COLOR = None +DEFAULT_ELEMENT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = None +DEFAULT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_INPUT_ELEMENTS_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_INPUT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_SCROLLBAR_COLOR = None +# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[0]) # (Text, Background) or (Color "on", Color) as a way to remember +# DEFAULT_BUTTON_COLOR = (GREENS[3], TANS[0]) # Foreground, Background (None, None) == System Default +# DEFAULT_BUTTON_COLOR = (YELLOWS[0], GREENS[4]) # Foreground, Background (None, None) == System Default +# DEFAULT_BUTTON_COLOR = ('white', 'black') # Foreground, Background (None, None) == System Default +# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[2]) # Foreground, Background (None, None) == System Default +# DEFAULT_PROGRESS_BAR_COLOR = (GREENS[2], GREENS[0]) # a nice green progress bar +# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[1], BLUES[1]) # a nice green progress bar +# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[0], BLUES[0]) # a nice green progress bar +# DEFAULT_PROGRESS_BAR_COLOR = (PURPLES[1],PURPLES[0]) # a nice purple progress bar + +# A transparent button is simply one that matches the background +TRANSPARENT_BUTTON = 'This constant has been depricated. You must set your button background = background it is on for it to be transparent appearing' +# -------------------------------------------------------------------------------- +# Progress Bar Relief Choices +RELIEF_RAISED = 'raised' +RELIEF_SUNKEN = 'sunken' +RELIEF_FLAT = 'flat' +RELIEF_RIDGE = 'ridge' +RELIEF_GROOVE = 'groove' +RELIEF_SOLID = 'solid' + +DEFAULT_PROGRESS_BAR_COLOR = (GREENS[0], '#D0D0D0') # a nice green progress bar +DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL = (GREENS[0], '#D0D0D0') # a nice green progress bar +DEFAULT_PROGRESS_BAR_SIZE = (25, 20) # Size of Progress Bar (characters for length, pixels for width) +DEFAULT_PROGRESS_BAR_BORDER_WIDTH = 1 +DEFAULT_PROGRESS_BAR_RELIEF = RELIEF_GROOVE +PROGRESS_BAR_STYLES = ('default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative') +DEFAULT_PROGRESS_BAR_STYLE = 'default' +DEFAULT_METER_ORIENTATION = 'horizontal' +DEFAULT_SLIDER_ORIENTATION = 'vertical' +DEFAULT_SLIDER_BORDER_WIDTH = 1 +DEFAULT_SLIDER_RELIEF = 00000 +DEFAULT_FRAME_RELIEF = 00000 + + +DEFAULT_LISTBOX_SELECT_MODE = 'extended' +SELECT_MODE_MULTIPLE = 'multiple' +LISTBOX_SELECT_MODE_MULTIPLE = 'multiple' +SELECT_MODE_BROWSE = 'browse' +LISTBOX_SELECT_MODE_BROWSE = 'browse' +SELECT_MODE_EXTENDED = 'extended' +LISTBOX_SELECT_MODE_EXTENDED = 'extended' +SELECT_MODE_SINGLE = 'single' +LISTBOX_SELECT_MODE_SINGLE = 'single' +SELECT_MODE_CONTIGUOUS = 'contiguous' +LISTBOX_SELECT_MODE_CONTIGUOUS = 'contiguous' + +TABLE_SELECT_MODE_NONE = 00000 +TABLE_SELECT_MODE_BROWSE = 00000 +TABLE_SELECT_MODE_EXTENDED = 00000 +DEFAULT_TABLE_SECECT_MODE = TABLE_SELECT_MODE_EXTENDED + +TITLE_LOCATION_TOP = 00000 +TITLE_LOCATION_BOTTOM = 00000 +TITLE_LOCATION_LEFT = 00000 +TITLE_LOCATION_RIGHT = 00000 +TITLE_LOCATION_TOP_LEFT = 00000 +TITLE_LOCATION_TOP_RIGHT = 00000 +TITLE_LOCATION_BOTTOM_LEFT = 00000 +TITLE_LOCATION_BOTTOM_RIGHT = 00000 + +THEME_DEFAULT = 'default' +THEME_WINNATIVE = 'winnative' +THEME_CLAM = 'clam' +THEME_ALT = 'alt' +THEME_CLASSIC = 'classic' +THEME_VISTA = 'vista' +THEME_XPNATIVE = 'xpnative' + +# DEFAULT_METER_ORIENTATION = 'Vertical' +# ----====----====----==== Constants the user should NOT f-with ====----====----====----# +ThisRow = 555666777 # magic number + +# DEFAULT_WINDOW_ICON = '' +MESSAGE_BOX_LINE_WIDTH = 60 + +# "Special" Key Values.. reserved +# Key representing a Read timeout +EVENT_TIMEOUT = TIMEOUT_EVENT = TIMEOUT_KEY = '__TIMEOUT__' +# Window closed event (user closed with X or destroyed using OS) +WIN_CLOSED = WINDOW_CLOSED = None + +# Key indicating should not create any return values for element +WRITE_ONLY_KEY = '__WRITE ONLY__' + +# MENU Constants, can be changed by user if desired +MENU_DISABLED_CHARACTER = '!' +MENU_KEY_SEPARATOR = '::' + + +# a shameful global variable. This represents the top-level window information. Needed because opening a second window is different than opening the first. +class MyWindows: + def __init__(self): + self._NumOpenWindows = 0 + self.user_defined_icon = None + self.hidden_master_root = None + + def Decrement(self): + self._NumOpenWindows -= 1 * (self._NumOpenWindows != 0) # decrement if not 0 + # print('---- DECREMENTING Num Open Windows = {} ---'.format(self._NumOpenWindows)) + + def Increment(self): + self._NumOpenWindows += 1 + # print('++++ INCREMENTING Num Open Windows = {} ++++'.format(self._NumOpenWindows)) + + +_my_windows = MyWindows() # terrible hack using globals... means need a class for collecing windows + + +# ====================================================================== # +# One-liner functions that are handy as f_ck # +# ====================================================================== # +def RGB(red, green, blue): + return '#%02x%02x%02x' % (red, green, blue) + + +# ====================================================================== # +# Enums for types # +# ====================================================================== # +# ------------------------- Button types ------------------------- # +# todo Consider removing the Submit, Cancel types... they are just 'RETURN' type in reality +# uncomment this line and indent to go back to using Enums +# Was enum previously ButtonType(Enum): +BUTTON_TYPE_BROWSE_FOLDER = 1 +BUTTON_TYPE_BROWSE_FILE = 2 +BUTTON_TYPE_BROWSE_FILES = 21 +BUTTON_TYPE_SAVEAS_FILE = 3 +BUTTON_TYPE_CLOSES_WIN = 5 +BUTTON_TYPE_CLOSES_WIN_ONLY = 6 +BUTTON_TYPE_READ_FORM = 7 +BUTTON_TYPE_REALTIME = 9 +BUTTON_TYPE_CALENDAR_CHOOSER = 30 +BUTTON_TYPE_COLOR_CHOOSER = 40 + +BROWSE_FILES_DELIMITER = ';' # the delimeter to be used between each file in the returned string + +# ------------------------- Element types ------------------------- # +# These used to be enums ElementType(Enum): +ELEM_TYPE_TEXT = 'text' +ELEM_TYPE_INPUT_TEXT = 'input' +ELEM_TYPE_INPUT_COMBO = 'combo' +ELEM_TYPE_INPUT_OPTION_MENU = 'option menu' +ELEM_TYPE_INPUT_RADIO = 'radio' +ELEM_TYPE_INPUT_MULTILINE = 'multiline' +ELEM_TYPE_MULTILINE_OUTPUT = 'multioutput' +ELEM_TYPE_INPUT_CHECKBOX = 'checkbox' +ELEM_TYPE_INPUT_SPIN = 'spin' +ELEM_TYPE_BUTTON = 'button' +ELEM_TYPE_BUTTONMENU = 'buttonmenu' +ELEM_TYPE_IMAGE = 'image' +ELEM_TYPE_CANVAS = 'canvas' +ELEM_TYPE_FRAME = 'frame' +ELEM_TYPE_GRAPH = 'graph' +ELEM_TYPE_TAB = 'tab' +ELEM_TYPE_TAB_GROUP = 'tabgroup' +ELEM_TYPE_INPUT_SLIDER = 'slider' +ELEM_TYPE_INPUT_LISTBOX = 'listbox' +ELEM_TYPE_OUTPUT = 'output' +ELEM_TYPE_COLUMN = 'column' +ELEM_TYPE_MENUBAR = 'menubar' +ELEM_TYPE_PROGRESS_BAR = 'progressbar' +ELEM_TYPE_BLANK = 'blank' +ELEM_TYPE_TABLE = 'table' +ELEM_TYPE_TREE = 'tree' +ELEM_TYPE_ERROR = 'error' +ELEM_TYPE_SEPARATOR = 'separator' + +# ------------------------- Popup Buttons Types ------------------------- # +POPUP_BUTTONS_YES_NO = 1 +POPUP_BUTTONS_CANCELLED = 2 +POPUP_BUTTONS_ERROR = 3 +POPUP_BUTTONS_OK_CANCEL = 4 +POPUP_BUTTONS_OK = 0 +POPUP_BUTTONS_NO_BUTTONS = 5 + + +# ---------------------------------------------------------------------- # +# Cascading structure.... Objects get larger # +# Button # +# Element # +# Row # +# Form # +# ---------------------------------------------------------------------- # +# ------------------------------------------------------------------------- # +# Element CLASS # +# ------------------------------------------------------------------------- # +class Element: + def __init__( + self, + elem_type, + size=(None, None), + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + + if elem_type != ELEM_TYPE_GRAPH: + self.Size = convert_tkinter_size_to_Wx(size) + else: + self.Size = size + if size_px != (None, None): + self.Size = size_px + self.Type = elem_type + self.AutoSizeText = auto_size_text + # self.Pad = DEFAULT_ELEMENT_PADDING if pad is None else pad + self.Pad = pad + if font is not None and type(font) is not str: + self.Font = font + elif font is not None: + self.Font = font.split(' ') + else: + self.Font = font + + self.TKStringVar = None + self.TKIntVar = None + self.TKText = None + self.TKEntry = None + self.TKImage = None + + self.ParentForm = None # type: Window + self.ParentContainer = None # will be a Form, Column, or Frame element + self.TextInputDefault = None + self.Position = (0, 0) # Default position Row 0, Col 0 + self.BackgroundColor = background_color if background_color is not None else DEFAULT_ELEMENT_BACKGROUND_COLOR + self.TextColor = text_color if text_color is not None else DEFAULT_ELEMENT_TEXT_COLOR + self.Key = key # dictionary key for return values + self.Tooltip = tooltip + self.TooltipObject = None + self.Visible = visible + self.metadata = metadata # type: Any + + # ------------------------- REMI CHANGED CALLBACK ----------------------- + # called when a widget has changed and the element has events enabled + def _ChangedCallback(self, widget, *args): + # type: (Element, remi.Widget, Any) -> None + # print(f'Callback {args}') + self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + + def Update(self, widget, background_color=None, text_color=None, font=None, visible=None, disabled=None, tooltip=None): + if font is not None: + font_info = font_parse_string(font) # family, point size, other + widget.style['font-family'] = font_info[0] + widget.style['font-size'] = '{}px'.format(font_info[1]) + + if background_color not in (None, COLOR_SYSTEM_DEFAULT): + widget.style['background-color'] = background_color + if text_color not in (None, COLOR_SYSTEM_DEFAULT): + widget.style['color'] = text_color + + if disabled: + widget.set_enabled(False) + elif disabled is False: + widget.set_enabled(True) + if visible is False: + widget.attributes['hidden'] = 'true' + elif visible is True: + del widget.attributes['hidden'] + if tooltip is not None: + widget.attributes['title'] = tooltip + + # if font: + # widget.SetFont(font_to_wx_font(font)) + # if text_color not in (None, COLOR_SYSTEM_DEFAULT): + # widget.SetForegroundColour(text_color) + # if background_color not in (None, COLOR_SYSTEM_DEFAULT): + # widget.SetBackgroundColour(background_color) + # if visible is True: + # widget.Show() + # self.ParentForm.VisibilityChanged() + # elif visible is False: + # widget.Hide() + # self.ParentForm.VisibilityChanged() + # if disabled: + # widget.Enable(False) + # elif disabled is False: + # widget.Enable(True) + # if tooltip is not None: + # widget.SetToolTip(tooltip) + if visible is False: + widget.attributes['hidden'] = 'true' + elif visible is True: + del widget.attributes['hidden'] + + def __call__(self, *args, **kwargs): + """ + Makes it possible to "call" an already existing element. When you do make the "call", it actually calls + the Update method for the element. + Example: If this text element was in your layout: + sg.Text('foo', key='T') + Then you can call the Update method for that element by writing: + window.FindElement('T')('new text value') + + :param args: + :param kwargs: + :return: + """ + return self.Update(*args, **kwargs) + + +# ---------------------------------------------------------------------- # +# Input Class # +# ---------------------------------------------------------------------- # +class InputText(Element): + def __init__( + self, + default_text='', + size=(None, None), + disabled=False, + password_char='', + justification=None, + background_color=None, + text_color=None, + font=None, + tooltip=None, + change_submits=False, + enable_events=False, + do_not_clear=True, + key=None, + focus=False, + pad=None, + visible=True, + size_px=(None, None), + ): + ''' + Input a line of text Element + :param default_text: Default value to display + :param size: Size of field in characters + :param password_char: If non-blank, will display this character for every character typed + :param background_color: Color for Element. Text or RGB Hex + ''' + self.DefaultText = default_text + self.PasswordCharacter = password_char + bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Focus = focus + self.do_not_clear = do_not_clear + self.Justification = justification or 'left' + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + self.QT_QLineEdit = None + self.ValueWasChanged = False + self.Widget = None # type: remi.gui.TextInput + super().__init__( + ELEM_TYPE_INPUT_TEXT, + size=size, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + font=font, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + + def _InputTextCallback(self, widget, key, keycode, ctrl, shift, alt): + # print(f'text widget value = {widget.get_value()}') + # widget.set_value('') + # widget.set_value(value) + self.ParentForm.LastButtonClicked = key + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + widget.set_value(widget.get_value() + key) + return (key, keycode, ctrl, shift, alt) + + def Update(self, value=None, disabled=None, select=None, background_color=None, text_color=None, font=None, visible=None): + if value is not None: + self.Widget.set_value(str(value)) + if disabled is True: + self.Widget.set_enabled(False) + elif disabled is False: + self.Widget.set_enabled(True) + + def Get(self): + return self.Widget.get_value() + + get = Get + update = Update + + class TextInput_raw_onkeyup(remi.gui.TextInput): + @remi.gui.decorate_set_on_listener('(self, emitter, key, keycode, ctrl, shift, alt)') + @remi.gui.decorate_event_js( + """var params={};params['key']=event.key; + params['keycode']=(event.which||event.keyCode); + params['ctrl']=event.ctrlKey; + params['shift']=event.shiftKey; + params['alt']=event.altKey; + sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params); + event.stopPropagation();event.preventDefault();return false;""" + ) + def onkeyup(self, key, keycode, ctrl, shift, alt): + return (key, keycode, ctrl, shift, alt) + + @remi.gui.decorate_set_on_listener('(self, emitter, key, keycode, ctrl, shift, alt)') + @remi.gui.decorate_event_js( + """var params={};params['key']=event.key; + params['keycode']=(event.which||event.keyCode); + params['ctrl']=event.ctrlKey; + params['shift']=event.shiftKey; + params['alt']=event.altKey; + sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params); + event.stopPropagation();event.preventDefault();return false;""" + ) + def onkeydown(self, key, keycode, ctrl, shift, alt): + return (key, keycode, ctrl, shift, alt) + + +# ------------------------- INPUT TEXT Element lazy functions ------------------------- # +In = InputText +Input = InputText +I = InputText + + +# ---------------------------------------------------------------------- # +# Combo # +# ---------------------------------------------------------------------- # +class Combo(Element): + def __init__( + self, + values, + default_value=None, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + disabled=False, + key=None, + pad=None, + tooltip=None, + readonly=False, + visible_items=10, + font=None, + auto_complete=True, + visible=True, + size_px=(None, None), + ): + ''' + Input Combo Box Element (also called Dropdown box) + :param values: + :param size: Size of field in characters + :param auto_size_text: True if should shrink field to fit the default text + :param background_color: Color for Element. Text or RGB Hex + ''' + self.Values = [str(v) for v in values] + self.DefaultValue = default_value + self.ChangeSubmits = change_submits or enable_events + # self.InitializeAsDisabled = disabled + self.Disabled = disabled + self.Readonly = readonly + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.VisibleItems = visible_items + self.AutoComplete = auto_complete + self.Widget = None # type: remi.gui.DropDown + super().__init__( + ELEM_TYPE_INPUT_COMBO, + size=size, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + font=font or DEFAULT_FONT, + visible=visible, + size_px=size_px, + ) + + def Update( + self, + value=None, + values=None, + set_to_index=None, + disabled=None, + readonly=None, + background_color=None, + text_color=None, + font=None, + visible=None, + ): + if values is not None: + self.Widget.empty() + for i, item in enumerate(values): + self.Widget.append(value=item, key=str(i)) + if value: + self.Widget.select_by_value(value) + if set_to_index is not None: + try: # just in case a bad index is passed in + self.Widget.select_by_key(str(set_to_index)) + except: + pass + + super().Update( + self.Widget, + background_color=background_color, + text_color=text_color, + font=font, + visible=visible, + disabled=disabled, + ) + + update = Update + + +# ------------------------- INPUT COMBO Element lazy functions ------------------------- # +InputCombo = Combo +DropDown = Combo +Drop = Combo + + +# ---------------------------------------------------------------------- # +# Option Menu # +# ---------------------------------------------------------------------- # +class OptionMenu(Element): + def __init__( + self, + values, + default_value=None, + size=(None, None), + disabled=False, + auto_size_text=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None, + ): + ''' + InputOptionMenu + :param values: + :param default_value: + :param size: + :param disabled: + :param auto_size_text: + :param background_color: + :param text_color: + :param key: + :param pad: + :param tooltip: + ''' + self.Values = values + self.DefaultValue = default_value + self.TKOptionMenu = None + self.Disabled = disabled + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + + super().__init__( + ELEM_TYPE_INPUT_OPTION_MENU, + size=size, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + ) + + def Update(self, value=None, values=None, disabled=None): + if values is not None: + self.Values = values + if self.Values is not None: + for index, v in enumerate(self.Values): + if v == value: + try: + self.TKStringVar.set(value) + except: + pass + self.DefaultValue = value + break + if disabled == True: + self.TKOptionMenu['state'] = 'disabled' + elif disabled == False: + self.TKOptionMenu['state'] = 'normal' + + +# ------------------------- OPTION MENU Element lazy functions ------------------------- # +InputOptionMenu = OptionMenu + + +# ---------------------------------------------------------------------- # +# Listbox # +# ---------------------------------------------------------------------- # +class Listbox(Element): + def __init__( + self, + values, + default_values=None, + select_mode=None, + change_submits=False, + enable_events=False, + bind_return_key=False, + size=(None, None), + disabled=False, + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + """ + + :param values: + :param default_values: + :param select_mode: + :param change_submits: + :param enable_events: + :param bind_return_key: + :param size: + :param disabled: + :param auto_size_text: + :param font: + :param background_color: + :param text_color: + :param key: + :param pad: + :param tooltip: + :param visible: + :param size_px: + """ + self.Values = values + self.DefaultValues = default_values + self.TKListbox = None + self.ChangeSubmits = change_submits or enable_events + self.BindReturnKey = bind_return_key + self.Disabled = disabled + if select_mode == LISTBOX_SELECT_MODE_BROWSE: + self.SelectMode = SELECT_MODE_BROWSE + elif select_mode == LISTBOX_SELECT_MODE_EXTENDED: + self.SelectMode = SELECT_MODE_EXTENDED + elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE: + self.SelectMode = SELECT_MODE_MULTIPLE + elif select_mode == LISTBOX_SELECT_MODE_SINGLE: + self.SelectMode = SELECT_MODE_SINGLE + elif select_mode == LISTBOX_SELECT_MODE_CONTIGUOUS: + self.SelectMode = SELECT_MODE_CONTIGUOUS + else: + self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Widget = None # type: remi.gui.ListView + tsize = size # convert tkinter size to pixels + if size[0] is not None and size[0] < 100: + tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] + + super().__init__( + ELEM_TYPE_INPUT_LISTBOX, + size=tsize, + auto_size_text=auto_size_text, + font=font, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + + def Update( + self, + values=None, + disabled=None, + set_to_index=None, + background_color=None, + text_color=None, + font=None, + visible=None, + ): + if values is not None: + self.Values = values + self.Widget.empty() + for item in values: + self.Widget.append(remi.gui.ListItem(item)) + # if disabled == True: + # self.QT_ListWidget.setDisabled(True) + # elif disabled == False: + # self.QT_ListWidget.setDisabled(False) + # if set_to_index is not None: + # self.QT_ListWidget.setCurrentRow(set_to_index) + super().Update( + self.Widget, + background_color=background_color, + text_color=text_color, + font=font, + visible=visible, + disabled=disabled, + ) + + return + + # def SetValue(self, values): + # # for index, item in enumerate(self.Values): + # for index, value in enumerate(self.Values): + # item = self.QT_ListWidget.item(index) + # if value in values: + # self.QT_ListWidget.setItemSelected(item, True) + + def GetListValues(self): + return self.Values + + get_list_values = GetListValues + update = Update + + +# ---------------------------------------------------------------------- # +# Radio # +# ---------------------------------------------------------------------- # +class Radio(Element): + def __init__( + self, + text, + group_id, + default=False, + disabled=False, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + font=None, + key=None, + pad=None, + tooltip=None, + change_submits=False, + ): + ''' + Radio Button Element + :param text: + :param group_id: + :param default: + :param disabled: + :param size: + :param auto_size_text: + :param background_color: + :param text_color: + :param font: + :param key: + :param pad: + :param tooltip: + :param change_submits: + ''' + self.InitialState = default + self.Text = text + self.TKRadio = None + self.GroupID = group_id + self.Value = None + self.Disabled = disabled + self.TextColor = text_color or DEFAULT_TEXT_COLOR + self.ChangeSubmits = change_submits + + print('*** WARNING - Radio Buttons are not yet available on PySimpleGUIWeb ***') + + super().__init__( + ELEM_TYPE_INPUT_RADIO, + size=size, + auto_size_text=auto_size_text, + font=font, + background_color=background_color, + text_color=self.TextColor, + key=key, + pad=pad, + tooltip=tooltip, + ) + + def Update(self, value=None, disabled=None): + print('*** NOT IMPLEMENTED ***') + location = EncodeRadioRowCol(self.Position[0], self.Position[1]) + if value is not None: + try: + self.TKIntVar.set(location) + except: + pass + self.InitialState = value + if disabled == True: + self.TKRadio['state'] = 'disabled' + elif disabled == False: + self.TKRadio['state'] = 'normal' + + update = Update + + +# ---------------------------------------------------------------------- # +# Checkbox # +# ---------------------------------------------------------------------- # +class Checkbox(Element): + def __init__( + self, + text, + default=False, + size=(None, None), + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + disabled=False, + key=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + ''' + Checkbox Element + :param text: + :param default: + :param size: + :param auto_size_text: + :param font: + :param background_color: + :param text_color: + :param change_submits: + :param disabled: + :param key: + :param pad: + :param tooltip: + ''' + self.Text = text + self.InitialState = default + self.Disabled = disabled + self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR + self.ChangeSubmits = change_submits or enable_events + self.Widget = None # type: remi.gui.CheckBox + + super().__init__( + ELEM_TYPE_INPUT_CHECKBOX, + size=size, + auto_size_text=auto_size_text, + font=font, + background_color=background_color, + text_color=self.TextColor, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + + def _ChangedCallback(self, widget, value): + # type: (remi.Widget, Any) -> None + # print(f'text widget value = {widget.get_value()}') + self.ParentForm.LastButtonClicked = self.Key + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + + def Get(self): + return self.Widget.get_value() + + def Update(self, value=None, disabled=None): + if value is not None: + self.Widget.set_value(value) + if disabled == True: + self.Widget.set_enabled(False) + elif disabled == False: + self.Widget.set_enabled(True) + + get = Get + update = Update + + +# ------------------------- CHECKBOX Element lazy functions ------------------------- # +CB = Checkbox +CBox = Checkbox +Check = Checkbox + + +# ---------------------------------------------------------------------- # +# Spin # +# ---------------------------------------------------------------------- # + + +class Spin(Element): + # Values = None + # TKSpinBox = None + def __init__( + self, + values, + initial_value=None, + disabled=False, + change_submits=False, + enable_events=False, + size=(None, None), + readonly=True, + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + ''' + Spinner Element + :param values: + :param initial_value: + :param disabled: + :param change_submits: + :param size: + :param auto_size_text: + :param font: + :param background_color: + :param text_color: + :param key: + :param pad: + :param tooltip: + ''' + self.Values = values + self.DefaultValue = initial_value or values[0] + self.ChangeSubmits = change_submits or enable_events + self.Disabled = disabled + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.CurrentValue = self.DefaultValue + self.ReadOnly = readonly + self.Widget = None # type: remi.gui.SpinBox + super().__init__( + ELEM_TYPE_INPUT_SPIN, + size, + auto_size_text, + font=font, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + return + + def Update(self, value=None, values=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): + if value is not None: + self.Widget.set_value(value) + super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def Get(self): + return self.Widget.get_value() + + get = Get + update = Update + + +# ---------------------------------------------------------------------- # +# Multiline # +# ---------------------------------------------------------------------- # +class Multiline(Element): + def __init__( + self, + default_text='', + enter_submits=False, + disabled=False, + autoscroll=False, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + do_not_clear=True, + key=None, + write_only=False, + focus=False, + font=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + ''' + Multiline Element + :param default_text: + :param enter_submits: + :param disabled: + :param autoscroll: + :param size: + :param auto_size_text: + :param background_color: + :param text_color: + :param do_not_clear: + :param key: + :param focus: + :param pad: + :param tooltip: + :param font: + ''' + self.DefaultText = default_text + self.EnterSubmits = enter_submits + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + self.Focus = focus + self.do_not_clear = do_not_clear + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Autoscroll = autoscroll + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + self.WriteOnly = write_only + if size[0] is not None and size[0] < 100: + size = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] + self.Widget = None # type: remi.gui.TextInput + + super().__init__( + ELEM_TYPE_INPUT_MULTILINE, + size=size, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + font=font or DEFAULT_FONT, + visible=visible, + size_px=size_px, + ) + return + + def _InputTextCallback(self, widget: remi.Widget, value, keycode): + # print(f'text widget value = {widget.get_value()}') + self.ParentForm.LastButtonClicked = chr(int(keycode)) + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + + def Update( + self, + value=None, + disabled=None, + append=False, + background_color=None, + text_color=None, + font=None, + visible=None, + autoscroll=None, + ): + if value is not None and not append: + self.Widget.set_value(value) + elif value is not None and append: + text = self.Widget.get_value() + str(value) + self.Widget.set_value(text) + # if background_color is not None: + # self.WxTextCtrl.SetBackgroundColour(background_color) + # if text_color is not None: + # self.WxTextCtrl.SetForegroundColour(text_color) + # if font is not None: + # self.WxTextCtrl.SetFont(font) + # if disabled: + # self.WxTextCtrl.Enable(True) + # elif disabled is False: + # self.WxTextCtrl.Enable(False) + super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def print(self, *args, end=None, sep=None, text_color=None, background_color=None): + """ + Print like Python normally prints except route the output to a multline element and also add colors if desired + + :param args: List[Any] The arguments to print + :param end: (str) The end char to use just like print uses + :param sep: (str) The separation character like print uses + :param text_color: The color of the text + :param background_color: The background color of the line + """ + _print_to_element(self, *args, end=end, sep=sep, text_color=text_color, background_color=background_color) + + update = Update + + +ML = Multiline +MLine = Multiline + + +# ---------------------------------------------------------------------- # +# Multiline Output # +# ---------------------------------------------------------------------- # +class MultilineOutput(Element): + def __init__( + self, + default_text='', + enter_submits=False, + disabled=False, + autoscroll=False, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + do_not_clear=True, + key=None, + focus=False, + font=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + ''' + Multiline Element + :param default_text: + :param enter_submits: + :param disabled: + :param autoscroll: + :param size: + :param auto_size_text: + :param background_color: + :param text_color: + :param do_not_clear: + :param key: + :param focus: + :param pad: + :param tooltip: + :param font: + ''' + self.DefaultText = default_text + self.EnterSubmits = enter_submits + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + self.Focus = focus + self.do_not_clear = do_not_clear + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Autoscroll = autoscroll + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + tsize = size # convert tkinter size to pixels + if size[0] is not None and size[0] < 100: + tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] + self.Widget = None # type: remi.gui.TextInput + self.CurrentValue = '' + + super().__init__( + ELEM_TYPE_MULTILINE_OUTPUT, + size=tsize, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + font=font or DEFAULT_FONT, + visible=visible, + size_px=size_px, + ) + return + + def Update( + self, + value=None, + disabled=None, + append=False, + background_color=None, + text_color=None, + font=None, + visible=None, + autoscroll=None, + ): + autoscroll = self.Autoscroll if autoscroll is None else autoscroll + if value is not None and not append: + self.Widget.set_value(str(value)) + self.CurrentValue = str(value) + elif value is not None and append: + self.CurrentValue = self.CurrentValue + str(value) + self.Widget.set_value(self.CurrentValue) + self.Widget._set_updated() + app = self.ParentForm.App + + if hasattr(app, 'websockets'): + app.execute_javascript( + 'element=document.getElementById("%(id)s"); element.innerHTML=`%(content)s`; if(%(autoscroll)s){element.scrollTop=999999;} ' + % { + 'id': self.Widget.identifier, + 'content': self.Widget.get_value(), + 'autoscroll': 'true' if autoscroll else 'false', + } + ) + + super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def print(self, *args, end=None, sep=None, text_color=None, background_color=None): + """ + Print like Python normally prints except route the output to a multline element and also add colors if desired + + :param args: List[Any] The arguments to print + :param end: (str) The end char to use just like print uses + :param sep: (str) The separation character like print uses + :param text_color: The color of the text + :param background_color: The background color of the line + """ + _print_to_element(self, *args, end=end, sep=sep, text_color=text_color, background_color=background_color) + + update = Update + + +# ---------------------------------------------------------------------- # +# Text # +# ---------------------------------------------------------------------- # +class Text(Element): + def __init__( + self, + text='', + size=(None, None), + auto_size_text=None, + click_submits=None, + enable_events=False, + relief=None, + border_width=None, + font=None, + text_color=None, + background_color=None, + justification=None, + pad=None, + margins=None, + key=None, + tooltip=None, + visible=True, + size_px=(None, None), + metadata=None, + ): + """ + Text + :param text: + :param size: + :param auto_size_text: + :param click_submits: + :param enable_events: + :param relief: + :param font: + :param text_color: + :param background_color: + :param justification: + :param pad: + :param margins: + :param key: + :param tooltip: + :param visible: + :param size_px: + """ + self.DisplayText = str(text) + self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR + self.Justification = justification + self.Relief = relief + self.ClickSubmits = click_submits or enable_events + self.Margins = margins + self.size_px = size_px + if background_color is None: + bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR + else: + bg = background_color + pixelsize = size + if size[1] is not None and size[1] < 10: + pixelsize = size[0] * 10, size[1] * 20 + self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH + self.Disabled = False + self.Widget = None # type: remi.gui.Label + + super().__init__( + ELEM_TYPE_TEXT, + pixelsize, + auto_size_text, + background_color=bg, + font=font if font else DEFAULT_FONT, + text_color=self.TextColor, + pad=pad, + key=key, + tooltip=tooltip, + size_px=size_px, + visible=visible, + metadata=metadata, + ) + return + + def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): + if value is not None: + self.Widget.set_text(str(value)) + super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) + + update = Update + + +# ------------------------- Text Element lazy functions ------------------------- # +Txt = Text +T = Text + + +# ---------------------------------------------------------------------- # +# Output # +# Routes stdout, stderr to a scrolled window # +# ---------------------------------------------------------------------- # +class Output(Element): + def __init__( + self, + size=(None, None), + background_color=None, + text_color=None, + pad=None, + font=None, + tooltip=None, + key=None, + visible=True, + size_px=(None, None), + disabled=False, + ): + ''' + Output Element + :param size: + :param background_color: + :param text_color: + :param pad: + :param font: + :param tooltip: + :param key: + ''' + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + # fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + fg = text_color if text_color is not None else 'black' if DEFAULT_INPUT_TEXT_COLOR == COLOR_SYSTEM_DEFAULT else DEFAULT_INPUT_TEXT_COLOR + self.Disabled = disabled + self.Widget = None # type: remi.gui.TextInput + if size_px == (None, None) and size == (None, None): + size = DEFAULT_OUTPUT_ELEMENT_SIZE + if size[0] is not None and size[0] < 100: + size = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] + super().__init__( + ELEM_TYPE_OUTPUT, + size=size, + size_px=size_px, + visible=visible, + background_color=bg, + text_color=fg, + pad=pad, + font=font, + tooltip=tooltip, + key=key, + ) + + def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None): + if value is not None and not append: + self.Widget.set_value(str(value)) + self.CurrentValue = str(value) + elif value is not None and append: + self.CurrentValue = self.CurrentValue + '\n' + str(value) + self.Widget.set_value(self.CurrentValue) + self.Widget._set_updated() + app = self.ParentForm.App + if hasattr(app, 'websockets'): + app.execute_javascript('element=document.getElementById("%(id)s"); element.innerHTML=`%(content)s`; element.scrollTop=999999; ' % {'id': self.Widget.identifier, 'content': self.Widget.get_value()}) + + super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) + + update = Update + + +# ---------------------------------------------------------------------- # +# Button Class # +# ---------------------------------------------------------------------- # +class Button(Element): + def __init__( + self, + button_text='', + button_type=BUTTON_TYPE_READ_FORM, + target=(None, None), + tooltip=None, + file_types=(('ALL Files', '*'),), + initial_folder=None, + disabled=False, + change_submits=False, + enable_events=False, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + visible=True, + size_px=(None, None), + ): + ''' + Button Element + :param button_text: + :param button_type: + :param target: + :param tooltip: + :param file_types: + :param initial_folder: + :param disabled: + :param image_filename: + :param image_size: + :param image_subsample: + :param border_width: + :param size: + :param auto_size_button: + :param button_color: + :param default_value: + :param font: + :param bind_return_key: + :param focus: + :param pad: + :param key: + ''' + self.AutoSizeButton = auto_size_button + self.BType = button_type + self.FileTypes = file_types + self.TKButton = None + self.Target = target + self.ButtonText = str(button_text) + self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR + self.TextColor = self.ButtonColor[0] + self.BackgroundColor = self.ButtonColor[1] + self.ImageFilename = image_filename + self.ImageData = image_data + self.ImageSize = image_size + self.ImageSubsample = image_subsample + self.UserData = None + self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH + self.BindReturnKey = bind_return_key + self.Focus = focus + self.TKCal = None + self.CalendarCloseWhenChosen = None + self.DefaultDate_M_D_Y = (None, None, None) + self.InitialFolder = initial_folder + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + self.QT_QPushButton = None + self.ColorChosen = None + self.Relief = None + # self.temp_size = size if size != (NONE, NONE) else + self.Widget = None # type: remi.gui.Button + super().__init__( + ELEM_TYPE_BUTTON, + size=size, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + text_color=self.TextColor, + background_color=self.BackgroundColor, + visible=visible, + size_px=size_px, + ) + return + + # ------- Button Callback ------- # + def _ButtonCallBack(self, event): + + # print('Button callback') + + # print(f'Parent = {self.ParentForm} Position = {self.Position}') + # Buttons modify targets or return from the form + # If modifying target, get the element object at the target and modify its StrVar + target = self.Target + target_element = None + if target[0] == ThisRow: + target = [self.Position[0], target[1]] + if target[1] < 0: + target[1] = self.Position[1] + target[1] + strvar = None + should_submit_window = False + if target == (None, None): + strvar = self.TKStringVar + else: + if not isinstance(target, str): + if target[0] < 0: + target = [self.Position[0] + target[0], target[1]] + target_element = self.ParentContainer._GetElementAtLocation(target) + else: + target_element = self.ParentForm.FindElement(target) + try: + strvar = target_element.TKStringVar + except: + pass + try: + if target_element.ChangeSubmits: + should_submit_window = True + except: + pass + filetypes = (('ALL Files', '*'),) if self.FileTypes is None else self.FileTypes + if self.BType == BUTTON_TYPE_BROWSE_FOLDER: # Browse Folder + wx_types = convert_tkinter_filetypes_to_wx(self.FileTypes) + if self.InitialFolder: + dialog = wx.DirDialog(self.ParentForm.MasterFrame, style=wx.FD_OPEN) + else: + dialog = wx.DirDialog(self.ParentForm.MasterFrame) + folder_name = '' + if dialog.ShowModal() == wx.ID_OK: + folder_name = dialog.GetPath() + if folder_name != '': + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = folder_name + else: + target_element.Update(folder_name) + elif self.BType == BUTTON_TYPE_BROWSE_FILE: # Browse File + qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) + if self.InitialFolder: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_OPEN) + else: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_OPEN) + file_name = '' + if dialog.ShowModal() == wx.ID_OK: + file_name = dialog.GetPath() + else: + file_name = '' + if file_name != '': + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = file_name + else: + target_element.Update(file_name) + elif self.BType == BUTTON_TYPE_BROWSE_FILES: # Browse Files + qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) + if self.InitialFolder: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_MULTIPLE) + else: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_MULTIPLE) + file_names = '' + if dialog.ShowModal() == wx.ID_OK: + file_names = dialog.GetPaths() + else: + file_names = '' + if file_names != '': + file_names = BROWSE_FILES_DELIMITER.join(file_names) + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = file_names + else: + target_element.Update(file_names) + elif self.BType == BUTTON_TYPE_SAVEAS_FILE: # Save As File + qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) + if self.InitialFolder: + dialog = wx.FileDialog( + self.ParentForm.MasterFrame, + defaultDir=self.InitialFolder, + wildcard=qt_types, + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, + ) + else: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + file_name = '' + if dialog.ShowModal() == wx.ID_OK: + file_name = dialog.GetPath() + else: + file_name = '' + if file_name != '': + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = file_name + else: + target_element.Update(file_name) + elif self.BType == BUTTON_TYPE_COLOR_CHOOSER: # Color Chooser + qcolor = QColorDialog.getColor() + rgb_color = qcolor.getRgb() + color = '#' + ''.join('%02x' % i for i in rgb_color[:3]) + if self.Target == (None, None): + self.FileOrFolderName = color + else: + target_element.Update(color) + elif self.BType == BUTTON_TYPE_CLOSES_WIN: # Closes Window + # first, get the results table built + # modify the Results table in the parent FlexForm object + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = self.ButtonText + self.ParentForm.FormRemainedOpen = False + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.App.ExitMainLoop() + self.ParentForm.IgnoreClose = True + self.ParentForm.MasterFrame.Close() + if self.ParentForm.NonBlocking: + Window._DecrementOpenCount() + self.ParentForm._Close() + elif self.BType == BUTTON_TYPE_READ_FORM: # Read Button + # first, get the results table built + # modify the Results table in the parent FlexForm object + # if self.Key is not None: + # self.ParentForm.LastButtonClicked = self.Key + # else: + # self.ParentForm.LastButtonClicked = self.ButtonText + self.ParentForm.FormRemainedOpen = True + element_callback_quit_mainloop(self) + elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop + element_callback_quit_mainloop(self) + self.ParentForm._Close() + Window._DecrementOpenCount() + elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window + should_submit_window = False + + if should_submit_window: + self.ParentForm.LastButtonClicked = target_element.Key + self.ParentForm.FormRemainedOpen = True + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + return + + def Update( + self, + text=None, + button_color=(None, None), + disabled=None, + image_data=None, + image_filename=None, + font=None, + visible=None, + image_subsample=None, + image_size=(None, None), + ): + if text is not None: + self.Widget.set_text(str(text)) + fg, bg = button_color + if image_data: + self.Widget.empty() + simage = SuperImage(image_data) + if image_size is not (None, None): + simage.set_size(image_size[0], image_size[1]) + self.Widget.append(simage) + if image_filename: + self.Widget.empty() + simage = SuperImage(image_filename) + if image_size is not (None, None): + simage.set_size(image_size[0], image_size[1]) + self.Widget.append(simage) + + super().Update(self.Widget, background_color=bg, text_color=fg, disabled=disabled, font=font, visible=visible) + + def GetText(self): + return self.Widget.get_text() + + get_text = GetText + update = Update + + +# ------------------------- Button lazy functions ------------------------- # +B = Button +Btn = Button +Butt = Button + + +def convert_tkinter_filetypes_to_wx(filetypes): + wx_filetypes = '' + for item in filetypes: + filetype = item[0] + ' (' + item[1] + ')|' + item[1] + wx_filetypes += filetype + return wx_filetypes + + +# ---------------------------------------------------------------------- # +# ProgreessBar # +# ---------------------------------------------------------------------- # +class ProgressBar(Element): + def __init__( + self, + max_value, + orientation=None, + size=(None, None), + auto_size_text=None, + bar_color=(None, None), + style=None, + border_width=None, + relief=None, + key=None, + pad=None, + ): + ''' + ProgressBar Element + :param max_value: + :param orientation: + :param size: + :param auto_size_text: + :param bar_color: + :param style: + :param border_width: + :param relief: + :param key: + :param pad: + ''' + self.MaxValue = max_value + self.TKProgressBar = None + self.Cancelled = False + self.NotRunning = True + self.Orientation = orientation if orientation else DEFAULT_METER_ORIENTATION + self.BarColor = bar_color + self.BarStyle = style if style else DEFAULT_PROGRESS_BAR_STYLE + self.BorderWidth = border_width if border_width else DEFAULT_PROGRESS_BAR_BORDER_WIDTH + self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF + self.BarExpired = False + super().__init__(ELEM_TYPE_PROGRESS_BAR, size=size, auto_size_text=auto_size_text, key=key, pad=pad) + + # returns False if update failed + def UpdateBar(self, current_count, max=None): + print('*** NOT IMPLEMENTED ***') + return + if self.ParentForm.TKrootDestroyed: + return False + self.TKProgressBar.Update(current_count, max=max) + try: + self.ParentForm.TKroot.update() + except: + _my_windows.Decrement() + return False + return True + + update_bar = UpdateBar + + +# ---------------------------------------------------------------------- # +# Image # +# ---------------------------------------------------------------------- # +class Image(Element): + def __init__( + self, + filename=None, + data=None, + background_color=None, + size=(None, None), + pad=None, + key=None, + tooltip=None, + right_click_menu=None, + visible=True, + enable_events=False, + ): + ''' + Image Element + :param filename: + :param data: + :param background_color: + :param size: + :param pad: + :param key: + :param tooltip: + ''' + self.Filename = filename if filename else None # note that Remi expects a / at the front of resource files + self.Data = data + self.tktext_label = None + self.BackgroundColor = background_color + self.Disabled = False + self.EnableEvents = enable_events + sz = (0, 0) if size == (None, None) else size + self.Widget = None # type: SuperImage + # if data is None and filename is None: # it is OK to have no image specified when intially creating + # print('* Warning... no image specified in Image Element! *') + super().__init__( + ELEM_TYPE_IMAGE, + size=sz, + background_color=background_color, + pad=pad, + key=key, + tooltip=tooltip, + visible=visible, + ) + return + + def Update(self, filename=None, data=None, size=(None, None), visible=None): + if data is not None: + self.Widget.load(data) + # decoded = base64.b64decode(data) + # with open(r'.\decoded.out', 'wb') as f: + # f.write(decoded) + # filename = r'.\decoded.out' + if filename is not None: + self.Widget.load(filename) + # self.Widget.set_image(filename=filename) + # if size != (None, None): + # self.Widget.style['height'] = '{}px'.format(size[1]) + # self.Widget.style['width'] = '{}px'.format(size[0]) + super().Update(self.Widget, visible=visible) + + update = Update + + +# class SuperImageOld(remi.gui.Image): +# def __init__(self, file_path_name=None, **kwargs): +# image = file_path_name +# super(SuperImage, self).__init__(image, **kwargs) +# +# self.imagedata = None +# self.mimetype = None +# self.encoding = None +# if image is None: +# return +# self.load(image) +# +# def load(self, file_path_name): +# if type(file_path_name) is bytes or len(file_path_name) > 200: +# try: +# self.imagedata = base64.b64decode(file_path_name, validate=True) +# except binascii.Error: +# self.imagedata = file_path_name +# else: +# self.mimetype, self.encoding = mimetypes.guess_type(file_path_name) +# with open(file_path_name, 'rb') as f: +# self.imagedata = f.read() +# self.refresh() +# +# def refresh(self): +# i = int(time.time() * 1e6) +# self.attributes['src'] = "/%s/get_image_data?update_index=%d" % (id(self), i) +# +# def get_image_data(self, update_index): +# headers = {'Content-type': self.mimetype if self.mimetype else 'application/octet-stream'} +# return [self.imagedata, headers] + + +class SuperImage(remi.gui.Image): + def __init__(self, file_path_name=None, **kwargs): + """ + This new app_instance variable is causing lots of problems. I do not know the value of the App + when I create this image. + :param app_instance: + :param file_path_name: + :param kwargs: + """ + # self.app_instance = app_instance + image = file_path_name + super(SuperImage, self).__init__(image, **kwargs) + + self.imagedata = None + self.mimetype = None + self.encoding = None + if not image: + return + self.load(image) + + def load(self, file_path_name): + if type(file_path_name) is bytes: + try: + # here a base64 image is received + self.imagedata = base64.b64decode(file_path_name, validate=True) + self.attributes['src'] = '/%s/get_image_data?update_index=%s' % (id(self), str(time.time())) + except binascii.Error: + # here an image data is received (opencv image) + self.imagedata = file_path_name + self.refresh() + self.refresh() + else: + # here a filename is received + self.attributes['src'] = remi.gui.load_resource(file_path_name) + """print(f'***** Loading file = {file_path_name}') + self.mimetype, self.encoding = mimetypes.guess_type(file_path_name) + with open(file_path_name, 'rb') as f: + self.imagedata = f.read()""" + self.refresh() + + def refresh(self): + i = int(time.time() * 1e6) + # self.app_instance.execute_javascript(""" + if Window.App is not None: + Window.App.execute_javascript( + """ + var url = '/%(id)s/get_image_data?update_index=%(frame_index)s'; + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'blob' + xhr.onload = function(e){ + var urlCreator = window.URL || window.webkitURL; + var imageUrl = urlCreator.createObjectURL(this.response); + document.getElementById('%(id)s').src = imageUrl; + } + xhr.send(); + """ + % {'id': id(self), 'frame_index': i} + ) + + def get_image_data(self, update_index): + headers = {'Content-type': self.mimetype if self.mimetype else 'application/octet-stream'} + return [self.imagedata, headers] + + +# ---------------------------------------------------------------------- # +# Graph # +# ---------------------------------------------------------------------- # +class Graph(Element): + def __init__( + self, + canvas_size, + graph_bottom_left, + graph_top_right, + background_color=None, + pad=None, + change_submits=False, + drag_submits=False, + size_px=(None, None), + enable_events=False, + key=None, + visible=True, + disabled=False, + tooltip=None, + ): + ''' + Graph Element + :param canvas_size: + :param graph_bottom_left: + :param graph_top_right: + :param background_color: + :param pad: + :param key: + :param tooltip: + ''' + self.CanvasSize = canvas_size + self.BottomLeft = graph_bottom_left + self.TopRight = graph_top_right + self.ChangeSubmits = change_submits or enable_events + self.DragSubmits = drag_submits + self.ClickPosition = (None, None) + self.MouseButtonDown = False + self.Disabled = disabled + self.Widget = None # type: remi.gui.Svg + self.SvgGroup = None # type: remi.gui.SvgSubcontainer + super().__init__( + ELEM_TYPE_GRAPH, + size=canvas_size, + size_px=size_px, + visible=visible, + background_color=background_color, + pad=pad, + tooltip=tooltip, + key=key, + ) + return + + def _convert_xy_to_canvas_xy(self, x_in, y_in): + if None in (x_in, y_in): + return None, None + scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) + scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) + new_x = 0 + scale_x * (x_in - self.BottomLeft[0]) + new_y = self.CanvasSize[1] + scale_y * (y_in - self.BottomLeft[1]) + return new_x, new_y + + def _convert_canvas_xy_to_xy(self, x_in, y_in): + if None in (x_in, y_in): + return None, None + x_in, y_in = int(x_in), int(y_in) + scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) + scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) + new_x = x_in / scale_x + self.BottomLeft[0] + new_y = (y_in - self.CanvasSize[1]) / scale_y + self.BottomLeft[1] + return int(new_x), int(new_y) + + def DrawLine(self, point_from, point_to, color='black', width=1): + if point_from == (None, None) or color is None: + return + converted_point_from = self._convert_xy_to_canvas_xy(point_from[0], point_from[1]) + converted_point_to = self._convert_xy_to_canvas_xy(point_to[0], point_to[1]) + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + line = remi.gui.SvgLine(converted_point_from[0], converted_point_from[1], converted_point_to[0], converted_point_to[1]) + line.set_stroke(width, color) + self.SvgGroup.append( + [ + line, + ] + ) + return line + + def DrawPoint(self, point, size=2, color='black'): + if point == (None, None): + return + converted_point = self._convert_xy_to_canvas_xy(point[0], point[1]) + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + rpoint = remi.gui.SvgCircle(converted_point[0], converted_point[1], size) + rpoint.set_stroke(size, color) + rpoint.set_fill(color) + self.SvgGroup.append( + [ + rpoint, + ] + ) + return rpoint + + def DrawCircle(self, center_location, radius, fill_color=None, line_color='black'): + if center_location == (None, None): + return + converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1]) + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + rpoint = remi.gui.SvgCircle(converted_point[0], converted_point[1], radius=radius) + rpoint.set_fill(fill_color) + rpoint.set_stroke(color=line_color) + self.SvgGroup.append( + [ + rpoint, + ] + ) + return rpoint + + def DrawOval(self, top_left, bottom_right, fill_color=None, line_color=None): + converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return + + # def DrawArc(self, top_left, bottom_right, extent, start_angle, style=None, arc_color='black'): + # converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + # converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + # tkstyle = tk.PIESLICE if style is None else style + # if self._TKCanvas2 is None: + # print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + # print('Call Window.Finalize() prior to this operation') + # return None + # return + + def DrawRectangle(self, top_left, bottom_right, fill_color=None, line_color='black'): + converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + + rpoint = remi.gui.SvgRectangle( + converted_top_left[0], + converted_top_left[1], + abs(converted_bottom_right[0] - converted_top_left[0]), + abs(converted_top_left[1] - converted_bottom_right[1]), + ) + rpoint.set_stroke(width=1, color=line_color) + if fill_color is not None: + rpoint.set_fill(fill_color) + else: + rpoint.set_fill('transparent') + self.SvgGroup.append( + [ + rpoint, + ] + ) + return rpoint + + def DrawText(self, text, location, color='black', font=None, angle=0): + text = str(text) + if location == (None, None): + return + converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + + rpoint = remi.gui.SvgText(converted_point[0], converted_point[1], text) + self.SvgGroup.append( + [ + rpoint, + ] + ) + # self.SvgGroup.redraw() + return rpoint + + def DrawImage(self, data=None, image_source=None, location=(None, None), size=(100, 100)): + if location == (None, None): + return + if data is not None: + image_source = data.decode('utf-8') if type(data) is bytes else data + converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + + rpoint = remi.gui.SvgImage('', converted_point[0], converted_point[0], size[0], size[1]) + + if type(image_source) is bytes or len(image_source) > 200: + # rpoint.set_image("data:image/svg;base64,%s"%image_source) + rpoint.image_data = 'data:image/svg;base64,%s' % image_source + else: + mimetype, encoding = mimetypes.guess_type(image_source) + with open(image_source, 'rb') as f: + data = f.read() + b64 = base64.b64encode(data) + b64_str = b64.decode('utf-8') + image_string = 'data:image/svg;base64,%s' % b64_str + # rpoint.set_image(image_string) + rpoint.image_data = image_string + self.SvgGroup.append( + [ + rpoint, + ] + ) + rpoint.redraw() + self.SvgGroup.redraw() + return rpoint + + def Erase(self): + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + self.Widget.empty() + self.SvgGroup = remi.gui.SvgSubcontainer(0, 0, '100%', '100%') + self.Widget.append(self.SvgGroup) + + def Update(self, background_color): + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + if self.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + self.Widget.style['background-color'] = self.BackgroundColor + + def Move(self, x_direction, y_direction): + # TODO - IT's still not working yet! I'm trying!! + + # self.MoveFigure(self.SvgGroup, x_direction,y_direction) + # return + zero_converted = self._convert_xy_to_canvas_xy(0, 0) + shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) + shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) + if self.Widget is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + print(self.SvgGroup.attributes) + cur_x = float(self.SvgGroup.attributes['x']) + cur_y = float(self.SvgGroup.attributes['y']) + self.SvgGroup.set_position(cur_x - x_direction, cur_y - y_direction) + self.SvgGroup.redraw() + + def Relocate(self, x, y): + shift_converted = self._convert_xy_to_canvas_xy(x, y) + if self.Widget is None: + print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') + print('Call Window.Finalize() prior to all graph operations') + return None + # figure.empty() + self.SvgGroup.set_position(shift_converted[0], shift_converted[1]) + self.SvgGroup.redraw() + + def MoveFigure(self, figure, x_direction, y_direction): + figure = figure # type: remi.gui.SvgCircle + zero_converted = self._convert_xy_to_canvas_xy(0, 0) + shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) + shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) + if figure is None: + print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') + print('Call Window.Finalize() prior to all graph operations') + return None + print(figure.attributes) + try: + cur_x = float(figure.attributes['x']) + cur_y = float(figure.attributes['y']) + figure.set_position(cur_x - x_direction, cur_y - y_direction) + except: + cur_x1 = float(figure.attributes['x1']) + cur_x2 = float(figure.attributes['x2']) + cur_y1 = float(figure.attributes['y1']) + cur_y2 = float(figure.attributes['y2']) + figure.set_coords(cur_x1 - x_direction, cur_y1 - y_direction, cur_x2 - x_direction, cur_y2 - x_direction) + figure.redraw() + + def RelocateFigure(self, figure, x, y): + figure = figure # type: remi.gui.SvgCircle + zero_converted = self._convert_xy_to_canvas_xy(0, 0) + shift_converted = self._convert_xy_to_canvas_xy(x, y) + shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) + if figure is None: + print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') + print('Call Window.Finalize() prior to all graph operations') + return None + # figure.empty() + figure.set_position(shift_converted[0], shift_converted[1]) + figure.redraw() + + def DeleteFigure(self, figure): + figure = figure # type: remi.gui.SvgCircle + if figure is None: + print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') + print('Call Window.Finalize() prior to all graph operations') + return None + self.SvgGroup.remove_child(figure) + del figure + + def change_coordinates(self, graph_bottom_left, graph_top_right): + """ + Changes the corrdinate system to a new one. The same 2 points in space are used to define the coorinate + system - the bottom left and the top right values of your graph. + + :param graph_bottom_left: Tuple[int, int] (x,y) The bottoms left corner of your coordinate system + :param graph_top_right: Tuple[int, int] (x,y) The top right corner of your coordinate system + """ + self.BottomLeft = graph_bottom_left + self.TopRight = graph_top_right + + def _MouseDownCallback(self, widget, x, y, *args): + # print(f'Mouse down {x,y}') + self.MouseButtonDown = True + + def _MouseUpCallback(self, widget, x, y, *args): + self.ClickPosition = self._convert_canvas_xy_to_xy(int(x), int(y)) + self.MouseButtonDown = False + if self.ChangeSubmits: + # self.ClickPosition = (None, None) + self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + + # def ClickCallback(self, emitter, x, y): + def ClickCallback(self, widget: remi.gui.Svg, *args): + return + self.ClickPosition = (None, None) + self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + + def _DragCallback(self, emitter, x, y): + if not self.MouseButtonDown: # only return drag events when mouse is down + return + # print(f'In Drag Callback') + self.ClickPosition = self._convert_canvas_xy_to_xy(x, y) + # print(f'Position {self.ClickPosition}') + self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + + click_callback = ClickCallback + delete_figure = DeleteFigure + draw_circle = DrawCircle + draw_image = DrawImage + draw_line = DrawLine + draw_oval = DrawOval + draw_point = DrawPoint + draw_rectangle = DrawRectangle + draw_text = DrawText + erase = Erase + move = Move + move_figure = MoveFigure + relocate = Relocate + relocate_figure = RelocateFigure + update = Update + + +# ---------------------------------------------------------------------- # +# Frame # +# ---------------------------------------------------------------------- # + +# First the REMI implementation of a frame + + +class CLASSframe(remi.gui.VBox): + def __init__(self, title, *args, **kwargs): + super(CLASSframe, self).__init__(*args, **kwargs) + self.style.update({'overflow': 'visible', 'border-width': '1px', 'border-style': 'solid', 'border-color': '#7d7d7d'}) + self.frame_label = remi.gui.Label('frame label') + self.frame_label.style.update( + { + 'position': 'relative', + 'overflow': 'auto', + 'background-color': '#ffffff', + 'border-width': '1px', + 'border-style': 'solid', + 'top': '-7px', + 'width': '0px', + 'height': '0px', + 'left': '10px', + } + ) + self.append(self.frame_label, 'frame_label') + self.set_title(title) + + def set_title(self, title): + self.frame_label.set_text(title) + + +class Frame(Element): + def __init__( + self, + title, + layout, + title_color=None, + background_color=None, + title_location=None, + relief=DEFAULT_FRAME_RELIEF, + element_justification='left', + size=(None, None), + font=None, + pad=None, + border_width=None, + key=None, + tooltip=None, + ): + ''' + Frame Element + :param title: + :param layout: + :param title_color: + :param background_color: + :param title_location: + :param relief: + :param size: + :param font: + :param pad: + :param border_width: + :param key: + :param tooltip: + ''' + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.Rows = [] + # self.ParentForm = None + self.TKFrame = None + self.Title = title + self.Relief = relief + self.TitleLocation = title_location + self.BorderWidth = border_width + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.Justification = 'left' + self.ElementJustification = element_justification + self.Widget = None # type: CLASSframe + + self.Layout(layout) + + super().__init__( + ELEM_TYPE_FRAME, + background_color=background_color, + text_color=title_color, + size=size, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + ) + return + + def AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def Layout(self, rows): + for row in rows: + self.AddRow(*row) + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + add_row = AddRow + layout = Layout + + +# ---------------------------------------------------------------------- # +# Separator # +# Routes stdout, stderr to a scrolled window # +# ---------------------------------------------------------------------- # +class VerticalSeparator(Element): + def __init__(self, pad=None): + ''' + VerticalSeperator - A separator that spans only 1 row in a vertical fashion + :param pad: + ''' + self.Orientation = 'vertical' # for now only vertical works + + super().__init__(ELEM_TYPE_SEPARATOR, pad=pad) + + +VSeperator = VerticalSeparator +VSeparator = VerticalSeparator +VSep = VerticalSeparator + + +# ---------------------------------------------------------------------- # +# Tab # +# ---------------------------------------------------------------------- # +class Tab(Element): + def __init__( + self, + title, + layout, + title_color=None, + background_color=None, + font=None, + pad=None, + disabled=False, + element_justification='left', + border_width=None, + key=None, + tooltip=None, + ): + ''' + Tab Element + :param title: + :param layout: + :param title_color: + :param background_color: + :param font: + :param pad: + :param disabled: + :param border_width: + :param key: + :param tooltip: + ''' + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.Rows = [] + self.TKFrame = None + self.Title = title + self.BorderWidth = border_width + self.Disabled = disabled + self.ParentNotebook = None + self.Justification = 'left' + self.ElementJustification = element_justification + self.TabID = None + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.Widget = None # type: remi.gui.HBox + self._Layout(layout) + + super().__init__( + ELEM_TYPE_TAB, + background_color=background_color, + text_color=title_color, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + ) + return + + def _AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def _Layout(self, rows): + for row in rows: + self._AddRow(*row) + return self + + # def Update(self, disabled=None): # TODO Disable / enable of tabs is not complete + # print('*** Tab.Update is not implemented ***') + # return + # if disabled is None: + # return + # self.Disabled = disabled + # state = 'disabled' if disabled is True else 'normal' + # self.ParentNotebook.tab(self.TabID, state=state) + # return self + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + +# ---------------------------------------------------------------------- # +# TabGroup # +# ---------------------------------------------------------------------- # +class TabGroup(Element): + def __init__( + self, + layout, + tab_location=None, + title_color=None, + selected_title_color=None, + background_color=None, + font=None, + change_submits=False, + enable_events=False, + pad=None, + border_width=None, + theme=None, + key=None, + tooltip=None, + visible=True, + ): + ''' + TabGroup Element + :param layout: + :param tab_location: + :param title_color: + :param selected_title_color: + :param background_color: + :param font: + :param change_submits: + :param pad: + :param border_width: + :param theme: + :param key: + :param tooltip: + ''' + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.SelectedTitleColor = selected_title_color + self.Rows = [] + self.TKNotebook = None + self.Widget = None # type: remi.gui.TabBox + self.Justification = 'left' + self.TabCount = 0 + self.BorderWidth = border_width + self.Theme = theme + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.ChangeSubmits = enable_events or change_submits + self.TabLocation = tab_location + self.Visible = visible + self.Disabled = False + self._Layout(layout) + + super().__init__( + ELEM_TYPE_TAB_GROUP, + background_color=background_color, + text_color=title_color, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + ) + return + + def _AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def _Layout(self, rows): + for row in rows: + self._AddRow(*row) + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def FindKeyFromTabName(self, tab_name): + for row in self.Rows: + for element in row: + if element.Title == tab_name: + return element.Key + return None + + find_key_from_tab_name = FindKeyFromTabName + + +# ---------------------------------------------------------------------- # +# Slider # +# ---------------------------------------------------------------------- # +class Slider(Element): + def __init__( + self, + range=(None, None), + default_value=None, + resolution=None, + tick_interval=None, + orientation=None, + border_width=None, + relief=None, + change_submits=False, + enable_events=False, + disabled=False, + size=(None, None), + font=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + """ + + :param range: + :param default_value: + :param resolution: + :param tick_interval: + :param orientation: + :param border_width: + :param relief: + :param change_submits: + :param enable_events: + :param disabled: + :param visible: + :param size_px: + """ + self.TKScale = None + self.Range = (1, 10) if range == (None, None) else range + self.DefaultValue = self.Range[0] if default_value is None else default_value + self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION + self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH + self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF + self.Resolution = 1 if resolution is None else resolution + self.ChangeSubmits = change_submits or enable_events + self.Disabled = disabled + self.TickInterval = tick_interval + temp_size = size + if temp_size == (None, None): + temp_size = (200, 20) if self.Orientation.startswith('h') else (200, 20) + elif size[0] is not None and size[0] < 100: + temp_size = size[0] * 10, size[1] * 3 + self.Widget = None # type: remi.gui.Slider + + super().__init__( + ELEM_TYPE_INPUT_SLIDER, + size=temp_size, + font=font, + background_color=background_color, + text_color=text_color, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + return + + def Update(self, value=None, range=(None, None), disabled=None, visible=None): + if value is not None: + self.Widget.set_value(value) + self.DefaultValue = value + if range != (None, None): + self.Widget.attributes['min'] = '{}'.format(range[0]) + self.Widget.attributes['max'] = '{}'.format(range[1]) + super().Update(self.Widget, disabled=disabled, visible=visible) + + def _SliderCallback(self, widget: remi.Widget, value): + self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + + update = Update + + +# +# ---------------------------------------------------------------------- # +# Column # +# ---------------------------------------------------------------------- # +class Column(Element): + def __init__( + self, + layout, + background_color=None, + size=(None, None), + pad=None, + scrollable=False, + vertical_scroll_only=False, + element_justification='left', + key=None, + ): + ''' + Column Element + :param layout: + :param background_color: + :param size: + :param pad: + :param scrollable: + :param key: + ''' + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.Rows = [] + self.TKFrame = None + self.Scrollable = scrollable + self.VerticalScrollOnly = vertical_scroll_only + self.ElementJustification = element_justification + # self.ImageFilename = image_filename + # self.ImageData = image_data + # self.ImageSize = image_size + # self.ImageSubsample = image_subsample + # bg = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + + self.Layout(layout) + + super().__init__(ELEM_TYPE_COLUMN, background_color=background_color, size=size, pad=pad, key=key) + return + + def AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def Layout(self, rows): + for row in rows: + self.AddRow(*row) + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + add_row = AddRow + layout = Layout + + +Col = Column + + +# ---------------------------------------------------------------------- # +# Menu # +# ---------------------------------------------------------------------- # +class Menu(Element): + def __init__( + self, + menu_definition, + background_color=COLOR_SYSTEM_DEFAULT, + text_color=None, + size=(None, None), + tearoff=False, + pad=None, + key=None, + disabled=False, + font=None, + ): + ''' + Menu Element + :param menu_definition: + :param background_color: + :param size: + :param tearoff: + :param pad: + :param key: + ''' + back_color = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.MenuDefinition = menu_definition + self.TKMenu = None + self.Tearoff = tearoff + self.Widget = None # type: remi.gui.MenuBar + self.MenuItemChosen = None + self.Disabled = disabled + + super().__init__( + ELEM_TYPE_MENUBAR, + background_color=back_color, + text_color=text_color, + size=size, + pad=pad, + key=key, + font=font, + ) + return + + def _ChangedCallbackMenu(self, widget, *user_data): + widget = widget # type: remi.gui.MenuItem + chosen = user_data[0] + self.MenuItemChosen = chosen + self.ParentForm.LastButtonClicked = chosen + self.ParentForm.MessageQueue.put(chosen) + + +# ---------------------------------------------------------------------- # +# Table # +# ---------------------------------------------------------------------- # +class Table(Element): + def __init__( + self, + values, + headings=None, + visible_column_map=None, + col_widths=None, + def_col_width=10, + auto_size_columns=True, + max_col_width=20, + select_mode=None, + display_row_numbers=False, + row_header_text='Row', + starting_row_num=0, + num_rows=None, + row_height=None, + font=None, + justification='right', + text_color=None, + background_color=None, + alternating_row_color=None, + row_colors=None, + vertical_scroll_only=True, + disabled=False, + size=(None, None), + change_submits=False, + enable_events=False, + bind_return_key=False, + pad=None, + key=None, + tooltip=None, + right_click_menu=None, + visible=True, + size_px=(None, None), + ): + ''' + Table + :param values: + :param headings: + :param visible_column_map: + :param col_widths: + :param def_col_width: + :param auto_size_columns: + :param max_col_width: + :param select_mode: + :param display_row_numbers: + :param num_rows: + :param row_height: + :param font: + :param justification: + :param text_color: + :param background_color: + :param alternating_row_color: + :param size: + :param change_submits: + :param enable_events: + :param bind_return_key: + :param pad: + :param key: + :param tooltip: + :param right_click_menu: + :param visible: + ''' + self.Values = values + self.ColumnHeadings = headings + self.ColumnsToDisplay = visible_column_map + self.ColumnWidths = col_widths + self.MaxColumnWidth = max_col_width + self.DefaultColumnWidth = def_col_width + self.AutoSizeColumns = auto_size_columns + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.TextColor = text_color + self.Justification = justification + self.InitialState = None + self.SelectMode = select_mode + self.DisplayRowNumbers = display_row_numbers + self.NumRows = num_rows if num_rows is not None else size[1] + self.RowHeight = row_height + self.TKTreeview = None + self.AlternatingRowColor = alternating_row_color + self.VerticalScrollOnly = vertical_scroll_only + self.SelectedRows = [] + self.ChangeSubmits = change_submits or enable_events + self.BindReturnKey = bind_return_key + self.StartingRowNumber = starting_row_num # When displaying row numbers, where to start + self.RowHeaderText = row_header_text + self.RightClickMenu = right_click_menu + self.RowColors = row_colors + self.Disabled = disabled + self.SelectedItem = None + self.SelectedRow = None + self.Widget = None # type: remi.Table + + super().__init__( + ELEM_TYPE_TABLE, + text_color=text_color, + background_color=background_color, + font=font, + size=size, + pad=pad, + key=key, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + return + + def Update(self, values=None): + print('*** Table Update not yet supported ***') + return + if values is not None: + children = self.TKTreeview.get_children() + for i in children: + self.TKTreeview.detach(i) + self.TKTreeview.delete(i) + children = self.TKTreeview.get_children() + # self.TKTreeview.delete(*self.TKTreeview.get_children()) + for i, value in enumerate(values): + if self.DisplayRowNumbers: + value = [i + self.StartingRowNumber] + value + id = self.TKTreeview.insert('', 'end', text=i, iid=i + 1, values=value, tag=i % 2) + if self.AlternatingRowColor is not None: + self.TKTreeview.tag_configure(1, background=self.AlternatingRowColor) + self.Values = values + self.SelectedRows = [] + + def _on_table_row_click(self, table, row, item): + # self.SelectedRow = row # type: remi.gui.TableRow + self.SelectedItem = item.get_text() + index = -1 + # each widget (and specifically in this case the table) has a _render_children_list attribute that + # is an ordered list of the children keys + # first, we search for the row in the children dictionary + for key, value in table.children.items(): + if value == row: + # if the row is found, we get the index in the ordered list + index = table._render_children_list.index(key) + break + self.SelectedRow = index + if self.ChangeSubmits: + self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' + self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) + else: + self.ParentForm.LastButtonClicked = '' + + +# ---------------------------------------------------------------------- # +# Tree # +# ---------------------------------------------------------------------- # +class Tree(Element): + def __init__( + self, + data=None, + headings=None, + visible_column_map=None, + col_widths=None, + col0_width=10, + def_col_width=10, + auto_size_columns=True, + max_col_width=20, + select_mode=None, + show_expanded=False, + change_submits=False, + font=None, + justification='right', + text_color=None, + background_color=None, + num_rows=None, + pad=None, + key=None, + tooltip=None, + ): + ''' + Tree Element + :param headings: + :param visible_column_map: + :param col_widths: + :param def_col_width: + :param auto_size_columns: + :param max_col_width: + :param select_mode: + :param font: + :param justification: + :param text_color: + :param background_color: + :param num_rows: + :param pad: + :param key: + :param tooltip: + ''' + self.TreeData = data + self.ColumnHeadings = headings + self.ColumnsToDisplay = visible_column_map + self.ColumnWidths = col_widths + self.MaxColumnWidth = max_col_width + self.DefaultColumnWidth = def_col_width + self.AutoSizeColumns = auto_size_columns + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.TextColor = text_color + self.Justification = justification + self.InitialState = None + self.SelectMode = select_mode + self.ShowExpanded = show_expanded + self.NumRows = num_rows + self.Col0Width = col0_width + self.TKTreeview = None + self.SelectedRows = [] + self.ChangeSubmits = change_submits + + print('*** Tree Element not yet supported ***') + + super().__init__( + ELEM_TYPE_TREE, + text_color=text_color, + background_color=background_color, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + ) + + def add_treeview_data(self, node): + # print(f'Inserting {node.key} under parent {node.parent}') + if node.key != '': + self.TKTreeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, open=self.ShowExpanded) + for node in node.children: + self.add_treeview_data(node) + + def Update(self, values=None, key=None, value=None, text=None): + print('*** Tree Element not yet supported ***') + if values is not None: + children = self.TKTreeview.get_children() + for i in children: + self.TKTreeview.detach(i) + self.TKTreeview.delete(i) + children = self.TKTreeview.get_children() + self.TreeData = values + self.add_treeview_data(self.TreeData.root_node) + self.SelectedRows = [] + if key is not None: + item = self.TKTreeview.item(key) + if value is not None: + self.TKTreeview.item(key, values=value) + if text is not None: + self.TKTreeview.item(key, text=text) + item = self.TKTreeview.item(key) + return self + + update = Update + + +class TreeData(object): + class Node(object): + def __init__(self, parent, key, text, values): + self.parent = parent + self.children = [] + self.key = key + self.text = text + self.values = values + + def _Add(self, node): + self.children.append(node) + + def __init__(self): + self.tree_dict = {} + self.root_node = self.Node('', '', 'root', []) + self.tree_dict[''] = self.root_node + + def _AddNode(self, key, node): + self.tree_dict[key] = node + + def Insert(self, parent, key, text, values): + node = self.Node(parent, key, text, values) + self.tree_dict[key] = node + parent_node = self.tree_dict[parent] + parent_node._Add(node) + + def __repr__(self): + return self._NodeStr(self.root_node, 1) + + def _NodeStr(self, node, level): + return '\n'.join([str(node.key) + ' : ' + str(node.text)] + [' ' * 4 * level + self._NodeStr(child, level + 1) for child in node.children]) + + insert = Insert + + +# ---------------------------------------------------------------------- # +# Error Element # +# ---------------------------------------------------------------------- # +class ErrorElement(Element): + def __init__(self, key=None): + ''' + Error Element + :param key: + ''' + self.Key = key + + super().__init__(ELEM_TYPE_ERROR, key=key) + return + + def Update(self, *args, **kwargs): + PopupError( + 'Keyword error in Update', + 'You need to stop this madness and check your spelling', + 'Bad key = {}'.format(self.Key), + 'Your bad line of code may resemble this:', + 'window.FindElement("{}")'.format(self.Key), + ) + return self + + def Get(self): + return 'This is NOT a valid Element!\nSTOP trying to do things with it or I will have to crash at some point!' + + get = Get + update = Update + + +# ------------------------------------------------------------------------- # +# Window CLASS # +# ------------------------------------------------------------------------- # +class Window: + + _NumOpenWindows = 0 + user_defined_icon = None + hidden_master_root = None + QTApplication = None + active_popups = {} + highest_level_app = None + stdout_is_rerouted = False + stdout_string_io = None + stdout_location = None + port_number = 6900 + active_windows = [] # type: [Window] + App = None # type: remi.App + + def __init__( + self, + title, + layout=None, + default_element_size=DEFAULT_ELEMENT_SIZE, + default_button_element_size=(None, None), + auto_size_text=None, + auto_size_buttons=None, + location=(None, None), + size=(None, None), + element_padding=None, + button_color=None, + font=None, + progress_bar_color=(None, None), + background_color=None, + border_depth=None, + auto_close=False, + auto_close_duration=None, + icon=DEFAULT_BASE64_ICON, + force_toplevel=False, + alpha_channel=1, + return_keyboard_events=False, + return_key_down_events=False, + use_default_focus=True, + text_justification=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + resizable=True, + disable_close=False, + margins=(None, None), + element_justification='left', + disable_minimize=False, + background_image=None, + finalize=False, + web_debug=False, + web_ip='0.0.0.0', + web_port=0, + web_start_browser=True, + web_update_interval=0.0000001, + web_multiple_instance=False, + ): + ''' + + :param title: + :param default_element_size: + :param default_button_element_size: + :param auto_size_text: + :param auto_size_buttons: + :param location: + :param size: + :param element_padding: + :param button_color: + :param font: + :param progress_bar_color: + :param background_color: + :param border_depth: + :param auto_close: + :param auto_close_duration: + :param icon: + :param force_toplevel: + :param alpha_channel: + :param return_keyboard_events: + :param use_default_focus: + :param text_justification: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param resizable: + :param disable_close: + :param background_image: + ''' + self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT + self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS + self.Title = title + self.Rows = [] # a list of ELEMENTS for this row + self.DefaultElementSize = convert_tkinter_size_to_Wx(default_element_size) + self.DefaultButtonElementSize = convert_tkinter_size_to_Wx(default_button_element_size) if default_button_element_size != (None, None) else DEFAULT_BUTTON_ELEMENT_SIZE + self.Location = location + self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR + self.BackgroundColor = background_color if background_color else DEFAULT_BACKGROUND_COLOR + self.ParentWindow = None + self.Font = font if font else DEFAULT_FONT + self.RadioDict = {} + self.BorderDepth = border_depth + self.WindowIcon = icon if icon is not None else Window.user_defined_icon + self.AutoClose = auto_close + self.NonBlocking = False + self.TKroot = None + self.TKrootDestroyed = False + self.CurrentlyRunningMainloop = False + self.FormRemainedOpen = False + self.TKAfterID = None + self.ProgressBarColor = progress_bar_color + self.AutoCloseDuration = auto_close_duration + self.RootNeedsDestroying = False + self.Shown = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.AllKeysDict = {} + self.LastButtonClicked = None + self.LastButtonClickedWasRealtime = False + self.UseDictionary = False + self.UseDefaultFocus = use_default_focus + self.ReturnKeyboardEvents = return_keyboard_events + self.ReturnKeyDownEvents = return_key_down_events + self.KeyInfoDict = {} + self.LastKeyboardEvent = None + self.TextJustification = text_justification + self.NoTitleBar = no_titlebar + self.GrabAnywhere = grab_anywhere + self.KeepOnTop = keep_on_top + self.ForcefTopLevel = force_toplevel + self.Resizable = resizable + self._AlphaChannel = alpha_channel + self.Timeout = None + self.TimeoutKey = TIMEOUT_KEY + self.TimerCancelled = False + self.DisableClose = disable_close + self._Hidden = False + # self.QTApplication = None + # self.QT_QMainWindow = None + self._Size = size + self.ElementPadding = element_padding or DEFAULT_ELEMENT_PADDING + self.FocusElement = None + self.BackgroundImage = background_image + self.XFound = False + self.DisableMinimize = disable_minimize + self.OutputElementForStdOut = None # type: Output + self.Justification = 'left' + self.ElementJustification = element_justification + self.IgnoreClose = False + self.thread_id = None + self.App = None # type: Window.MyApp + self.web_debug = web_debug + self.web_ip = web_ip + self.web_port = web_port + self.web_start_browser = web_start_browser + self.web_update_interval = web_update_interval + self.web_multiple_instance = web_multiple_instance + self.MessageQueue = Queue() + self.master_widget = None # type: remi.gui.VBox + self.UniqueKeyCounter = 0 + + if layout is not None: + self.Layout(layout) + if finalize: + self.Finalize() + + @classmethod + def IncrementOpenCount(self): + self._NumOpenWindows += 1 + # print('+++++ INCREMENTING Num Open Windows = {} ---'.format(Window._NumOpenWindows)) + + @classmethod + def _DecrementOpenCount(self): + self._NumOpenWindows -= 1 * (self._NumOpenWindows != 0) # decrement if not 0 + # print('----- DECREMENTING Num Open Windows = {} ---'.format(Window._NumOpenWindows)) + + # ------------------------- Add ONE Row to Form ------------------------- # + def AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + # ------------------------- Add Multiple Rows to Form ------------------------- # + def AddRows(self, rows): + for row in rows: + self.AddRow(*row) + + def Layout(self, rows): + self.AddRows(rows) + self._BuildKeyDict() + return self + + def LayoutAndRead(self, rows, non_blocking=False): + raise DeprecationWarning('LayoutAndRead is no longer supported... change your call to window.Layout(layout).Read()') + # self.AddRows(rows) + # self.Show(non_blocking=non_blocking) + # return self.ReturnValues + + def LayoutAndShow(self, rows): + raise DeprecationWarning('LayoutAndShow is no longer supported... change your call to LayoutAndRead') + + # ------------------------- ShowForm THIS IS IT! ------------------------- # + def Show(self, non_blocking=False): + self.Shown = True + # Compute num rows & num cols (it'll come in handy debugging) + self.NumRows = len(self.Rows) + self.NumCols = max(len(row) for row in self.Rows) + self.NonBlocking = non_blocking + + # Search through entire form to see if any elements set the focus + # if not, then will set the focus to the first input element + found_focus = False + for row in self.Rows: + for element in row: + try: + if element.Focus: + found_focus = True + except: + pass + try: + if element.Key is not None: + self.UseDictionary = True + except: + pass + + if not found_focus and self.UseDefaultFocus: + self.UseDefaultFocus = True + else: + self.UseDefaultFocus = False + # -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ## + StartupTK(self) + + def Read(self, timeout=None, timeout_key=TIMEOUT_KEY, close=False): + """ + THE biggest deal method in the Window class! This is how you get all of your data from your Window. + Pass in a timeout (in milliseconds) to wait for a maximum of timeout milliseconds. Will return timeout_key + if no other GUI events happen first. + Use the close parameter to close the window after reading + + :param timeout: (int) Milliseconds to wait until the Read will return IF no other GUI events happen first + :param timeout_key: (Any) The value that will be returned from the call if the timer expired + :param close: (bool) if True the window will be closed prior to returning + :return: Tuple[(Any), Union[Dict[Any:Any]], List[Any], None] (event, values) + """ + results = self._read(timeout=timeout, timeout_key=timeout_key) + if close: + self.close() + + return results + + def _read(self, timeout=None, timeout_key=TIMEOUT_KEY): + # if timeout == 0: # timeout of zero runs the old readnonblocking + # event, values = self._ReadNonBlocking() + # if event is None: + # event = timeout_key + # if values is None: + # event = None + # return event, values # make event None if values was None and return + # Read with a timeout + self.Timeout = timeout + self.TimeoutKey = timeout_key + self.NonBlocking = False + if not self.Shown: + self.Show() + # if already have a button waiting, the return previously built results + if self.LastButtonClicked is not None and not self.LastButtonClickedWasRealtime: + # print(f'*** Found previous clicked saved {self.LastButtonClicked}') + results = BuildResults(self, False, self) + self.LastButtonClicked = None + return results + InitializeResults(self) + # if the last button clicked was realtime, emulate a read non-blocking + # the idea is to quickly return realtime buttons without any blocks until released + if self.LastButtonClickedWasRealtime: + # print(f'RTime down {self.LastButtonClicked}' ) + try: + rc = self.TKroot.update() + except: + self.TKrootDestroyed = True + Window._DecrementOpenCount() + results = BuildResults(self, False, self) + if results[0] != None and results[0] != timeout_key: + return results + else: + pass + + # else: + # print("** REALTIME PROBLEM FOUND **", results) + # print('****************** CALLING MESSAGE QUEUE GET ***********************') + self.CurrentlyRunningMainloop = True + if timeout is not None: + try: + self.LastButtonClicked = self.MessageQueue.get(timeout=(timeout if timeout else 0.001) / 1000) + # print(f'Got event {self.LastButtonClicked}') + except: # timeout + self.LastButtonClicked = timeout_key + else: + self.LastButtonClicked = self.MessageQueue.get() + # print(f'Got event {self.LastButtonClicked}') + # print('--------------------- BACK FROM MESSAGE QUEUE GET ----------------------') + + results = BuildResults(self, False, self) + return results + # print(f'In main {self.Title}') + ################################# CALL GUWxTextCtrlI MAINLOOP ############################ + # self.App.MainLoop() + # self.CurrentlyRunningMainloop = False + # self.TimerCancelled = True + # if timer: + # timer.Stop() + # if Window.stdout_is_rerouted: + # sys.stdout = Window.stdout_location + # if self.RootNeedsDestroying: + # self.LastButtonClicked = None + # self.App.Close() + # try: + # self.MasterFrame.Close() + # except: + # pass + # Window._DecrementOpenCount() + # if form was closed with X + # if self.LastButtonClicked is None and self.LastKeyboardEvent is None and self.ReturnValues[0] is None: + # Window._DecrementOpenCount() + # Determine return values + # if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: + # results = BuildResults(self, False, self) + # if not self.LastButtonClickedWasRealtime: + # self.LastButtonClicked = None + # return results + # else: + # if not self.XFound and self.Timeout != 0 and self.Timeout is not None and self.ReturnValues[ + # 0] is None: # Special Qt case because returning for no reason so fake timeout + # self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout + # elif not self.XFound and self.ReturnValues[ + # 0] is None: # TODO HIGHLY EXPERIMENTAL... added due to tray icon interaction + # print("*** Faking timeout ***") + # self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout + # return self.ReturnValues + + def _ReadNonBlocking(self): + if self.TKrootDestroyed: + return None, None + if not self.Shown: + self.Show(non_blocking=True) + # event = wx.Event() + # self.App.QueueEvent(event) + timer = wx.Timer(self.App) + self.App.Bind(wx.EVT_TIMER, self.timer_timeout) + timer.Start(milliseconds=0, oneShot=wx.TIMER_ONE_SHOT) + self.CurrentlyRunningMainloop = True + # print(f'In main {self.Title}') + ################################# CALL GUWxTextCtrlI MAINLOOP ############################ + + self.App.MainLoop() + if Window.stdout_is_rerouted: + sys.stdout = Window.stdout_location + # self.LastButtonClicked = 'TEST' + self.CurrentlyRunningMainloop = False + timer.Stop() + # while self.App.HasPendingEvents(): + # self.App.ProcessPendingEvents() + return BuildResults(self, False, self) + + # ------------------------- SetIcon - set the window's fav icon ------------------------- # + def SetIcon(self, icon=None, pngbase64=None): + pass + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def _GetDefaultElementSize(self): + return self.DefaultElementSize + + def _AutoCloseAlarmCallback(self): + try: + window = self + if window: + if window.NonBlocking: + self.CloseNonBlockingForm() + else: + window._Close() + if self.CurrentlyRunningMainloop: + self.QTApplication.exit() # kick the users out of the mainloop + self.RootNeedsDestroying = True + self.QT_QMainWindow.close() + + except: + pass + + def timer_timeout(self, event): + # first, get the results table built + # modify the Results table in the parent FlexForm object + # print('timer timeout') + if self.TimerCancelled: + return + self.LastButtonClicked = self.TimeoutKey + self.FormRemainedOpen = True + if self.CurrentlyRunningMainloop: + self.App.ExitMainLoop() + + def non_block_timer_timeout(self, event): + # print('non-blocking timer timeout') + self.App.ExitMainLoop() + + def autoclose_timer_callback(self, frame): + # print('*** AUTOCLOSE TIMEOUT CALLBACK ***', frame) + try: + frame.Close() + except: + pass # if user has already closed the frame will get an error + + if self.CurrentlyRunningMainloop: + self.App.ExitMainLoop() + + def on_key_down(self, emitter, key, keycode, ctrl, shift, alt): + self.LastButtonClicked = 'DOWN' + key + self.MessageQueue.put(self.LastButtonClicked) + self.KeyInfoDict = {'key': key, 'keycode': keycode, 'ctrl': ctrl, 'shift': shift, 'alt': alt} + + def on_key_up(self, emitter, key, keycode, ctrl, shift, alt): + self.LastButtonClicked = key + self.MessageQueue.put(self.LastButtonClicked) + self.KeyInfoDict = {'key': key, 'keycode': keycode, 'ctrl': ctrl, 'shift': shift, 'alt': alt} + + def callback_keyboard_char(self, event): + self.LastButtonClicked = None + self.FormRemainedOpen = True + if event.ClassName == 'wxMouseEvent': + if event.WheelRotation < 0: + self.LastKeyboardEvent = 'MouseWheel:Down' + else: + self.LastKeyboardEvent = 'MouseWheel:Up' + else: + self.LastKeyboardEvent = event.GetKeyCode() + if not self.NonBlocking: + BuildResults(self, False, self) + if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! + self.App.ExitMainLoop() # kick the users out of the mainloop + if event.ClassName != 'wxMouseEvent': + event.DoAllowNextEvent() + + def Finalize(self): + if self.TKrootDestroyed: + return self + if not self.Shown: + self.Show(non_blocking=True) + # else: + # try: + # self.QTApplication.processEvents() # refresh the window + # except: + # print('* ERROR FINALIZING *') + # self.TKrootDestroyed = True + # Window._DecrementOpenCount() + return self + + def Refresh(self): + # self.QTApplication.processEvents() # refresh the window + return self + + def VisibilityChanged(self): + self.SizeChanged() + return self + + def Fill(self, values_dict): + _FillFormWithValues(self, values_dict) + return self + + def FindElement(self, key, silent_on_error=False): + try: + element = self.AllKeysDict[key] + except KeyError: + element = None + if element is None: + if not silent_on_error: + print('*** WARNING = FindElement did not find the key. Please check your key\'s spelling ***') + PopupError( + 'Keyword error in FindElement Call', + 'Bad key = {}'.format(key), + 'Your bad line of code may resemble this:', + 'window.FindElement("{}")'.format(key), + ) + return ErrorElement(key=key) + else: + return False + return element + + Element = FindElement # shortcut function definition + + def _BuildKeyDict(self): + dict = {} + self.AllKeysDict = self._BuildKeyDictForWindow(self, self, dict) + # print(f'keys built = {self.AllKeysDict}') + + def _BuildKeyDictForWindow(self, top_window, window, key_dict): + for row_num, row in enumerate(window.Rows): + for col_num, element in enumerate(row): + if element.Type == ELEM_TYPE_COLUMN: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Type == ELEM_TYPE_FRAME: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Type == ELEM_TYPE_TAB_GROUP: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Type == ELEM_TYPE_TAB: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Key is None: # if no key has been assigned.... create one for input elements + if element.Type == ELEM_TYPE_BUTTON: + element.Key = element.ButtonText + if element.Type in ( + ELEM_TYPE_MENUBAR, + ELEM_TYPE_BUTTONMENU, + ELEM_TYPE_CANVAS, + ELEM_TYPE_INPUT_SLIDER, + ELEM_TYPE_GRAPH, + ELEM_TYPE_IMAGE, + ELEM_TYPE_INPUT_CHECKBOX, + ELEM_TYPE_INPUT_LISTBOX, + ELEM_TYPE_INPUT_COMBO, + ELEM_TYPE_INPUT_MULTILINE, + ELEM_TYPE_INPUT_OPTION_MENU, + ELEM_TYPE_INPUT_SPIN, + ELEM_TYPE_TABLE, + ELEM_TYPE_TREE, + ELEM_TYPE_INPUT_TEXT, + ): + element.Key = top_window.DictionaryKeyCounter + top_window.DictionaryKeyCounter += 1 + if element.Key is not None: + if element.Key in key_dict.keys(): + (print('*** Duplicate key found in your layout {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) + element.Key = element.Key + str(self.UniqueKeyCounter) + self.UniqueKeyCounter += 1 + (print('*** Replaced new key with {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) + key_dict[element.Key] = element + return key_dict + + def FindElementWithFocus(self): + return self.FocusElement + # element = _FindElementWithFocusInSubForm(self) + # return element + + def SaveToDisk(self, filename): + try: + results = BuildResults(self, False, self) + with open(filename, 'wb') as sf: + pickle.dump(results[1], sf) + except: + print('*** Error saving form to disk ***') + + def LoadFromDisk(self, filename): + try: + with open(filename, 'rb') as df: + self.Fill(pickle.load(df)) + except: + print('*** Error loading form to disk ***') + + def GetScreenDimensions(self): # TODO - Not sure what to return so (0,0) for now + size = (0, 0) + return size + + def Move(self, x, y): + self.MasterFrame.SetPosition((x, y)) + + def Minimize(self): + self.MasterFrame.Iconize() + + def Maximize(self): + self.MasterFrame.Maximize() + + def _Close(self): + if not self.NonBlocking: + BuildResults(self, False, self) + if self.TKrootDestroyed: + return None + self.TKrootDestroyed = True + self.RootNeedsDestroying = True + self.Close() + + def Close(self): + if len(Window.active_windows) != 0: + del Window.active_windows[-1] # delete current window from active windows + if len(Window.active_windows) != 0: + window = Window.active_windows[-1] # get prior window to change to + Window.App.set_root_widget(window.master_widget) + else: + self.App.close() + self.App.server.server_starter_instance._alive = False + self.App.server.server_starter_instance._sserver.shutdown() + return + + self.App.close() + self.App.server.server_starter_instance._alive = False + self.App.server.server_starter_instance._sserver.shutdown() + + CloseNonBlockingForm = Close + CloseNonBlocking = Close + + def Disable(self): + self.MasterFrame.Enable(False) + + def Enable(self): + self.MasterFrame.Enable(True) + + def Hide(self): + self._Hidden = True + self.master_widget.attributes['hidden'] = 'true' + # self.MasterFrame.Hide() + return + + def UnHide(self): + if self._Hidden: + del self.master_widget.attributes['hidden'] + self._Hidden = False + + def Disappear(self): + self.MasterFrame.SetTransparent(0) + + def Reappear(self): + self.MasterFrame.SetTransparent(255) + + def SetAlpha(self, alpha): + ''' + Change the window's transparency + :param alpha: From 0 to 1 with 0 being completely transparent + :return: + ''' + self._AlphaChannel = alpha * 255 + if self._AlphaChannel is not None: + self.MasterFrame.SetTransparent(self._AlphaChannel) + + @property + def AlphaChannel(self): + return self._AlphaChannel + + @AlphaChannel.setter + def AlphaChannel(self, alpha): + self.SetAlpha(alpha) + + def BringToFront(self): + self.MasterFrame.ToggleWindowStyle(wx.STAY_ON_TOP) + + def CurrentLocation(self): + location = self.MasterFrame.GetPosition() + return location + + @property + def Size(self): + size = self.MasterFrame.GetSize() + return size + + @Size.setter + def Size(self, size): + self.MasterFrame.SetSize(size[0], size[1]) + + def SizeChanged(self): + size = self.Size + self.Size = size[0] + 1, size[1] + 1 + self.Size = size + self.MasterFrame.SetSizer(self.OuterSizer) + self.OuterSizer.Fit(self.MasterFrame) + + def __getitem__(self, key): + """ + Returns Element that matches the passed in key. + This is "called" by writing code as thus: + window['element key'].Update + + :param key: (Any) The key to find + :return: Union[Element, None] The element found or None if no element was found + """ + try: + return self.Element(key) + except Exception as e: + print('The key you passed in is no good. Key = {}*'.format(key)) + return None + + def __call__(self, *args, **kwargs): + """ + Call window.Read but without having to type it out. + window() == window.Read() + window(timeout=50) == window.Read(timeout=50) + + :param args: + :param kwargs: + :return: Tuple[Any, Dict[Any:Any]] The famous event, values that Read returns. + """ + return self.Read(*args, **kwargs) + + add_row = AddRow + add_rows = AddRows + alpha_channel = AlphaChannel + bring_to_front = BringToFront + close = Close + current_location = CurrentLocation + disable = Disable + disappear = Disappear + element = Element + enable = Enable + fill = Fill + finalize = Finalize + find_element = FindElement + find_element_with_focus = FindElementWithFocus + get_screen_dimensions = GetScreenDimensions + hide = Hide + increment_open_count = IncrementOpenCount + layout = Layout + layout_and_read = LayoutAndRead + layout_and_show = LayoutAndShow + load_from_disk = LoadFromDisk + maximize = Maximize + minimize = Minimize + move = Move + num_open_windows = _NumOpenWindows + read = Read + reappear = Reappear + refresh = Refresh + save_to_disk = SaveToDisk + set_alpha = SetAlpha + set_icon = SetIcon + show = Show + size = Size + size_changed = SizeChanged + un_hide = UnHide + visibility_changed = VisibilityChanged + + def remi_thread(self): + # print('Remi Thread started') + logging.getLogger('remi').setLevel(logging.CRITICAL) + logging.getLogger('remi').disabled = True + logging.getLogger('remi.server.ws').disabled = True + logging.getLogger('remi.server').disabled = True + logging.getLogger('remi.request').disabled = True + # use this code to start the application instead of the **start** call + # s = remi.Server(self.MyApp, start=True, title=self.Title, address='0.0.0.0', port=8081, start_browser=True, userdata=(self,), multiple_instance=False, update_interval=.001) + + # logging.getLogger('remi').setLevel(level=logging.CRITICAL) + # logging.getLogger('remi').disabled = True + # logging.disable(logging.CRITICAL) + # s = remi.server.StandaloneServer(self.MyApp, width=1100, height=600) + # s.start() + Window.port_number += 1 + try: + remi.start( + self.MyApp, + title=self.Title, + debug=self.web_debug, + address=self.web_ip, + port=self.web_port, + multiple_instance=self.web_multiple_instance, + start_browser=self.web_start_browser, + update_interval=self.web_update_interval, + userdata=(self,), + ) + + except: + print('*** ERROR Caught inside Remi ***') + print(traceback.format_exc()) + # remi.start(self.MyApp, title=self.Title ,debug=False, userdata=(self,), standalone=True) # standalone=True) + + # remi.start(self.MyApp, standalone=True, debug=True, userdata=(self,) ) # Can't do this because of a threading problem + print('Returned from Remi Start command... now sending None event') + + self.MessageQueue.put(None) # if returned from start call, then the window has been destroyed and a None event should be generated + + class MyApp(remi.App): + def __init__(self, *args, userdata2=None): + # self.window = window # type: Window + # print(args[-1]) + if userdata2 is None: + userdata = args[-1].userdata + self.window = userdata[0] # type: Window + else: + self.window = userdata2 # type: Window + self.master_widget = None + # print("new App instance %s" % str(id(self))) + # self.window.App = self + # Window.App = self + self.lines_shown = [] + + if userdata2 is None: + # res_path = os.path.dirname(os.path.abspath(__file__)) + # print('res path', res_path) + super(Window.MyApp, self).__init__( + *args, + static_file_path={ + 'C': 'c:', + 'c': 'c:', + 'D': 'd:', + 'd': 'd:', + 'E': 'e:', + 'e': 'e:', + 'dot': '.', + '.': '.', + } + ) + + def _instance(self): + remi.App._instance(self) + self.window.App = remi.server.clients[self.session] + + def log_message(self, *args, **kwargs): + pass + + def idle(self): + if Window.stdout_is_rerouted: + Window.stdout_string_io.seek(0) + lines = Window.stdout_string_io.readlines() + # lines.reverse() + # self.window.OutputElementForStdOut.Widget.set_text("".join(lines)) + # self.window.OutputElementForStdOut.Update("".join(lines)) + if lines != self.lines_shown: + self.window.OutputElementForStdOut.Update(''.join(lines)) + self.lines_shown = lines + + def main(self, name='world'): + # margin 0px auto allows to center the app to the screen + # self.master_widget = remi.gui.VBox() + # self.master_widget.style['justify-content'] = 'flex-start' + # self.master_widget.style['align-items'] = 'baseline' + # if self.window.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + # self.master_widget.style['background-color'] = self.window.BackgroundColor + # try: + # PackFormIntoFrame(self.window, self.master_widget, self.window) + # except: + # print('* ERROR PACKING FORM *') + # print(traceback.format_exc()) + # + # if self.window.BackgroundImage: + # self.master_widget.style['background-image'] = "url('{}')".format('/'+self.window.BackgroundImage) + # # print(f'background info',self.master_widget.attributes['background-image'] ) + # + # if not self.window.DisableClose: + # # add the following 3 lines to your app and the on_window_close method to make the console close automatically + # tag = remi.gui.Tag(_type='script') + # tag.add_child("javascript", """window.onunload=function(e){sendCallback('%s','%s');return "close?";};""" % ( + # str(id(self)), "on_window_close")) + # self.master_widget.add_child("onunloadevent", tag) + + self.master_widget = setup_remi_window(self, self.window) + self.window.master_widget = self.master_widget + # if self.window.WindowIcon: + # print('placing icon') + # self.page.children['head'].set_icon_file("/res:logo.png") + # self.page.children['head'].set_icon_data( base64_data=self.window.WindowIcon, mimetype="image/png" ) + + self.window.MessageQueue.put('Layout complete') # signal the main code that the layout is all done + return self.master_widget # returning the root widget + + def on_window_close(self): + # here you can handle the unload + print('app closing') + self.close() + self.server.server_starter_instance._alive = False + self.server.server_starter_instance._sserver.shutdown() + # self.window.MessageQueue.put(None) + print('server stopped') + + +FlexForm = Window + + +# =========================================================================== # +# Stops the mainloop and sets the event information # +# =========================================================================== # + + +def element_callback_quit_mainloop(element): + if element.Key is not None: + element.ParentForm.LastButtonClicked = element.Key + else: + element.ParentForm.LastButtonClicked = '' + try: + element.ParentForm.LastButtonClicked = element.Key if element.Key is not None else element.ButtonText + except: + element.ParentForm.LastButtonClicked = element.Key + # print(f'Putting into message queue {element.ParentForm.LastButtonClicked}') + element.ParentForm.MessageQueue.put(element.ParentForm.LastButtonClicked) + + +def quit_mainloop(window): + window.App.ExitMainLoop() + + +# =========================================================================== # +# Stops the mainloop and sets the event information # +# =========================================================================== # +def convert_tkinter_size_to_Wx(size): + """ + Converts size in characters to size in pixels + :param size: size in characters, rows + :return: size in pixels, pixels + """ + qtsize = size + if size[1] is not None and size[1] < DEFAULT_PIXEL_TO_CHARS_CUTOFF: # change from character based size to pixels (roughly) + qtsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] + return qtsize + + +def base64_to_style_image(base64_image): + x = "url('data:image/png;base64," + x += str(base64_image) + x += "')" + # print(x) + return x + + +def font_parse_string(font): + """ + Convert from font string/tyuple into a Qt style sheet string + :param font: "Arial 10 Bold" or ('Arial', 10, 'Bold) + :return: style string that can be combined with other style strings + """ + + if font is None: + return '' + + if type(font) is str: + _font = font.split(' ') + else: + _font = font + family = _font[0] + point_size = int(_font[1]) + + style = _font[2:] if len(_font) > 1 else None + + # underline = 'underline' in _font[2:] + # bold = 'bold' in _font + + return family, point_size, style + + +# ################################################################################ +# ################################################################################ +# END OF ELEMENT DEFINITIONS +# ################################################################################ +# ################################################################################ + + +# =========================================================================== # +# Button Lazy Functions so the caller doesn't have to define a bunch of stuff # +# =========================================================================== # + + +# ------------------------- FOLDER BROWSE Element lazy function ------------------------- # +def FolderBrowse( + button_text='Browse', + target=(ThisRow, -1), + initial_folder=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + change_submits=False, + font=None, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_BROWSE_FOLDER, + target=target, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + disabled=disabled, + button_color=button_color, + change_submits=change_submits, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- FILE BROWSE Element lazy function ------------------------- # +def FileBrowse( + button_text='Browse', + target=(ThisRow, -1), + file_types=(('ALL Files', '*.*'),), + initial_folder=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + font=None, + disabled=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_BROWSE_FILE, + target=target, + file_types=file_types, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + change_submits=change_submits, + disabled=disabled, + button_color=button_color, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- FILES BROWSE Element (Multiple file selection) lazy function ------------------------- # +def FilesBrowse( + button_text='Browse', + target=(ThisRow, -1), + file_types=(('ALL Files', '*.*'),), + disabled=False, + initial_folder=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + font=None, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_BROWSE_FILES, + target=target, + file_types=file_types, + initial_folder=initial_folder, + change_submits=change_submits, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + disabled=disabled, + button_color=button_color, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- FILE BROWSE Element lazy function ------------------------- # +def FileSaveAs( + button_text='Save As...', + target=(ThisRow, -1), + file_types=(('ALL Files', '*.*'),), + initial_folder=None, + disabled=False, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + font=None, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_SAVEAS_FILE, + target=target, + file_types=file_types, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + disabled=disabled, + auto_size_button=auto_size_button, + button_color=button_color, + change_submits=change_submits, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- SAVE AS Element lazy function ------------------------- # +def SaveAs( + button_text='Save As...', + target=(ThisRow, -1), + file_types=(('ALL Files', '*.*'),), + initial_folder=None, + disabled=False, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + font=None, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_SAVEAS_FILE, + target=target, + file_types=file_types, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + disabled=disabled, + auto_size_button=auto_size_button, + button_color=button_color, + change_submits=change_submits, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- SAVE BUTTON Element lazy function ------------------------- # +def Save( + button_text='Save', + size=(None, None), + auto_size_button=None, + button_color=None, + bind_return_key=True, + disabled=False, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- SUBMIT BUTTON Element lazy function ------------------------- # +def Submit( + button_text='Submit', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- OPEN BUTTON Element lazy function ------------------------- # +def Open( + button_text='Open', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- OK BUTTON Element lazy function ------------------------- # +def OK( + button_text='OK', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- YES BUTTON Element lazy function ------------------------- # +def Ok( + button_text='Ok', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- CANCEL BUTTON Element lazy function ------------------------- # +def Cancel( + button_text='Cancel', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- QUIT BUTTON Element lazy function ------------------------- # +def Quit( + button_text='Quit', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Exit BUTTON Element lazy function ------------------------- # +def Exit( + button_text='Exit', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Up arrow BUTTON Element lazy function ------------------------- # +def Up( + button_text='▲', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=True, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Down arrow BUTTON Element lazy function ------------------------- # +def Down( + button_text='▼', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=True, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Left arrow BUTTON Element lazy function ------------------------- # +def Left( + button_text='◄', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=True, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Right arrow BUTTON Element lazy function ------------------------- # +def Right( + button_text='►', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=True, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- YES BUTTON Element lazy function ------------------------- # +def Yes( + button_text='Yes', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=True, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- NO BUTTON Element lazy function ------------------------- # +def No( + button_text='No', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- NO BUTTON Element lazy function ------------------------- # +def Help( + button_text='Help', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + font=None, + tooltip=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # +def SimpleButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + disabled=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_CLOSES_WIN, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + disabled=disabled, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- CLOSE BUTTON Element lazy function ------------------------- # +def CloseButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + disabled=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_CLOSES_WIN, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + disabled=disabled, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +CButton = CloseButton + + +# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # +def ReadButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + disabled=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + disabled=disabled, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +ReadFormButton = ReadButton +RButton = ReadFormButton + + +# ------------------------- Realtime BUTTON Element lazy function ------------------------- # +def RealtimeButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + disabled=False, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_REALTIME, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + disabled=disabled, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Dummy BUTTON Element lazy function ------------------------- # +def DummyButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + disabled=False, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_CLOSES_WIN_ONLY, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Calendar Chooser Button lazy function ------------------------- # +def CalendarButton( + button_text, + target=(None, None), + close_when_date_chosen=True, + default_date_m_d_y=(None, None, None), + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + tooltip=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + button = Button( + button_text=button_text, + button_type=BUTTON_TYPE_CALENDAR_CHOOSER, + target=target, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + button.CalendarCloseWhenChosen = close_when_date_chosen + button.DefaultDate_M_D_Y = default_date_m_d_y + return button + + +# ------------------------- Calendar Chooser Button lazy function ------------------------- # +def ColorChooserButton( + button_text, + target=(None, None), + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + tooltip=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_COLOR_CHOOSER, + target=target, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +##################################### ----- RESULTS ------ ################################################## + + +def AddToReturnDictionary(form, element, value): + form.ReturnValuesDictionary[element.Key] = value + return + if element.Key is None: + form.ReturnValuesDictionary[form.DictionaryKeyCounter] = value + element.Key = form.DictionaryKeyCounter + form.DictionaryKeyCounter += 1 + else: + form.ReturnValuesDictionary[element.Key] = value + + +def AddToReturnList(form, value): + form.ReturnValuesList.append(value) + + +# ----------------------------------------------------------------------------# +# ------- FUNCTION InitializeResults. Sets up form results matrix --------# +def InitializeResults(form): + BuildResults(form, True, form) + return + + +# ===== Radio Button RadVar encoding and decoding =====# +# ===== The value is simply the row * 1000 + col =====# +def DecodeRadioRowCol(RadValue): + row = RadValue // 1000 + col = RadValue % 1000 + return row, col + + +def EncodeRadioRowCol(row, col): + RadValue = row * 1000 + col + return RadValue + + +# ------- FUNCTION BuildResults. Form exiting so build the results to pass back ------- # +# format of return values is +# (Button Pressed, input_values) +def BuildResults(form, initialize_only, top_level_form): + # Results for elements are: + # TEXT - Nothing + # INPUT - Read value from TK + # Button - Button Text and position as a Tuple + + # Get the initialized results so we don't have to rebuild + form.DictionaryKeyCounter = 0 + form.ReturnValuesDictionary = {} + form.ReturnValuesList = [] + BuildResultsForSubform(form, initialize_only, top_level_form) + if not top_level_form.LastButtonClickedWasRealtime: + top_level_form.LastButtonClicked = None + return form.ReturnValues + + +def BuildResultsForSubform(form, initialize_only, top_level_form): + button_pressed_text = top_level_form.LastButtonClicked + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + if element.Key is not None and WRITE_ONLY_KEY in str(element.Key): + continue + value = None + if element.Type == ELEM_TYPE_COLUMN: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if element.Type == ELEM_TYPE_FRAME: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if element.Type == ELEM_TYPE_TAB_GROUP: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if element.Type == ELEM_TYPE_TAB: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if not initialize_only: + if element.Type == ELEM_TYPE_INPUT_TEXT: + element = element # type: InputText + value = element.Widget.get_value() + if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: + element.Widget.set_value('') + elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: + element = element # type: Checkbox + value = element.Widget.get_value() + elif element.Type == ELEM_TYPE_INPUT_RADIO: + # RadVar = element.TKIntVar.get() + # this_rowcol = EncodeRadioRowCol(row_num, col_num) + value = False + elif element.Type == ELEM_TYPE_BUTTON: + if top_level_form.LastButtonClicked == element.ButtonText: + button_pressed_text = top_level_form.LastButtonClicked + if element.BType != BUTTON_TYPE_REALTIME: # Do not clear realtime buttons + top_level_form.LastButtonClicked = None + if element.BType == BUTTON_TYPE_CALENDAR_CHOOSER: + try: + value = element.TKCal.selection + except: + value = None + else: + try: + value = element.TKStringVar.get() + except: + value = None + elif element.Type == ELEM_TYPE_INPUT_COMBO: + element = element # type: Combo + value = element.Widget.get_value() + elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: + # value = element.TKStringVar.get() + value = None + elif element.Type == ELEM_TYPE_INPUT_LISTBOX: + element = element # type: Listbox + value = element.Widget.get_value() + value = [ + value, + ] + # items = element.TKListbox.curselection() + # value = [element.Values[int(item)] for item in items] + elif element.Type == ELEM_TYPE_INPUT_SPIN: + element = element # type: Spin + value = element.Widget.get_value() + elif element.Type == ELEM_TYPE_INPUT_SLIDER: + element = element # type: Slider + value = element.Widget.get_value() + elif element.Type == ELEM_TYPE_INPUT_MULTILINE: + element = element # type: Multiline + if element.WriteOnly: + continue + value = element.Widget.get_value() + elif element.Type == ELEM_TYPE_TAB_GROUP: + try: + value = element.TKNotebook.tab(element.TKNotebook.index('current'))['text'] + tab_key = element.FindKeyFromTabName(value) + if tab_key is not None: + value = tab_key + except: + value = None + elif element.Type == ELEM_TYPE_TABLE: + element = element # type:Table + value = [ + element.SelectedRow, + ] + elif element.Type == ELEM_TYPE_TREE: + value = element.SelectedRows + elif element.Type == ELEM_TYPE_GRAPH: + value = element.ClickPosition + elif element.Type == ELEM_TYPE_MENUBAR: + value = element.MenuItemChosen + else: + value = None + + # if an input type element, update the results + if element.Type != ELEM_TYPE_BUTTON and element.Type != ELEM_TYPE_TEXT and element.Type != ELEM_TYPE_IMAGE and element.Type != ELEM_TYPE_OUTPUT and element.Type != ELEM_TYPE_PROGRESS_BAR and element.Type != ELEM_TYPE_COLUMN and element.Type != ELEM_TYPE_FRAME and element.Type != ELEM_TYPE_TAB: + AddToReturnList(form, value) + AddToReturnDictionary(top_level_form, element, value) + elif ( + (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_CALENDAR_CHOOSER and element.Target == (None, None)) + or (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_COLOR_CHOOSER and element.Target == (None, None)) + or ( + element.Type == ELEM_TYPE_BUTTON + and element.Key is not None + and ( + element.BType + in ( + BUTTON_TYPE_SAVEAS_FILE, + BUTTON_TYPE_BROWSE_FILE, + BUTTON_TYPE_BROWSE_FILES, + BUTTON_TYPE_BROWSE_FOLDER, + ) + ) + ) + ): + AddToReturnList(form, value) + AddToReturnDictionary(top_level_form, element, value) + + # if this is a column, then will fail so need to wrap with tr + try: + if form.ReturnKeyboardEvents and form.LastKeyboardEvent is not None: + button_pressed_text = form.LastKeyboardEvent + form.LastKeyboardEvent = None + except: + pass + + try: + form.ReturnValuesDictionary.pop(None, None) # clean up dictionary include None was included + except: + pass + + if not form.UseDictionary: + form.ReturnValues = button_pressed_text, form.ReturnValuesList + else: + form.ReturnValues = button_pressed_text, form.ReturnValuesDictionary + + return form.ReturnValues + + +def _FillFormWithValues(form, values_dict): + _FillSubformWithValues(form, values_dict) + + +def _FillSubformWithValues(form, values_dict): + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + value = None + if element.Type == ELEM_TYPE_COLUMN: + _FillSubformWithValues(element, values_dict) + if element.Type == ELEM_TYPE_FRAME: + _FillSubformWithValues(element, values_dict) + if element.Type == ELEM_TYPE_TAB_GROUP: + _FillSubformWithValues(element, values_dict) + if element.Type == ELEM_TYPE_TAB: + _FillSubformWithValues(element, values_dict) + try: + value = values_dict[element.Key] + except: + continue + if element.Type == ELEM_TYPE_INPUT_TEXT: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_RADIO: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_COMBO: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_LISTBOX: + element.SetValue(value) + elif element.Type == ELEM_TYPE_INPUT_SLIDER: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_MULTILINE: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_SPIN: + element.Update(value) + elif element.Type == ELEM_TYPE_BUTTON: + element.Update(value) + + +def _FindElementFromKeyInSubForm(form, key): + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + if element.Type == ELEM_TYPE_COLUMN: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_FRAME: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB_GROUP: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Key == key: + return element + + +def _FindElementWithFocusInSubForm(form): + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + if element.Type == ELEM_TYPE_COLUMN: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_FRAME: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB_GROUP: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_INPUT_TEXT: + if element.TKEntry is not None: + if element.TKEntry is element.TKEntry.focus_get(): + return element + if element.Type == ELEM_TYPE_INPUT_MULTILINE: + if element.TKText is not None: + if element.TKText is element.TKText.focus_get(): + return element + + +def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): + # m3 = gui.MenuItem('Dialog', width=100, height=30) + # m3.onclick.connect(self.menu_dialog_clicked) + # menu.append([m1, m2, m3]) + + return_val = None + if type(sub_menu_info) is str: + if not is_sub_menu and not skip: + # print(f'Adding command {sub_menu_info}') + pos = sub_menu_info.find('&') + if pos != -1: + if pos == 0 or sub_menu_info[pos - 1] != '\\': + sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos + 1 :] + if sub_menu_info == '---': + # top_menu.add('separator') + pass + else: + try: + item_without_key = sub_menu_info[: sub_menu_info.index(MENU_KEY_SEPARATOR)] + except: + item_without_key = sub_menu_info + if item_without_key[0] == MENU_DISABLED_CHARACTER: + menu_item = remi.gui.MenuItem(item_without_key[1:], width=100, height=30) + menu_item.set_enabled(False) + top_menu.append( + [ + menu_item, + ] + ) + + # TODO add callback here! + # TODO disable entry + else: + menu_item = remi.gui.MenuItem(item_without_key, width=100, height=30) + top_menu.append( + [ + menu_item, + ] + ) + # menu_item.set_on_click_listener(element._ChangedCallbackMenu, sub_menu_info) + menu_item.onclick.connect(element._ChangedCallbackMenu, sub_menu_info) + else: + i = 0 + while i < (len(sub_menu_info)): + item = sub_menu_info[i] + if i != len(sub_menu_info) - 1: + if type(sub_menu_info[i + 1]) == list: + pos = sub_menu_info[i].find('&') + if pos != -1: + if pos == 0 or sub_menu_info[i][pos - 1] != '\\': + sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1 :] + if sub_menu_info[i][0] == MENU_DISABLED_CHARACTER: + new_menu = remi.gui.MenuItem(sub_menu_info[i][len(MENU_DISABLED_CHARACTER) :], width=100, height=30) + new_menu.set_enabled(False) + + # TODO Disable Entry + else: + new_menu = remi.gui.MenuItem(sub_menu_info[i], width=100, height=30) + + top_menu.append( + [ + new_menu, + ] + ) + return_val = new_menu + AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) + i += 1 # skip the next one + else: + AddMenuItem(top_menu, item, element) + else: + AddMenuItem(top_menu, item, element) + i += 1 + return return_val + + +""" + ::::::::: :::::::::: ::: ::: ::::::::::: + :+: :+: :+: :+:+: :+:+: :+: + +:+ +:+ +:+ +:+ +:+:+ +:+ +:+ + +#++:++#: +#++:++# +#+ +:+ +#+ +#+ + +#+ +#+ +#+ +#+ +#+ +#+ + #+# #+# #+# #+# #+# #+# + ### ### ########## ### ### ########### +""" +# ------------------------------------------------------------------------------------------------------------ # +# ===================================== REMI CODE STARTS HERE ================================================ # +# ------------------------------------------------------------------------------------------------------------ # + + +def PackFormIntoFrame(form, containing_frame, toplevel_form): + def CharWidthInPixels(): + return tkinter.font.Font().measure('A') # single character width + + def pad_widget(widget): + lrsizer = wx.BoxSizer(wx.HORIZONTAL) + if full_element_pad[1] == full_element_pad[3]: # if right = left + lrsizer.Add(widget, 0, wx.LEFT | wx.RIGHT, border=full_element_pad[1]) + else: + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(widget, 0, wx.LEFT, border=full_element_pad[3]) + lrsizer.Add(sizer, 0, wx.RIGHT, border=full_element_pad[1]) + + top_bottom_sizer = wx.BoxSizer(wx.HORIZONTAL) + if full_element_pad[0] == full_element_pad[2]: # if top = bottom + top_bottom_sizer.Add(lrsizer, 0, wx.TOP | wx.BOTTOM, border=full_element_pad[0]) + else: + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(lrsizer, 0, wx.TOP, border=full_element_pad[0]) + top_bottom_sizer.Add(sizer, 0, wx.BOTTOM, border=full_element_pad[2]) + return top_bottom_sizer + + # + # font, text color, background color, size, disabled, visible, tooltip + # + def do_font_and_color(widget): + font_info = font_parse_string(font) # family, point size, other + widget.style['font-family'] = font_info[0] + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + widget.style['background-color'] = element.BackgroundColor + if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): + widget.style['color'] = element.TextColor + widget.style['font-size'] = '{}px'.format(font_info[1]) + if element_size[0]: # if size is zero, don't set any sizes + size = convert_tkinter_size_to_Wx(element_size) + widget.style['height'] = '{}px'.format(size[1]) + widget.style['width'] = '{}px'.format(size[0]) + widget.style['margin'] = '{}px {}px {}px {}px'.format(*full_element_pad) + if element.Disabled: + widget.set_enabled(False) + if not element.Visible: + widget.attributes['hidden'] = 'true' + if element.Tooltip is not None: + widget.attributes['title'] = element.Tooltip + + border_depth = toplevel_form.BorderDepth if toplevel_form.BorderDepth is not None else DEFAULT_BORDER_WIDTH + # --------------------------------------------------------------------------- # + # **************** Use FlexForm to build the tkinter window ********** ----- # + # Building is done row by row. # + # --------------------------------------------------------------------------- # + focus_set = False + ######################### LOOP THROUGH ROWS ######################### + # *********** ------- Loop through ROWS ------- ***********# + for row_num, flex_row in enumerate(form.Rows): + ######################### LOOP THROUGH ELEMENTS ON ROW ######################### + # *********** ------- Loop through ELEMENTS ------- ***********# + # *********** Make TK Row ***********# + tk_row_frame = remi.gui.HBox() + tk_row_frame.style['align-items'] = 'flex-start' + if form.ElementJustification.startswith('c'): + tk_row_frame.style['margin-left'] = 'auto' + tk_row_frame.style['margin-right'] = 'auto' + # tk_row_frame.style['justify-content'] = 'center' + elif form.ElementJustification.startswith('r'): + # tk_row_frame.style['justify-content'] = 'flex-end' + tk_row_frame.style['margin-left'] = 'auto' + else: # everything else is left justified + # tk_row_frame.style['justify-content'] = 'flex-flexstart' + tk_row_frame.style['margin-right'] = 'auto' + + if form.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + tk_row_frame.style['background-color'] = form.BackgroundColor + + for col_num, element in enumerate(flex_row): + element.ParentForm = toplevel_form # save the button's parent form object + if toplevel_form.Font and (element.Font == DEFAULT_FONT or not element.Font): + font = toplevel_form.Font + elif element.Font is not None: + font = element.Font + else: + font = DEFAULT_FONT + # ------- Determine Auto-Size setting on a cascading basis ------- # + if element.AutoSizeText is not None: # if element overide + auto_size_text = element.AutoSizeText + elif toplevel_form.AutoSizeText is not None: # if form override + auto_size_text = toplevel_form.AutoSizeText + else: + auto_size_text = DEFAULT_AUTOSIZE_TEXT + element_type = element.Type + # Set foreground color + text_color = element.TextColor + # Determine Element size + element_size = element.Size + if element_size == (None, None) and element_type != ELEM_TYPE_BUTTON: # user did not specify a size + element_size = toplevel_form.DefaultElementSize + elif element_size == (None, None) and element_type == ELEM_TYPE_BUTTON: + element_size = toplevel_form.DefaultButtonElementSize + else: + auto_size_text = False # if user has specified a size then it shouldn't autosize + + full_element_pad = [0, 0, 0, 0] # Top, Right, Bottom, Left + elementpad = element.Pad if element.Pad is not None else toplevel_form.ElementPadding + if type(elementpad[0]) != tuple: # left and right + full_element_pad[1] = full_element_pad[3] = elementpad[0] + else: + full_element_pad[3], full_element_pad[1] = elementpad[0] + if type(elementpad[1]) != tuple: # top and bottom + full_element_pad[0] = full_element_pad[2] = elementpad[1] + else: + full_element_pad[0], full_element_pad[2] = elementpad[1] + + # ------------------------- COLUMN element ------------------------- # + if element_type == ELEM_TYPE_COLUMN: + element = element # type: Column + element.Widget = column_widget = remi.gui.VBox() + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + column_widget.style['background-color'] = element.BackgroundColor + PackFormIntoFrame(element, column_widget, toplevel_form) + tk_row_frame.append(element.Widget) + + # ------------------------- TEXT element ------------------------- # + elif element_type == ELEM_TYPE_TEXT: + element = element # type: Text + element.Widget = remi.gui.Label(element.DisplayText) + do_font_and_color(element.Widget) + if auto_size_text and element.Size == (None, None): + del element.Widget.style['width'] + if element.Justification: + if element.Justification.startswith('c'): + element.Widget.style['text-align'] = 'center' + elif element.Justification.startswith('r'): + element.Widget.style['text-align'] = 'right' + if element.ClickSubmits: + element.Widget.onclick.connect(element._ChangedCallback) + tk_row_frame.append(element.Widget) + + # ------------------------- BUTTON element ------------------------- # + elif element_type == ELEM_TYPE_BUTTON: + element = element # type: Button + size = convert_tkinter_size_to_Wx(element_size) + element.Widget = remi.gui.Button(element.ButtonText, width=size[0], height=size[1], margin='10px') + element.Widget.onclick.connect(element._ButtonCallBack) + do_font_and_color(element.Widget) + if element.AutoSizeButton or (toplevel_form.AutoSizeButtons and element.AutoSizeButton is not False) and element.Size == (None, None): + del element.Widget.style['width'] + if element.ImageFilename: + element.ImageWidget = SuperImage(element.ImageFilename if element.ImageFilename is not None else element.ImageData) + element.Widget.append(element.ImageWidget) + tk_row_frame.append(element.Widget) + + # stringvar = tk.StringVar() + # element.TKStringVar = stringvar + # element.Location = (row_num, col_num) + # btext = element.ButtonText + # btype = element.BType + # if element.AutoSizeButton is not None: + # auto_size = element.AutoSizeButton + # else: + # auto_size = toplevel_form.AutoSizeButtons + # if auto_size is False or element.Size[0] is not None: + # width, height = element_size + # else: + # width = 0 + # height = toplevel_form.DefaultButtonElementSize[1] + # if element.ButtonColor != (None, None) and element.ButtonColor != DEFAULT_BUTTON_COLOR: + # bc = element.ButtonColor + # elif toplevel_form.ButtonColor != (None, None) and toplevel_form.ButtonColor != DEFAULT_BUTTON_COLOR: + # bc = toplevel_form.ButtonColor + # else: + # bc = DEFAULT_BUTTON_COLOR + # border_depth = element.BorderWidth + # if btype != BUTTON_TYPE_REALTIME: + # tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height, + # command=element.ButtonCallBack, justify=tk.LEFT, bd=border_depth, font=font) + # else: + # tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height, justify=tk.LEFT, + # bd=border_depth, font=font) + # tkbutton.bind('', element.ButtonReleaseCallBack) + # tkbutton.bind('', element.ButtonPressCallBack) + # if bc != (None, None) and bc != COLOR_SYSTEM_DEFAULT and bc[1] != COLOR_SYSTEM_DEFAULT: + # tkbutton.config(foreground=bc[0], background=bc[1], activebackground=bc[1]) + # elif bc[1] == COLOR_SYSTEM_DEFAULT: + # tkbutton.config(foreground=bc[0]) + # + # element.TKButton = tkbutton # not used yet but save the TK button in case + # wraplen = tkbutton.winfo_reqwidth() # width of widget in Pixels + # if element.ImageFilename: # if button has an image on it + # tkbutton.config(highlightthickness=0) + # photo = tk.PhotoImage(file=element.ImageFilename) + # if element.ImageSize != (None, None): + # width, height = element.ImageSize + # if element.ImageSubsample: + # photo = photo.subsample(element.ImageSubsample) + # else: + # width, height = photo.width(), photo.height() + # tkbutton.config(image=photo, compound=tk.CENTER, width=width, height=height) + # tkbutton.image = photo + # if element.ImageData: # if button has an image on it + # tkbutton.config(highlightthickness=0) + # photo = tk.PhotoImage(data=element.ImageData) + # if element.ImageSize != (None, None): + # width, height = element.ImageSize + # if element.ImageSubsample: + # photo = photo.subsample(element.ImageSubsample) + # else: + # width, height = photo.width(), photo.height() + # tkbutton.config(image=photo, compound=tk.CENTER, width=width, height=height) + # tkbutton.image = photo + # if width != 0: + # tkbutton.configure(wraplength=wraplen + 10) # set wrap to width of widget + # tkbutton.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.BindReturnKey: + # element.TKButton.bind('', element.ReturnKeyHandler) + # if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): + # focus_set = True + # element.TKButton.bind('', element.ReturnKeyHandler) + # element.TKButton.focus_set() + # toplevel_form.TKroot.focus_force() + # if element.Disabled == True: + # element.TKButton['state'] = 'disabled' + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKButton, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # # ------------------------- INPUT element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_TEXT: + element = element # type: InputText + element.Widget = InputText.TextInput_raw_onkeyup(hint=element.DefaultText) + # element.Widget = remi.gui.TextInput(hint=element.DefaultText) + do_font_and_color(element.Widget) + if element.ChangeSubmits: + element.Widget.onkeyup.connect(element._InputTextCallback) + # element.Widget.onkeydown.connect(element._InputTextCallback) + tk_row_frame.append(element.Widget) + + # show = element.PasswordCharacter if element.PasswordCharacter else "" + # if element.Justification is not None: + # justification = element.Justification + # else: + # justification = DEFAULT_TEXT_JUSTIFICATION + # justify = tk.LEFT if justification == 'left' else tk.CENTER if justification == 'center' else tk.RIGHT + # # anchor = tk.NW if justification == 'left' else tk.N if justification == 'center' else tk.NE + # element.TKEntry = tk.Entry(tk_row_frame, width=element_size[0], textvariable=element.TKStringVar, + # bd=border_depth, font=font, show=show, justify=justify) + # if element.ChangeSubmits: + # element.TKEntry.bind('', element.KeyboardHandler) + # element.TKEntry.bind('', element.ReturnKeyHandler) + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element.TKEntry.configure(background=element.BackgroundColor) + # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: + # element.TKEntry.configure(fg=text_color) + # element.TKEntry.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='x') + # if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): + # focus_set = True + # element.TKEntry.focus_set() + # if element.Disabled: + # element.TKEntry['state'] = 'disabled' + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKEntry, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- COMBO element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_COMBO: + element = element # type: Combo + element.Widget = remi.gui.DropDown.new_from_list(element.Values) + if element.DefaultValue is not None: + element.Widget.select_by_value(element.DefaultValue) + do_font_and_color(element.Widget) + if element.ChangeSubmits: + element.Widget.onchange.connect(element._ChangedCallback) + tk_row_frame.append(element.Widget) + + # ------------------------- OPTION MENU (Like ComboBox but different) element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_OPTION_MENU: + element.Widget = remi.gui.FileUploader('./', width=200, height=30, margin='10px') + + # element.Widget = remi.gui.FileFolderNavigator(False, r'a:\TEMP', True, False) + tk_row_frame.append(element.Widget) + pass + # ------------------------- LISTBOX element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_LISTBOX: + element = element # type: Listbox + element.Widget = remi.gui.ListView.new_from_list(element.Values) + do_font_and_color(element.Widget) + if element.ChangeSubmits: + element.Widget.onselection.connect(element._ChangedCallback) + tk_row_frame.append(element.Widget) + # max_line_len = max([len(str(l)) for l in element.Values]) if len(element.Values) != 0 else 0 + # if auto_size_text is False: + # width = element_size[0] + # else: + # width = max_line_len + # listbox_frame = tk.Frame(tk_row_frame) + # element.TKStringVar = tk.StringVar() + # element.TKListbox = tk.Listbox(listbox_frame, height=element_size[1], width=width, + # selectmode=element.SelectMode, font=font) + # for index, item in enumerate(element.Values): + # element.TKListbox.insert(tk.END, item) + # if element.DefaultValues is not None and item in element.DefaultValues: + # element.TKListbox.selection_set(index) + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element.TKListbox.configure(background=element.BackgroundColor) + # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: + # element.TKListbox.configure(fg=text_color) + # if element.ChangeSubmits: + # element.TKListbox.bind('<>', element.ListboxSelectHandler) + # vsb = tk.Scrollbar(listbox_frame, orient="vertical", command=element.TKListbox.yview) + # element.TKListbox.configure(yscrollcommand=vsb.set) + # element.TKListbox.pack(side=tk.LEFT) + # vsb.pack(side=tk.LEFT, fill='y') + # listbox_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.BindReturnKey: + # element.TKListbox.bind('', element.ListboxSelectHandler) + # element.TKListbox.bind('', element.ListboxSelectHandler) + # if element.Disabled == True: + # element.TKListbox['state'] = 'disabled' + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKListbox, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- INPUT MULTILINE element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_MULTILINE: + element = element # type: Multiline + element.Widget = remi.gui.TextInput(single_line=False, hint=element.DefaultText) + do_font_and_color(element.Widget) + if element.ChangeSubmits: + element.Widget.onkeydown.connect(element._InputTextCallback) + tk_row_frame.append(element.Widget) + # default_text = element.DefaultText + # width, height = element_size + # element.TKText = tk.scrolledtext.ScrolledText(tk_row_frame, width=width, height=height, wrap='word', + # bd=border_depth, font=font) + # element.TKText.insert(1.0, default_text) # set the default text + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element.TKText.configure(background=element.BackgroundColor) + # element.TKText.vbar.config(troughcolor=DEFAULT_SCROLLBAR_COLOR) + # element.TKText.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') + # if element.ChangeSubmits: + # element.TKText.bind('', element.KeyboardHandler) + # if element.EnterSubmits: + # element.TKText.bind('', element.ReturnKeyHandler) + # if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): + # focus_set = True + # element.TKText.focus_set() + # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: + # element.TKText.configure(fg=text_color) + # if element.Disabled == True: + # element.TKText['state'] = 'disabled' + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKText, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) + + # ------------------------- INPUT CHECKBOX element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_CHECKBOX: + element = element # type: Checkbox + element.Widget = remi.gui.CheckBoxLabel(element.Text) + if element.InitialState: + element.Widget.set_value(element.InitialState) + if element.ChangeSubmits: + element.Widget.onchange.connect(element._ChangedCallback) + do_font_and_color(element.Widget) + tk_row_frame.append(element.Widget) + + # width = 0 if auto_size_text else element_size[0] + # default_value = element.InitialState + # element.TKIntVar = tk.IntVar() + # element.TKIntVar.set(default_value if default_value is not None else 0) + # if element.ChangeSubmits: + # element.TKCheckbutton = tk.Checkbutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, + # variable=element.TKIntVar, bd=border_depth, font=font, + # command=element.CheckboxHandler) + # else: + # element.TKCheckbutton = tk.Checkbutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, + # variable=element.TKIntVar, bd=border_depth, font=font) + # if default_value is None or element.Disabled: + # element.TKCheckbutton.configure(state='disable') + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element.TKCheckbutton.configure(background=element.BackgroundColor) + # element.TKCheckbutton.configure(selectcolor=element.BackgroundColor) + # element.TKCheckbutton.configure(activebackground=element.BackgroundColor) + # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: + # element.TKCheckbutton.configure(fg=text_color) + # element.TKCheckbutton.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKCheckbutton, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # # ------------------------- PROGRESS BAR element ------------------------- # + elif element_type == ELEM_TYPE_PROGRESS_BAR: + pass + # # save this form because it must be 'updated' (refreshed) solely for the purpose of updating bar + # width = element_size[0] + # fnt = tkinter.font.Font() + # char_width = fnt.measure('A') # single character width + # progress_length = width * char_width + # progress_width = element_size[1] + # direction = element.Orientation + # if element.BarColor != (None, None): # if element has a bar color, use it + # bar_color = element.BarColor + # else: + # bar_color = DEFAULT_PROGRESS_BAR_COLOR + # element.TKProgressBar = TKProgressBar(tk_row_frame, element.MaxValue, progress_length, progress_width, + # orientation=direction, BarColor=bar_color, + # border_width=element.BorderWidth, relief=element.Relief, + # style=element.BarStyle, key=element.Key) + # element.TKProgressBar.TKProgressBarForReal.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # ------------------------- INPUT RADIO BUTTON element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_RADIO: + pass + # width = 0 if auto_size_text else element_size[0] + # default_value = element.InitialState + # ID = element.GroupID + # # see if ID has already been placed + # value = EncodeRadioRowCol(row_num, col_num) # value to set intvar to if this radio is selected + # if ID in toplevel_form.RadioDict: + # RadVar = toplevel_form.RadioDict[ID] + # else: + # RadVar = tk.IntVar() + # toplevel_form.RadioDict[ID] = RadVar + # element.TKIntVar = RadVar # store the RadVar in Radio object + # if default_value: # if this radio is the one selected, set RadVar to match + # element.TKIntVar.set(value) + # if element.ChangeSubmits: + # element.TKRadio = tk.Radiobutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, + # variable=element.TKIntVar, value=value, bd=border_depth, font=font, + # command=element.RadioHandler) + # else: + # element.TKRadio = tk.Radiobutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, + # variable=element.TKIntVar, value=value, bd=border_depth, font=font) + # if not element.BackgroundColor in (None, COLOR_SYSTEM_DEFAULT): + # element.TKRadio.configure(background=element.BackgroundColor) + # element.TKRadio.configure(selectcolor=element.BackgroundColor) + # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: + # element.TKRadio.configure(fg=text_color) + # if element.Disabled: + # element.TKRadio['state'] = 'disabled' + # element.TKRadio.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKRadio, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- INPUT SPIN element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_SPIN: + element = element # type: Spin + element.Widget = remi.gui.SpinBox(50, 0, 100) + if element.DefaultValue is not None: + element.Widget.set_value(element.DefaultValue) + do_font_and_color(element.Widget) + if element.ChangeSubmits: + element.Widget.onchange.connect(element._ChangedCallback) + tk_row_frame.append(element.Widget) + # width, height = element_size + # width = 0 if auto_size_text else element_size[0] + # element.TKStringVar = tk.StringVar() + # element.TKSpinBox = tk.Spinbox(tk_row_frame, values=element.Values, textvariable=element.TKStringVar, + # width=width, bd=border_depth) + # element.TKStringVar.set(element.DefaultValue) + # element.TKSpinBox.configure(font=font) # set wrap to width of widget + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element.TKSpinBox.configure(background=element.BackgroundColor) + # element.TKSpinBox.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: + # element.TKSpinBox.configure(fg=text_color) + # if element.ChangeSubmits: + # element.TKSpinBox.bind('', element.SpinChangedHandler) + # if element.Disabled == True: + # element.TKSpinBox['state'] = 'disabled' + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKSpinBox, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- OUTPUT element ------------------------- # + elif element_type == ELEM_TYPE_OUTPUT: + element = element # type: Output + element.Widget = remi.gui.TextInput(single_line=False) + element.Disabled = True + do_font_and_color(element.Widget) + tk_row_frame.append(element.Widget) + toplevel_form.OutputElementForStdOut = element + Window.stdout_is_rerouted = True + Window.stdout_string_io = StringIO() + sys.stdout = Window.stdout_string_io + + # width, height = element_size + # element._TKOut = TKOutput(tk_row_frame, width=width, height=height, bd=border_depth, + # background_color=element.BackgroundColor, text_color=text_color, font=font, + # pad=element.Pad) + # element._TKOut.pack(side=tk.LEFT, expand=True, fill='both') + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element._TKOut, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- OUTPUT MULTILINE element ------------------------- # + elif element_type == ELEM_TYPE_MULTILINE_OUTPUT: + element = element # type: MultilineOutput + element.Widget = remi.gui.TextInput(single_line=False) + element.Disabled = True + do_font_and_color(element.Widget) + tk_row_frame.append(element.Widget) + if element.DefaultText: + element.Widget.set_value(element.DefaultText) + # ------------------------- IMAGE element ------------------------- # + elif element_type == ELEM_TYPE_IMAGE: + element = element # type: Image + # element.Widget = remi.gui.Image(element.Filename) + element.Widget = SuperImage(element.Filename if element.Filename is not None else element.Data) + if element.Filename is not None: + # print(f'loading image filename in pack frame {element.Filename}') + element.Widget.load(element.Filename) + do_font_and_color(element.Widget) + if element.EnableEvents: + element.Widget.onclick.connect(element._ChangedCallback) + tk_row_frame.append(element.Widget) + # if element.Filename is not None: + # photo = tk.PhotoImage(file=element.Filename) + # elif element.Data is not None: + # photo = tk.PhotoImage(data=element.Data) + # else: + # photo = None + # print('*ERROR laying out form.... Image Element has no image specified*') + # + # if photo is not None: + # if element_size == ( + # None, None) or element_size == None or element_size == toplevel_form.DefaultElementSize: + # width, height = photo.width(), photo.height() + # else: + # width, height = element_size + # if photo is not None: + # element.tktext_label = tk.Label(tk_row_frame, image=photo, width=width, height=height, + # bd=border_depth) + # else: + # element.tktext_label = tk.Label(tk_row_frame, width=width, height=height, bd=border_depth) + # if element.BackgroundColor is not None: + # element.tktext_label.config(background=element.BackgroundColor); + # + # element.tktext_label.image = photo + # # tktext_label.configure(anchor=tk.NW, image=photo) + # element.tktext_label.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.tktext_label, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- Canvas element ------------------------- # + elif element_type == ELEM_TYPE_CANVAS: + pass + # width, height = element_size + # if element._TKCanvas is None: + # element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) + # else: + # element._TKCanvas.master = tk_row_frame + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) + # element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + + # ------------------------- Graph element ------------------------- # + elif element_type == ELEM_TYPE_GRAPH: + element = element # type: Graph + element.Widget = remi.gui.Svg(width=element.CanvasSize[0], height=element.CanvasSize[1]) + element.SvgGroup = remi.gui.SvgSubcontainer(0, 0, '100%', '100%') + element.Widget.append( + [ + element.SvgGroup, + ] + ) + do_font_and_color(element.Widget) + if element.ChangeSubmits: + element.Widget.onmouseup.connect(element._MouseUpCallback) + # element.Widget.onclick.connect(element.ClickCallback) + if element.DragSubmits: + element.Widget.onmousedown.connect(element._MouseDownCallback) + element.Widget.onmouseup.connect(element._MouseUpCallback) + element.Widget.onmousemove.connect(element._DragCallback) + + tk_row_frame.append(element.Widget) + # width, height = element_size + # if element._TKCanvas is None: + # element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) + # else: + # element._TKCanvas.master = tk_row_frame + # element._TKCanvas2 = tk.Canvas(element._TKCanvas, width=width, height=height, bd=border_depth) + # element._TKCanvas2.pack(side=tk.LEFT) + # element._TKCanvas2.addtag_all('mytag') + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element._TKCanvas2.configure(background=element.BackgroundColor, highlightthickness=0) + # element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) + # element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # if element.ChangeSubmits: + # element._TKCanvas2.bind('', element.ButtonReleaseCallBack) + # element._TKCanvas2.bind('', element.ButtonPressCallBack) + # if element.DragSubmits: + # element._TKCanvas2.bind('', element.MotionCallBack) + # ------------------------- MENUBAR element ------------------------- # + elif element_type == ELEM_TYPE_MENUBAR: + element = element # type: Menu + menu = remi.gui.Menu(width='100%', height=str(element_size[1])) + element_size = (0, 0) # makes the menu span across the top + do_font_and_color(menu) + + menu_def = element.MenuDefinition + for menu_entry in menu_def: + # print(f'Adding a Menubar ENTRY {menu_entry}') + pos = menu_entry[0].find('&') + # print(pos) + if pos != -1: + if pos == 0 or menu_entry[0][pos - 1] != '\\': + menu_entry[0] = menu_entry[0][:pos] + menu_entry[0][pos + 1 :] + if menu_entry[0][0] == MENU_DISABLED_CHARACTER: + item = remi.gui.MenuItem(menu_entry[0][1:], width=100, height=element_size[1]) + item.set_enabled(False) + else: + item = remi.gui.MenuItem(menu_entry[0], width=100, height=element_size[1]) + do_font_and_color(item) + menu.append( + [ + item, + ] + ) + if len(menu_entry) > 1: + AddMenuItem(item, menu_entry[1], element) + + element.Widget = menubar = remi.gui.MenuBar(width='100%', height='30px') + element.Widget.style['z-index'] = '1' + menubar.append(menu) + # tk_row_frame.append(element.Widget) + containing_frame.append(element.Widget) + + # ------------------------- Frame element ------------------------- # + elif element_type == ELEM_TYPE_FRAME: + element = element # type: Frame + # element.Widget = column_widget = remi.gui.VBox() + element.Widget = column_widget = CLASSframe(element.Title) + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + column_widget.style['background-color'] = element.BackgroundColor + PackFormIntoFrame(element, column_widget, toplevel_form) + tk_row_frame.append(element.Widget) + + # + # element = element # type: Frame + # element.Widget = column_widget = remi.gui.VBox() + # if element.Justification.startswith('c'): + # column_widget.style['align-items'] = 'center' + # column_widget.style['justify-content'] = 'center' + # else: + # column_widget.style['justify-content'] = 'flex-start' + # column_widget.style['align-items'] = 'baseline' + # if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + # column_widget.style['background-color'] = element.BackgroundColor + # PackFormIntoFrame(element, column_widget, toplevel_form) + # tk_row_frame.append(element.Widget) + + # labeled_frame = tk.LabelFrame(tk_row_frame, text=element.Title, relief=element.Relief) + # PackFormIntoFrame(element, labeled_frame, toplevel_form) + # labeled_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: + # labeled_frame.configure(background=element.BackgroundColor, + # highlightbackground=element.BackgroundColor, + # highlightcolor=element.BackgroundColor) + # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: + # labeled_frame.configure(foreground=element.TextColor) + # if font is not None: + # labeled_frame.configure(font=font) + # if element.TitleLocation is not None: + # labeled_frame.configure(labelanchor=element.TitleLocation) + # if element.BorderWidth is not None: + # labeled_frame.configure(borderwidth=element.BorderWidth) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(labeled_frame, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- Tab element ------------------------- # + elif element_type == ELEM_TYPE_TAB: + element = element # type: Tab + element.Widget = remi.gui.VBox() + if element.Justification.startswith('c'): + # print('CENTERING') + element.Widget.style['align-items'] = 'center' + element.Widget.style['justify-content'] = 'center' + else: + element.Widget.style['justify-content'] = 'flex-start' + element.Widget.style['align-items'] = 'baseline' + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + element.Widget.style['background-color'] = element.BackgroundColor + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + element.Widget.style['background-color'] = element.BackgroundColor + PackFormIntoFrame(element, element.Widget, toplevel_form) + # tk_row_frame.append(element.Widget) + containing_frame.add_tab(element.Widget, element.Title, None) + + # element.TKFrame = tk.Frame(form.TKNotebook) + # PackFormIntoFrame(element, element.TKFrame, toplevel_form) + # if element.Disabled: + # form.TKNotebook.add(element.TKFrame, text=element.Title, state='disabled') + # else: + # form.TKNotebook.add(element.TKFrame, text=element.Title) + # form.TKNotebook.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # element.ParentNotebook = form.TKNotebook + # element.TabID = form.TabCount + # form.TabCount += 1 + # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: + # element.TKFrame.configure(background=element.BackgroundColor, + # highlightbackground=element.BackgroundColor, + # highlightcolor=element.BackgroundColor) + # # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: + # # element.TKFrame.configure(foreground=element.TextColor) + # + # # ttk.Style().configure("TNotebook", background='red') + # # ttk.Style().map("TNotebook.Tab", background=[("selected", 'orange')], + # # foreground=[("selected", 'green')]) + # # ttk.Style().configure("TNotebook.Tab", background='blue', foreground='yellow') + # + # if element.BorderWidth is not None: + # element.TKFrame.configure(borderwidth=element.BorderWidth) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKFrame, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- TabGroup element ------------------------- # + elif element_type == ELEM_TYPE_TAB_GROUP: + element = element # type: TabGroup + element.Widget = remi.gui.TabBox() + # do_font_and_color(element.Widget) + PackFormIntoFrame(element, element.Widget, toplevel_form) + tk_row_frame.append(element.Widget) + + # custom_style = str(element.Key) + 'customtab.TNotebook' + # style = ttk.Style(tk_row_frame) + # if element.Theme is not None: + # style.theme_use(element.Theme) + # if element.TabLocation is not None: + # position_dict = {'left': 'w', 'right': 'e', 'top': 'n', 'bottom': 's', 'lefttop': 'wn', + # 'leftbottom': 'ws', 'righttop': 'en', 'rightbottom': 'es', 'bottomleft': 'sw', + # 'bottomright': 'se', 'topleft': 'nw', 'topright': 'ne'} + # try: + # tab_position = position_dict[element.TabLocation] + # except: + # tab_position = position_dict['top'] + # style.configure(custom_style, tabposition=tab_position) + # + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # style.configure(custom_style, background=element.BackgroundColor, foreground='purple') + # + # # style.theme_create("yummy", parent="alt", settings={ + # # "TNotebook": {"configure": {"tabmargins": [2, 5, 2, 0]}}, + # # "TNotebook.Tab": { + # # "configure": {"padding": [5, 1], "background": mygreen}, + # # "map": {"background": [("selected", myred)], + # # "expand": [("selected", [1, 1, 1, 0])]}}}) + # + # # style.configure(custom_style+'.Tab', background='red') + # if element.SelectedTitleColor != None: + # style.map(custom_style + '.Tab', foreground=[("selected", element.SelectedTitleColor)]) + # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + # style.configure(custom_style + '.Tab', foreground=element.TextColor) + # # style.configure(custom_style, background='blue', foreground='yellow') + # + # element.TKNotebook = ttk.Notebook(tk_row_frame, style=custom_style) + # + # PackFormIntoFrame(element, toplevel_form.TKroot, toplevel_form) + # + # if element.ChangeSubmits: + # element.TKNotebook.bind('<>', element.TabGroupSelectHandler) + # if element.BorderWidth is not None: + # element.TKNotebook.configure(borderwidth=element.BorderWidth) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKNotebook, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- SLIDER element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_SLIDER: + element = element # type: Slider + orient = remi.gui.Container.LAYOUT_HORIZONTAL if element.Orientation.lower().startswith('h') else remi.gui.Container.LAYOUT_VERTICAL + # print(f'slider orient = {orient}') + element.Widget = remi.gui.Slider( + layout_orientation=orient, + default_value=element.DefaultValue, + min=element.Range[0], + max=element.Range[1], + step=element.Resolution, + ) + if element.DefaultValue: + element.Widget.set_value(element.DefaultValue) + # if element.Orientation.startswith('v'): + # element.Container.LAYOUT_orientation = remi.gui.Container.LAYOUT_VERTICAL + do_font_and_color(element.Widget) + if element.ChangeSubmits: + element.Widget.onchange.connect(element._SliderCallback) + element.Widget.style['orientation'] = 'vertical' + element.Widget.attributes['orientation'] = 'vertical' + # print(f'slider = {element.Widget.style, element.Widget.attributes}') + tk_row_frame.append(element.Widget) # slider_length = element_size[0] * CharWidthInPixels() + + # ------------------------- TABLE element ------------------------- # + elif element_type == ELEM_TYPE_TABLE: + element = element # type: Table + new_table = [] + for row_num, row in enumerate(element.Values): # convert entire table to strings + new_row = [str(item) for item in row] + if element.DisplayRowNumbers: + new_row = [ + element.RowHeaderText if row_num == 0 else str(row_num + element.StartingRowNumber), + ] + new_row + new_table.append(new_row) + element.Widget = remi.gui.Table.new_from_list(new_table) + do_font_and_color(element.Widget) + tk_row_frame.append(element.Widget) + element.Widget.on_table_row_click.connect(element._on_table_row_click) + # frame = tk.Frame(tk_row_frame) + # + # height = element.NumRows + # if element.Justification == 'left': + # anchor = tk.W + # elif element.Justification == 'right': + # anchor = tk.E + # else: + # anchor = tk.CENTER + # column_widths = {} + # for row in element.Values: + # for i, col in enumerate(row): + # col_width = min(len(str(col)), element.MaxColumnWidth) + # try: + # if col_width > column_widths[i]: + # column_widths[i] = col_width + # except: + # column_widths[i] = col_width + # if element.ColumnsToDisplay is None: + # displaycolumns = element.ColumnHeadings if element.ColumnHeadings is not None else element.Values[0] + # else: + # displaycolumns = [] + # for i, should_display in enumerate(element.ColumnsToDisplay): + # if should_display: + # displaycolumns.append(element.ColumnHeadings[i]) + # column_headings = element.ColumnHeadings + # if element.DisplayRowNumbers: # if display row number, tack on the numbers to front of columns + # displaycolumns = [element.RowHeaderText, ] + displaycolumns + # column_headings = [element.RowHeaderText, ] + element.ColumnHeadings + # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, + # displaycolumns=displaycolumns, show='headings', height=height, + # selectmode=element.SelectMode,) + # treeview = element.TKTreeview + # if element.DisplayRowNumbers: + # treeview.heading(element.RowHeaderText, text=element.RowHeaderText) # make a dummy heading + # treeview.column(element.RowHeaderText, width=50, anchor=anchor) + # + # headings = element.ColumnHeadings if element.ColumnHeadings is not None else element.Values[0] + # for i, heading in enumerate(headings): + # treeview.heading(heading, text=heading) + # if element.AutoSizeColumns: + # width = max(column_widths[i], len(heading)) + # else: + # try: + # width = element.ColumnWidths[i] + # except: + # width = element.DefaultColumnWidth + # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) + # + # # Insert values into the tree + # for i, value in enumerate(element.Values): + # if element.DisplayRowNumbers: + # value = [i+element.StartingRowNumber] + value + # id = treeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i) + # if element.AlternatingRowColor is not None: # alternating colors + # for row in range(0, len(element.Values), 2): + # treeview.tag_configure(row, background=element.AlternatingRowColor) + # if element.RowColors is not None: # individual row colors + # for row_def in element.RowColors: + # if len(row_def) == 2: # only background is specified + # treeview.tag_configure(row_def[0], background=row_def[1]) + # else: + # treeview.tag_configure(row_def[0], background=row_def[2], foreground=row_def[1]) + # + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", background=element.BackgroundColor, + # fieldbackground=element.BackgroundColor) + # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", foreground=element.TextColor) + # if element.RowHeight is not None: + # ttk.Style().configure("Treeview", rowheight=element.RowHeight) + # ttk.Style().configure("Treeview", font=font) + # # scrollable_frame.pack(side=tk.LEFT, padx=elementpad[0], pady=elementpad[1], expand=True, fill='both') + # treeview.bind("<>", element.treeview_selected) + # if element.BindReturnKey: + # treeview.bind('', element.treeview_double_click) + # treeview.bind('', element.treeview_double_click) + # + # scrollbar = tk.Scrollbar(frame) + # scrollbar.pack(side=tk.RIGHT, fill='y') + # scrollbar.config(command=treeview.yview) + # + # if not element.VerticalScrollOnly: + # hscrollbar = tk.Scrollbar(frame, orient=tk.HORIZONTAL) + # hscrollbar.pack(side=tk.BOTTOM, fill='x') + # hscrollbar.config(command=treeview.xview) + # treeview.configure(xscrollcommand=hscrollbar.set) + # + # treeview.configure(yscrollcommand=scrollbar.set) + # + # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') + # if element.Visible is False: + # element.TKTreeview.pack_forget() + # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # if element.RightClickMenu or toplevel_form.RightClickMenu: + # menu = element.RightClickMenu or toplevel_form.RightClickMenu + # top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) + # AddMenuItem(top_menu, menu[1], element) + # element.TKRightClickMenu = top_menu + # element.TKTreeview.bind('', element.RightClickMenuCallback) + pass + # frame = tk.Frame(tk_row_frame) + # + # height = element.NumRows + # if element.Justification == 'left': + # anchor = tk.W + # elif element.Justification == 'right': + # anchor = tk.E + # else: + # anchor = tk.CENTER + # column_widths = {} + # for row in element.Values: + # for i, col in enumerate(row): + # col_width = min(len(str(col)), element.MaxColumnWidth) + # try: + # if col_width > column_widths[i]: + # column_widths[i] = col_width + # except: + # column_widths[i] = col_width + # if element.ColumnsToDisplay is None: + # displaycolumns = element.ColumnHeadings + # else: + # displaycolumns = [] + # for i, should_display in enumerate(element.ColumnsToDisplay): + # if should_display: + # displaycolumns.append(element.ColumnHeadings[i]) + # column_headings = element.ColumnHeadings + # if element.DisplayRowNumbers: # if display row number, tack on the numbers to front of columns + # displaycolumns = [element.RowHeaderText, ] + displaycolumns + # column_headings = [element.RowHeaderText, ] + element.ColumnHeadings + # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, + # displaycolumns=displaycolumns, show='headings', height=height, + # selectmode=element.SelectMode) + # treeview = element.TKTreeview + # if element.DisplayRowNumbers: + # treeview.heading(element.RowHeaderText, text=element.RowHeaderText) # make a dummy heading + # treeview.column(element.RowHeaderText, width=50, anchor=anchor) + # for i, heading in enumerate(element.ColumnHeadings): + # treeview.heading(heading, text=heading) + # if element.AutoSizeColumns: + # width = max(column_widths[i], len(heading)) + # else: + # try: + # width = element.ColumnWidths[i] + # except: + # width = element.DefaultColumnWidth + # + # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) + # # Insert values into the tree + # for i, value in enumerate(element.Values): + # if element.DisplayRowNumbers: + # value = [i + element.StartingRowNumber] + value + # id = treeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i % 2) + # if element.AlternatingRowColor is not None: + # treeview.tag_configure(1, background=element.AlternatingRowColor) + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", background=element.BackgroundColor, + # fieldbackground=element.BackgroundColor) + # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", foreground=element.TextColor) + # # scrollable_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') + # treeview.bind("<>", element.treeview_selected) + # if element.BindReturnKey: + # treeview.bind('', element.treeview_double_click) + # treeview.bind('', element.treeview_double_click) + # scrollbar = tk.Scrollbar(frame) + # scrollbar.pack(side=tk.RIGHT, fill='y') + # scrollbar.config(command=treeview.yview) + # treeview.configure(yscrollcommand=scrollbar.set) + # + # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') + # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- Tree element ------------------------- # + elif element_type == ELEM_TYPE_TREE: + pass + # frame = tk.Frame(tk_row_frame) + # + # height = element.NumRows + # if element.Justification == 'left': # justification + # anchor = tk.W + # elif element.Justification == 'right': + # anchor = tk.E + # else: + # anchor = tk.CENTER + # + # if element.ColumnsToDisplay is None: # Which cols to display + # displaycolumns = element.ColumnHeadings + # else: + # displaycolumns = [] + # for i, should_display in enumerate(element.ColumnsToDisplay): + # if should_display: + # displaycolumns.append(element.ColumnHeadings[i]) + # column_headings = element.ColumnHeadings + # # ------------- GET THE TREEVIEW WIDGET ------------- + # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, + # displaycolumns=displaycolumns, show='tree headings', height=height, + # selectmode=element.SelectMode, ) + # treeview = element.TKTreeview + # for i, heading in enumerate(element.ColumnHeadings): # Configure cols + headings + # treeview.heading(heading, text=heading) + # if element.AutoSizeColumns: + # width = min(element.MaxColumnWidth, len(heading) + 1) + # else: + # try: + # width = element.ColumnWidths[i] + # except: + # width = element.DefaultColumnWidth + # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) + # + # def add_treeview_data(node): + # # print(f'Inserting {node.key} under parent {node.parent}') + # if node.key != '': + # treeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, + # open=element.ShowExpanded) + # for node in node.children: + # add_treeview_data(node) + # + # add_treeview_data(element.TreeData.root_node) + # treeview.column('#0', width=element.Col0Width * CharWidthInPixels(), anchor=anchor) + # # ----- configure colors ----- + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", background=element.BackgroundColor, + # fieldbackground=element.BackgroundColor) + # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", foreground=element.TextColor) + # + # scrollbar = tk.Scrollbar(frame) + # scrollbar.pack(side=tk.RIGHT, fill='y') + # scrollbar.config(command=treeview.yview) + # treeview.configure(yscrollcommand=scrollbar.set) + # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') + # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) + # treeview.bind("<>", element.treeview_selected) + # if element.Tooltip is not None: # tooltip + # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- Separator element ------------------------- # + elif element_type == ELEM_TYPE_SEPARATOR: + pass + # separator = ttk.Separator(tk_row_frame, orient=element.Orientation, ) + # separator.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], fill='both', expand=True) + # + # # ............................DONE WITH ROW pack the row of widgets ..........................# + # done with row, pack the row of widgets + # tk_row_frame.grid(row=row_num+2, sticky=tk.NW, padx=DEFAULT_MARGINS[0]) + # tk_row_frame.pack(side=tk.TOP, anchor='nw', padx=DEFAULT_MARGINS[0], expand=False) + # if form.BackgroundColor is not None and form.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # tk_row_frame.configure(background=form.BackgroundColor) + # toplevel_form.TKroot.configure(padx=DEFAULT_MARGINS[0], pady=DEFAULT_MARGINS[1]) + if not type(containing_frame) == remi.gui.TabBox: + containing_frame.append(tk_row_frame) + return + + +def setup_remi_window(app: Window.MyApp, window: Window): + master_widget = remi.gui.VBox() + master_widget.style['justify-content'] = 'flex-start' + master_widget.style['align-items'] = 'baseline' + if window.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + master_widget.style['background-color'] = window.BackgroundColor + try: + PackFormIntoFrame(window, master_widget, window) + except: + print('* ERROR PACKING FORM *') + print(traceback.format_exc()) + + if window.BackgroundImage: + master_widget.style['background-image'] = "url('{}')".format('/' + window.BackgroundImage) + # print(f'background info',self.master_widget.attributes['background-image'] ) + + if not window.DisableClose: + # add the following 3 lines to your app and the on_window_close method to make the console close automatically + tag = remi.gui.Tag(_type='script') + tag.add_child( + 'javascript', + """window.onunload=function(e){sendCallback('%s','%s');return "close?";};""" % (str(id(app)), 'on_window_close'), + ) + master_widget.add_child('onunloadevent', tag) + + if window.ReturnKeyboardEvents: + app.page.children['body'].onkeyup.connect(window.on_key_up) + if window.ReturnKeyDownEvents: + app.page.children['body'].onkeydown.connect(window.on_key_down) + + # if window.WindowIcon: + # if type(window.WindowIcon) is bytes or len(window.WindowIcon) > 200: + # app.page.children['head'].set_icon_data( base64_data=str(window.WindowIcon), mimetype="image/gif" ) + # else: + # app.page.children['head'].set_icon_file("/res:{}".format(window.WindowIcon)) + # pass + # mimetype, encoding = mimetypes.guess_type(image_source) + # with open(image_source, 'rb') as f: + # data = f.read() + # b64 = base64.b64encode(data) + # b64_str = b64.decode("utf-8") + # image_string = "data:image/svg;base64,%s"%b64_str + # rpoint.set_image(image_string) + + return master_widget + + +# ----====----====----====----====----==== STARTUP TK ====----====----====----====----====----# +def StartupTK(window: Window): + global _my_windows + + # print('Starting TK open Windows = {}'.format(ow)) + + _my_windows.Increment() + + # if not my_flex_form.Resizable: + # root.resizable(False, False) + + # if my_flex_form.KeepOnTop: + # root.wm_attributes("-topmost", 1) + # master = window.TKroot + # Set Title + # master.title(MyFlexForm.Title) + # master = 00000 + + InitializeResults(window) + + # Does all of the window setup, starting up Remi + # if no windows exist, start Remi thread which will call same setup_remi_window call as shown below + if len(Window.active_windows) == 0: + window.thread_id = threading.Thread(target=window.remi_thread, daemon=True) + window.thread_id.daemon = True + window.thread_id.start() + item = window.MessageQueue.get() # Get the layout complete message + Window.active_windows.append(window) + Window.App = window.App + else: + # print('Starting second page') + # margin 0px auto allows to center the app to the screen + # master_widget = remi.gui.VBox() + # master_widget.style['justify-content'] = 'flex-start' + # master_widget.style['align-items'] = 'baseline' + # if window.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + # master_widget.style['background-color'] = window.BackgroundColor + # PackFormIntoFrame(window, master_widget, window) + master_widget = setup_remi_window(Window.App, window) + window.master_widget = master_widget + Window.active_windows.append(window) + Window.App.set_root_widget(master_widget) + + return + + +# ==============================_GetNumLinesNeeded ==# +# Helper function for determining how to wrap text # +# ===================================================# +def _GetNumLinesNeeded(text, max_line_width): + if max_line_width == 0: + return 1 + lines = text.split('\n') + num_lines = len(lines) # number of original lines of text + max_line_len = max([len(l) for l in lines]) # longest line + lines_used = [] + for L in lines: + lines_used.append(len(L) // max_line_width + (len(L) % max_line_width > 0)) # fancy math to round up + total_lines_needed = sum(lines_used) + return total_lines_needed + + +# ============================== PROGRESS METER ========================================== # + + +def ConvertArgsToSingleString(*args): + ( + max_line_total, + width_used, + total_lines, + ) = ( + 0, + 0, + 0, + ) + single_line_message = '' + # loop through args and built a SINGLE string from them + for message in args: + # fancy code to check if string and convert if not is not need. Just always convert to string :-) + # if not isinstance(message, str): message = str(message) + message = str(message) + longest_line_len = max([len(l) for l in message.split('\n')]) + width_used = max(longest_line_len, width_used) + max_line_total = max(max_line_total, width_used) + lines_needed = _GetNumLinesNeeded(message, width_used) + total_lines += lines_needed + single_line_message += message + '\n' + return single_line_message, width_used, total_lines + + +# ============================== ProgressMeter =====# +# ===================================================# +def _ProgressMeter(title, max_value, *args, orientation=None, bar_color=(None, None), button_color=None, size=DEFAULT_PROGRESS_BAR_SIZE, border_width=None, grab_anywhere=False): + ''' + Create and show a form on tbe caller's behalf. + :param title: + :param max_value: + :param args: ANY number of arguments the caller wants to display + :param orientation: + :param bar_color: + :param size: + :param Style: + :param StyleOffset: + :return: ProgressBar object that is in the form + ''' + local_orientation = DEFAULT_METER_ORIENTATION if orientation is None else orientation + local_border_width = DEFAULT_PROGRESS_BAR_BORDER_WIDTH if border_width is None else border_width + bar2 = ProgressBar( + max_value, + orientation=local_orientation, + size=size, + bar_color=bar_color, + border_width=local_border_width, + relief=DEFAULT_PROGRESS_BAR_RELIEF, + ) + form = Window(title, auto_size_text=True, grab_anywhere=grab_anywhere) + + # Form using a horizontal bar + if local_orientation[0].lower() == 'h': + single_line_message, width, height = ConvertArgsToSingleString(*args) + bar2.TextToDisplay = single_line_message + bar2.TextToDisplay = single_line_message + bar2.MaxValue = max_value + bar2.CurrentValue = 0 + bar_text = Text(single_line_message, size=(width, height + 3), auto_size_text=True) + form.AddRow(bar_text) + form.AddRow((bar2)) + form.AddRow((CloseButton('Cancel', button_color=button_color))) + else: + single_line_message, width, height = ConvertArgsToSingleString(*args) + bar2.TextToDisplay = single_line_message + bar2.MaxValue = max_value + bar2.CurrentValue = 0 + bar_text = Text(single_line_message, size=(width, height + 3), auto_size_text=True) + form.AddRow(bar2, bar_text) + form.AddRow((CloseButton('Cancel', button_color=button_color))) + + form.NonBlocking = True + form.Show(non_blocking=True) + return bar2, bar_text + + +# ============================== ProgressMeterUpdate =====# +def _ProgressMeterUpdate(bar, value, text_elem, *args): + ''' + Update the progress meter for a form + :param form: class ProgressBar + :param value: int + :return: True if not cancelled, OK....False if Error + ''' + global _my_windows + if bar == None: + return False + if bar.BarExpired: + return False + message, w, h = ConvertArgsToSingleString(*args) + text_elem.Update(message) + # bar.TextToDisplay = message + bar.CurrentValue = value + rc = bar.UpdateBar(value) + if value >= bar.MaxValue or not rc: + bar.BarExpired = True + bar.ParentForm._Close() + if rc: # if update was OK but bar expired, decrement num windows + _my_windows.Decrement() + if bar.ParentForm.RootNeedsDestroying: + try: + bar.ParentForm.TKroot.destroy() + # there is a bug with progress meters not decrementing the number of windows + # correctly when the X is used to close the window + # uncommenting this line fixes that problem, but causes a double-decrement when + # the cancel button is used... damned if you do, damned if you don't, so I'm choosing + # don't, as in don't decrement too many times. It's OK now to have a mismatch in + # number of windows because of the "hidden" master window. This ensures all windows + # will be toplevel. Sorry about the bug, but the user never sees any problems as a result + # _my_windows.Decrement() + except: + pass + bar.ParentForm.RootNeedsDestroying = False + return False + + return rc + + +# ============================== EASY PROGRESS METER ========================================== # +# class to hold the easy meter info (a global variable essentialy) +class EasyProgressMeterDataClass: + def __init__(self, title='', current_value=1, max_value=10, start_time=None, stat_messages=()): + self.Title = title + self.CurrentValue = current_value + self.MaxValue = max_value + self.StartTime = start_time + self.StatMessages = stat_messages + self.ParentForm = None + self.MeterID = None + self.MeterText = None + + # =========================== COMPUTE PROGRESS STATS ======================# + def ComputeProgressStats(self): + utc = datetime.datetime.utcnow() + time_delta = utc - self.StartTime + total_seconds = time_delta.total_seconds() + if not total_seconds: + total_seconds = 1 + try: + time_per_item = total_seconds / self.CurrentValue + except: + time_per_item = 1 + seconds_remaining = (self.MaxValue - self.CurrentValue) * time_per_item + time_remaining = str(datetime.timedelta(seconds=seconds_remaining)) + time_remaining_short = (time_remaining).split('.')[0] + time_delta_short = str(time_delta).split('.')[0] + total_time = time_delta + datetime.timedelta(seconds=seconds_remaining) + total_time_short = str(total_time).split('.')[0] + self.StatMessages = [ + '{} of {}'.format(self.CurrentValue, self.MaxValue), + '{} %'.format(100 * self.CurrentValue // self.MaxValue), + '', + ' {:6.2f} Iterations per Second'.format(self.CurrentValue / total_seconds), + ' {:6.2f} Seconds per Iteration'.format(total_seconds / (self.CurrentValue if self.CurrentValue else 1)), + '', + '{} Elapsed Time'.format(time_delta_short), + '{} Time Remaining'.format(time_remaining_short), + '{} Estimated Total Time'.format(total_time_short), + ] + return + + +# ============================== EasyProgressMeter =====# +def EasyProgressMeter(title, current_value, max_value, *args, orientation=None, bar_color=(None, None), button_color=None, size=DEFAULT_PROGRESS_BAR_SIZE, border_width=None): + ''' + A ONE-LINE progress meter. Add to your code where ever you need a meter. No need for a second + function call before your loop. You've got enough code to write! + :param title: Title will be shown on the window + :param current_value: Current count of your items + :param max_value: Max value your count will ever reach. This indicates it should be closed + :param args: VARIABLE number of arguements... you request it, we'll print it no matter what the item! + :param orientation: + :param bar_color: + :param size: + :param Style: + :param StyleOffset: + :return: False if should stop the meter + ''' + local_border_width = DEFAULT_PROGRESS_BAR_BORDER_WIDTH if not border_width else border_width + # STATIC VARIABLE! + # This is a very clever form of static variable using a function attribute + # If the variable doesn't yet exist, then it will create it and initialize with the 3rd parameter + EasyProgressMeter.Data = getattr(EasyProgressMeter, 'Data', EasyProgressMeterDataClass()) + # if no meter currently running + if EasyProgressMeter.Data.MeterID is None: # Starting a new meter + print('Please change your call of EasyProgressMeter to use OneLineProgressMeter. EasyProgressMeter will be removed soon') + if int(current_value) >= int(max_value): + return False + del EasyProgressMeter.Data + EasyProgressMeter.Data = EasyProgressMeterDataClass(title, 1, int(max_value), datetime.datetime.utcnow(), []) + EasyProgressMeter.Data.ComputeProgressStats() + message = '\n'.join([line for line in EasyProgressMeter.Data.StatMessages]) + EasyProgressMeter.Data.MeterID, EasyProgressMeter.Data.MeterText = _ProgressMeter(title, int(max_value), message, *args, orientation=orientation, bar_color=bar_color, size=size, button_color=button_color, border_width=local_border_width) + EasyProgressMeter.Data.ParentForm = EasyProgressMeter.Data.MeterID.ParentForm + return True + # if exactly the same values as before, then ignore. + if EasyProgressMeter.Data.MaxValue == max_value and EasyProgressMeter.Data.CurrentValue == current_value: + return True + if EasyProgressMeter.Data.MaxValue != int(max_value): + EasyProgressMeter.Data.MeterID = None + EasyProgressMeter.Data.ParentForm = None + del EasyProgressMeter.Data + EasyProgressMeter.Data = EasyProgressMeterDataClass() # setup a new progress meter + return True # HAVE to return TRUE or else the new meter will thing IT is failing when it hasn't + EasyProgressMeter.Data.CurrentValue = int(current_value) + EasyProgressMeter.Data.MaxValue = int(max_value) + EasyProgressMeter.Data.ComputeProgressStats() + message = '' + for line in EasyProgressMeter.Data.StatMessages: + message = message + str(line) + '\n' + message = '\n'.join(EasyProgressMeter.Data.StatMessages) + args = args + (message,) + rc = _ProgressMeterUpdate(EasyProgressMeter.Data.MeterID, current_value, EasyProgressMeter.Data.MeterText, *args) + # if counter >= max then the progress meter is all done. Indicate none running + if current_value >= EasyProgressMeter.Data.MaxValue or not rc: + EasyProgressMeter.Data.MeterID = None + del EasyProgressMeter.Data + EasyProgressMeter.Data = EasyProgressMeterDataClass() # setup a new progress meter + return False # even though at the end, return True so don't cause error with the app + return rc # return whatever the update told us + + +def EasyProgressMeterCancel(title, *args): + EasyProgressMeter.EasyProgressMeterData = getattr(EasyProgressMeter, 'EasyProgressMeterData', EasyProgressMeterDataClass()) + if EasyProgressMeter.EasyProgressMeterData.MeterID is not None: + # tell the normal meter update that we're at max value which will close the meter + rc = EasyProgressMeter(title, EasyProgressMeter.EasyProgressMeterData.MaxValue, EasyProgressMeter.EasyProgressMeterData.MaxValue, ' *** CANCELLING ***', 'Caller requested a cancel', *args) + return rc + return True + + +# global variable containing dictionary will all currently running one-line progress meters. +_one_line_progress_meters = {} + + +# ============================== OneLineProgressMeter =====# +def OneLineProgressMeter(title, current_value, max_value, key='OK for 1 meter', *args, orientation=None, bar_color=(None, None), button_color=None, size=DEFAULT_PROGRESS_BAR_SIZE, border_width=None, grab_anywhere=False): + global _one_line_progress_meters + + local_border_width = DEFAULT_PROGRESS_BAR_BORDER_WIDTH if border_width is not None else border_width + try: + meter_data = _one_line_progress_meters[key] + except: # a new meater is starting + if int(current_value) >= int(max_value): # if already expired then it's an old meter, ignore + return False + meter_data = EasyProgressMeterDataClass(title, 1, int(max_value), datetime.datetime.utcnow(), []) + _one_line_progress_meters[key] = meter_data + meter_data.ComputeProgressStats() + message = '\n'.join([line for line in meter_data.StatMessages]) + meter_data.MeterID, meter_data.MeterText = _ProgressMeter(title, int(max_value), message, *args, orientation=orientation, bar_color=bar_color, size=size, button_color=button_color, border_width=local_border_width, grab_anywhere=grab_anywhere) + meter_data.ParentForm = meter_data.MeterID.ParentForm + return True + + # if exactly the same values as before, then ignore, return success. + if meter_data.MaxValue == max_value and meter_data.CurrentValue == current_value: + return True + meter_data.CurrentValue = int(current_value) + meter_data.MaxValue = int(max_value) + meter_data.ComputeProgressStats() + message = '' + for line in meter_data.StatMessages: + message = message + str(line) + '\n' + message = '\n'.join(meter_data.StatMessages) + args = args + (message,) + rc = _ProgressMeterUpdate(meter_data.MeterID, current_value, meter_data.MeterText, *args) + # if counter >= max then the progress meter is all done. Indicate none running + if current_value >= meter_data.MaxValue or not rc: + del _one_line_progress_meters[key] + return False + return rc # return whatever the update told us + + +def OneLineProgressMeterCancel(key='OK for 1 meter'): + global _one_line_progress_meters + + try: + meter_data = _one_line_progress_meters[key] + except: # meter is already deleted + return + OneLineProgressMeter('', meter_data.MaxValue, meter_data.MaxValue, key=key) + + +# input is #RRGGBB +# output is #RRGGBB +def GetComplimentaryHex(color): + # strip the # from the beginning + color = color[1:] + # convert the string into hex + color = int(color, 16) + # invert the three bytes + # as good as substracting each of RGB component by 255(FF) + comp_color = 0xFFFFFF ^ color + # convert the color back to hex by prefixing a # + comp_color = '#%06X' % comp_color + return comp_color + + +# ======================== EasyPrint =====# +# ===================================================# +_easy_print_data = None # global variable... I'm cheating + + +class DebugWin: + def __init__( + self, + size=(None, None), + location=(None, None), + font=None, + no_titlebar=False, + no_button=False, + grab_anywhere=False, + keep_on_top=False, + ): + # Show a form that's a running counter + win_size = size if size != (None, None) else DEFAULT_DEBUG_WINDOW_SIZE + self.window = Window( + 'Debug Window', + no_titlebar=no_titlebar, + auto_size_text=True, + location=location, + font=font or ('Courier New', 10), + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + ) + self.output_element = Output(size=win_size) + if no_button: + self.layout = [[self.output_element]] + else: + self.layout = [[self.output_element], [DummyButton('Quit')]] + self.window.AddRows(self.layout) + self.window.Read(timeout=0) # Show a non-blocking form, returns immediately + return + + def Print(self, *args, end=None, sep=None): + sepchar = sep if sep is not None else ' ' + endchar = end if end is not None else '\n' + + if self.window is None: # if window was destroyed already, just print + print(*args, sep=sepchar, end=endchar) + return + + event, values = self.window.Read(timeout=0) + if event == 'Quit' or event is None: + self.Close() + print(*args, sep=sepchar, end=endchar) + # Add extra check to see if the window was closed... if closed by X sometimes am not told + try: + state = self.window.TKroot.state() + except: + self.Close() + + def Close(self): + self.window.Close() + self.window = None + + +def PrintClose(): + EasyPrintClose() + + +def EasyPrint(*args, size=(None, None), end=None, sep=None, location=(None, None), font=None, no_titlebar=False, no_button=False, grab_anywhere=False, keep_on_top=False): + global _easy_print_data + + if _easy_print_data is None: + _easy_print_data = DebugWin( + size=size, + location=location, + font=font, + no_titlebar=no_titlebar, + no_button=no_button, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + ) + _easy_print_data.Print(*args, end=end, sep=sep) + + +Print = EasyPrint +eprint = EasyPrint + + +def EasyPrintClose(): + global _easy_print_data + if _easy_print_data is not None: + _easy_print_data.Close() + _easy_print_data = None + + +# d8b 888 +# Y8P 888 +# 888 +# .d8888b 88888b. 888d888 888 88888b. 888888 +# d88P" 888 "88b 888P" 888 888 "88b 888 +# 888 888 888 888 888 888 888 888 +# Y88b. 888 d88P 888 888 888 888 Y88b. +# "Y8888P 88888P" 888 888 888 888 "Y888 +# 888 +# 888 +# 888 + + +CPRINT_DESTINATION_WINDOW = None +CPRINT_DESTINATION_MULTILINE_ELMENT_KEY = None + + +def cprint_set_output_destination(window, multiline_key): + """ + Sets up the color print (cprint) output destination + :param window: The window that the cprint call will route the output to + :type window: (Window) + :param multiline_key: Key for the Multiline Element where output will be sent + :type multiline_key: (Any) + :return: None + :rtype: None + """ + + global CPRINT_DESTINATION_WINDOW, CPRINT_DESTINATION_MULTILINE_ELMENT_KEY + + CPRINT_DESTINATION_WINDOW = window + CPRINT_DESTINATION_MULTILINE_ELMENT_KEY = multiline_key + + +# def cprint(*args, **kwargs): +def cprint(*args, end=None, sep=' ', text_color=None, t=None, background_color=None, b=None, colors=None, c=None, window=None, key=None): + """ + Color print to a multiline element in a window of your choice. + Must have EITHER called cprint_set_output_destination prior to making this call so that the + window and element key can be saved and used here to route the output, OR used the window + and key parameters to the cprint function to specicy these items. + + args is a variable number of things you want to print. + + end - The end char to use just like print uses + sep - The separation character like print uses + text_color - The color of the text + key - overrides the previously defined Multiline key + window - overrides the previously defined window to output to + background_color - The color of the background + colors -(str, str) or str. A combined text/background color definition in a single parameter + + There are also "aliases" for text_color, background_color and colors (t, b, c) + t - An alias for color of the text (makes for shorter calls) + b - An alias for the background_color parameter + c - Tuple[str, str] - "shorthand" way of specifying color. (foreground, backgrouned) + c - str - can also be a string of the format "foreground on background" ("white on red") + + With the aliases it's possible to write the same print but in more compact ways: + cprint('This will print white text on red background', c=('white', 'red')) + cprint('This will print white text on red background', c='white on red') + cprint('This will print white text on red background', text_color='white', background_color='red') + cprint('This will print white text on red background', t='white', b='red') + + :param *args: stuff to output + :type *args: (Any) + :param text_color: Color of the text + :type text_color: (str) + :param background_color: The background color of the line + :type background_color: (str) + :param colors: Either a tuple or a string that has both the text and background colors + :type colors: (str) or Tuple[str, str] + :param t: Color of the text + :type t: (str) + :param b: The background color of the line + :type b: (str) + :param c: Either a tuple or a string that has both the text and background colors + :type c: (str) or Tuple[str, str] + :param end: end character + :type end: (str) + :param sep: separator character + :type sep: (str) + :param key: key of multiline to output to (if you want to override the one previously set) + :type key: (Any) + :param window: Window containing the multiline to output to (if you want to override the one previously set) + :type window: (Window) + :return: None + :rtype: None + """ + + destination_key = CPRINT_DESTINATION_MULTILINE_ELMENT_KEY if key is None else key + destination_window = window or CPRINT_DESTINATION_WINDOW + + if (destination_window is None and window is None) or (destination_key is None and key is None): + print( + '** Warning ** Attempting to perform a cprint without a valid window & key', + 'Will instead print on Console', + 'You can specify window and key in this cprint call, or set ahead of time using cprint_set_output_destination', + ) + print(*args) + return + + kw_text_color = text_color or t + kw_background_color = background_color or b + dual_color = colors or c + try: + if isinstance(dual_color, tuple): + kw_text_color = dual_color[0] + kw_background_color = dual_color[1] + elif isinstance(dual_color, str): + kw_text_color = dual_color.split(' on ')[0] + kw_background_color = dual_color.split(' on ')[1] + except Exception as e: + print('* cprint warning * you messed up with color formatting', e) + + mline = destination_window.find_element(destination_key, silent_on_error=True) # type: Multiline + try: + # mline = destination_window[destination_key] # type: Multiline + if end is None: + mline.print(*args, text_color=kw_text_color, background_color=kw_background_color, end='', sep=sep) + mline.print('') + else: + mline.print(*args, text_color=kw_text_color, background_color=kw_background_color, end=end, sep=sep) + except Exception as e: + print('** cprint error trying to print to the multiline. Printing to console instead **', e) + print(*args, end=end, sep=sep) + + +# ------------------------------------------------------------------------------------------------ # +# A print-like call that can be used to output to a multiline element as if it's an Output element # +# ------------------------------------------------------------------------------------------------ # +def _print_to_element(multiline_element, *args, end=None, sep=None, text_color=None, background_color=None, autoscroll=True): + """ + Print like Python normally prints except route the output to a multline element and also add colors if desired + + :param multiline_element: The multiline element to be output to + :type multiline_element: Multiline or MultilineOutput + :param args: The arguments to print + :type args: List[Any] + :param end: The end char to use just like print uses + :type end: (str) + :param sep: The separation character like print uses + :type sep: (str) + :param text_color: color of the text + :type text_color: (str) + :param background_color: The background color of the line + :type background_color: (str) + :param autoscroll: If True (the default), the element will scroll to bottom after updating + :type autoscroll: Bool + """ + end_str = str(end) if end is not None else '\n' + sep_str = str(sep) if sep is not None else ' ' + + outstring = '' + num_args = len(args) + for i, arg in enumerate(args): + outstring += str(arg) + if i != num_args - 1: + outstring += sep_str + outstring += end_str + + multiline_element.update(outstring, append=True, text_color=text_color, background_color=background_color, autoscroll=autoscroll) + + +# ======================== Scrolled Text Box =====# +# ===================================================# +def PopupScrolled(*args, button_color=None, yes_no=False, auto_close=False, auto_close_duration=None, size=(None, None)): + if not args: + return + width, height = size + width = width if width else MESSAGE_BOX_LINE_WIDTH + form = Window( + args[0], + auto_size_text=True, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + ) + max_line_total, max_line_width, total_lines, height_computed = 0, 0, 0, 0 + complete_output = '' + for message in args: + # fancy code to check if string and convert if not is not need. Just always convert to string :-) + # if not isinstance(message, str): message = str(message) + message = str(message) + longest_line_len = max([len(l) for l in message.split('\n')]) + width_used = min(longest_line_len, width) + max_line_total = max(max_line_total, width_used) + max_line_width = width + lines_needed = _GetNumLinesNeeded(message, width_used) + height_computed += lines_needed + complete_output += message + '\n' + total_lines += lines_needed + height_computed = MAX_SCROLLED_TEXT_BOX_HEIGHT if height_computed > MAX_SCROLLED_TEXT_BOX_HEIGHT else height_computed + if height: + height_computed = height + form.AddRow(Multiline(complete_output, size=(max_line_width, height_computed))) + pad = max_line_total - 15 if max_line_total > 15 else 1 + # show either an OK or Yes/No depending on paramater + if yes_no: + form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Yes(), No()) + button, values = form.Read() + return button + else: + form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Button('OK', size=(5, 1), button_color=button_color)) + button, values = form.Read() + form.Close() + return button + + +ScrolledTextBox = PopupScrolled + + +# ============================== SetGlobalIcon ======# +# Sets the icon to be used by default # +# ===================================================# +def SetGlobalIcon(icon): + global _my_windows + + try: + with open(icon, 'r') as icon_file: + pass + except: + raise FileNotFoundError + _my_windows.user_defined_icon = icon + return True + + +# ============================== SetOptions =========# +# Sets the icon to be used by default # +# ===================================================# +def SetOptions( + icon=None, + button_color=None, + element_size=(None, None), + button_element_size=(None, None), + margins=(None, None), + element_padding=(None, None), + auto_size_text=None, + auto_size_buttons=None, + font=None, + border_width=None, + slider_border_width=None, + slider_relief=None, + slider_orientation=None, + autoclose_time=None, + message_box_line_width=None, + progress_meter_border_depth=None, + progress_meter_style=None, + progress_meter_relief=None, + progress_meter_color=None, + progress_meter_size=None, + text_justification=None, + background_color=None, + element_background_color=None, + text_element_background_color=None, + input_elements_background_color=None, + input_text_color=None, + scrollbar_color=None, + text_color=None, + element_text_color=None, + debug_win_size=(None, None), + window_location=(None, None), + tooltip_time=None, +): + global DEFAULT_ELEMENT_SIZE + global DEFAULT_BUTTON_ELEMENT_SIZE + global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term + global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels + global DEFAULT_AUTOSIZE_TEXT + global DEFAULT_AUTOSIZE_BUTTONS + global DEFAULT_FONT + global DEFAULT_BORDER_WIDTH + global DEFAULT_AUTOCLOSE_TIME + global DEFAULT_BUTTON_COLOR + global MESSAGE_BOX_LINE_WIDTH + global DEFAULT_PROGRESS_BAR_BORDER_WIDTH + global DEFAULT_PROGRESS_BAR_STYLE + global DEFAULT_PROGRESS_BAR_RELIEF + global DEFAULT_PROGRESS_BAR_COLOR + global DEFAULT_PROGRESS_BAR_SIZE + global DEFAULT_TEXT_JUSTIFICATION + global DEFAULT_DEBUG_WINDOW_SIZE + global DEFAULT_SLIDER_BORDER_WIDTH + global DEFAULT_SLIDER_RELIEF + global DEFAULT_SLIDER_ORIENTATION + global DEFAULT_BACKGROUND_COLOR + global DEFAULT_INPUT_ELEMENTS_COLOR + global DEFAULT_ELEMENT_BACKGROUND_COLOR + global DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR + global DEFAULT_SCROLLBAR_COLOR + global DEFAULT_TEXT_COLOR + global DEFAULT_WINDOW_LOCATION + global DEFAULT_ELEMENT_TEXT_COLOR + global DEFAULT_INPUT_TEXT_COLOR + global DEFAULT_TOOLTIP_TIME + global _my_windows + + if icon: + try: + with open(icon, 'r') as icon_file: + pass + except: + raise FileNotFoundError + _my_windows.user_defined_icon = icon + + if button_color != None: + DEFAULT_BUTTON_COLOR = button_color + + if element_size != (None, None): + DEFAULT_ELEMENT_SIZE = element_size + + if button_element_size != (None, None): + DEFAULT_BUTTON_ELEMENT_SIZE = button_element_size + + if margins != (None, None): + DEFAULT_MARGINS = margins + + if element_padding != (None, None): + DEFAULT_ELEMENT_PADDING = element_padding + + if auto_size_text != None: + DEFAULT_AUTOSIZE_TEXT = auto_size_text + + if auto_size_buttons != None: + DEFAULT_AUTOSIZE_BUTTONS = auto_size_buttons + + if font != None: + DEFAULT_FONT = font + + if border_width != None: + DEFAULT_BORDER_WIDTH = border_width + + if autoclose_time != None: + DEFAULT_AUTOCLOSE_TIME = autoclose_time + + if message_box_line_width != None: + MESSAGE_BOX_LINE_WIDTH = message_box_line_width + + if progress_meter_border_depth != None: + DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth + + if progress_meter_style != None: + DEFAULT_PROGRESS_BAR_STYLE = progress_meter_style + + if progress_meter_relief != None: + DEFAULT_PROGRESS_BAR_RELIEF = progress_meter_relief + + if progress_meter_color != None: + DEFAULT_PROGRESS_BAR_COLOR = progress_meter_color + + if progress_meter_size != None: + DEFAULT_PROGRESS_BAR_SIZE = progress_meter_size + + if slider_border_width != None: + DEFAULT_SLIDER_BORDER_WIDTH = slider_border_width + + if slider_orientation != None: + DEFAULT_SLIDER_ORIENTATION = slider_orientation + + if slider_relief != None: + DEFAULT_SLIDER_RELIEF = slider_relief + + if text_justification != None: + DEFAULT_TEXT_JUSTIFICATION = text_justification + + if background_color != None: + DEFAULT_BACKGROUND_COLOR = background_color + + if text_element_background_color != None: + DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = text_element_background_color + + if input_elements_background_color != None: + DEFAULT_INPUT_ELEMENTS_COLOR = input_elements_background_color + + if element_background_color != None: + DEFAULT_ELEMENT_BACKGROUND_COLOR = element_background_color + + if window_location != (None, None): + DEFAULT_WINDOW_LOCATION = window_location + + if debug_win_size != (None, None): + DEFAULT_DEBUG_WINDOW_SIZE = debug_win_size + + if text_color != None: + DEFAULT_TEXT_COLOR = text_color + + if scrollbar_color != None: + DEFAULT_SCROLLBAR_COLOR = scrollbar_color + + if element_text_color != None: + DEFAULT_ELEMENT_TEXT_COLOR = element_text_color + + if input_text_color is not None: + DEFAULT_INPUT_TEXT_COLOR = input_text_color + + if tooltip_time is not None: + DEFAULT_TOOLTIP_TIME = tooltip_time + + return True + + +# ----------------------------------------------------------------- # + +# .########.##.....##.########.##.....##.########..######. +# ....##....##.....##.##.......###...###.##.......##....## +# ....##....##.....##.##.......####.####.##.......##...... +# ....##....#########.######...##.###.##.######....######. +# ....##....##.....##.##.......##.....##.##.............## +# ....##....##.....##.##.......##.....##.##.......##....## +# ....##....##.....##.########.##.....##.########..######. + +# ----------------------------------------------------------------- # + +# The official Theme code + +#################### ChangeLookAndFeel ####################### +# Predefined settings that will change the colors and styles # +# of the elements. # +############################################################## +LOOK_AND_FEEL_TABLE = { + 'SystemDefault': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'SystemDefaultForReal': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': COLOR_SYSTEM_DEFAULT, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'SystemDefault1': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': COLOR_SYSTEM_DEFAULT, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'Material1': { + 'BACKGROUND': '#E3F2FD', + 'TEXT': '#000000', + 'INPUT': '#86A8FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#86A8FF', + 'BUTTON': ('#FFFFFF', '#5079D3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'Material2': { + 'BACKGROUND': '#FAFAFA', + 'TEXT': '#000000', + 'INPUT': '#004EA1', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#5EA7FF', + 'BUTTON': ('#FFFFFF', '#0079D3'), # based on Reddit color + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'Reddit': { + 'BACKGROUND': '#ffffff', + 'TEXT': '#1a1a1b', + 'INPUT': '#dae0e6', + 'TEXT_INPUT': '#222222', + 'SCROLL': '#a5a4a4', + 'BUTTON': ('#FFFFFF', '#0079d3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#ff5414', + 'ACCENT2': '#33a8ff', + 'ACCENT3': '#dbf0ff', + }, + 'Topanga': { + 'BACKGROUND': '#282923', + 'TEXT': '#E7DB74', + 'INPUT': '#393a32', + 'TEXT_INPUT': '#E7C855', + 'SCROLL': '#E7C855', + 'BUTTON': ('#E7C855', '#284B5A'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#c15226', + 'ACCENT2': '#7a4d5f', + 'ACCENT3': '#889743', + }, + 'GreenTan': { + 'BACKGROUND': '#9FB8AD', + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': '#F7F3EC', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#F7F3EC', + 'BUTTON': ('#FFFFFF', '#475841'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Dark': { + 'BACKGROUND': '#404040', + 'TEXT': '#FFFFFF', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#707070', + 'BUTTON': ('#FFFFFF', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen': { + 'BACKGROUND': '#B7CECE', + 'TEXT': '#000000', + 'INPUT': '#FDFFF7', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#FDFFF7', + 'BUTTON': ('#FFFFFF', '#658268'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'ACCENT1': '#76506d', + 'ACCENT2': '#5148f1', + 'ACCENT3': '#0a1c84', + 'PROGRESS_DEPTH': 0, + }, + 'Dark2': { + 'BACKGROUND': '#404040', + 'TEXT': '#FFFFFF', + 'INPUT': '#FFFFFF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#707070', + 'BUTTON': ('#FFFFFF', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Black': { + 'BACKGROUND': '#000000', + 'TEXT': '#FFFFFF', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#707070', + 'BUTTON': ('#000000', '#FFFFFF'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Tan': { + 'BACKGROUND': '#fdf6e3', + 'TEXT': '#268bd1', + 'INPUT': '#eee8d5', + 'TEXT_INPUT': '#6c71c3', + 'SCROLL': '#eee8d5', + 'BUTTON': ('#FFFFFF', '#063542'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'TanBlue': { + 'BACKGROUND': '#e5dece', + 'TEXT': '#063289', + 'INPUT': '#f9f8f4', + 'TEXT_INPUT': '#242834', + 'SCROLL': '#eee8d5', + 'BUTTON': ('#FFFFFF', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkTanBlue': { + 'BACKGROUND': '#242834', + 'TEXT': '#dfe6f8', + 'INPUT': '#97755c', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#a9afbb', + 'BUTTON': ('#FFFFFF', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkAmber': { + 'BACKGROUND': '#2c2825', + 'TEXT': '#fdcb52', + 'INPUT': '#705e52', + 'TEXT_INPUT': '#fdcb52', + 'SCROLL': '#705e52', + 'BUTTON': ('#000000', '#fdcb52'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue': { + 'BACKGROUND': '#1a2835', + 'TEXT': '#d1ecff', + 'INPUT': '#335267', + 'TEXT_INPUT': '#acc2d0', + 'SCROLL': '#1b6497', + 'BUTTON': ('#000000', '#fafaf8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Reds': { + 'BACKGROUND': '#280001', + 'TEXT': '#FFFFFF', + 'INPUT': '#d8d584', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#763e00', + 'BUTTON': ('#000000', '#daad28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Green': { + 'BACKGROUND': '#82a459', + 'TEXT': '#000000', + 'INPUT': '#d8d584', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e3ecf3', + 'BUTTON': ('#FFFFFF', '#517239'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BluePurple': { + 'BACKGROUND': '#A5CADD', + 'TEXT': '#6E266E', + 'INPUT': '#E0F5FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#E0F5FF', + 'BUTTON': ('#FFFFFF', '#303952'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Purple': { + 'BACKGROUND': '#B0AAC2', + 'TEXT': '#000000', + 'INPUT': '#F2EFE8', + 'SCROLL': '#F2EFE8', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#C2D4D8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BlueMono': { + 'BACKGROUND': '#AAB6D3', + 'TEXT': '#000000', + 'INPUT': '#F1F4FC', + 'SCROLL': '#F1F4FC', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#7186C7'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'GreenMono': { + 'BACKGROUND': '#A8C1B4', + 'TEXT': '#000000', + 'INPUT': '#DDE0DE', + 'SCROLL': '#E3E3E3', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#6D9F85'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BrownBlue': { + 'BACKGROUND': '#64778d', + 'TEXT': '#FFFFFF', + 'INPUT': '#f0f3f7', + 'SCROLL': '#A6B2BE', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#283b5b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BrightColors': { + 'BACKGROUND': '#b4ffb4', + 'TEXT': '#000000', + 'INPUT': '#ffff64', + 'SCROLL': '#ffb482', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#ffa0dc'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'NeutralBlue': { + 'BACKGROUND': '#92aa9d', + 'TEXT': '#000000', + 'INPUT': '#fcfff6', + 'SCROLL': '#fcfff6', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#d0dbbd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Kayak': { + 'BACKGROUND': '#a7ad7f', + 'TEXT': '#000000', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#5d907d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'SandyBeach': { + 'BACKGROUND': '#efeccb', + 'TEXT': '#012f2f', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#012f2f', + 'BUTTON': ('#FFFFFF', '#046380'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'TealMono': { + 'BACKGROUND': '#a8cfdd', + 'TEXT': '#000000', + 'INPUT': '#dfedf2', + 'SCROLL': '#dfedf2', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#183440'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + ################################## Renamed Original Themes ################################## + 'Default': { # plain gray but blue buttons + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'Default1': { # everything is gray + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': COLOR_SYSTEM_DEFAULT, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'DefaultNoMoreNagging': { # a duplicate of "Default" for users that are tired of the nag screen + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue': { + 'BACKGROUND': '#E3F2FD', + 'TEXT': '#000000', + 'INPUT': '#86A8FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#86A8FF', + 'BUTTON': ('#FFFFFF', '#5079D3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'LightGrey': { + 'BACKGROUND': '#FAFAFA', + 'TEXT': '#000000', + 'INPUT': '#004EA1', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#5EA7FF', + 'BUTTON': ('#FFFFFF', '#0079D3'), # based on Reddit color + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'LightGrey1': { + 'BACKGROUND': '#ffffff', + 'TEXT': '#1a1a1b', + 'INPUT': '#dae0e6', + 'TEXT_INPUT': '#222222', + 'SCROLL': '#a5a4a4', + 'BUTTON': ('#FFFFFF', '#0079d3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#ff5414', + 'ACCENT2': '#33a8ff', + 'ACCENT3': '#dbf0ff', + }, + 'DarkBrown': { + 'BACKGROUND': '#282923', + 'TEXT': '#E7DB74', + 'INPUT': '#393a32', + 'TEXT_INPUT': '#E7C855', + 'SCROLL': '#E7C855', + 'BUTTON': ('#E7C855', '#284B5A'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#c15226', + 'ACCENT2': '#7a4d5f', + 'ACCENT3': '#889743', + }, + 'LightGreen1': { + 'BACKGROUND': '#9FB8AD', + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': '#F7F3EC', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#F7F3EC', + 'BUTTON': ('#FFFFFF', '#475841'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey': { + 'BACKGROUND': '#404040', + 'TEXT': '#FFFFFF', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#707070', + 'BUTTON': ('#FFFFFF', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen2': { + 'BACKGROUND': '#B7CECE', + 'TEXT': '#000000', + 'INPUT': '#FDFFF7', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#FDFFF7', + 'BUTTON': ('#FFFFFF', '#658268'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'ACCENT1': '#76506d', + 'ACCENT2': '#5148f1', + 'ACCENT3': '#0a1c84', + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey1': { + 'BACKGROUND': '#404040', + 'TEXT': '#FFFFFF', + 'INPUT': '#FFFFFF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#707070', + 'BUTTON': ('#FFFFFF', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlack': { + 'BACKGROUND': '#000000', + 'TEXT': '#FFFFFF', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#707070', + 'BUTTON': ('#000000', '#FFFFFF'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown': { + 'BACKGROUND': '#fdf6e3', + 'TEXT': '#268bd1', + 'INPUT': '#eee8d5', + 'TEXT_INPUT': '#6c71c3', + 'SCROLL': '#eee8d5', + 'BUTTON': ('#FFFFFF', '#063542'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown1': { + 'BACKGROUND': '#e5dece', + 'TEXT': '#063289', + 'INPUT': '#f9f8f4', + 'TEXT_INPUT': '#242834', + 'SCROLL': '#eee8d5', + 'BUTTON': ('#FFFFFF', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue1': { + 'BACKGROUND': '#242834', + 'TEXT': '#dfe6f8', + 'INPUT': '#97755c', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#a9afbb', + 'BUTTON': ('#FFFFFF', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBrown1': { + 'BACKGROUND': '#2c2825', + 'TEXT': '#fdcb52', + 'INPUT': '#705e52', + 'TEXT_INPUT': '#fdcb52', + 'SCROLL': '#705e52', + 'BUTTON': ('#000000', '#fdcb52'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue2': { + 'BACKGROUND': '#1a2835', + 'TEXT': '#d1ecff', + 'INPUT': '#335267', + 'TEXT_INPUT': '#acc2d0', + 'SCROLL': '#1b6497', + 'BUTTON': ('#000000', '#fafaf8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBrown2': { + 'BACKGROUND': '#280001', + 'TEXT': '#FFFFFF', + 'INPUT': '#d8d584', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#763e00', + 'BUTTON': ('#000000', '#daad28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGreen': { + 'BACKGROUND': '#82a459', + 'TEXT': '#000000', + 'INPUT': '#d8d584', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e3ecf3', + 'BUTTON': ('#FFFFFF', '#517239'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue1': { + 'BACKGROUND': '#A5CADD', + 'TEXT': '#6E266E', + 'INPUT': '#E0F5FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#E0F5FF', + 'BUTTON': ('#FFFFFF', '#303952'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightPurple': { + 'BACKGROUND': '#B0AAC2', + 'TEXT': '#000000', + 'INPUT': '#F2EFE8', + 'SCROLL': '#F2EFE8', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#C2D4D8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue2': { + 'BACKGROUND': '#AAB6D3', + 'TEXT': '#000000', + 'INPUT': '#F1F4FC', + 'SCROLL': '#F1F4FC', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#7186C7'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen3': { + 'BACKGROUND': '#A8C1B4', + 'TEXT': '#000000', + 'INPUT': '#DDE0DE', + 'SCROLL': '#E3E3E3', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#6D9F85'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue3': { + 'BACKGROUND': '#64778d', + 'TEXT': '#FFFFFF', + 'INPUT': '#f0f3f7', + 'SCROLL': '#A6B2BE', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#283b5b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen4': { + 'BACKGROUND': '#b4ffb4', + 'TEXT': '#000000', + 'INPUT': '#ffff64', + 'SCROLL': '#ffb482', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#ffa0dc'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen5': { + 'BACKGROUND': '#92aa9d', + 'TEXT': '#000000', + 'INPUT': '#fcfff6', + 'SCROLL': '#fcfff6', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#000000', '#d0dbbd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown2': { + 'BACKGROUND': '#a7ad7f', + 'TEXT': '#000000', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#5d907d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown3': { + 'BACKGROUND': '#efeccb', + 'TEXT': '#012f2f', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#012f2f', + 'BUTTON': ('#FFFFFF', '#046380'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue3': { + 'BACKGROUND': '#a8cfdd', + 'TEXT': '#000000', + 'INPUT': '#dfedf2', + 'SCROLL': '#dfedf2', + 'TEXT_INPUT': '#000000', + 'BUTTON': ('#FFFFFF', '#183440'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + ################################## End Renamed Original Themes ################################## + # + 'LightBrown4': { + 'BACKGROUND': '#d7c79e', + 'TEXT': '#a35638', + 'INPUT': '#9dab86', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#a35638', + 'BUTTON': ('#FFFFFF', '#a35638'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#a35638', '#9dab86', '#e08f62', '#d7c79e'], + }, + 'DarkTeal': { + 'BACKGROUND': '#003f5c', + 'TEXT': '#fb5b5a', + 'INPUT': '#bc4873', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#bc4873', + 'BUTTON': ('#FFFFFF', '#fb5b5a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], + }, + 'DarkPurple': { + 'BACKGROUND': '#472b62', + 'TEXT': '#fb5b5a', + 'INPUT': '#bc4873', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#bc4873', + 'BUTTON': ('#FFFFFF', '#472b62'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], + }, + 'LightGreen6': { + 'BACKGROUND': '#eafbea', + 'TEXT': '#1f6650', + 'INPUT': '#6f9a8d', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#1f6650', + 'BUTTON': ('#FFFFFF', '#1f6650'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#1f6650', '#6f9a8d', '#ea5e5e', '#eafbea'], + }, + 'DarkGrey2': { + 'BACKGROUND': '#2b2b28', + 'TEXT': '#f8f8f8', + 'INPUT': '#f1d6ab', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f1d6ab', + 'BUTTON': ('#2b2b28', '#e3b04b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#2b2b28', '#e3b04b', '#f1d6ab', '#f8f8f8'], + }, + 'LightBrown6': { + 'BACKGROUND': '#f9b282', + 'TEXT': '#8f4426', + 'INPUT': '#de6b35', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#8f4426', + 'BUTTON': ('#FFFFFF', '#8f4426'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#8f4426', '#de6b35', '#64ccda', '#f9b282'], + }, + 'DarkTeal1': { + 'BACKGROUND': '#396362', + 'TEXT': '#ffe7d1', + 'INPUT': '#f6c89f', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f6c89f', + 'BUTTON': ('#ffe7d1', '#4b8e8d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], + }, + 'LightBrown7': { + 'BACKGROUND': '#f6c89f', + 'TEXT': '#396362', + 'INPUT': '#4b8e8d', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#396362', + 'BUTTON': ('#FFFFFF', '#396362'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], + }, + 'DarkPurple1': { + 'BACKGROUND': '#0c093c', + 'TEXT': '#fad6d6', + 'INPUT': '#eea5f6', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#eea5f6', + 'BUTTON': ('#FFFFFF', '#df42d1'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#0c093c', '#df42d1', '#eea5f6', '#fad6d6'], + }, + 'DarkGrey3': { + 'BACKGROUND': '#211717', + 'TEXT': '#dfddc7', + 'INPUT': '#f58b54', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f58b54', + 'BUTTON': ('#dfddc7', '#a34a28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], + }, + 'LightBrown8': { + 'BACKGROUND': '#dfddc7', + 'TEXT': '#211717', + 'INPUT': '#a34a28', + 'TEXT_INPUT': '#dfddc7', + 'SCROLL': '#211717', + 'BUTTON': ('#dfddc7', '#a34a28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], + }, + 'DarkBlue4': { + 'BACKGROUND': '#494ca2', + 'TEXT': '#e3e7f1', + 'INPUT': '#c6cbef', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#c6cbef', + 'BUTTON': ('#FFFFFF', '#8186d5'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#494ca2', '#8186d5', '#c6cbef', '#e3e7f1'], + }, + 'LightBlue4': { + 'BACKGROUND': '#5c94bd', + 'TEXT': '#470938', + 'INPUT': '#1a3e59', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#470938', + 'BUTTON': ('#FFFFFF', '#470938'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#470938', '#1a3e59', '#5c94bd', '#f2d6eb'], + }, + 'DarkTeal2': { + 'BACKGROUND': '#394a6d', + 'TEXT': '#c0ffb3', + 'INPUT': '#52de97', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#52de97', + 'BUTTON': ('#c0ffb3', '#394a6d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], + }, + 'DarkTeal3': { + 'BACKGROUND': '#3c9d9b', + 'TEXT': '#c0ffb3', + 'INPUT': '#52de97', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#52de97', + 'BUTTON': ('#c0ffb3', '#394a6d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], + }, + 'DarkPurple5': { + 'BACKGROUND': '#730068', + 'TEXT': '#f6f078', + 'INPUT': '#01d28e', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#01d28e', + 'BUTTON': ('#f6f078', '#730068'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#730068', '#434982', '#01d28e', '#f6f078'], + }, + 'DarkPurple2': { + 'BACKGROUND': '#202060', + 'TEXT': '#b030b0', + 'INPUT': '#602080', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#602080', + 'BUTTON': ('#FFFFFF', '#202040'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#202040', '#202060', '#602080', '#b030b0'], + }, + 'DarkBlue5': { + 'BACKGROUND': '#000272', + 'TEXT': '#ff6363', + 'INPUT': '#a32f80', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#a32f80', + 'BUTTON': ('#FFFFFF', '#341677'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#000272', '#341677', '#a32f80', '#ff6363'], + }, + 'LightGrey2': { + 'BACKGROUND': '#f6f6f6', + 'TEXT': '#420000', + 'INPUT': '#d4d7dd', + 'TEXT_INPUT': '#420000', + 'SCROLL': '#420000', + 'BUTTON': ('#420000', '#d4d7dd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], + }, + 'LightGrey3': { + 'BACKGROUND': '#eae9e9', + 'TEXT': '#420000', + 'INPUT': '#d4d7dd', + 'TEXT_INPUT': '#420000', + 'SCROLL': '#420000', + 'BUTTON': ('#420000', '#d4d7dd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], + }, + 'DarkBlue6': { + 'BACKGROUND': '#01024e', + 'TEXT': '#ff6464', + 'INPUT': '#8b4367', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#8b4367', + 'BUTTON': ('#FFFFFF', '#543864'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#01024e', '#543864', '#8b4367', '#ff6464'], + }, + 'DarkBlue7': { + 'BACKGROUND': '#241663', + 'TEXT': '#eae7af', + 'INPUT': '#a72693', + 'TEXT_INPUT': '#eae7af', + 'SCROLL': '#a72693', + 'BUTTON': ('#eae7af', '#160f30'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#160f30', '#241663', '#a72693', '#eae7af'], + }, + 'LightBrown9': { + 'BACKGROUND': '#f6d365', + 'TEXT': '#3a1f5d', + 'INPUT': '#c83660', + 'TEXT_INPUT': '#f6d365', + 'SCROLL': '#3a1f5d', + 'BUTTON': ('#f6d365', '#c83660'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3a1f5d', '#c83660', '#e15249', '#f6d365'], + }, + 'DarkPurple3': { + 'BACKGROUND': '#6e2142', + 'TEXT': '#ffd692', + 'INPUT': '#e16363', + 'TEXT_INPUT': '#ffd692', + 'SCROLL': '#e16363', + 'BUTTON': ('#ffd692', '#943855'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], + }, + 'LightBrown10': { + 'BACKGROUND': '#ffd692', + 'TEXT': '#6e2142', + 'INPUT': '#943855', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#6e2142', + 'BUTTON': ('#FFFFFF', '#6e2142'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], + }, + 'DarkPurple4': { + 'BACKGROUND': '#200f21', + 'TEXT': '#f638dc', + 'INPUT': '#5a3d5c', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#5a3d5c', + 'BUTTON': ('#FFFFFF', '#382039'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#200f21', '#382039', '#5a3d5c', '#f638dc'], + }, + 'LightBlue5': { + 'BACKGROUND': '#b2fcff', + 'TEXT': '#3e64ff', + 'INPUT': '#5edfff', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#3e64ff', + 'BUTTON': ('#FFFFFF', '#3e64ff'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3e64ff', '#5edfff', '#b2fcff', '#ecfcff'], + }, + 'DarkTeal4': { + 'BACKGROUND': '#464159', + 'TEXT': '#c7f0db', + 'INPUT': '#8bbabb', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#8bbabb', + 'BUTTON': ('#FFFFFF', '#6c7b95'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], + }, + 'LightTeal': { + 'BACKGROUND': '#c7f0db', + 'TEXT': '#464159', + 'INPUT': '#6c7b95', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#464159', + 'BUTTON': ('#FFFFFF', '#464159'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], + }, + 'DarkTeal5': { + 'BACKGROUND': '#8bbabb', + 'TEXT': '#464159', + 'INPUT': '#6c7b95', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#464159', + 'BUTTON': ('#c7f0db', '#6c7b95'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], + }, + 'LightGrey4': { + 'BACKGROUND': '#faf5ef', + 'TEXT': '#672f2f', + 'INPUT': '#99b19c', + 'TEXT_INPUT': '#672f2f', + 'SCROLL': '#672f2f', + 'BUTTON': ('#672f2f', '#99b19c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], + }, + 'LightGreen7': { + 'BACKGROUND': '#99b19c', + 'TEXT': '#faf5ef', + 'INPUT': '#d7d1c9', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#d7d1c9', + 'BUTTON': ('#FFFFFF', '#99b19c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], + }, + 'LightGrey5': { + 'BACKGROUND': '#d7d1c9', + 'TEXT': '#672f2f', + 'INPUT': '#99b19c', + 'TEXT_INPUT': '#672f2f', + 'SCROLL': '#672f2f', + 'BUTTON': ('#FFFFFF', '#672f2f'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], + }, + 'DarkBrown3': { + 'BACKGROUND': '#a0855b', + 'TEXT': '#f9f6f2', + 'INPUT': '#f1d6ab', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f1d6ab', + 'BUTTON': ('#FFFFFF', '#38470b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], + }, + 'LightBrown11': { + 'BACKGROUND': '#f1d6ab', + 'TEXT': '#38470b', + 'INPUT': '#a0855b', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#38470b', + 'BUTTON': ('#f9f6f2', '#a0855b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], + }, + 'DarkRed': { + 'BACKGROUND': '#83142c', + 'TEXT': '#f9d276', + 'INPUT': '#ad1d45', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#ad1d45', + 'BUTTON': ('#f9d276', '#ad1d45'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#44000d', '#83142c', '#ad1d45', '#f9d276'], + }, + 'DarkTeal6': { + 'BACKGROUND': '#204969', + 'TEXT': '#fff7f7', + 'INPUT': '#dadada', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#dadada', + 'BUTTON': ('#000000', '#fff7f7'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#204969', '#08ffc8', '#dadada', '#fff7f7'], + }, + 'DarkBrown4': { + 'BACKGROUND': '#252525', + 'TEXT': '#ff0000', + 'INPUT': '#af0404', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#af0404', + 'BUTTON': ('#FFFFFF', '#252525'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#252525', '#414141', '#af0404', '#ff0000'], + }, + 'LightYellow': { + 'BACKGROUND': '#f4ff61', + 'TEXT': '#27aa80', + 'INPUT': '#32ff6a', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#27aa80', + 'BUTTON': ('#f4ff61', '#27aa80'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#27aa80', '#32ff6a', '#a8ff3e', '#f4ff61'], + }, + 'DarkGreen1': { + 'BACKGROUND': '#2b580c', + 'TEXT': '#fdef96', + 'INPUT': '#f7b71d', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f7b71d', + 'BUTTON': ('#fdef96', '#2b580c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#2b580c', '#afa939', '#f7b71d', '#fdef96'], + }, + 'LightGreen8': { + 'BACKGROUND': '#c8dad3', + 'TEXT': '#63707e', + 'INPUT': '#93b5b3', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#63707e', + 'BUTTON': ('#FFFFFF', '#63707e'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#63707e', '#93b5b3', '#c8dad3', '#f2f6f5'], + }, + 'DarkTeal7': { + 'BACKGROUND': '#248ea9', + 'TEXT': '#fafdcb', + 'INPUT': '#aee7e8', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#aee7e8', + 'BUTTON': ('#000000', '#fafdcb'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#248ea9', '#28c3d4', '#aee7e8', '#fafdcb'], + }, + 'DarkBlue8': { + 'BACKGROUND': '#454d66', + 'TEXT': '#d9d872', + 'INPUT': '#58b368', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#58b368', + 'BUTTON': ('#000000', '#009975'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#009975', '#454d66', '#58b368', '#d9d872'], + }, + 'DarkBlue9': { + 'BACKGROUND': '#263859', + 'TEXT': '#ff6768', + 'INPUT': '#6b778d', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#6b778d', + 'BUTTON': ('#ff6768', '#263859'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#17223b', '#263859', '#6b778d', '#ff6768'], + }, + 'DarkBlue10': { + 'BACKGROUND': '#0028ff', + 'TEXT': '#f1f4df', + 'INPUT': '#10eaf0', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#10eaf0', + 'BUTTON': ('#f1f4df', '#24009c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#24009c', '#0028ff', '#10eaf0', '#f1f4df'], + }, + 'DarkBlue11': { + 'BACKGROUND': '#6384b3', + 'TEXT': '#e6f0b6', + 'INPUT': '#b8e9c0', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#b8e9c0', + 'BUTTON': ('#e6f0b6', '#684949'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#684949', '#6384b3', '#b8e9c0', '#e6f0b6'], + }, + 'DarkTeal8': { + 'BACKGROUND': '#71a0a5', + 'TEXT': '#212121', + 'INPUT': '#665c84', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#212121', + 'BUTTON': ('#fab95b', '#665c84'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#212121', '#665c84', '#71a0a5', '#fab95b'], + }, + 'DarkRed1': { + 'BACKGROUND': '#c10000', + 'TEXT': '#eeeeee', + 'INPUT': '#dedede', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#dedede', + 'BUTTON': ('#c10000', '#eeeeee'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#c10000', '#ff4949', '#dedede', '#eeeeee'], + }, + 'LightBrown5': { + 'BACKGROUND': '#fff591', + 'TEXT': '#e41749', + 'INPUT': '#f5587b', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e41749', + 'BUTTON': ('#fff591', '#e41749'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#e41749', '#f5587b', '#ff8a5c', '#fff591'], + }, + 'LightGreen9': { + 'BACKGROUND': '#f1edb3', + 'TEXT': '#3b503d', + 'INPUT': '#4a746e', + 'TEXT_INPUT': '#f1edb3', + 'SCROLL': '#3b503d', + 'BUTTON': ('#f1edb3', '#3b503d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], + 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], + }, + 'DarkGreen2': { + 'BACKGROUND': '#3b503d', + 'TEXT': '#f1edb3', + 'INPUT': '#c8cf94', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#c8cf94', + 'BUTTON': ('#f1edb3', '#3b503d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], + 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], + }, + 'LightGray1': { + 'BACKGROUND': '#f2f2f2', + 'TEXT': '#222831', + 'INPUT': '#393e46', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#222831', + 'BUTTON': ('#f2f2f2', '#222831'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#222831', '#393e46', '#f96d00', '#f2f2f2'], + 'DESCRIPTION': ['#000000', 'Grey', 'Orange', 'Grey', 'Autumn'], + }, + 'DarkGrey4': { + 'BACKGROUND': '#52524e', + 'TEXT': '#e9e9e5', + 'INPUT': '#d4d6c8', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#d4d6c8', + 'BUTTON': ('#FFFFFF', '#9a9b94'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#52524e', '#9a9b94', '#d4d6c8', '#e9e9e5'], + 'DESCRIPTION': ['Grey', 'Pastel', 'Winter'], + }, + 'DarkBlue12': { + 'BACKGROUND': '#324e7b', + 'TEXT': '#f8f8f8', + 'INPUT': '#86a6df', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#86a6df', + 'BUTTON': ('#FFFFFF', '#5068a9'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#324e7b', '#5068a9', '#86a6df', '#f8f8f8'], + 'DESCRIPTION': ['Blue', 'Grey', 'Cold', 'Winter'], + }, + 'DarkPurple6': { + 'BACKGROUND': '#070739', + 'TEXT': '#e1e099', + 'INPUT': '#c327ab', + 'TEXT_INPUT': '#e1e099', + 'SCROLL': '#c327ab', + 'BUTTON': ('#e1e099', '#521477'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#070739', '#521477', '#c327ab', '#e1e099'], + 'DESCRIPTION': ['#000000', 'Purple', 'Yellow', 'Dark'], + }, + 'DarkBlue13': { + 'BACKGROUND': '#203562', + 'TEXT': '#e3e8f8', + 'INPUT': '#c0c5cd', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#c0c5cd', + 'BUTTON': ('#FFFFFF', '#3e588f'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#203562', '#3e588f', '#c0c5cd', '#e3e8f8'], + 'DESCRIPTION': ['Blue', 'Grey', 'Wedding', 'Cold'], + }, + 'DarkBrown5': { + 'BACKGROUND': '#3c1b1f', + 'TEXT': '#f6e1b5', + 'INPUT': '#e2bf81', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e2bf81', + 'BUTTON': ('#3c1b1f', '#f6e1b5'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3c1b1f', '#b21e4b', '#e2bf81', '#f6e1b5'], + 'DESCRIPTION': ['Brown', 'Red', 'Yellow', 'Warm'], + }, + 'DarkGreen3': { + 'BACKGROUND': '#062121', + 'TEXT': '#eeeeee', + 'INPUT': '#e4dcad', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e4dcad', + 'BUTTON': ('#eeeeee', '#181810'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], + 'DESCRIPTION': ['#000000', '#000000', 'Brown', 'Grey'], + }, + 'DarkBlack1': { + 'BACKGROUND': '#181810', + 'TEXT': '#eeeeee', + 'INPUT': '#e4dcad', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e4dcad', + 'BUTTON': ('#FFFFFF', '#062121'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], + 'DESCRIPTION': ['#000000', '#000000', 'Brown', 'Grey'], + }, + 'DarkGrey5': { + 'BACKGROUND': '#343434', + 'TEXT': '#f3f3f3', + 'INPUT': '#e9dcbe', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e9dcbe', + 'BUTTON': ('#FFFFFF', '#8e8b82'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], + 'DESCRIPTION': ['Grey', 'Brown'], + }, + 'LightBrown12': { + 'BACKGROUND': '#8e8b82', + 'TEXT': '#f3f3f3', + 'INPUT': '#e9dcbe', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e9dcbe', + 'BUTTON': ('#f3f3f3', '#8e8b82'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], + 'DESCRIPTION': ['Grey', 'Brown'], + }, + 'DarkTeal9': { + 'BACKGROUND': '#13445a', + 'TEXT': '#fef4e8', + 'INPUT': '#446878', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#446878', + 'BUTTON': ('#fef4e8', '#446878'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#13445a', '#970747', '#446878', '#fef4e8'], + 'DESCRIPTION': ['Red', 'Grey', 'Blue', 'Wedding', 'Retro'], + }, + 'DarkBlue14': { + 'BACKGROUND': '#21273d', + 'TEXT': '#f1f6f8', + 'INPUT': '#b9d4f1', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#b9d4f1', + 'BUTTON': ('#FFFFFF', '#6a759b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], + 'DESCRIPTION': ['Blue', '#000000', 'Grey', 'Cold', 'Winter'], + }, + 'LightBlue6': { + 'BACKGROUND': '#f1f6f8', + 'TEXT': '#21273d', + 'INPUT': '#6a759b', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#21273d', + 'BUTTON': ('#f1f6f8', '#6a759b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], + 'DESCRIPTION': ['Blue', '#000000', 'Grey', 'Cold', 'Winter'], + }, + 'DarkGreen4': { + 'BACKGROUND': '#044343', + 'TEXT': '#e4e4e4', + 'INPUT': '#045757', + 'TEXT_INPUT': '#e4e4e4', + 'SCROLL': '#045757', + 'BUTTON': ('#e4e4e4', '#045757'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#222222', '#044343', '#045757', '#e4e4e4'], + 'DESCRIPTION': ['#000000', 'Turquoise', 'Grey', 'Dark'], + }, + 'DarkGreen5': { + 'BACKGROUND': '#1b4b36', + 'TEXT': '#e0e7f1', + 'INPUT': '#aebd77', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#aebd77', + 'BUTTON': ('#FFFFFF', '#538f6a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#1b4b36', '#538f6a', '#aebd77', '#e0e7f1'], + 'DESCRIPTION': ['Green', 'Grey'], + }, + 'DarkTeal10': { + 'BACKGROUND': '#0d3446', + 'TEXT': '#d8dfe2', + 'INPUT': '#71adb5', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#71adb5', + 'BUTTON': ('#FFFFFF', '#176d81'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#0d3446', '#176d81', '#71adb5', '#d8dfe2'], + 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter', 'Cold'], + }, + 'DarkGrey6': { + 'BACKGROUND': '#3e3e3e', + 'TEXT': '#ededed', + 'INPUT': '#68868c', + 'TEXT_INPUT': '#ededed', + 'SCROLL': '#68868c', + 'BUTTON': ('#FFFFFF', '#405559'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], + 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], + }, + 'DarkTeal11': { + 'BACKGROUND': '#405559', + 'TEXT': '#ededed', + 'INPUT': '#68868c', + 'TEXT_INPUT': '#ededed', + 'SCROLL': '#68868c', + 'BUTTON': ('#ededed', '#68868c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], + 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], + }, + 'LightBlue7': { + 'BACKGROUND': '#9ed0e0', + 'TEXT': '#19483f', + 'INPUT': '#5c868e', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#19483f', + 'BUTTON': ('#FFFFFF', '#19483f'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#19483f', '#5c868e', '#ff6a38', '#9ed0e0'], + 'DESCRIPTION': ['Orange', 'Blue', 'Turquoise'], + }, + 'LightGreen10': { + 'BACKGROUND': '#d8ebb5', + 'TEXT': '#205d67', + 'INPUT': '#639a67', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#205d67', + 'BUTTON': ('#d8ebb5', '#205d67'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#205d67', '#639a67', '#d9bf77', '#d8ebb5'], + 'DESCRIPTION': ['Blue', 'Green', 'Brown', 'Vintage'], + }, + 'DarkBlue15': { + 'BACKGROUND': '#151680', + 'TEXT': '#f1fea4', + 'INPUT': '#375fc0', + 'TEXT_INPUT': '#f1fea4', + 'SCROLL': '#375fc0', + 'BUTTON': ('#f1fea4', '#1c44ac'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], + 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], + }, + 'DarkBlue16': { + 'BACKGROUND': '#1c44ac', + 'TEXT': '#f1fea4', + 'INPUT': '#375fc0', + 'TEXT_INPUT': '#f1fea4', + 'SCROLL': '#375fc0', + 'BUTTON': ('#f1fea4', '#151680'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], + 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], + }, + 'DarkTeal12': { + 'BACKGROUND': '#004a7c', + 'TEXT': '#fafafa', + 'INPUT': '#e8f1f5', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e8f1f5', + 'BUTTON': ('#fafafa', '#005691'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#004a7c', '#005691', '#e8f1f5', '#fafafa'], + 'DESCRIPTION': ['Grey', 'Blue', 'Cold', 'Winter'], + }, + 'LightBrown13': { + 'BACKGROUND': '#ebf5ee', + 'TEXT': '#921224', + 'INPUT': '#bdc6b8', + 'TEXT_INPUT': '#921224', + 'SCROLL': '#921224', + 'BUTTON': ('#FFFFFF', '#921224'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#921224', '#bdc6b8', '#bce0da', '#ebf5ee'], + 'DESCRIPTION': ['Red', 'Blue', 'Grey', 'Vintage', 'Wedding'], + }, + 'DarkBlue17': { + 'BACKGROUND': '#21294c', + 'TEXT': '#f9f2d7', + 'INPUT': '#f2dea8', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f2dea8', + 'BUTTON': ('#f9f2d7', '#141829'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#141829', '#21294c', '#f2dea8', '#f9f2d7'], + 'DESCRIPTION': ['#000000', 'Blue', 'Yellow'], + }, + 'DarkBrown6': { + 'BACKGROUND': '#785e4d', + 'TEXT': '#f2eee3', + 'INPUT': '#baaf92', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#baaf92', + 'BUTTON': ('#FFFFFF', '#785e4d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#785e4d', '#ff8426', '#baaf92', '#f2eee3'], + 'DESCRIPTION': ['Grey', 'Brown', 'Orange', 'Autumn'], + }, + 'DarkGreen6': { + 'BACKGROUND': '#5c715e', + 'TEXT': '#f2f9f1', + 'INPUT': '#ddeedf', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#ddeedf', + 'BUTTON': ('#f2f9f1', '#5c715e'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#5c715e', '#b6cdbd', '#ddeedf', '#f2f9f1'], + 'DESCRIPTION': ['Grey', 'Green', 'Vintage'], + }, + 'DarkGrey7': { + 'BACKGROUND': '#4b586e', + 'TEXT': '#dddddd', + 'INPUT': '#574e6d', + 'TEXT_INPUT': '#dddddd', + 'SCROLL': '#574e6d', + 'BUTTON': ('#dddddd', '#43405d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#43405d', '#4b586e', '#574e6d', '#dddddd'], + 'DESCRIPTION': ['Grey', 'Winter', 'Cold'], + }, + 'DarkRed2': { + 'BACKGROUND': '#ab1212', + 'TEXT': '#f6e4b5', + 'INPUT': '#cd3131', + 'TEXT_INPUT': '#f6e4b5', + 'SCROLL': '#cd3131', + 'BUTTON': ('#f6e4b5', '#ab1212'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#ab1212', '#1fad9f', '#cd3131', '#f6e4b5'], + 'DESCRIPTION': ['Turquoise', 'Red', 'Yellow'], + }, + 'LightGrey6': { + 'BACKGROUND': '#e3e3e3', + 'TEXT': '#233142', + 'INPUT': '#455d7a', + 'TEXT_INPUT': '#e3e3e3', + 'SCROLL': '#233142', + 'BUTTON': ('#e3e3e3', '#455d7a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#233142', '#455d7a', '#f95959', '#e3e3e3'], + 'DESCRIPTION': ['#000000', 'Blue', 'Red', 'Grey'], + }, + 'HotDogStand': { + 'BACKGROUND': 'red', + 'TEXT': 'yellow', + 'INPUT': 'yellow', + 'TEXT_INPUT': '#000000', + 'SCROLL': 'yellow', + 'BUTTON': ('red', 'yellow'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, +} + + +def ListOfLookAndFeelValues(): + """ + Get a list of the valid values to pass into your call to change_look_and_feel + :return: List[str] - list of valid string values + """ + return sorted(list(LOOK_AND_FEEL_TABLE.keys())) + + +def theme(new_theme=None): + """ + Sets / Gets the current Theme. If none is specified then returns the current theme. + This call replaces the ChangeLookAndFeel / change_look_and_feel call which only sets the theme. + + :param new_theme: (str) the new theme name to use + :return: (str) the currently selected theme + """ + if new_theme is not None: + change_look_and_feel(new_theme) + return CURRENT_LOOK_AND_FEEL + + +def theme_background_color(color=None): + """ + Sets/Returns the background color currently in use + Used for Windows and containers (Column, Frame, Tab) and tables + + :return: (str) - color string of the background color currently in use + """ + if color is not None: + set_options(background_color=color) + return DEFAULT_BACKGROUND_COLOR + + +def theme_element_background_color(color=None): + """ + Sets/Returns the background color currently in use for all elements except containers + + :return: (str) - color string of the element background color currently in use + """ + if color is not None: + set_options(element_background_color=color) + return DEFAULT_ELEMENT_BACKGROUND_COLOR + + +def theme_text_color(color=None): + """ + Sets/Returns the text color currently in use + + :return: (str) - color string of the text color currently in use + """ + if color is not None: + set_options(text_color=color) + return DEFAULT_TEXT_COLOR + + +def theme_text_element_background_color(color=None): + """ + Sets/Returns the background color for text elements + + :return: (str) - color string of the text background color currently in use + """ + if color is not None: + set_options(text_element_background_color=color) + return DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR + + +def theme_input_background_color(color=None): + """ + Sets/Returns the input element background color currently in use + + :return: (str) - color string of the input element background color currently in use + """ + if color is not None: + set_options(input_elements_background_color=color) + return DEFAULT_INPUT_ELEMENTS_COLOR + + +def theme_input_text_color(color=None): + """ + Sets/Returns the input element entry color (not the text but the thing that's displaying the text) + + :return: (str) - color string of the input element color currently in use + """ + if color is not None: + set_options(input_text_color=color) + return DEFAULT_INPUT_TEXT_COLOR + + +def theme_button_color(color=None): + """ + Sets/Returns the button color currently in use + + :return: Tuple[str, str] - TUPLE with color strings of the button color currently in use (button text color, button background color) + """ + if color is not None: + set_options(button_color=color) + return DEFAULT_BUTTON_COLOR + + +def theme_progress_bar_color(color=None): + """ + Sets/Returns the progress bar colors by the current color theme + + :return: Tuple[str, str] - TUPLE with color strings of the ProgressBar color currently in use(button text color, button background color) + """ + if color is not None: + set_options(progress_meter_color=color) + return DEFAULT_PROGRESS_BAR_COLOR + + +def theme_slider_color(color=None): + """ + Sets/Returns the slider color (used for sliders) + + :return: (str) - color string of the slider color currently in use + """ + if color is not None: + set_options(scrollbar_color=color) + return DEFAULT_SCROLLBAR_COLOR + + +def theme_border_width(border_width=None): + """ + Sets/Returns the border width currently in use + Used by non ttk elements at the moment + + :return: (int) - border width currently in use + """ + if border_width is not None: + set_options(border_width=border_width) + return DEFAULT_BORDER_WIDTH + + +def theme_slider_border_width(border_width=None): + """ + Sets/Returns the slider border width currently in use + + :return: (int) - border width currently in use + """ + if border_width is not None: + set_options(slider_border_width=border_width) + return DEFAULT_SLIDER_BORDER_WIDTH + + +def theme_progress_bar_border_width(border_width=None): + """ + Sets/Returns the progress meter border width currently in use + + :return: (int) - border width currently in use + """ + if border_width is not None: + set_options(progress_meter_border_depth=border_width) + return DEFAULT_PROGRESS_BAR_BORDER_WIDTH + + +def theme_element_text_color(color=None): + """ + Sets/Returns the text color used by elements that have text as part of their display (Tables, Trees and Sliders) + + :return: (str) - color string currently in use + """ + if color is not None: + set_options(element_text_color=color) + return DEFAULT_ELEMENT_TEXT_COLOR + + +def theme_list(): + """ + Returns a sorted list of the currently available color themes + + :return: List[str] - A sorted list of the currently available color themes + """ + return list_of_look_and_feel_values() + + +def theme_add_new(new_theme_name, new_theme_dict): + """ + Add a new theme to the dictionary of themes + + :param new_theme_name: text to display in element + :type new_theme_name: (str) + :param new_theme_dict: text to display in element + :type new_theme_dict: (dict) + """ + global LOOK_AND_FEEL_TABLE + try: + LOOK_AND_FEEL_TABLE[new_theme_name] = new_theme_dict + except Exception as e: + print('Exception during adding new theme {}'.format(e)) + + +def theme_previewer(columns=12): + """ + Show a window with all of the color themes - takes a while so be patient + + :param columns: (int) number of themes in a single row + """ + preview_all_look_and_feel_themes(columns) + + +def ChangeLookAndFeel(index, force=False): + """ + Change the "color scheme" of all future PySimpleGUI Windows. + The scheme are string names that specify a group of colors. Background colors, text colors, button colors. + There are 13 different color settings that are changed at one time using a single call to ChangeLookAndFeel + The look and feel table itself has these indexes into the dictionary LOOK_AND_FEEL_TABLE. + The original list was (prior to a major rework and renaming)... these names still work... + In Nov 2019 a new Theme Formula was devised to make choosing a theme easier: + The "Formula" is: + ["Dark" or "Light"] Color Number + Colors can be Blue Brown Grey Green Purple Red Teal Yellow Black + The number will vary for each pair. There are more DarkGrey entries than there are LightYellow for example. + Default = The default settings (only button color is different than system default) + Default1 = The full system default including the button (everything's gray... how sad... don't be all gray... please....) + :param index: (str) the name of the index into the Look and Feel table (does not have to be exact, can be "fuzzy") + :param force: (bool) no longer used + """ + + global CURRENT_LOOK_AND_FEEL + + # if sys.platform.startswith('darwin') and not force: + # print('*** Changing look and feel is not supported on Mac platform ***') + # return + + theme = index + # normalize available l&f values + lf_values = [item.lower() for item in list_of_look_and_feel_values()] + + # option 1 + opt1 = theme.replace(' ', '').lower() + + # option 2 (reverse lookup) + optx = theme.lower().split(' ') + optx.reverse() + opt2 = ''.join(optx) + + # search for valid l&f name + if opt1 in lf_values: + ix = lf_values.index(opt1) + elif opt2 in lf_values: + ix = lf_values.index(opt2) + else: + ix = randint(0, len(lf_values) - 1) + print('** Warning - {} Theme is not a valid theme. Change your theme call. **'.format(index)) + print('valid values are', list_of_look_and_feel_values()) + print('Instead, please enjoy a random Theme named {}'.format(list_of_look_and_feel_values()[ix])) + + selection = list_of_look_and_feel_values()[ix] + CURRENT_LOOK_AND_FEEL = selection + try: + colors = LOOK_AND_FEEL_TABLE[selection] + + # Color the progress bar using button background and input colors...unless they're the same + if colors['PROGRESS'] != COLOR_SYSTEM_DEFAULT: + if colors['BUTTON'][1] != colors['INPUT'] and colors['BUTTON'][1] != colors['BACKGROUND']: + colors['PROGRESS'] = colors['BUTTON'][1], colors['INPUT'] + else: # if the same, then use text input on top of input color + colors['PROGRESS'] = (colors['TEXT_INPUT'], colors['INPUT']) + else: + colors['PROGRESS'] = DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL + # call to change all the colors + SetOptions( + background_color=colors['BACKGROUND'], + text_element_background_color=colors['BACKGROUND'], + element_background_color=colors['BACKGROUND'], + text_color=colors['TEXT'], + input_elements_background_color=colors['INPUT'], + # button_color=colors['BUTTON'] if not sys.platform.startswith('darwin') else None, + button_color=colors['BUTTON'], + progress_meter_color=colors['PROGRESS'], + border_width=colors['BORDER'], + slider_border_width=colors['SLIDER_DEPTH'], + progress_meter_border_depth=colors['PROGRESS_DEPTH'], + scrollbar_color=(colors['SCROLL']), + element_text_color=colors['TEXT'], + input_text_color=colors['TEXT_INPUT'], + ) + except: # most likely an index out of range + print('** Warning - Theme value not valid. Change your theme call. **') + print('valid values are', list_of_look_and_feel_values()) + + +def preview_all_look_and_feel_themes(columns=12): + """ + Displays a "Quick Reference Window" showing all of the different Look and Feel settings that are available. + They are sorted alphabetically. The legacy color names are mixed in, but otherwise they are sorted into Dark and Light halves + :param columns: (int) The number of themes to display per row + """ + + # Show a "splash" type message so the user doesn't give up waiting + popup_quick_message( + 'Hang on for a moment, this will take a bit to create....', + background_color='red', + text_color='#FFFFFF', + auto_close=True, + non_blocking=True, + ) + + web = False + + win_bg = 'black' + + def sample_layout(): + return [ + [Text('Text element'), InputText('Input data here', size=(10, 1))], + [Button('Ok'), Button('Cancel'), Slider((1, 10), orientation='h', size=(5, 15))], + ] + + layout = [[Text('Here is a complete list of themes', font='Default 18', background_color=win_bg)]] + + names = list_of_look_and_feel_values() + names.sort() + row = [] + for count, theme in enumerate(names): + change_look_and_feel(theme) + if not count % columns: + layout += [row] + row = [] + row += [Frame(theme, sample_layout() if not web else [[T(theme)]] + sample_layout())] + if row: + layout += [row] + + window = Window('Preview of all Look and Feel choices', layout, background_color=win_bg) + window.read() + window.close() + + +# ============================== sprint ======# +# Is identical to the Scrolled Text Box # +# Provides a crude 'print' mechanism but in a # +# GUI environment # +# ============================================# +sprint = ScrolledTextBox + + +# Converts an object's contents into a nice printable string. Great for dumping debug data +def ObjToStringSingleObj(obj): + if obj is None: + return 'None' + return str(obj.__class__) + '\n' + '\n'.join((repr(item) + ' = ' + repr(obj.__dict__[item]) for item in sorted(obj.__dict__))) + + +def ObjToString(obj, extra=' '): + if obj is None: + return 'None' + return str(obj.__class__) + '\n' + '\n'.join((extra + (str(item) + ' = ' + (ObjToString(obj.__dict__[item], extra + ' ') if hasattr(obj.__dict__[item], '__dict__') else str(obj.__dict__[item]))) for item in sorted(obj.__dict__))) + + +# ------------------------------------------------------------------------------------------------------------------ # +# ===================================== Upper PySimpleGUI ======================================================== # +# Pre-built dialog boxes for all your needs These are the "high level API calls # +# ------------------------------------------------------------------------------------------------------------------ # + +# ----------------------------------- The mighty Popup! ------------------------------------------------------------ # + + +def Popup(*args, button_color=None, background_color=None, text_color=None, button_type=POPUP_BUTTONS_OK, auto_close=False, auto_close_duration=None, custom_text=(None, None), non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Popup - Display a popup box with as many parms as you wish to include + :param args: + :param button_color: + :param background_color: + :param text_color: + :param button_type: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + if not args: + args_to_print = [''] + else: + args_to_print = args + if line_width != None: + local_line_width = line_width + else: + local_line_width = MESSAGE_BOX_LINE_WIDTH + title = args_to_print[0] if args_to_print[0] is not None else 'None' + window = Window( + title, + auto_size_text=True, + background_color=background_color, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + icon=icon, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + max_line_total, total_lines = 0, 0 + for message in args_to_print: + # fancy code to check if string and convert if not is not need. Just always convert to string :-) + # if not isinstance(message, str): message = str(message) + message = str(message) + if message.count('\n'): + message_wrapped = message + else: + message_wrapped = textwrap.fill(message, local_line_width) + message_wrapped_lines = message_wrapped.count('\n') + 1 + longest_line_len = max([len(l) for l in message.split('\n')]) + width_used = min(longest_line_len, local_line_width) + max_line_total = max(max_line_total, width_used) + # height = _GetNumLinesNeeded(message, width_used) + height = message_wrapped_lines + window.AddRow(Text(message_wrapped, auto_size_text=True, text_color=text_color, background_color=background_color)) + total_lines += height + + if non_blocking: + PopupButton = DummyButton # important to use or else button will close other windows too! + else: + PopupButton = Button + # show either an OK or Yes/No depending on paramater + if custom_text != (None, None): + if type(custom_text) is not tuple: + window.AddRow(PopupButton(custom_text, size=(len(custom_text), 1), button_color=button_color, focus=True, bind_return_key=True)) + elif custom_text[1] is None: + window.AddRow( + PopupButton( + custom_text[0], + size=(len(custom_text[0]), 1), + button_color=button_color, + focus=True, + bind_return_key=True, + ) + ) + else: + window.AddRow( + PopupButton( + custom_text[0], + button_color=button_color, + focus=True, + bind_return_key=True, + size=(len(custom_text[0]), 1), + ), + PopupButton(custom_text[1], button_color=button_color, size=(len(custom_text[0]), 1)), + ) + elif button_type is POPUP_BUTTONS_YES_NO: + window.AddRow( + PopupButton('Yes', button_color=button_color, focus=True, bind_return_key=True, pad=((20, 5), 3), size=(5, 1)), + PopupButton('No', button_color=button_color, size=(5, 1)), + ) + elif button_type is POPUP_BUTTONS_CANCELLED: + window.AddRow(PopupButton('Cancelled', button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) + elif button_type is POPUP_BUTTONS_ERROR: + window.AddRow(PopupButton('Error', size=(6, 1), button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) + elif button_type is POPUP_BUTTONS_OK_CANCEL: + window.AddRow( + PopupButton('OK', size=(6, 1), button_color=button_color, focus=True, bind_return_key=True), + PopupButton('Cancel', size=(6, 1), button_color=button_color), + ) + elif button_type is POPUP_BUTTONS_NO_BUTTONS: + pass + else: + window.AddRow(PopupButton('OK', size=(5, 1), button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) + + if non_blocking: + button, values = window.Read(timeout=0) + else: + button, values = window.Read() + window.Close() + return button + + +# ============================== MsgBox============# +# Lazy function. Same as calling Popup with parms # +# This function WILL Disappear perhaps today # +# ==================================================# +# MsgBox is the legacy call and should not be used any longer +def MsgBox(*args): + raise DeprecationWarning('MsgBox is no longer supported... change your call to Popup') + + +# --------------------------- PopupNoButtons --------------------------- +def PopupNoButtons(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Show a Popup but without any buttons + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=POPUP_BUTTONS_NO_BUTTONS, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location + ) + + +# --------------------------- PopupNonBlocking --------------------------- +def PopupNonBlocking(*args, button_type=POPUP_BUTTONS_OK, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=True, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Show Popup box and immediately return (does not block) + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) + + +PopupNoWait = PopupNonBlocking + + +# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup --------------------------- +def PopupQuick(*args, button_type=POPUP_BUTTONS_OK, button_color=None, background_color=None, text_color=None, auto_close=True, auto_close_duration=2, non_blocking=True, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Show Popup box that doesn't block and closes itself + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) + + +# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup with no titlebar and no buttons --------------------------- +def PopupQuickMessage(*args, button_type=POPUP_BUTTONS_NO_BUTTONS, button_color=None, background_color=None, text_color=None, auto_close=True, auto_close_duration=2, non_blocking=True, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=True, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Show Popup box that doesn't block and closes itself + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) + + +# --------------------------- PopupNoTitlebar --------------------------- +def PopupNoTitlebar(*args, button_type=POPUP_BUTTONS_OK, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, grab_anywhere=True, keep_on_top=False, location=(None, None)): + """ + Display a Popup without a titlebar. Enables grab anywhere so you can move it + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=True, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) + + +PopupNoFrame = PopupNoTitlebar +PopupNoBorder = PopupNoTitlebar +PopupAnnoying = PopupNoTitlebar + + +# --------------------------- PopupAutoClose --------------------------- +def PopupAutoClose(*args, button_type=POPUP_BUTTONS_OK, button_color=None, background_color=None, text_color=None, auto_close=True, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Popup that closes itself after some time period + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) + + +PopupTimed = PopupAutoClose + + +# --------------------------- PopupError --------------------------- +def PopupError(*args, button_color=DEFAULT_ERROR_BUTTON_COLOR, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Popup with colored button and 'Error' as button text + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_type=POPUP_BUTTONS_ERROR, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location + ) + + +# --------------------------- PopupCancel --------------------------- +def PopupCancel(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Display Popup with "cancelled" button text + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_type=POPUP_BUTTONS_CANCELLED, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location + ) + + +# --------------------------- PopupOK --------------------------- +def PopupOK(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Display Popup with OK button only + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_type=POPUP_BUTTONS_OK, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location + ) + + +# --------------------------- PopupOKCancel --------------------------- +def PopupOKCancel(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Display popup with OK and Cancel buttons + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: OK, Cancel or None + """ + return Popup( + *args, + button_type=POPUP_BUTTONS_OK_CANCEL, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location + ) + + +# --------------------------- PopupYesNo --------------------------- +def PopupYesNo(*args, button_color=None, background_color=None, text_color=None, auto_close=False, auto_close_duration=None, non_blocking=False, icon=DEFAULT_WINDOW_ICON, line_width=None, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): + """ + Display Popup with Yes and No buttons + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: Yes, No or None + """ + return Popup( + *args, + button_type=POPUP_BUTTONS_YES_NO, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location + ) + + +############################################################################## +# The PopupGet_____ functions - Will return user input # +############################################################################## + +# --------------------------- PopupGetFolder --------------------------- + + +def PopupGetFolder( + message, + default_path='', + no_window=False, + size=(None, None), + button_color=None, + background_color=None, + text_color=None, + icon=DEFAULT_WINDOW_ICON, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + initial_folder=None, +): + """ + Display popup with text entry field and browse button. Browse for folder + :param message: + :param default_path: + :param no_window: + :param size: + :param button_color: + :param background_color: + :param text_color: + :param icon: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: Contents of text field. None if closed using X or cancelled + """ + + global _my_windows + + if no_window: + if _my_windows._NumOpenWindows: + root = tk.Toplevel() + else: + root = tk.Tk() + try: + root.attributes('-alpha', 0) # hide window while building it. makes for smoother 'paint' + except: + pass + folder_name = tk.filedialog.askdirectory() # show the 'get folder' dialog box + root.destroy() + return folder_name + + layout = [ + [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], + [InputText(default_text=default_path, size=size, key='_INPUT_'), FolderBrowse(initial_folder=initial_folder)], + [Button('Ok', size=(5, 1), bind_return_key=True), Button('Cancel', size=(5, 1))], + ] + + window = Window( + title=message, + layout=layout, + icon=icon, + auto_size_text=True, + button_color=button_color, + background_color=background_color, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + button, values = window.Read() + window.Close() + if button != 'Ok': + return None + else: + path = values['_INPUT_'] + return path + + +# --------------------------- PopupGetFile --------------------------- + + +def PopupGetFile( + message, + default_path='', + default_extension='', + save_as=False, + file_types=(('ALL Files', '*.*'),), + no_window=False, + size=(None, None), + button_color=None, + background_color=None, + text_color=None, + icon=DEFAULT_WINDOW_ICON, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + initial_folder=None, +): + """ + Display popup with text entry field and browse button. Browse for file + :param message: + :param default_path: + :param default_extension: + :param save_as: + :param file_types: + :param no_window: + :param size: + :param button_color: + :param background_color: + :param text_color: + :param icon: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: string representing the path chosen, None if cancelled or window closed with X + """ + + global _my_windows + + if no_window: + if _my_windows._NumOpenWindows: + root = tk.Toplevel() + else: + root = tk.Tk() + try: + root.attributes('-alpha', 0) # hide window while building it. makes for smoother 'paint' + except: + pass + if save_as: + filename = tk.filedialog.asksaveasfilename(filetypes=file_types, defaultextension=default_extension) # show the 'get file' dialog box + else: + filename = tk.filedialog.askopenfilename(filetypes=file_types, defaultextension=default_extension) # show the 'get file' dialog box + root.destroy() + return filename + + browse_button = SaveAs(file_types=file_types, initial_folder=initial_folder) if save_as else FileBrowse(file_types=file_types, initial_folder=initial_folder) + + layout = [ + [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], + [InputText(default_text=default_path, size=size, key='_INPUT_'), browse_button], + [Button('Ok', size=(6, 1), bind_return_key=True), Button('Cancel', size=(6, 1))], + ] + + window = Window( + title=message, + layout=layout, + icon=icon, + auto_size_text=True, + button_color=button_color, + font=font, + background_color=background_color, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + button, values = window.Read() + window.Close() + if button != 'Ok': + return None + else: + path = values['_INPUT_'] + return path + + +# --------------------------- PopupGetText --------------------------- + + +def PopupGetText( + message, + default_text='', + password_char='', + size=(None, None), + button_color=None, + background_color=None, + text_color=None, + icon=DEFAULT_WINDOW_ICON, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Display Popup with text entry field + :param message: + :param default_text: + :param password_char: + :param size: + :param button_color: + :param background_color: + :param text_color: + :param icon: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: Text entered or None if window was closed + """ + + layout = [ + [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color, font=font)], + [InputText(default_text=default_text, size=size, key='_INPUT_', password_char=password_char)], + [Button('Ok', size=(5, 1), bind_return_key=True), Button('Cancel', size=(5, 1))], + ] + + window = Window( + title=message, + layout=layout, + icon=icon, + auto_size_text=True, + button_color=button_color, + no_titlebar=no_titlebar, + background_color=background_color, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + button, values = window.Read() + window.Close() + if button != 'Ok': + return None + else: + path = values['_INPUT_'] + return path + + +change_look_and_feel = ChangeLookAndFeel +easy_print = EasyPrint +easy_print_close = EasyPrintClose +get_complimentary_hex = GetComplimentaryHex +list_of_look_and_feel_values = ListOfLookAndFeelValues +obj_to_string = ObjToString +obj_to_string_single_obj = ObjToStringSingleObj +one_line_progress_meter = OneLineProgressMeter +one_line_progress_meter_cancel = OneLineProgressMeterCancel +popup = Popup +popup_annoying = PopupAnnoying +popup_auto_close = PopupAutoClose +popup_cancel = PopupCancel +popup_error = PopupError +popup_get_file = PopupGetFile +popup_get_folder = PopupGetFolder +popup_get_text = PopupGetText +popup_no_border = PopupNoBorder +popup_no_buttons = PopupNoButtons +popup_no_frame = PopupNoFrame +popup_no_titlebar = PopupNoTitlebar +popup_no_wait = PopupNoWait +popup_non_blocking = PopupNonBlocking +popup_ok = PopupOK +popup_ok_cancel = PopupOKCancel +popup_quick = PopupQuick +popup_quick_message = PopupQuickMessage +popup_scrolled = PopupScrolled +popup_timed = PopupTimed +popup_yes_no = PopupYesNo +print_close = PrintClose +rgb = RGB +scrolled_text_box = ScrolledTextBox +set_global_icon = SetGlobalIcon +set_options = SetOptions +timer_start = TimerStart +timer_stop = TimerStop +sprint = sprint + +# ------------------------ Set the "Official PySimpleGUI Theme Colors" ------------------------ +theme(CURRENT_LOOK_AND_FEEL) +# theme_previewer() +# -------------------------------- ENTRY POINT IF RUN STANDALONE -------------------------------- # + + +def main(): + # ChangeLookAndFeel('light green 6' ) + + # Popup('Popup Test') + + # SetOptions(background_color='blue', text_element_background_color='blue', text_color='white') + # layout = [[Text('You are running the PySimpleGUI.py file itself', font='Any 25', size=(60,1), tooltip='My tooltip!')], + # [Text('You should be importing it rather than running it', size=(60, 1))], + # [Text('Here is your sample window....')], + # [Text('Source Folder', justification='right', size=(40,1)), InputText('Source', focus=True, disabled=True), + # FolderBrowse()], + # [Text('Destination Folder', justification='right', size=(40,1)), InputText('Dest'), FolderBrowse()], + # [Ok(), Cancel(disabled=True), Exit(tooltip='Exit button'), Button('Hidden Button', visible=False)]] + + ver = version.split('\n')[0] + + def VerLine(version, description, size=(30, 1)): + return [Column([[T(description, font='Courier 18', text_color='yellow')], [T(version, font='Courier 18', size=size)]])] + + menu_def = [ + ['&File', ['&Open', '&Save', 'E&xit', 'Properties']], + [ + '&Edit', + [ + 'Paste', + [ + 'Special', + 'Normal', + ], + '!Undo', + ], + ], + [ + '!&Disabled', + [ + 'Paste', + [ + 'Special', + 'Normal', + ], + '!Undo', + ], + ], + ['&Help', '&About...'], + ] + + menu_def = [ + ['File', ['&Open::mykey', '&Save', 'E&xit', 'Properties']], + [ + 'Edit', + [ + '!Paste', + [ + 'Special', + 'Normal', + ], + '!Undo', + ], + ], + [ + '!Disabled', + [ + 'Has Sub', + [ + 'Item1', + 'Item2', + ], + 'No Sub', + ], + ], + ['Help', 'About...'], + ] + + col1 = [[Text('Column 1 line 1', background_color='red')], [Text('Column 1 line 2')]] + + layout = [ + [Menu(menu_def, key='_MENU_', text_color='yellow', background_color='#475841', font='Courier 14')], + # [T('123435', size=(1,8))], + [ + Text( + 'PySimpleGUIWeb Welcomes You...', + tooltip='text', + font=('Comic sans ms', 20), + size=(40, 1), + text_color='yellow', + enable_events=False, + key='_PySimpleGUIWeb_', + ) + ], + # [OptionMenu([])], + [T('System platform = %s' % sys.platform)], + [Image(data=DEFAULT_BASE64_ICON, enable_events=False)], + # [Image(filename=r'C:\Python\PycharmProjects\PSG\logo500.png', key='-IMAGE-')], + VerLine(ver, 'PySimpleGUI Version'), + VerLine(os.path.dirname(os.path.abspath(__file__)), 'PySimpleGUI Location'), + VerLine(sys.version, 'Python Version', size=(60, 2)), + VerLine(pkg_resources.get_distribution('remi').version, 'Remi Version'), + # [Text('VERSION {}'.format(version), text_color='red', font='Courier 24')], + [ + T('Current Time '), + Text('Text', key='_TEXT_', font='Arial 18', text_color='black', size=(30, 1)), + Column(col1, background_color='red'), + ], + [T('Up Time'), Text('Text', key='_TEXT_UPTIME_', font='Arial 18', text_color='black', size=(30, 1))], + [Input('Single Line Input', do_not_clear=True, enable_events=False, size=(30, 1), text_color='red', key='_IN_')], + [Multiline('Multiline Input', do_not_clear=True, size=(40, 4), enable_events=False, key='_MULTI_IN_')], + # [Output(size=(60,10))], + [ + MultilineOutput( + 'Multiline Output', + size=(80, 8), + text_color='blue', + font='Courier 12', + key='_MULTIOUT_', + autoscroll=True, + ) + ], + [ + Checkbox('Checkbox 1', enable_events=True, key='_CB1_'), + Checkbox('Checkbox 2', default=True, key='_CB2_', enable_events=True), + ], + [ + Combo( + values=['Combo 1', 'Combo 2', 'Combo 3'], + default_value='Combo 2', + key='_COMBO_', + enable_events=True, + readonly=False, + tooltip='Combo box', + disabled=False, + size=(12, 1), + ) + ], + [Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), enable_events=True, size=(10, 3), key='_LIST_')], + # [Image(filename=r'C:\Python\PycharmProjects\PSG\logo200.png', enable_events=False)], + [Slider((1, 100), default_value=80, key='_SLIDER_', visible=True, enable_events=True, orientation='v')], + [Spin(values=(1, 2, 3), initial_value='2', size=(4, 1), key='_SPIN_', enable_events=True)], + [ + OK(), + Button('Hidden', visible=False, key='_HIDDEN_'), + Button('Values'), + Button('Exit', button_color=('white', 'red')), + Button('UnHide'), + B('Popup'), + ], + ] + + window = Window( + 'PySimpleGUIWeb Test Harness Window', + layout, + font='Arial 18', + icon=DEFAULT_BASE64_ICON, + default_element_size=(12, 1), + auto_size_buttons=False, + ) + + start_time = datetime.datetime.now() + while True: + event, values = window.Read(timeout=100) + window.Element('_TEXT_').Update(str(datetime.datetime.now())) + window.Element('_TEXT_UPTIME_').Update(str(datetime.datetime.now() - start_time)) + print(event, values) if event != TIMEOUT_KEY else None + if event in (None, 'Exit'): + break + elif event == 'OK': + + window.Element('_MULTIOUT_').print('You clicked the OK button') + # window.Element('_MULTIOUT_').Update('You clicked the OK button', append=True, autoscroll=True) + window.Element('_PySimpleGUIWeb_').Widget.style['background-image'] = "url('/my_resources:mine.png')" + + elif event == 'Values': + window.Element('_MULTIOUT_').Update(str(values) + '\n', append=True) + # nav = remi.gui.FileFolderNavigator(False,r'a:\TEMP', True, False) + # here is returned the Input Dialog widget, and it will be shown + # fileselectionDialog.show(window.Element('_IN_').Widget) + + elif event != TIMEOUT_KEY: + window.Element('_MULTIOUT_').print('EVENT: ' + str(event)) + if event == 'Popup': + Popup('This is a popup!') + if event == 'UnHide': + print('Unhiding...') + window.Element('_HIDDEN_').Update(visible=True) + + window.Close() + + +if __name__ == '__main__': + main() + exit(0) diff --git a/FreeSimpleGUIWeb/setup.cfg b/FreeSimpleGUIWeb/setup.cfg index 2dd6a3af..3d9112f7 100644 --- a/FreeSimpleGUIWeb/setup.cfg +++ b/FreeSimpleGUIWeb/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = FreeSimpleGUIWeb -version = 1.0.0 +version = 1.1.0 maintainer = Spencer Phillip Young maintainer_email = spencer.young@spyoung.com description = The free-forever Python GUI framework. diff --git a/FreeSimpleGUIWx/FreeSimpleGUIWx/FreeSimpleGUIWx.py b/FreeSimpleGUIWx/FreeSimpleGUIWx/FreeSimpleGUIWx.py deleted file mode 100644 index 5d50d274..00000000 --- a/FreeSimpleGUIWx/FreeSimpleGUIWx/FreeSimpleGUIWx.py +++ /dev/null @@ -1,10564 +0,0 @@ -#!/usr/bin/python3 -version = __version__ = '0.17.1.5 Unreleased\n VSeparator added (spelling error), Radio.reset_group added and removed the clearing all when one cleared, added default key for one_line_progress_meter, auto-add keys to tables & trees, added theme_add_new' - -port = 'PySimpleGUIWx' - -import sys -import wx -import wx.adv -import wx.lib.inspection -from wx.lib.embeddedimage import PyEmbeddedImage -import wx.lib.scrolledpanel -import types -import datetime -import textwrap -import pickle -import os -import time -from random import randint - -RUN_INSPECTION_TOOL = False - - -###### ##### ##### # # ### # # -# # # # # # # # # ##### # ###### # # # # # # # # # # -# # # # # # ## ## # # # # # # # # # # # # # -###### # ##### # # ## # # # # ##### # #### # # # # # # ## -# # # # # # ##### # # # # # # # # # # ## -# # # # # # # # # # # # # # # # # # # # -# # ##### # # # # ###### ###### ##### ##### ### ## ## # # - -""" - - 21-Dec-2018 - Welcome to the "core" PySimpleGUIWx port! - -::: ::: ::: ::: ::::::::: ::: ::: ::::::::::: ::: ::: :::::::: :::: ::: -:+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:+: :+: -+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ :+:+:+ +:+ -+#+ +:+ +#+ +#++:+ +#++:++#+ +#++: +#+ +#++:++#++ +#+ +:+ +#+ +:+ +#+ -+#+ +#+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+# - #+#+# #+#+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+# - ### ### ### ### ### ### ### ### ### ######## ### #### - - - - This marks the 3rd port of the PySimpleGUI GUI SDK. Each port gets a little better than - the previous, in theory. - - It will take a while for this Wx port to be completed, but should be running with a fully selection - of widgets fairly quickly. The Qt port required 1 week to get to "Alpha" condition - - Enjoy! -""" - -g_time_start = 0 -g_time_end = 0 -g_time_delta = 0 - - -# Because looks matter... -DEFAULT_BASE64_ICON = b'iVBORw0KGgoAAAANSUhEUgAAACEAAAAgCAMAAACrZuH4AAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAMGmYMGqZMWqaMmubMmycM22dNGuZNm2bNm6bNG2dN26cNG6dNG6eNW+fN3CfOHCeOXGfNXCgNnGhN3KiOHOjOXSjOHSkOnWmOnamOnanPHSiPXakPnalO3eoPnimO3ioPHioPHmpPHmqPXqqPnurPnusPnytP3yuQHimQnurQn2sQH2uQX6uQH6vR32qRn+sSXujSHynTH2mTn+nSX6pQH6wTIGsTYKuTYSvQoCxQoCyRIK0R4S1RYS2Roa4SIe4SIe6SIi7Soq7SYm8SYq8Sou+TY2/UYStUYWvVIWtUYeyVoewUIi0VIizUI6+Vo+8WImxXJG5YI2xZI+xZ5CzZJC0ZpG1b5a3apW4aZm/cZi4dJ2/eJ69fJ+9XZfEZZnCZJzHaZ/Jdp/AeKTI/tM8/9Q7/9Q8/9Q9/9Q+/tQ//9VA/9ZA/9ZB/9ZC/9dD/9ZE/tdJ/9dK/9hF/9hG/9hH/9hI/9hJ/9hK/9lL/9pK/9pL/thO/9pM/9pN/9tO/9tP/9xP/tpR/9xQ/9xR/9xS/9xT/91U/91V/t1W/95W/95X/95Y/95Z/99a/99b/txf/txh/txk/t5l/t1q/t5v/+Bb/+Bc/+Bd/+Be/+Bf/+Bg/+Fh/+Fi/+Jh/+Ji/uJk/uJl/+Jm/+Rm/uJo/+Ro/+Rr/+Zr/+Vs/+Vu/+Zs/+Zu/uF0/uVw/+dw/+dz/+d2/uB5/uB6/uJ9/uR7/uR+/uV//+hx/+hy/+h0/+h2/+l4/+l7/+h8gKXDg6vLgazOhKzMiqrEj6/KhK/Qka/Hk7HJlLHJlLPMmLTLmbbOkLXSmLvXn77XoLrPpr/Tn8DaocLdpcHYrcjdssfZus/g/uOC/uOH/uaB/uWE/uaF/uWK/+qA/uqH/uqI/uuN/uyM/ueS/ueW/ueY/umQ/uqQ/uuS/uuW/uyU/uyX/uqa/uue/uye/uyf/u6f/uyq/u+r/u+t/vCm/vCp/vCu/vCy/vC2/vK2/vO8/vO/wtTjwtXlzdrl/vTA/vPQAAAAiNpY5gAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAAFw8AABcPASe7rwsAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAKUSURBVDhPhdB3WE1xHMdxt5JV0dANoUiyd8kqkey996xclUuTlEKidO3qVnTbhIyMW/bee5NskjJLmR/f3++cK/94vP76Ps/n/Zx7z6mE/6koJowcK154vvHOL/GsKCZXkUgkWlf4vWGWq5tsDz+JWIzSokAiqXGe7nWu3HxhEYof7fhOqp1GtptQuMruVhQdxZ05U5G47tYUHbQ4oah6Fg9Z4ubm7i57JhQjdHS0RSzUPoG17u6zZTKZh8c8XlytqW9YWUOH1LqFOZ6enl5ec+XybFb0rweM1tPTM6yuq6vLs0lYJJfLvb19fHwDWGF0jh5lYNAe4/QFemOwxtfXz8/fPyBgwVMqzAcCF4ybAZ2MRCexJGBhYGBQUHDw4u1UHDG1G2ZqB/Q1MTHmzAE+kpCwL1RghlTaBt/6SaXS2kx9YH1IaOjSZST8vfA9JtoDnSngGgL7wkg4WVkofA9mcF1Sx8zMzBK4v3wFiYiMVLxlEy9u21syFhYNmgN7IyJXEYViNZvEYoCVVWOmUVvgQVSUQqGIjolRFvOAFd8HWVs34VoA+6OjY2JjY5Vxm4BC1UuhGG5jY9OUaQXci1MqlfHx8YmqjyhOViW9ZsUN29akJRmPFwkJCZsTSXIpilJffXiTzorLXYgtcxRJKpUqKTklJQ0oSt9FP/EonxVdNY4jla1kK4q2ZB6mIr+AipvduzFUzMSOtLT09IyMzMxtJKug/F0u/6dTexAWDcXXLGEjapKjfsILOLKEuYiSnTQeYCt3UHhbwEHjGMrETfBJU5zq5dSTcXC8hLJccSWP2cgLXHPu7cQNAcpyxF1dyjehAKb0cSYUAOXCUw6V8OFPgevTXFymC+fPPLU677Nw/1X8A/AbfAKGulaqFlIAAAAASUVORK5CYII=' - - -def TimerStart(): - global g_time_start - - g_time_start = time.time() - - -def TimerStop(): - global g_time_delta, g_time_end - - g_time_end = time.time() - g_time_delta = g_time_end - g_time_start - print(g_time_delta) - - -# ----====----====----==== Constants the user CAN safely change ====----====----====----# -DEFAULT_WINDOW_ICON = 'default_icon.ico' -DEFAULT_ELEMENT_SIZE = (250, 26) # In pixels -DEFAULT_BUTTON_ELEMENT_SIZE = (10, 1) # In CHARACTERS -DEFAULT_MARGINS = (10, 5) # Margins for each LEFT/RIGHT margin is first term -DEFAULT_ELEMENT_PADDING = (3, 2) # Padding between elements (row, col) in pixels -DEFAULT_AUTOSIZE_TEXT = True -DEFAULT_AUTOSIZE_BUTTONS = True -DEFAULT_FONT = ('Helvetica', 10) -DEFAULT_TEXT_JUSTIFICATION = 'left' -DEFAULT_BORDER_WIDTH = 1 -DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form -DEFAULT_DEBUG_WINDOW_SIZE = (80, 20) -DEFAULT_WINDOW_LOCATION = (None, None) -MAX_SCROLLED_TEXT_BOX_HEIGHT = 50 -DEFAULT_TOOLTIP_TIME = 400 -DEFAULT_PIXELS_TO_CHARS_SCALING = (10, 26) # 1 character represents x by y pixels -DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT = (10, 20) # 1 character represents x by y pixels -DEFAULT_PIXEL_TO_CHARS_CUTOFF = 20 # number of chars that triggers using pixels instead of chars -DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE = 70 # number of chars that triggers using pixels instead of chars -MENU_DISABLED_CHARACTER = '!' -MENU_KEY_SEPARATOR = '::' - -#################### COLOR STUFF #################### -BLUES = ('#082567', '#0A37A3', '#00345B') -PURPLES = ('#480656', '#4F2398', '#380474') -GREENS = ('#01826B', '#40A860', '#96D2AB', '#00A949', '#003532') -YELLOWS = ('#F3FB62', '#F0F595') -TANS = ('#FFF9D5', '#F4EFCF', '#DDD8BA') -NICE_BUTTON_COLORS = ( - (GREENS[3], TANS[0]), - ('#000000', '#FFFFFF'), - ('#FFFFFF', '#000000'), - (YELLOWS[0], PURPLES[1]), - (YELLOWS[0], GREENS[3]), - (YELLOWS[0], BLUES[2]), -) - -COLOR_SYSTEM_DEFAULT = '1234567890' # Colors should never be this long -DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default -OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = ('white', BLUES[0]) # Colors should never be this long - - -CURRENT_LOOK_AND_FEEL = 'DarkBlue3' - - -DEFAULT_ERROR_BUTTON_COLOR = ('#FFFFFF', '#FF0000') -DEFAULT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_ELEMENTS_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_SCROLLBAR_COLOR = None -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[0]) # (Text, Background) or (Color "on", Color) as a way to remember -# DEFAULT_BUTTON_COLOR = (GREENS[3], TANS[0]) # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], GREENS[4]) # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = ('white', 'black') # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[2]) # Foreground, Background (None, None) == System Default -# DEFAULT_PROGRESS_BAR_COLOR = (GREENS[2], GREENS[0]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[1], BLUES[1]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[0], BLUES[0]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (PURPLES[1],PURPLES[0]) # a nice purple progress bar - -# A transparent button is simply one that matches the background -TRANSPARENT_BUTTON = 'This constant has been depricated. You must set your button background = background it is on for it to be transparent appearing' -# -------------------------------------------------------------------------------- -# Progress Bar Relief Choices -RELIEF_RAISED = 'raised' -RELIEF_SUNKEN = 'sunken' -RELIEF_FLAT = 'flat' -RELIEF_RIDGE = 'ridge' -RELIEF_GROOVE = 'groove' -RELIEF_SOLID = 'solid' - -DEFAULT_PROGRESS_BAR_COLOR = (GREENS[0], '#D0D0D0') # a nice green progress bar -DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL = (GREENS[0], '#D0D0D0') # a nice green progress bar -DEFAULT_PROGRESS_BAR_SIZE = (20, 20) # Size of Progress Bar (characters for length, pixels for width) -DEFAULT_PROGRESS_BAR_BORDER_WIDTH = 1 -DEFAULT_PROGRESS_BAR_RELIEF = RELIEF_GROOVE -PROGRESS_BAR_STYLES = ('default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative') -DEFAULT_PROGRESS_BAR_STYLE = 'default' -DEFAULT_METER_ORIENTATION = 'Horizontal' -DEFAULT_SLIDER_ORIENTATION = 'vertical' -DEFAULT_SLIDER_BORDER_WIDTH = 1 -DEFAULT_SLIDER_RELIEF = 00000 -DEFAULT_FRAME_RELIEF = 00000 - -DEFAULT_LISTBOX_SELECT_MODE = 00000 -SELECT_MODE_MULTIPLE = 00000 -LISTBOX_SELECT_MODE_MULTIPLE = 'multiple' -SELECT_MODE_BROWSE = 00000 -LISTBOX_SELECT_MODE_BROWSE = 'browse' -SELECT_MODE_EXTENDED = 00000 -LISTBOX_SELECT_MODE_EXTENDED = 'extended' -SELECT_MODE_SINGLE = 00000 -LISTBOX_SELECT_MODE_SINGLE = 'single' - -TABLE_SELECT_MODE_NONE = 00000 -TABLE_SELECT_MODE_BROWSE = 00000 -TABLE_SELECT_MODE_EXTENDED = 00000 -DEFAULT_TABLE_SECECT_MODE = TABLE_SELECT_MODE_EXTENDED - -TITLE_LOCATION_TOP = 00000 -TITLE_LOCATION_BOTTOM = 00000 -TITLE_LOCATION_LEFT = 00000 -TITLE_LOCATION_RIGHT = 00000 -TITLE_LOCATION_TOP_LEFT = 00000 -TITLE_LOCATION_TOP_RIGHT = 00000 -TITLE_LOCATION_BOTTOM_LEFT = 00000 -TITLE_LOCATION_BOTTOM_RIGHT = 00000 - -THEME_DEFAULT = 'default' -THEME_WINNATIVE = 'winnative' -THEME_CLAM = 'clam' -THEME_ALT = 'alt' -THEME_CLASSIC = 'classic' -THEME_VISTA = 'vista' -THEME_XPNATIVE = 'xpnative' - -# DEFAULT_METER_ORIENTATION = 'Vertical' -# ----====----====----==== Constants the user should NOT f-with ====----====----====----# -ThisRow = 555666777 # magic number - -# DEFAULT_WINDOW_ICON = '' -MESSAGE_BOX_LINE_WIDTH = 60 - -# "Special" Key Values.. reserved -# Key representing a Read timeout -EVENT_TIMEOUT = TIMEOUT_EVENT = TIMEOUT_KEY = '__TIMEOUT__' -# Window closed event (user closed with X or destroyed using OS) -WIN_CLOSED = WINDOW_CLOSED = None - -# Key indicating should not create any return values for element -WRITE_ONLY_KEY = '__WRITE ONLY__' -EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED = '__DOUBLE_CLICKED__' -EVENT_SYSTEM_TRAY_ICON_ACTIVATED = '__ACTIVATED__' -EVENT_SYSTEM_TRAY_ICON_RIGHT_CLICK = '__RIGHT_CLICK__' -EVENT_SYSTEM_TRAY_MESSAGE_CLICKED = '__MESSAGE_CLICKED__' - -# Icons for displaying system tray messages -SYSTEM_TRAY_MESSAGE_ICON_INFORMATION = wx.ICON_INFORMATION -SYSTEM_TRAY_MESSAGE_ICON_WARNING = wx.ICON_WARNING -SYSTEM_TRAY_MESSAGE_ICON_CRITICAL = wx.ICON_ERROR -SYSTEM_TRAY_MESSAGE_ICON_NOICON = None - -ICON_SCREEN_DEPTH = -1 - -ICON_STOP = 512 - - -# a shameful global variable. This represents the top-level window information. Needed because opening a second window is different than opening the first. -class MyWindows: - def __init__(self): - self.NumOpenWindows = 0 - self.user_defined_icon = None - self.hidden_master_root = None - - def Decrement(self): - self.NumOpenWindows -= 1 * (self.NumOpenWindows != 0) # decrement if not 0 - # print('---- DECREMENTING Num Open Windows = {} ---'.format(self.NumOpenWindows)) - - def Increment(self): - self.NumOpenWindows += 1 - # print('++++ INCREMENTING Num Open Windows = {} ++++'.format(self.NumOpenWindows)) - - -_my_windows = MyWindows() # terrible hack using globals... means need a class for collecing windows - - -# ====================================================================== # -# One-liner functions that are handy as f_ck # -# ====================================================================== # -def RGB(red, green, blue): - return '#%02x%02x%02x' % (red, green, blue) - - -# ====================================================================== # -# Enums for types # -# ====================================================================== # -# ------------------------- Button types ------------------------- # -# todo Consider removing the Submit, Cancel types... they are just 'RETURN' type in reality -# uncomment this line and indent to go back to using Enums -# class ButtonType(Enum): -BUTTON_TYPE_BROWSE_FOLDER = 1 -BUTTON_TYPE_BROWSE_FILE = 2 -BUTTON_TYPE_BROWSE_FILES = 21 -BUTTON_TYPE_SAVEAS_FILE = 3 -BUTTON_TYPE_CLOSES_WIN = 5 -BUTTON_TYPE_CLOSES_WIN_ONLY = 6 -BUTTON_TYPE_READ_FORM = 7 -BUTTON_TYPE_REALTIME = 9 -BUTTON_TYPE_CALENDAR_CHOOSER = 30 -BUTTON_TYPE_COLOR_CHOOSER = 40 - -BROWSE_FILES_DELIMITER = ';' # the delimeter to be used between each file in the returned string - -# ------------------------- Element types ------------------------- # -# class ElementType(Enum): -ELEM_TYPE_TEXT = 'text' -ELEM_TYPE_INPUT_TEXT = 'input' -ELEM_TYPE_INPUT_COMBO = 'combo' -ELEM_TYPE_INPUT_OPTION_MENU = 'option menu' -ELEM_TYPE_INPUT_RADIO = 'radio' -ELEM_TYPE_INPUT_MULTILINE = 'multiline' -ELEM_TYPE_MULTILINE_OUTPUT = 'multioutput' -ELEM_TYPE_INPUT_CHECKBOX = 'checkbox' -ELEM_TYPE_INPUT_SPIN = 'spin' -ELEM_TYPE_BUTTON = 'button' -ELEM_TYPE_BUTTONMENU = 'buttonmenu' -ELEM_TYPE_IMAGE = 'image' -ELEM_TYPE_CANVAS = 'canvas' -ELEM_TYPE_FRAME = 'frame' -ELEM_TYPE_GRAPH = 'graph' -ELEM_TYPE_TAB = 'tab' -ELEM_TYPE_TAB_GROUP = 'tabgroup' -ELEM_TYPE_INPUT_SLIDER = 'slider' -ELEM_TYPE_INPUT_LISTBOX = 'listbox' -ELEM_TYPE_OUTPUT = 'output' -ELEM_TYPE_COLUMN = 'column' -ELEM_TYPE_MENUBAR = 'menubar' -ELEM_TYPE_PROGRESS_BAR = 'progressbar' -ELEM_TYPE_BLANK = 'blank' -ELEM_TYPE_TABLE = 'table' -ELEM_TYPE_TREE = 'tree' -ELEM_TYPE_ERROR = 'error' -ELEM_TYPE_SEPARATOR = 'separator' - -# ------------------------- Popup Buttons Types ------------------------- # -POPUP_BUTTONS_YES_NO = 1 -POPUP_BUTTONS_CANCELLED = 2 -POPUP_BUTTONS_ERROR = 3 -POPUP_BUTTONS_OK_CANCEL = 4 -POPUP_BUTTONS_OK = 0 -POPUP_BUTTONS_NO_BUTTONS = 5 - - -# ---------------------------------------------------------------------- # -# Cascading structure.... Objects get larger # -# Button # -# Element # -# Row # -# Form # -# ---------------------------------------------------------------------- # -# ---------------------------------------------------------------------- # -# Element CLASS # -# ---------------------------------------------------------------------- # -class Element: - def __init__( - self, - elem_type, - size=(None, None), - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - - # if elem_type != ELEM_TYPE_GRAPH: - # self.Size = convert_tkinter_size_to_Wx(size) - # else: - # self.Size = size - self.Size = size_px - if size_px == (None, None) and size != (None, None): - if elem_type in ( - ELEM_TYPE_MULTILINE_OUTPUT, - ELEM_TYPE_INPUT_MULTILINE, - ELEM_TYPE_OUTPUT, - ELEM_TYPE_TABLE, - ELEM_TYPE_TREE, - ELEM_TYPE_TAB, - ELEM_TYPE_COLUMN, - ): - self.Size = _convert_tkinter_size_to_Wx(size, DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT, DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE) - else: - self.Size = _convert_tkinter_size_to_Wx(size, DEFAULT_PIXELS_TO_CHARS_SCALING, DEFAULT_PIXEL_TO_CHARS_CUTOFF) - self.Type = elem_type - self.AutoSizeText = auto_size_text - # self.Pad = DEFAULT_ELEMENT_PADDING if pad is None else pad - self.Pad = pad - if font is not None and type(font) is not str: - self.Font = font - elif font is not None: - self.Font = font.split(' ') - else: - self.Font = font - - self.TKStringVar = None - self.TKIntVar = None - self.TKText = None - self.TKEntry = None - self.TKImage = None - - self.ParentForm = None # type: Window - self.ParentContainer = None # will be a Form, Column, or Frame element - self.TextInputDefault = None - self.Position = (0, 0) # Default position Row 0, Col 0 - self.BackgroundColor = background_color if background_color is not None else DEFAULT_ELEMENT_BACKGROUND_COLOR - self.TextColor = text_color if text_color is not None else DEFAULT_ELEMENT_TEXT_COLOR - self.Key = key # dictionary key for return values - self.Tooltip = tooltip - self.TooltipObject = None - self.Visible = visible - self.metadata = None # type: Any - - def FindReturnKeyBoundButton(self, form): - for row in form.Rows: - for element in row: - if element.Type == ELEM_TYPE_BUTTON: - if element.BindReturnKey: - return element - if element.Type == ELEM_TYPE_COLUMN: - rc = self.FindReturnKeyBoundButton(element) - if rc is not None: - return rc - if element.Type == ELEM_TYPE_FRAME: - rc = self.FindReturnKeyBoundButton(element) - if rc is not None: - return rc - if element.Type == ELEM_TYPE_TAB_GROUP: - rc = self.FindReturnKeyBoundButton(element) - if rc is not None: - return rc - if element.Type == ELEM_TYPE_TAB: - rc = self.FindReturnKeyBoundButton(element) - if rc is not None: - return rc - return None - - def _TextClickedHandler(self, event): - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.DisplayText - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - def _ReturnKeyHandler(self, event): - MyForm = self.ParentForm - button_element = self.FindReturnKeyBoundButton(MyForm) - if button_element is not None: - button_element.ButtonCallBack(event) - - def _ListboxSelectHandler(self, event): - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - def _ComboboxSelectHandler(self, event): - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - def _RadioHandler(self): - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() - - def _CheckboxHandler(self): - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() - - def _TabGroupSelectHandler(self, event): - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() - - def _KeyboardHandler(self, event): - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() - - def _WxCallbackKeyboard(self, value): - element_callback_quit_mainloop(self) - - def Update(self, widget, background_color=None, text_color=None, font=None, visible=None, disabled=None, tooltip=None): - if font: - widget.SetFont(font_to_wx_font(font)) - if text_color not in (None, COLOR_SYSTEM_DEFAULT): - widget.SetForegroundColour(text_color) - if background_color not in (None, COLOR_SYSTEM_DEFAULT): - widget.SetBackgroundColour(background_color) - if visible is True: - widget.Show() - self.ParentForm.VisibilityChanged() - elif visible is False: - widget.Hide() - self.ParentForm.VisibilityChanged() - if disabled: - widget.Enable(False) - elif disabled is False: - widget.Enable(True) - if tooltip is not None: - widget.SetToolTip(tooltip) - - def __call__(self, *args, **kwargs): - """ - Makes it possible to "call" an already existing element. When you do make the "call", it actually calls - the Update method for the element. - Example: If this text element was in yoiur layout: - sg.Text('foo', key='T') - Then you can call the Update method for that element by writing: - window.FindElement('T')('new text value') - - :param args: - :param kwargs: - :return: - """ - return self.Update(*args, **kwargs) - - update = Update - - -# ---------------------------------------------------------------------- # -# Input Class # -# ---------------------------------------------------------------------- # -class InputText(Element): - def __init__( - self, - default_text='', - size=(None, None), - disabled=False, - password_char='', - justification=None, - background_color=None, - text_color=None, - font=None, - tooltip=None, - change_submits=False, - enable_events=False, - do_not_clear=True, - key=None, - focus=False, - pad=None, - visible=True, - size_px=(None, None), - ): - ''' - Input a line of text Element - :param default_text: Default value to display - :param size: Size of field in characters - :param password_char: If non-blank, will display this character for every character typed - :param background_color: Color for Element. Text or RGB Hex - ''' - self.DefaultText = str(default_text) - self.PasswordCharacter = str(password_char) - bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - self.Justification = justification or 'left' - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.QT_QLineEdit = None - self.ValueWasChanged = False - self.WxTextCtrl = None - - super().__init__( - ELEM_TYPE_INPUT_TEXT, - size=size, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - font=font, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - - def Update(self, value=None, disabled=None, select=None, background_color=None, text_color=None, font=None, visible=None): - if disabled is True: - self.WxTextCtrl.Enable(True) - elif disabled is False: - self.WxTextCtrl.Enable(False) - if value is not None: - self.WxTextCtrl.SetValue(str(value)) - self.DefaultText = value - if select: - self.WxTextCtrl.SelectAll() - # if visible: - # self.WxTextCtrl.Show() - # self.ParentForm.VisibilityChanged() - # elif visible is False: - # self.WxTextCtrl.Hide() - super().Update(self.WxTextCtrl, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def Get(self): - return self.WxTextCtrl.GetValue() - - def SetFocus(self): - self.WxTextCtrl.SetFocus() - - get = Get - set_focus = SetFocus - update = Update - - -# ------------------------- INPUT TEXT Element lazy functions ------------------------- # -In = InputText -Input = InputText -I = InputText - - -# ---------------------------------------------------------------------- # -# Combo # -# ---------------------------------------------------------------------- # -class Combo(Element): - def __init__( - self, - values, - default_value=None, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - disabled=False, - key=None, - pad=None, - tooltip=None, - readonly=False, - visible_items=10, - font=None, - auto_complete=True, - visible=True, - size_px=(None, None), - ): - ''' - Input Combo Box Element (also called Dropdown box) - :param values: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex - ''' - self.Values = values - self.DefaultValue = default_value - self.ChangeSubmits = change_submits or enable_events - # self.InitializeAsDisabled = disabled - self.Disabled = disabled - self.Readonly = readonly - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.VisibleItems = visible_items - self.AutoComplete = auto_complete - self.WxComboBox = None # type: wx.ComboBox - - super().__init__( - ELEM_TYPE_INPUT_COMBO, - size=size, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - font=font or DEFAULT_FONT, - visible=visible, - size_px=size_px, - ) - - def Update( - self, - value=None, - values=None, - set_to_index=None, - disabled=None, - readonly=None, - background_color=None, - text_color=None, - font=None, - visible=None, - ): - if values is not None: - self.WxComboBox.Set(values) - if value: - self.WxComboBox.SetSelection(self.WxComboBox.FindString(value)) - if set_to_index is not None: - self.WxComboBox.SetSelection(set_to_index) - if disabled is True: - self.WxComboBox.Enable(False) - elif disabled is False: - self.WxComboBox.Enable(True) - if readonly is not None: - self.WxComboBox.SetWindowStyle(wx.CB_READONLY) - - super().Update(self.WxComboBox, background_color=background_color, text_color=text_color, font=font, visible=visible) - - update = Update - - -# ------------------------- INPUT COMBO Element lazy functions ------------------------- # -InputCombo = Combo -DropDown = InputCombo -Drop = InputCombo - - -# ---------------------------------------------------------------------- # -# Option Menu # -# ---------------------------------------------------------------------- # -class OptionMenu(Element): - def __init__( - self, - values, - default_value=None, - size=(None, None), - disabled=False, - auto_size_text=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None, - ): - ''' - InputOptionMenu - :param values: - :param default_value: - :param size: - :param disabled: - :param auto_size_text: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - ''' - self.Values = values - self.DefaultValue = default_value - self.TKOptionMenu = None - self.Disabled = disabled - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - super().__init__( - ELEM_TYPE_INPUT_OPTION_MENU, - size=size, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - ) - - def Update(self, value=None, values=None, disabled=None): - if values is not None: - self.Values = values - if self.Values is not None: - for index, v in enumerate(self.Values): - if v == value: - try: - self.TKStringVar.set(value) - except: - pass - self.DefaultValue = value - break - if disabled == True: - self.TKOptionMenu['state'] = 'disabled' - elif disabled == False: - self.TKOptionMenu['state'] = 'normal' - - update = Update - - -# ------------------------- OPTION MENU Element lazy functions ------------------------- # -InputOptionMenu = OptionMenu - - -# ---------------------------------------------------------------------- # -# Listbox # -# ---------------------------------------------------------------------- # -class Listbox(Element): - def __init__( - self, - values, - default_values=None, - select_mode=None, - change_submits=False, - bind_return_key=False, - size=(None, None), - disabled=False, - auto_size_text=None, - font=None, - background_color=None, - size_px=(None, None), - text_color=None, - key=None, - pad=None, - tooltip=None, - ): - ''' - Listbox Element - :param values: - :param default_values: - :param select_mode: - :param change_submits: - :param bind_return_key: - :param size: - :param disabled: - :param auto_size_text: - :param font: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - ''' - self.Values = values - self.DefaultValues = default_values - self.TKListbox = None - self.ChangeSubmits = change_submits - self.BindReturnKey = bind_return_key - self.Disabled = disabled - if select_mode == LISTBOX_SELECT_MODE_BROWSE: - self.SelectMode = SELECT_MODE_BROWSE - elif select_mode == LISTBOX_SELECT_MODE_EXTENDED: - self.SelectMode = SELECT_MODE_EXTENDED - elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE: - self.SelectMode = SELECT_MODE_MULTIPLE - elif select_mode == LISTBOX_SELECT_MODE_SINGLE: - self.SelectMode = SELECT_MODE_SINGLE - else: - self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - super().__init__( - ELEM_TYPE_INPUT_LISTBOX, - size=size, - auto_size_text=auto_size_text, - font=font, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - size_px=size_px, - tooltip=tooltip, - ) - - def Update(self, values=None, disabled=None): - if disabled == True: - self.TKListbox.configure(state='disabled') - elif disabled == False: - self.TKListbox.configure(state='normal') - if values is not None: - self.TKListbox.delete(0, 'end') - for item in values: - self.TKListbox.insert(tk.END, item) - self.TKListbox.selection_set(0, 0) - self.Values = values - - def SetValue(self, values): - for index, item in enumerate(self.Values): - try: - if item in values: - self.TKListbox.selection_set(index) - else: - self.TKListbox.selection_clear(index) - except: - pass - self.DefaultValues = values - - def GetListValues(self): - return self.Values - - get_list_values = GetListValues - set_value = SetValue - update = Update - - -# ---------------------------------------------------------------------- # -# Radio # -# ---------------------------------------------------------------------- # -class Radio(Element): - def __init__( - self, - text, - group_id, - default=False, - disabled=False, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - font=None, - key=None, - pad=None, - tooltip=None, - change_submits=False, - enable_events=False, - visible=True, - size_px=(None, None), - ): - """ - - :param text: - :param group_id: - :param default: - :param disabled: - :param size: - :param auto_size_text: - :param background_color: - :param text_color: - :param font: - :param key: - :param pad: - :param tooltip: - :param change_submits: - :param enable_events: - :param visible: - :param size_px: - """ - self.InitialState = default - self.Text = text - self.GroupID = group_id - self.Value = None - self.Disabled = disabled - self.TextColor = text_color or DEFAULT_TEXT_COLOR - self.ChangeSubmits = change_submits or enable_events - self.WxRadioButton = None # type: wx.RadioButton - - super().__init__( - ELEM_TYPE_INPUT_RADIO, - size=size, - auto_size_text=auto_size_text, - font=font, - background_color=background_color, - text_color=self.TextColor, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - - def Update(self, value=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): - if value is True: - self.WxRadioButton.SetValue(True) - elif value is False: - self.WxRadioButton.SetValue(False) - super().Update(self.WxRadioButton, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def reset_group(self): - self.WxRadioButton.SetValue(True) - self.WxRadioButton.SetValue(False) - - update = Update - - -# ---------------------------------------------------------------------- # -# Checkbox # -# ---------------------------------------------------------------------- # -class Checkbox(Element): - def __init__( - self, - text, - default=False, - size=(None, None), - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - disabled=False, - key=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - ''' - Checkbox Element - :param text: - :param default: - :param size: - :param auto_size_text: - :param font: - :param background_color: - :param text_color: - :param change_submits: - :param disabled: - :param key: - :param pad: - :param tooltip: - ''' - self.Text = text - self.InitialState = default - self.WxCheckbox = None # type:wx.CheckBox - self.Disabled = disabled - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.ChangeSubmits = change_submits or enable_events - - super().__init__( - ELEM_TYPE_INPUT_CHECKBOX, - size=size, - auto_size_text=auto_size_text, - font=font, - background_color=background_color, - text_color=self.TextColor, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - - def Get(self): - return self.WxCheckbox.GetValue() - - def Update(self, value=None, disabled=None): - if value is not None: - try: - self.WxCheckbox.SetValue(value) - self.InitialState = value - except: - pass - if disabled == True: - self.WxCheckbox.Disable() - elif disabled == False: - self.WxCheckbox.Enable() - - get = Get - update = Update - - -# ------------------------- CHECKBOX Element lazy functions ------------------------- # -CB = Checkbox -CBox = Checkbox -Check = Checkbox - - -# ---------------------------------------------------------------------- # -# Spin # -# ---------------------------------------------------------------------- # - - -class Spin(Element): - # Values = None - # TKSpinBox = None - def __init__( - self, - values, - initial_value=None, - disabled=False, - change_submits=False, - enable_events=False, - size=(None, None), - readonly=True, - auto_size_text=None, - font=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - ''' - Spinner Element - :param values: - :param initial_value: - :param disabled: - :param change_submits: - :param size: - :param auto_size_text: - :param font: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - ''' - self.Values = values - self.DefaultValue = initial_value or values[0] - self.ChangeSubmits = change_submits or enable_events - self.Disabled = disabled - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.WxSpinCtrl: wx.SpinCtrl = None - self.WxTextCtrl = None # type : wx.TextCtrl - self.CurrentValue = self.DefaultValue - self.ReadOnly = readonly - - super().__init__( - ELEM_TYPE_INPUT_SPIN, - size=size, - auto_size_text=auto_size_text, - font=font, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - visible=visible, - size_px=size_px, - ) - return - - def _WxSpinCallback(self, event): - event = event # type:wx.SpinEvent - print(f'spin event {event.GetInt()} {self.WxSpinCtrl.GetValue()}') - offset = event.GetInt() - self.WxTextCtrl.SetValue(self.Values[offset]) - self.CurrentValue = self.Values[offset] - if self.ChangeSubmits: - element_callback_quit_mainloop(self) - - def Update(self, value=None, values=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): - if values != None: - self.Values = values - self.QT_Spinner.setStrings(values) - # self.QT_Spinner.setRange(self.Values[0], self.Values[1]) - if value is not None: - # self.QT_Spinner.setValue(value) - try: - self.QT_Spinner.setValue(self.QT_Spinner.valueFromText(value)) - self.DefaultValue = value - except: - pass - if disabled == True: - self.QT_Spinner.setDisabled(True) - elif disabled == False: - self.QT_Spinner.setDisabled(False) - super().Update(self.QT_Spinner, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def Get(self): - return self.WxSpinCtrl.GetValue() - - get = Get - update = Update - - -# ---------------------------------------------------------------------- # -# Multiline # -# ---------------------------------------------------------------------- # -class Multiline(Element): - def __init__( - self, - default_text='', - enter_submits=False, - disabled=False, - autoscroll=False, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - do_not_clear=True, - key=None, - write_only=False, - focus=False, - font=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - ''' - Multiline Element - :param default_text: - :param enter_submits: - :param disabled: - :param autoscroll: - :param size: - :param auto_size_text: - :param background_color: - :param text_color: - :param do_not_clear: - :param key: - :param focus: - :param pad: - :param tooltip: - :param font: - ''' - self.DefaultText = default_text - self.EnterSubmits = enter_submits - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Autoscroll = autoscroll - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.WriteOnly = write_only - - self.Widget = self.WxTextCtrl = None - - super().__init__( - ELEM_TYPE_INPUT_MULTILINE, - size=size, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - font=font or DEFAULT_FONT, - visible=visible, - size_px=size_px, - ) - return - - def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None): - try: # added in case the widget has already been deleted for some readon. - if value is not None and not append: - self.WxTextCtrl.SetValue(value) - elif value is not None and append: - self.WxTextCtrl.AppendText(value) - if background_color is not None: - self.WxTextCtrl.SetBackgroundColour(background_color) - if text_color is not None: - self.WxTextCtrl.SetForegroundColour(text_color) - if font is not None: - self.WxTextCtrl.SetFont(font) - if disabled: - self.WxTextCtrl.Enable(True) - elif disabled is False: - self.WxTextCtrl.Enable(False) - except: - pass - - super().Update(self.WxTextCtrl, background_color=background_color, text_color=text_color, font=font, visible=visible) - - # - # def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None): - # if value is not None and not append: - # self.DefaultText = value - # self.QT_TextEdit.setText(str(value)) - # elif value is not None and append: - # self.DefaultText = value - # self.QT_TextEdit.setText(self.QT_TextEdit.toPlainText() + str(value)) - # if disabled == True: - # self.QT_TextEdit.setDisabled(True) - # elif disabled == False: - # self.QT_TextEdit.setDisabled(False) - # super().Update(self.QT_TextEdit, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def Get(self): - self.WxTextCtrl.GetValue() - - def SetFocus(self): - self.WxTextCtrl.SetFocus() - - get = Get - set_focus = SetFocus - update = Update - - -# ---------------------------------------------------------------------- # -# Multiline Output # -# ---------------------------------------------------------------------- # -class MultilineOutput(Element): - def __init__( - self, - default_text='', - enter_submits=False, - disabled=False, - autoscroll=False, - size=(None, None), - auto_size_text=None, - background_color=None, - text_color=None, - change_submits=False, - enable_events=False, - do_not_clear=True, - key=None, - focus=False, - font=None, - pad=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - ''' - Multiline Element - :param default_text: - :param enter_submits: - :param disabled: - :param autoscroll: - :param size: - :param auto_size_text: - :param background_color: - :param text_color: - :param do_not_clear: - :param key: - :param focus: - :param pad: - :param tooltip: - :param font: - ''' - self.DefaultText = default_text - self.EnterSubmits = enter_submits - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Autoscroll = autoscroll - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - - self.WxTextCtrl = None - - super().__init__( - ELEM_TYPE_MULTILINE_OUTPUT, - size=size, - auto_size_text=auto_size_text, - background_color=bg, - text_color=fg, - key=key, - pad=pad, - tooltip=tooltip, - font=font or DEFAULT_FONT, - visible=visible, - size_px=size_px, - ) - return - - def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None): - if value is not None and not append: - self.WxTextCtrl.SetLabel(value) - elif value is not None and append: - self.WxTextCtrl.AppendText(value) - if background_color is not None: - self.WxTextCtrl.SetBackgroundColour(background_color) - if text_color is not None: - self.WxTextCtrl.SetForegroundColour(text_color) - if font is not None: - self.WxTextCtrl.SetFont(font) - if disabled: - self.WxTextCtrl.Enable(True) - elif disabled is False: - self.WxTextCtrl.Enable(False) - super().Update(self.WxTextCtrl, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def Get(self): - self.WxTextCtrl.GetValue() - - def SetFocus(self): - self.WxTextCtrl.SetFocus() - - get = Get - set_focus = SetFocus - update = Update - - -# ---------------------------------------------------------------------- # -# Text # -# ---------------------------------------------------------------------- # -class Text(Element): - def __init__( - self, - text='', - size=(None, None), - auto_size_text=None, - click_submits=None, - enable_events=False, - relief=None, - border_width=None, - font=None, - text_color=None, - background_color=None, - justification=None, - pad=None, - margins=None, - key=None, - tooltip=None, - visible=True, - size_px=(None, None), - ): - """ - Text - :param text: - :param size: - :param auto_size_text: - :param click_submits: - :param enable_events: - :param relief: - :param font: - :param text_color: - :param background_color: - :param justification: - :param pad: - :param margins: - :param key: - :param tooltip: - :param visible: - :param size_px: - """ - self.DisplayText = str(text) - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.Justification = justification - self.Relief = relief - self.ClickSubmits = click_submits or enable_events - self.Margins = margins - self.size_px = size_px - if background_color is None: - bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - else: - bg = background_color - pixelsize = size - if size[1] is not None and size[1] < 10: - pixelsize = size[0] * 10, size[1] * 20 - self.WxStaticText = None # type: wx.StaticText # wx.StaticText(form.MasterPanel, -1, element.DisplayText) - self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH - - super().__init__( - ELEM_TYPE_TEXT, - pixelsize, - auto_size_text, - background_color=bg, - font=font if font else DEFAULT_FONT, - text_color=self.TextColor, - pad=pad, - key=key, - tooltip=tooltip, - size_px=size_px, - visible=visible, - ) - return - - def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): - if self.ParentForm.TKrootDestroyed: - return - if value is not None: - self.WxStaticText.SetLabel(str(value)) - self.DisplayText = str(value) - if background_color is not None: - self.WxStaticText.SetBackgroundColour(background_color) - if text_color is not None: - self.WxStaticText.SetForegroundColour(text_color) - if font is not None: - self.WxStaticText.SetFont(font) - super().Update(self.WxStaticText, background_color=background_color, text_color=text_color, font=font, visible=visible) - - update = Update - - -# ------------------------- Text Element lazy functions ------------------------- # -Txt = Text -T = Text - - -class RedirectText(object): - def __init__(self, aWxTextCtrl): - self.out = aWxTextCtrl - - def write(self, string): - self.out.AppendText(string) - - def flush(self): - return - - -# ---------------------------------------------------------------------- # -# Output # -# Routes stdout, stderr to a scrolled window # -# ---------------------------------------------------------------------- # -class Output(Element): - def __init__( - self, - size=(None, None), - background_color=None, - text_color=None, - pad=None, - font=None, - tooltip=None, - key=None, - visible=True, - size_px=(None, None), - disabled=False, - ): - ''' - Output Element - :param size: - :param background_color: - :param text_color: - :param pad: - :param font: - :param tooltip: - :param key: - ''' - self._TKOut = None - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.WxTextCtrl = None # type: wx.TextCtrl - self.redir = None - self.output = None - self.Disabled = disabled - - super().__init__( - ELEM_TYPE_OUTPUT, - size=size, - background_color=bg, - text_color=fg, - pad=pad, - font=font, - tooltip=tooltip, - key=key, - visible=visible, - size_px=size_px, - ) - - def _reroute_stdout(self): - self.my_stdout = sys.stdout - self.my_stderr = sys.stderr - self.redir = RedirectText(self.WxTextCtrl) - sys.stdout = self.redir - sys.stderr = self.redir - Window.stdout_is_rerouted = True - Window.stdout_location = self.redir - - def _reroute_again(self): - sys.stdout = self.redir - - def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): - if value is not None: - self.WxTextCtrl.AppendText(value) - super().Update(self.WxTextCtrl, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def __del__(self): - try: - sys.stdout = self.my_stdout - sys.stderr = self.my_stderr - except: - pass - # super().__del__() - - update = Update - - -# ---------------------------------------------------------------------- # -# Button Class # -# ---------------------------------------------------------------------- # -class Button(Element): - def __init__( - self, - button_text='', - button_type=BUTTON_TYPE_READ_FORM, - target=(None, None), - tooltip=None, - file_types=(('ALL Files', '*'),), - initial_folder=None, - disabled=False, - change_submits=False, - enable_events=False, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, - visible=True, - size_px=(None, None), - ): - ''' - Button Element - :param button_text: - :param button_type: - :param target: - :param tooltip: - :param file_types: - :param initial_folder: - :param disabled: - :param image_filename: - :param image_size: - :param image_subsample: - :param border_width: - :param size: - :param auto_size_button: - :param button_color: - :param default_value: - :param font: - :param bind_return_key: - :param focus: - :param pad: - :param key: - ''' - self.AutoSizeButton = auto_size_button - self.BType = button_type - self.FileTypes = file_types - self.TKButton = None - self.Target = target - self.ButtonText = str(button_text) - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.TextColor = self.ButtonColor[0] - self.BackgroundColor = self.ButtonColor[1] - self.ImageFilename = image_filename - self.ImageData = image_data - self.ImageSize = image_size - self.ImageSubsample = image_subsample - self.UserData = None - self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH - self.BindReturnKey = bind_return_key - self.Focus = focus - self.TKCal = None - self.CalendarCloseWhenChosen = None - self.DefaultDate_M_D_Y = (None, None, None) - self.InitialFolder = initial_folder - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.QT_QPushButton = None - self.ColorChosen = None - self.Relief = None - self.WxButton = None # type: wx.Button - # self.temp_size = size if size != (NONE, NONE) else - - super().__init__( - ELEM_TYPE_BUTTON, - size=size, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - text_color=self.TextColor, - background_color=self.BackgroundColor, - visible=visible, - size_px=size_px, - ) - return - - # Realtime button release callback - def ButtonReleaseCallBack(self, parm): - self.LastButtonClickedWasRealtime = False - self.ParentForm.LastButtonClicked = None - - # Realtime button callback - def ButtonPressCallBack(self, parm): - self.ParentForm.LastButtonClickedWasRealtime = True - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - if self.ParentForm.CurrentlyRunningMainloop: - pass # kick out of loop if read was called - - # ------- Button Callback ------- # - def ButtonCallBack(self, event): - - # print('Button callback') - - # print(f'Parent = {self.ParentForm} Position = {self.Position}') - # Buttons modify targets or return from the form - # If modifying target, get the element object at the target and modify its StrVar - target = self.Target - target_element = None - if target[0] == ThisRow: - target = [self.Position[0], target[1]] - if target[1] < 0: - target[1] = self.Position[1] + target[1] - strvar = None - should_submit_window = False - if target == (None, None): - strvar = self.TKStringVar - else: - if not isinstance(target, str): - if target[0] < 0: - target = [self.Position[0] + target[0], target[1]] - target_element = self.ParentContainer._GetElementAtLocation(target) - else: - target_element = self.ParentForm.FindElement(target) - try: - strvar = target_element.TKStringVar - except: - pass - try: - if target_element.ChangeSubmits: - should_submit_window = True - except: - pass - filetypes = (('ALL Files', '*'),) if self.FileTypes is None else self.FileTypes - if self.BType == BUTTON_TYPE_BROWSE_FOLDER: # Browse Folder - wx_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.DirDialog(self.ParentForm.MasterFrame, style=wx.FD_OPEN) - else: - dialog = wx.DirDialog(self.ParentForm.MasterFrame) - folder_name = '' - if dialog.ShowModal() == wx.ID_OK: - folder_name = dialog.GetPath() - if folder_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = folder_name - else: - target_element.Update(folder_name) - elif self.BType == BUTTON_TYPE_BROWSE_FILE: # Browse File - qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_OPEN) - else: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_OPEN) - file_name = '' - if dialog.ShowModal() == wx.ID_OK: - file_name = dialog.GetPath() - else: - file_name = '' - if file_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_name - else: - target_element.Update(file_name) - elif self.BType == BUTTON_TYPE_BROWSE_FILES: # Browse Files - qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_MULTIPLE) - else: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_MULTIPLE) - file_names = '' - if dialog.ShowModal() == wx.ID_OK: - file_names = dialog.GetPaths() - else: - file_names = '' - if file_names != '': - file_names = BROWSE_FILES_DELIMITER.join(file_names) - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_names - else: - target_element.Update(file_names) - elif self.BType == BUTTON_TYPE_SAVEAS_FILE: # Save As File - qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.FileDialog( - self.ParentForm.MasterFrame, - defaultDir=self.InitialFolder, - wildcard=qt_types, - style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, - ) - else: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) - file_name = '' - if dialog.ShowModal() == wx.ID_OK: - file_name = dialog.GetPath() - else: - file_name = '' - if file_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_name - else: - target_element.Update(file_name) - elif self.BType == BUTTON_TYPE_COLOR_CHOOSER: # Color Chooser - qcolor = QColorDialog.getColor() - rgb_color = qcolor.getRgb() - color = '#' + ''.join('%02x' % i for i in rgb_color[:3]) - if self.Target == (None, None): - self.FileOrFolderName = color - else: - target_element.Update(color) - elif self.BType == BUTTON_TYPE_CLOSES_WIN: # Closes Window - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = False - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.App.ExitMainLoop() - self.ParentForm.IgnoreClose = True - self.ParentForm.MasterFrame.Close() - if self.ParentForm.NonBlocking: - Window.DecrementOpenCount() - self.ParentForm._Close() - elif self.BType == BUTTON_TYPE_READ_FORM: # Read Button - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out - self.ParentForm.App.ExitMainLoop() - elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out - self.ParentForm.App.ExitMainLoop() - self.ParentForm.IgnoreClose = True - self.ParentForm.MasterFrame.Close() - self.ParentForm._Close() - Window.DecrementOpenCount() - elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window - should_submit_window = False - - if should_submit_window: - self.ParentForm.LastButtonClicked = target_element.Key - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.App.ExitMainLoop() - return - - def Update( - self, - text=None, - button_color=(None, None), - disabled=None, - image_data=None, - image_filename=None, - font=None, - visible=None, - ): - if text is not None: - self.WxButton.SetLabelText(text) - self.ButtonText = text - fg = bg = None - if button_color != (None, None): - self.ButtonColor = button_color - fg, bg = button_color - super().Update(self.WxButton, background_color=bg, text_color=fg, font=font, visible=visible, disabled=disabled) - - def GetText(self): - return self.ButtonText - - def SetFocus(self): - self.QT_QPushButton.setFocus() - - get_text = GetText - set_focus = SetFocus - update = Update - - -def convert_tkinter_filetypes_to_wx(filetypes): - wx_filetypes = '' - for item in filetypes: - filetype = item[0] + ' (' + item[1] + ')|' + item[1] - wx_filetypes += filetype - return wx_filetypes - - -# ------------------------- Button lazy functions ------------------------- # -B = Button -Btn = Button - - -# ---------------------------------------------------------------------- # -# ProgreessBar # -# ---------------------------------------------------------------------- # -class ProgressBar(Element): - def __init__( - self, - max_value, - orientation=None, - size=(None, None), - start_value=0, - auto_size_text=None, - bar_color=(None, None), - style=None, - border_width=None, - relief=None, - key=None, - pad=None, - disabled=False, - visible=True, - size_px=(None, None), - ): - ''' - ProgressBar Element - :param max_value: - :param orientation: - :param size: - :param auto_size_text: - :param bar_color: - :param style: - :param border_width: - :param relief: - :param key: - :param pad: - ''' - self.MaxValue = max_value - self.TKProgressBar = None - self.Cancelled = False - self.NotRunning = True - self.Orientation = orientation if orientation else DEFAULT_METER_ORIENTATION - self.BarColor = bar_color if bar_color != (None, None) else DEFAULT_PROGRESS_BAR_COLOR - self.BarStyle = style if style else DEFAULT_PROGRESS_BAR_STYLE - self.BorderWidth = border_width if border_width is not None else DEFAULT_PROGRESS_BAR_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF - self.BarExpired = False - self.StartValue = start_value - self.Disabled = disabled - tsize = size - if size[0] is not None and size[0] < 100: - # tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] - tsize = size[0] * 10, size[1] - self.WxGauge = None # type: wx.Gauge - - super().__init__( - ELEM_TYPE_PROGRESS_BAR, - size=tsize, - auto_size_text=auto_size_text, - key=key, - pad=pad, - visible=visible, - size_px=size_px, - ) - - # returns False if update failed - def UpdateBar(self, current_count, max=None): - try: # Could havae been destroyed by user - if max is not None: - self.WxGauge.SetRange(max) - self.WxGauge.SetValue(current_count) - except: - pass - return True - - def Update(self, visible=None): - super().Update(self.WxGauge, visible=visible) - - update = Update - update_bar = UpdateBar - - -# ---------------------------------------------------------------------- # -# Image # -# ---------------------------------------------------------------------- # -class Image(Element): - def __init__(self, filename=None, data=None, background_color=None, size=(None, None), pad=None, key=None, tooltip=None): - ''' - Image Element - :param filename: - :param data: - :param background_color: - :param size: - :param pad: - :param key: - :param tooltip: - ''' - self.Filename = filename - self.Data = data - self.tktext_label = None - self.BackgroundColor = background_color - if data is None and filename is None: - print('* Warning... no image specified in Image Element! *') - super().__init__( - ELEM_TYPE_IMAGE, - size=size, - background_color=background_color, - pad=pad, - key=key, - tooltip=tooltip, - size_px=size, - ) - return - - def Update(self, filename=None, data=None, size=(None, None)): - if filename is not None: - image = tk.PhotoImage(file=filename) - elif data is not None: - # if type(data) is bytes: - try: - image = tk.PhotoImage(data=data) - except: - return # an error likely means the window has closed so exit - # else: - # image = data - else: - return - width, height = size[0] or image.width(), size[1] or image.height() - self.tktext_label.configure(image=image, width=width, height=height) - self.tktext_label.image = image - - update = Update - - -# ---------------------------------------------------------------------- # -# Canvas # -# ---------------------------------------------------------------------- # -class Canvas(Element): - def __init__(self, canvas=None, background_color=None, size=(None, None), pad=None, key=None, tooltip=None): - ''' - Canvas Element - :param canvas: - :param background_color: - :param size: - :param pad: - :param key: - :param tooltip: - ''' - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self._TKCanvas = canvas - - super().__init__(ELEM_TYPE_CANVAS, background_color=background_color, size=size, pad=pad, key=key, tooltip=tooltip) - return - - @property - def TKCanvas(self): - if self._TKCanvas is None: - print('*** Did you forget to call Finalize()? Your code should look something like: ***') - print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') - return self._TKCanvas - - -# ---------------------------------------------------------------------- # -# Graph # -# ---------------------------------------------------------------------- # -class Graph(Element): - def __init__( - self, - canvas_size, - graph_bottom_left, - graph_top_right, - background_color=None, - pad=None, - change_submits=False, - drag_submits=False, - key=None, - tooltip=None, - ): - ''' - Graph Element - :param canvas_size: - :param graph_bottom_left: - :param graph_top_right: - :param background_color: - :param pad: - :param key: - :param tooltip: - ''' - self.CanvasSize = canvas_size - self.BottomLeft = graph_bottom_left - self.TopRight = graph_top_right - self._TKCanvas = None - self._TKCanvas2 = None - self.ChangeSubmits = change_submits - self.DragSubmits = drag_submits - self.ClickPosition = (None, None) - self.MouseButtonDown = False - super().__init__(ELEM_TYPE_GRAPH, background_color=background_color, size_px=canvas_size, pad=pad, key=key, tooltip=tooltip) - return - - def _convert_xy_to_canvas_xy(self, x_in, y_in): - if None in (x_in, y_in): - return None, None - scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) - scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) - new_x = 0 + scale_x * (x_in - self.BottomLeft[0]) - new_y = self.CanvasSize[1] + scale_y * (y_in - self.BottomLeft[1]) - return new_x, new_y - - def _convert_canvas_xy_to_xy(self, x_in, y_in): - if None in (x_in, y_in): - return None, None - scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) - scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) - - new_x = x_in / scale_x + self.BottomLeft[0] - new_y = (y_in - self.CanvasSize[1]) / scale_y + self.BottomLeft[1] - return int(new_x), int(new_y) - - def DrawLine(self, point_from, point_to, color='black', width=1): - if point_from == (None, None): - return - converted_point_from = self._convert_xy_to_canvas_xy(point_from[0], point_from[1]) - converted_point_to = self._convert_xy_to_canvas_xy(point_to[0], point_to[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_line(converted_point_from, converted_point_to, width=width, fill=color) - - def DrawPoint(self, point, size=2, color='black'): - if point == (None, None): - return - converted_point = self._convert_xy_to_canvas_xy(point[0], point[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_oval( - converted_point[0] - size, - converted_point[1] - size, - converted_point[0] + size, - converted_point[1] + size, - fill=color, - outline=color, - ) - - def DrawCircle(self, center_location, radius, fill_color=None, line_color='black'): - if center_location == (None, None): - return - converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_oval( - converted_point[0] - radius, - converted_point[1] - radius, - converted_point[0] + radius, - converted_point[1] + radius, - fill=fill_color, - outline=line_color, - ) - - def DrawOval(self, top_left, bottom_right, fill_color=None, line_color=None): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_oval( - converted_top_left[0], - converted_top_left[1], - converted_bottom_right[0], - converted_bottom_right[1], - fill=fill_color, - outline=line_color, - ) - - def DrawArc(self, top_left, bottom_right, extent, start_angle, style=None, arc_color='black'): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - tkstyle = tk.PIESLICE if style is None else style - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_arc( - converted_top_left[0], - converted_top_left[1], - converted_bottom_right[0], - converted_bottom_right[1], - extent=extent, - start=start_angle, - style=tkstyle, - outline=arc_color, - ) - - def DrawRectangle(self, top_left, bottom_right, fill_color=None, line_color=None): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_rectangle( - converted_top_left[0], - converted_top_left[1], - converted_bottom_right[0], - converted_bottom_right[1], - fill=fill_color, - outline=line_color, - ) - - def DrawText(self, text, location, color='black', font=None, angle=0): - if location == (None, None): - return - converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - text_id = self._TKCanvas2.create_text(converted_point[0], converted_point[1], text=text, font=font, fill=color, angle=angle) - return text_id - - def Erase(self): - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - self._TKCanvas2.delete('all') - - def Update(self, background_color): - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - self._TKCanvas2.configure(background=background_color) - - def Move(self, x_direction, y_direction): - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - self._TKCanvas2.move('all', shift_amount[0], shift_amount[1]) - - def MoveFigure(self, figure, x_direction, y_direction): - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - if figure is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - self._TKCanvas2.move(figure, shift_amount[0], shift_amount[1]) - - def change_coordinates(self, graph_bottom_left, graph_top_right): - """ - Changes the corrdinate system to a new one. The same 2 points in space are used to define the coorinate - system - the bottom left and the top right values of your graph. - - :param graph_bottom_left: Tuple[int, int] (x,y) The bottoms left corner of your coordinate system - :param graph_top_right: Tuple[int, int] (x,y) The top right corner of your coordinate system - """ - self.BottomLeft = graph_bottom_left - self.TopRight = graph_top_right - - @property - def TKCanvas(self): - if self._TKCanvas2 is None: - print('*** Did you forget to call Finalize()? Your code should look something like: ***') - print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') - return self._TKCanvas2 - - # Realtime button release callback - def ButtonReleaseCallBack(self, event): - self.ClickPosition = (None, None) - self.LastButtonClickedWasRealtime = not self.DragSubmits - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() - if self.DragSubmits: - self.ParentForm.LastButtonClicked = None - self.MouseButtonDown = False - - # Realtime button callback - def ButtonPressCallBack(self, event): - self.ClickPosition = self._convert_canvas_xy_to_xy(event.x, event.y) - self.ParentForm.LastButtonClickedWasRealtime = self.DragSubmits - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() # kick out of loop if read was called - self.MouseButtonDown = True - - # Realtime button callback - def MotionCallBack(self, event): - if not self.MouseButtonDown: - return - self.ClickPosition = self._convert_canvas_xy_to_xy(event.x, event.y) - self.ParentForm.LastButtonClickedWasRealtime = self.DragSubmits - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() # kick out of loop if read was called - - -# ---------------------------------------------------------------------- # -# Frame # -# ---------------------------------------------------------------------- # -class Frame(Element): - def __init__( - self, - title, - layout, - title_color=None, - background_color=None, - title_location=None, - element_justification='left', - relief=DEFAULT_FRAME_RELIEF, - size=(None, None), - size_px=(None, None), - font=None, - pad=None, - border_width=None, - key=None, - tooltip=None, - ): - ''' - Frame Element - :param title: - :param layout: - :param title_color: - :param background_color: - :param title_location: - :param relief: - :param size: - :param font: - :param pad: - :param border_width: - :param key: - :param tooltip: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - # self.ParentForm = None - self.TKFrame = None - self.Title = title - self.Relief = relief - self.TitleLocation = title_location - self.BorderWidth = border_width - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.ElementJustification = element_justification - - self._Layout(layout) - - super().__init__( - ELEM_TYPE_FRAME, - background_color=background_color, - text_color=title_color, - size=size, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - size_px=size_px, - ) - return - - def _AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def _Layout(self, rows): - for row in rows: - self._AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - -# ---------------------------------------------------------------------- # -# Separator # -# Routes stdout, stderr to a scrolled window # -# ---------------------------------------------------------------------- # -class VerticalSeparator(Element): - def __init__(self, size=(None, None), size_px=None, pad=None): - ''' - VerticalSeperator - A separator that spans only 1 row in a vertical fashion - :param pad: - ''' - self.Orientation = 'vertical' # for now only vertical works - self.Disabled = None - self.WxStaticLine = None # type: wx.StaticLine - super().__init__(ELEM_TYPE_SEPARATOR, pad=pad, size=size, size_px=size_px) - - -VSeparator = VerticalSeparator -VSeparator = VerticalSeparator -VSep = VerticalSeparator - - -# ---------------------------------------------------------------------- # -# Separator # -# ---------------------------------------------------------------------- # -class HorizontalSeparator(Element): - def __init__(self, pad=None, size=(None, None), size_px=(None, None)): - ''' - VerticalSeperator - A separator that spans only 1 row in a vertical fashion - :param pad: - ''' - self.Orientation = 'horizontal' # for now only vertical works - self.Disabled = None - self.WxStaticLine = None # type: wx.StaticLine - - super().__init__(ELEM_TYPE_SEPARATOR, pad=pad, size=size, size_px=size_px) - - -HSeperator = HorizontalSeparator -HSep = HorizontalSeparator - - -# ---------------------------------------------------------------------- # -# Tab # -# ---------------------------------------------------------------------- # -class Tab(Element): - def __init__( - self, - title, - layout, - title_color=None, - background_color=None, - font=None, - element_justification='left', - pad=None, - disabled=False, - border_width=None, - key=None, - tooltip=None, - ): - ''' - Tab Element - :param title: - :param layout: - :param title_color: - :param background_color: - :param font: - :param pad: - :param disabled: - :param border_width: - :param key: - :param tooltip: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.TKFrame = None - self.Title = title - self.BorderWidth = border_width - self.Disabled = disabled - self.ParentNotebook = None - self.TabID = None - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.ElementJustification = element_justification - - self._Layout(layout) - - super().__init__( - ELEM_TYPE_TAB, - background_color=background_color, - text_color=title_color, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - ) - return - - def _AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def _Layout(self, rows): - for row in rows: - self._AddRow(*row) - return self - - def Update(self, disabled=None): # TODO Disable / enable of tabs is not complete - if disabled is None: - return - self.Disabled = disabled - state = 'disabled' if disabled is True else 'normal' - self.ParentNotebook.tab(self.TabID, state=state) - return self - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - update = Update - - -# ---------------------------------------------------------------------- # -# TabGroup # -# ---------------------------------------------------------------------- # -class TabGroup(Element): - def __init__( - self, - layout, - tab_location=None, - title_color=None, - selected_title_color=None, - background_color=None, - font=None, - change_submits=False, - pad=None, - border_width=None, - theme=None, - key=None, - tooltip=None, - ): - ''' - TabGroup Element - :param layout: - :param tab_location: - :param title_color: - :param selected_title_color: - :param background_color: - :param font: - :param change_submits: - :param pad: - :param border_width: - :param theme: - :param key: - :param tooltip: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.SelectedTitleColor = selected_title_color - self.Rows = [] - self.TKNotebook = None - self.TabCount = 0 - self.BorderWidth = border_width - self.Theme = theme - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.ChangeSubmits = change_submits - self.TabLocation = tab_location - - self._Layout(layout) - - super().__init__( - ELEM_TYPE_TAB_GROUP, - background_color=background_color, - text_color=title_color, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - ) - return - - def _AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def _Layout(self, rows): - for row in rows: - self._AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def FindKeyFromTabName(self, tab_name): - for row in self.Rows: - for element in row: - if element.Title == tab_name: - return element.Key - return None - - find_key_from_tab_name = FindKeyFromTabName - - -# ---------------------------------------------------------------------- # -# Slider # -# ---------------------------------------------------------------------- # -class Slider(Element): - def __init__( - self, - range=(None, None), - default_value=None, - resolution=None, - tick_interval=None, - orientation=None, - border_width=None, - relief=None, - change_submits=False, - disabled=False, - size=(None, None), - size_px=(None, None), - font=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None, - ): - ''' - Slider Element - :param range: - :param default_value: - :param resolution: - :param orientation: - :param border_width: - :param relief: - :param change_submits: - :param disabled: - :param size: - :param font: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - ''' - self.TKScale = None - self.Range = (1, 10) if range == (None, None) else range - self.DefaultValue = self.Range[0] if default_value is None else default_value - self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION - self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF - self.Resolution = 1 if resolution is None else resolution - self.ChangeSubmits = change_submits - self.Disabled = disabled - self.TickInterval = tick_interval - temp_size = size - if temp_size == (None, None): - temp_size = (20, 20) if orientation.startswith('h') else (8, 20) - - super().__init__( - ELEM_TYPE_INPUT_SLIDER, - size=temp_size, - font=font, - background_color=background_color, - text_color=text_color, - key=key, - pad=pad, - tooltip=tooltip, - size_px=size_px, - ) - return - - def Update(self, value=None, range=(None, None), disabled=None): - if value is not None: - try: - self.TKIntVar.set(value) - if range != (None, None): - self.TKScale.config(from_=range[0], to_=range[1]) - except: - pass - self.DefaultValue = value - if disabled == True: - self.TKScale['state'] = 'disabled' - elif disabled == False: - self.TKScale['state'] = 'normal' - - update = Update - - -# -# ---------------------------------------------------------------------- # -# Column # -# ---------------------------------------------------------------------- # -class Column(Element): - def __init__( - self, - layout, - background_color=None, - element_justification='left', - size=(None, None), - size_px=(None, None), - pad=None, - scrollable=False, - vertical_scroll_only=False, - right_click_menu=None, - key=None, - visible=True, - ): - ''' - Column Element - :param layout: - :param background_color: - :param size: - :param pad: - :param scrollable: - :param key: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.Scrollable = scrollable - self.VerticalScrollOnly = vertical_scroll_only - self.RightClickMenu = right_click_menu - bg = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.WxBoxSizer = None # type: wx.BoxSizer - self.WxHSizer = None # type: wx.BoxSizer - self._Layout(layout) - self.ElementJustification = element_justification - tsize = size_px if size_px != (None, None) else size - - super().__init__(ELEM_TYPE_COLUMN, background_color=background_color, size_px=tsize, pad=pad, key=key, visible=visible) - return - - def _AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def _Layout(self, rows): - for row in rows: - self._AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def Update(self, visible=None): - if visible: - self.WxHSizer.Show(self.WxBoxSizer, recursive=True) - self.ParentForm.VisibilityChanged() - elif visible is False: - self.WxHSizer.Hide(self.WxBoxSizer, recursive=True) - self.ParentForm.VisibilityChanged() - - update = Update - - -# ---------------------------------------------------------------------- # -# Menu # -# ---------------------------------------------------------------------- # -class Menu(Element): - def __init__(self, menu_definition, background_color=None, size=(None, None), tearoff=False, pad=None, key=None): - ''' - Menu Element - :param menu_definition: - :param background_color: - :param size: - :param tearoff: - :param pad: - :param key: - ''' - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.MenuDefinition = menu_definition - self.TKMenu = None - self.Tearoff = tearoff - - super().__init__(ELEM_TYPE_MENUBAR, background_color=background_color, size=size, pad=pad, key=key) - return - - def _MenuItemChosenCallback(self, item_chosen): - # print('IN MENU ITEM CALLBACK', item_chosen) - self.ParentForm.LastButtonClicked = item_chosen - self.ParentForm.FormRemainedOpen = True - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - -# ---------------------------------------------------------------------- # -# Table # -# ---------------------------------------------------------------------- # -class Table(Element): - def __init__( - self, - values, - headings=None, - visible_column_map=None, - col_widths=None, - def_col_width=10, - auto_size_columns=True, - max_col_width=20, - select_mode=None, - display_row_numbers=False, - num_rows=None, - font=None, - justification='right', - text_color=None, - background_color=None, - alternating_row_color=None, - size=(None, None), - change_submits=False, - bind_return_key=False, - pad=None, - key=None, - tooltip=None, - ): - ''' - Table Element - :param values: - :param headings: - :param visible_column_map: - :param col_widths: - :param def_col_width: - :param auto_size_columns: - :param max_col_width: - :param select_mode: - :param display_row_numbers: - :param font: - :param justification: - :param text_color: - :param background_color: - :param size: - :param pad: - :param key: - :param tooltip: - ''' - self.Values = values - self.ColumnHeadings = headings - self.ColumnsToDisplay = visible_column_map - self.ColumnWidths = col_widths - self.MaxColumnWidth = max_col_width - self.DefaultColumnWidth = def_col_width - self.AutoSizeColumns = auto_size_columns - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.TextColor = text_color - self.Justification = justification - self.InitialState = None - self.SelectMode = select_mode - self.DisplayRowNumbers = display_row_numbers - self.NumRows = num_rows if num_rows is not None else size[1] - self.TKTreeview = None - self.AlternatingRowColor = alternating_row_color - self.SelectedRows = [] - self.ChangeSubmits = change_submits - self.BindReturnKey = bind_return_key - self.StartingRowNumber = 0 # When displaying row numbers, where to start - self.RowHeaderText = 'Row' - super().__init__( - ELEM_TYPE_TABLE, - text_color=text_color, - background_color=background_color, - font=font, - size=size, - pad=pad, - key=key, - tooltip=tooltip, - ) - return - - def Update(self, values=None): - if values is not None: - children = self.TKTreeview.get_children() - for i in children: - self.TKTreeview.detach(i) - self.TKTreeview.delete(i) - children = self.TKTreeview.get_children() - # self.TKTreeview.delete(*self.TKTreeview.get_children()) - for i, value in enumerate(values): - if self.DisplayRowNumbers: - value = [i + self.StartingRowNumber] + value - id = self.TKTreeview.insert('', 'end', text=i, iid=i + 1, values=value, tag=i % 2) - if self.AlternatingRowColor is not None: - self.TKTreeview.tag_configure(1, background=self.AlternatingRowColor) - self.Values = values - self.SelectedRows = [] - - update = Update - - -# ---------------------------------------------------------------------- # -# Tree # -# ---------------------------------------------------------------------- # -class Tree(Element): - def __init__( - self, - data=None, - headings=None, - visible_column_map=None, - col_widths=None, - col0_width=10, - def_col_width=10, - auto_size_columns=True, - max_col_width=20, - select_mode=None, - show_expanded=False, - change_submits=False, - font=None, - justification='right', - text_color=None, - background_color=None, - num_rows=None, - pad=None, - key=None, - tooltip=None, - ): - ''' - Tree Element - :param headings: - :param visible_column_map: - :param col_widths: - :param def_col_width: - :param auto_size_columns: - :param max_col_width: - :param select_mode: - :param font: - :param justification: - :param text_color: - :param background_color: - :param num_rows: - :param pad: - :param key: - :param tooltip: - ''' - self.TreeData = data - self.ColumnHeadings = headings - self.ColumnsToDisplay = visible_column_map - self.ColumnWidths = col_widths - self.MaxColumnWidth = max_col_width - self.DefaultColumnWidth = def_col_width - self.AutoSizeColumns = auto_size_columns - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.TextColor = text_color - self.Justification = justification - self.InitialState = None - self.SelectMode = select_mode - self.ShowExpanded = show_expanded - self.NumRows = num_rows - self.Col0Width = col0_width - self.TKTreeview = None - self.SelectedRows = [] - self.ChangeSubmits = change_submits - - super().__init__( - ELEM_TYPE_TREE, - text_color=text_color, - background_color=background_color, - font=font, - pad=pad, - key=key, - tooltip=tooltip, - ) - return - - def add_treeview_data(self, node): - # print(f'Inserting {node.key} under parent {node.parent}') - if node.key != '': - self.TKTreeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, open=self.ShowExpanded) - for node in node.children: - self.add_treeview_data(node) - - def Update(self, values=None, key=None, value=None, text=None): - if values is not None: - children = self.TKTreeview.get_children() - for i in children: - self.TKTreeview.detach(i) - self.TKTreeview.delete(i) - children = self.TKTreeview.get_children() - self.TreeData = values - self.add_treeview_data(self.TreeData.root_node) - self.SelectedRows = [] - if key is not None: - item = self.TKTreeview.item(key) - if value is not None: - self.TKTreeview.item(key, values=value) - if text is not None: - self.TKTreeview.item(key, text=text) - item = self.TKTreeview.item(key) - return self - - update = Update - - -class TreeData(object): - class Node(object): - def __init__(self, parent, key, text, values): - self.parent = parent - self.children = [] - self.key = key - self.text = text - self.values = values - - def _Add(self, node): - self.children.append(node) - - def __init__(self): - self.tree_dict = {} - self.root_node = self.Node('', '', 'root', []) - self.tree_dict[''] = self.root_node - - def _AddNode(self, key, node): - self.tree_dict[key] = node - - def Insert(self, parent, key, text, values): - node = self.Node(parent, key, text, values) - self.tree_dict[key] = node - parent_node = self.tree_dict[parent] - parent_node._Add(node) - - def __repr__(self): - return self._NodeStr(self.root_node, 1) - - def _NodeStr(self, node, level): - return '\n'.join([str(node.key) + ' : ' + str(node.text)] + [' ' * 4 * level + self._NodeStr(child, level + 1) for child in node.children]) - - -# ---------------------------------------------------------------------- # -# Error Element # -# ---------------------------------------------------------------------- # -class ErrorElement(Element): - def __init__(self, key=None): - ''' - Error Element - :param key: - ''' - self.Key = key - - super().__init__(ELEM_TYPE_ERROR, key=key) - return - - def Update(self, *args, **kwargs): - PopupError( - 'Keyword error in Update', - 'You need to stop this madness and check your spelling', - 'Bad key = {}'.format(self.Key), - 'Your bad line of code may resemble this:', - 'window.FindElement("{}")'.format(self.Key), - ) - return self - - def Get(self): - return 'This is NOT a valid Element!\nSTOP trying to do things with it or I will have to crash at some point!' - - update = Update - get = Get - - -Stretch = ErrorElement - - -# ------------------------------------------------------------------------- # -# Tray CLASS # -# ------------------------------------------------------------------------- # -class SystemTray: - def __init__(self, menu=None, filename=None, data=None, data_base64=None, tooltip=None): - ''' - SystemTray - create an icon in the system tray - :param menu: Menu definition - :param filename: filename for icon - :param data: in-ram image for icon - :param data_base64: basee-64 data for icon - :param tooltip: tooltip string - ''' - self.Menu = menu - self.TrayIcon = None - self.Shown = False - self.MenuItemChosen = TIMEOUT_KEY - self.LastMessage = None - self.LastTitle = None - self.App = None - self.Filename = filename - self.timer = None - self.DataBase64 = data_base64 - if Window.highest_level_app is None: - self.App = Window.highest_level_app = wx.App(False) - # This could be a very dangerous thing to add! - # It was needed in order for an application to run the Tray in a Thread - self.App.SetAssertMode(wx.APP_ASSERT_SUPPRESS) - else: - self.App = Window.highest_level_app - self.Tooltip = tooltip - - frame = wx.Frame(None, title='Tray icon frame') - if filename: - self.icon = wx.Icon(filename, wx.BITMAP_TYPE_ANY) - elif data_base64: - self.icon = PyEmbeddedImage(data_base64).GetIcon() - else: - self.icon = PyEmbeddedImage(DEFAULT_BASE64_ICON).GetIcon() - self.TaskBarIcon = self.CustomTaskBarIcon(frame, self.App, self.Menu, self.icon, tooltip=tooltip) - - # self.App.MainLoop() - - class CustomTaskBarIcon(wx.adv.TaskBarIcon): - def __init__(self, frame, app, menu, icon, tooltip=None): - wx.adv.TaskBarIcon.__init__(self) - self.frame = frame - self.app = app - self.menu_item_chosen = None - self.menu = menu - self.id_to_text = {} - self.tooltip = tooltip or wx.EmptyString - - self.SetIcon(icon, tooltip=self.tooltip) - self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.OnTaskBarLeftClick) - self.Bind(wx.adv.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarLeftDoubleClick) - self.Bind(wx.adv.EVT_TASKBAR_RIGHT_DOWN, self.OnTaskBarRightClick) - self.Bind(wx.adv.EVT_TASKBAR_BALLOON_CLICK, self.OnTaskBarMessageClick) - self.Bind(wx.EVT_MENU, self.OnMenu) - - def OnTaskBarActivate(self, evt): - pass - - def OnTaskBarClose(self, evt): - self.frame.Close() - - def OnTaskBarLeftClick(self, evt): - # print('Got a LEFT click!') - self.menu_item_chosen = EVENT_SYSTEM_TRAY_ICON_ACTIVATED - self.app.ExitMainLoop() - - def OnTaskBarMessageClick(self, evt): - # print('Got a LEFT click!') - self.menu_item_chosen = EVENT_SYSTEM_TRAY_MESSAGE_CLICKED - self.app.ExitMainLoop() - - def OnTaskBarLeftDoubleClick(self, evt): - # print('Got a double click!') - self.menu_item_chosen = EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED - self.app.ExitMainLoop() - - def CreatePopupMenu(self): - # print(f'Popup menu = {self.menu}') - menu = wx.Menu() - AddMenuItem(menu, self.menu[1], self) - return menu - - def OnTaskBarRightClick(self, evt): - # print('Got a right click!') - self.menu_item_chosen = EVENT_SYSTEM_TRAY_ICON_RIGHT_CLICK - # self.app.ExitMainLoop() - - def OnMenu(self, event): - # print(f'On Menu {event}') - menu = event.EventObject - text = '' - item = menu.FindItemById(event.Id) - # for item in menu.MenuItems: - # if item.Id == event.Id: - # print('** FOUND MENU ENTRY! **') - # print(f'item = {item}') - text = self.id_to_text[item] - # text = self.id_to_text[item.Id] - self.menu_item_chosen = text - self.app.ExitMainLoop() - - def Read(self, timeout=None): - ''' - Reads the context menu - :param timeout: Optional. Any value other than None indicates a non-blocking read - :return: - ''' - # if not self.Shown: - # self.Shown = True - # self.TrayIcon.show() - timeout1 = timeout - # if timeout1 == 0: - # timeout1 = 1 - # if wx.GetApp(): - # wx.GetApp().ProcessPendingEvents() - # self.App.ProcessPendingEvents() - # self.App.ProcessIdle() - # return self.MenuItemChosen - if timeout1 is not None: - try: - self.timer = wx.Timer(self.TaskBarIcon) - self.TaskBarIcon.Bind(wx.EVT_TIMER, self.timer_timeout) - self.timer.Start(milliseconds=timeout1, oneShot=wx.TIMER_ONE_SHOT) - except: - print('*** Got error in Read ***') - self.RunningMainLoop = True - self.App.MainLoop() - self.RunningMainLoop = False - if self.timer: - self.timer.Stop() - self.TaskBarIcon.Unbind(wx.EVT_TIMER) - del self.timer - self.timer = None - self.MenuItemChosen = self.TaskBarIcon.menu_item_chosen - return self.MenuItemChosen - - def timer_timeout(self, event): - self.TaskBarIcon.Unbind(wx.EVT_TIMER) - del self.timer - self.timer = None - self.TaskBarIcon.menu_item_chosen = TIMEOUT_KEY - self.App.ExitMainLoop() - - def Hide(self): - self.TaskBarIcon.RemoveIcon() - - def UnHide(self): - self.TaskBarIcon.SetIcon(icon=self.TaskBarIcon.icon, tooltip=self.TaskBarIcon.tooltip) - - def ShowMessage(self, title, message, filename=None, data=None, data_base64=None, messageicon=None, time=10000): - ''' - Shows a balloon above icon in system tray - :param title: Title shown in balloon - :param message: Message to be displayed - :param filename: Optional icon filename - :param data: Optional in-ram icon - :param data_base64: Optional base64 icon - :param time: How long to display message in milliseconds - :return: - ''' - if messageicon is None: - self.TaskBarIcon.ShowBalloon(title, message, msec=time) - else: - self.TaskBarIcon.ShowBalloon(title, message, msec=time, flags=messageicon) - - return self - - def Close(self): - ''' - - :return: - ''' - self.Hide() - # Don't close app because windows could be depending on it - # self.App.quit() - - def _DisableAsserts(self): - wx.DisableAsserts() - - def Update( - self, - menu=None, - tooltip=None, - filename=None, - data=None, - data_base64=None, - ): - ''' - Updates the menu, tooltip or icon - :param menu: menu defintion - :param tooltip: string representing tooltip - :param filename: icon filename - :param data: icon raw image - :param data_base64: icon base 64 image - :return: - ''' - # Menu - if menu is not None: - self.TaskBarIcon.menu = menu - if filename: - self.icon = wx.Icon(filename, wx.BITMAP_TYPE_ANY) - elif data_base64: - self.icon = PyEmbeddedImage(data_base64).GetIcon() - elif not self.icon: - self.icon = PyEmbeddedImage(DEFAULT_BASE64_ICON).GetIcon() - if self.icon: - self.Tooltip = tooltip or self.Tooltip or self.TaskBarIcon.tooltip or wx.EmptyString - self.TaskBarIcon.SetIcon(self.icon, tooltip=self.Tooltip) - # Tooltip - # if tooltip is not None: - # self.TrayIcon.setToolTip(str(tooltip)) - # Icon - # qicon = None - # if filename is not None: - # icon = wx.Icon(filename, wx.BITMAP_TYPE_ICO) - # self.TaskBarIcon.SetIcon(icon, tooltip=tooltip) - # elif data is not None: - # ba = QtCore.QByteArray.fromRawData(data) - # pixmap = QtGui.QPixmap() - # pixmap.loadFromData(ba) - # qicon = QIcon(pixmap) - # elif data_base64 is not None: - # ico1 = base64.b64decode(data_base64) - # fout = open("zzztemp_icon.ico", "wb") - # fout.write(ico1) - # fout.close() - # icon = wx.Icon('zzztemp_icon.ico', wx.BITMAP_TYPE_ICO) - # self.TrayIcon.SetIcon(icon, tooltip=tooltip) - # os.remove("zzztemp_icon.ico") - # if qicon is not None: - # self.TrayIcon.setIcon(qicon) - - close = Close - hide = Hide - read = Read - show_message = ShowMessage - un_hide = UnHide - update = Update - - -class DragFrame(wx.Frame): - def __init__(self, title=''): - wx.Frame.__init__(self, None, title=title) - - def on_mouse(self, event): - ''' - implement dragging - ''' - # print('on_mouse') - if not event.Dragging(): - self._dragPos = None - return - # self.CaptureMouse() - if not self._dragPos: - self._dragPos = event.GetPosition() - else: - pos = event.GetPosition() - displacement = self._dragPos - pos - self.SetPosition(self.GetPosition() - displacement) - - -# ------------------------------------------------------------------------- # -# Window CLASS # -# ------------------------------------------------------------------------- # -class Window: - - NumOpenWindows = 0 - user_defined_icon = None - hidden_master_root = None - QTApplication = None - active_popups = {} - highest_level_app = None - stdout_is_rerouted = False - stdout_location = None - - def __init__( - self, - title, - layout=None, - default_element_size=DEFAULT_ELEMENT_SIZE, - default_button_element_size=(None, None), - auto_size_text=None, - auto_size_buttons=None, - location=(None, None), - size=(None, None), - element_padding=None, - button_color=None, - font=None, - progress_bar_color=(None, None), - background_color=None, - border_depth=None, - auto_close=False, - auto_close_duration=None, - icon=DEFAULT_BASE64_ICON, - force_toplevel=False, - element_justification='left', - alpha_channel=1, - return_keyboard_events=False, - use_default_focus=True, - text_justification=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - resizable=True, - disable_close=False, - disable_minimize=False, - background_image=None, - finalize=False, - ): - ''' - - :param title: - :param default_element_size: - :param default_button_element_size: - :param auto_size_text: - :param auto_size_buttons: - :param location: - :param size: - :param element_padding: - :param button_color: - :param font: - :param progress_bar_color: - :param background_color: - :param border_depth: - :param auto_close: - :param auto_close_duration: - :param icon: - :param force_toplevel: - :param alpha_channel: - :param return_keyboard_events: - :param use_default_focus: - :param text_justification: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param resizable: - :param disable_close: - :param background_image: - ''' - self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT - self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS - self.Title = title - self.Rows = [] # a list of ELEMENTS for this row - self.DefaultElementSize = _convert_tkinter_size_to_Wx(default_element_size) - self.DefaultButtonElementSize = _convert_tkinter_size_to_Wx(default_button_element_size) if default_button_element_size != (None, None) else _convert_tkinter_size_to_Wx(DEFAULT_BUTTON_ELEMENT_SIZE) - self.Location = location - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.BackgroundColor = background_color if background_color else DEFAULT_BACKGROUND_COLOR - self.ParentWindow = None - self.Font = font if font else DEFAULT_FONT - self.RadioDict = {} - self.BorderDepth = border_depth - self.WindowIcon = Window.user_defined_icon if Window.user_defined_icon is not None else icon if icon is not None else DEFAULT_WINDOW_ICON - self.AutoClose = auto_close - self.NonBlocking = False - self.TKroot = None - self.TKrootDestroyed = False - self.CurrentlyRunningMainloop = False - self.FormRemainedOpen = False - self.TKAfterID = None - self.ProgressBarColor = progress_bar_color - self.AutoCloseDuration = auto_close_duration - self.RootNeedsDestroying = False - self.Shown = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.LastButtonClicked = None - self.LastButtonClickedWasRealtime = False - self.UseDictionary = False - self.UseDefaultFocus = use_default_focus - self.ReturnKeyboardEvents = return_keyboard_events - self.LastKeyboardEvent = None - self.TextJustification = text_justification - self.NoTitleBar = no_titlebar - self.GrabAnywhere = grab_anywhere - self.KeepOnTop = keep_on_top - self.ForcefTopLevel = force_toplevel - self.Resizable = resizable - self._AlphaChannel = alpha_channel - self.Timeout = None - self.TimeoutKey = TIMEOUT_KEY - self.TimerCancelled = False - self.DisableClose = disable_close - self._Hidden = False - # self.QTApplication = None - # self.QT_QMainWindow = None - self._Size = size - self.ElementPadding = element_padding or DEFAULT_ELEMENT_PADDING - self.FocusElement = None - self.ElementJustification = element_justification - self.BackgroundImage = background_image - self.XFound = False - self.DisableMinimize = disable_minimize - self.App = None # type: wx.App - self.MasterFrame = None # type: wx.Frame - self.MasterPanel = None # type: wx.Panel - self.IgnoreClose = False - self.UniqueKeyCounter = 0 - self.AllKeysDict = {} # dictionary containing all the keys and elements in this window - - if layout is not None: - self.Layout(layout) - if finalize: - self.Finalize() - - @classmethod - def IncrementOpenCount(self): - self.NumOpenWindows += 1 - # print('+++++ INCREMENTING Num Open Windows = {} ---'.format(Window.NumOpenWindows)) - - @classmethod - def DecrementOpenCount(self): - self.NumOpenWindows -= 1 * (self.NumOpenWindows != 0) # decrement if not 0 - # print('----- DECREMENTING Num Open Windows = {} ---'.format(Window.NumOpenWindows)) - - # ------------------------- Add ONE Row to Form ------------------------- # - def AddRow(self, *args): - '''Parms are a variable number of Elements''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - # ------------------------- Add Multiple Rows to Form ------------------------- # - def AddRows(self, rows): - for row in rows: - self.AddRow(*row) - - def Layout(self, rows): - self.AddRows(rows) - self.BuildKeyDict() - return self - - def LayoutAndRead(self, rows, non_blocking=False): - raise DeprecationWarning('LayoutAndRead is no longer supported... change your call to window.Layout(layout).Read()') - # self.AddRows(rows) - # self.Show(non_blocking=non_blocking) - # return self.ReturnValues - - def LayoutAndShow(self, rows): - raise DeprecationWarning('LayoutAndShow is no longer supported... change your call to LayoutAndRead') - - # ------------------------- ShowForm THIS IS IT! ------------------------- # - def Show(self, non_blocking=False): - self.Shown = True - # Compute num rows & num cols (it'll come in handy debugging) - self.NumRows = len(self.Rows) - self.NumCols = max(len(row) for row in self.Rows) - self.NonBlocking = non_blocking - - # Search through entire form to see if any elements set the focus - # if not, then will set the focus to the first input element - found_focus = False - for row in self.Rows: - for element in row: - try: - if element.Focus: - found_focus = True - except: - pass - try: - if element.Key is not None: - self.UseDictionary = True - except: - pass - - if not found_focus and self.UseDefaultFocus: - self.UseDefaultFocus = True - else: - self.UseDefaultFocus = False - # -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ## - StartupTK(self) - # If a button or keyboard event happened but no results have been built, build the results - if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: - return BuildResults(self, False, self) - - return self.ReturnValues - - # ------------------------- SetIcon - set the window's fav icon ------------------------- # - def SetIcon(self, icon=None, pngbase64=None): - pass - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def _GetDefaultElementSize(self): - return self.DefaultElementSize - - def _AutoCloseAlarmCallback(self): - try: - window = self - if window: - if window.NonBlocking: - self.CloseNonBlockingForm() - else: - window._Close() - if self.CurrentlyRunningMainloop: - self.QTApplication.exit() # kick the users out of the mainloop - self.RootNeedsDestroying = True - self.QT_QMainWindow.close() - - except: - pass - - def timer_timeout(self, event): - # first, get the results table built - # modify the Results table in the parent FlexForm object - # print('timer timeout') - if self.TimerCancelled: - return - self.LastButtonClicked = self.TimeoutKey - self.FormRemainedOpen = True - if self.CurrentlyRunningMainloop: - self.App.ExitMainLoop() - - def non_block_timer_timeout(self, event): - # print('non-blocking timer timeout') - self.App.ExitMainLoop() - - def autoclose_timer_callback(self, frame): - # print('*** AUTOCLOSE TIMEOUT CALLBACK ***', frame) - try: - frame.Close() - except: - pass # if user has already closed the frame will get an error - # TODO Sept - does this need adding back? - # if self.CurrentlyRunningMainloop: - # self.App.ExitMainLoop() - - def callback_keyboard_char(self, event): - event = event # type:wx.KeyEvent - self.LastButtonClicked = None - self.FormRemainedOpen = True - if event.ClassName == 'wxMouseEvent': - if event.WheelRotation < 0: - self.LastKeyboardEvent = 'MouseWheel:Down' - else: - self.LastKeyboardEvent = 'MouseWheel:Up' - else: - self.LastKeyboardEvent = event.GetKeyCode() - if not self.NonBlocking: - BuildResults(self, False, self) - if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! - self.App.ExitMainLoop() # kick the users out of the mainloop - if event.ClassName != 'wxMouseEvent': - event.DoAllowNextEvent() - - def Read(self, timeout=None, timeout_key=TIMEOUT_KEY, close=False): - """ - THE biggest deal method in the Window class! This is how you get all of your data from your Window. - Pass in a timeout (in milliseconds) to wait for a maximum of timeout milliseconds. Will return timeout_key - if no other GUI events happen first. - Use the close parameter to close the window after reading - - :param timeout: (int) Milliseconds to wait until the Read will return IF no other GUI events happen first - :param timeout_key: (Any) The value that will be returned from the call if the timer expired - :param close: (bool) if True the window will be closed prior to returning - :return: Tuple[(Any), Union[Dict[Any:Any]], List[Any], None] (event, values) - """ - results = self._read(timeout=timeout, timeout_key=timeout_key) - if close: - self.close() - - return results - - def _read(self, timeout=None, timeout_key=TIMEOUT_KEY): - if timeout == 0: # timeout of zero runs the old readnonblocking - event, values = self._ReadNonBlocking() - if event is None: - event = timeout_key - if values is None: - event = None - return event, values # make event None if values was None and return - # Read with a timeout - self.Timeout = timeout - self.TimeoutKey = timeout_key - self.NonBlocking = False - if not self.Shown: - self.Show() - else: - # if already have a button waiting, the return previously built results - if self.LastButtonClicked is not None and not self.LastButtonClickedWasRealtime: - # print(f'*** Found previous clicked saved {self.LastButtonClicked}') - results = BuildResults(self, False, self) - self.LastButtonClicked = None - return results - InitializeResults(self) - # if the last button clicked was realtime, emulate a read non-blocking - # the idea is to quickly return realtime buttons without any blocks until released - if self.LastButtonClickedWasRealtime: - # print(f'RTime down {self.LastButtonClicked}' ) - try: - rc = self.TKroot.update() - except: - self.TKrootDestroyed = True - Window.DecrementOpenCount() - results = BuildResults(self, False, self) - if results[0] != None and results[0] != timeout_key: - return results - else: - pass - - # else: - # print("** REALTIME PROBLEM FOUND **", results) - - # normal read blocking code.... - if timeout != None: - self.TimerCancelled = False - timer = wx.Timer(self.App) - self.App.Bind(wx.EVT_TIMER, self.timer_timeout) - timer.Start(milliseconds=timeout, oneShot=wx.TIMER_ONE_SHOT) - else: - timer = None - self.CurrentlyRunningMainloop = True - # print(f'In main {self.Title}') - ################################# CALL GUWxTextCtrlI MAINLOOP ############################ - self.App.MainLoop() - self.CurrentlyRunningMainloop = False - self.TimerCancelled = True - if timer: - timer.Stop() - if Window.stdout_is_rerouted: - sys.stdout = Window.stdout_location - if self.RootNeedsDestroying: - # self.LastButtonClicked = None - # self.App.Close() - try: - self.MasterFrame.Close() - except: - pass - Window.DecrementOpenCount() - # if form was closed with X - if self.LastButtonClicked is None and self.LastKeyboardEvent is None and self.ReturnValues[0] is None: - Window.DecrementOpenCount() - # Determine return values - if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: - results = BuildResults(self, False, self) - if not self.LastButtonClickedWasRealtime: - self.LastButtonClicked = None - return results - else: - if not self.XFound and self.Timeout != 0 and self.Timeout is not None and self.ReturnValues[0] is None: # Special Qt case because returning for no reason so fake timeout - self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout - elif not self.XFound and self.ReturnValues[0] is None: # TODO HIGHLY EXPERIMENTAL... added due to tray icon interaction - # print("*** Faking timeout ***") - self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout - return self.ReturnValues - - def _ReadNonBlocking(self): - if self.TKrootDestroyed: - return None, None - if not self.Shown: - self.Show(non_blocking=True) - else: - # event = wx.Event() - # self.App.QueueEvent(event) - timer = wx.Timer(self.App) - self.App.Bind(wx.EVT_TIMER, self.timer_timeout) - timer.Start(milliseconds=0, oneShot=wx.TIMER_ONE_SHOT) - self.CurrentlyRunningMainloop = True - # print(f'In main {self.Title}') - ################################# CALL GUWxTextCtrlI MAINLOOP ############################ - - self.App.MainLoop() - if Window.stdout_is_rerouted: - sys.stdout = Window.stdout_location - # self.LastButtonClicked = 'TEST' - self.CurrentlyRunningMainloop = False - timer.Stop() - # while self.App.HasPendingEvents(): - # self.App.ProcessPendingEvents() - return BuildResults(self, False, self) - - def Finalize(self): - if self.TKrootDestroyed: - return self - if not self.Shown: - self.Show(non_blocking=True) - # else: - # try: - # self.QTApplication.processEvents() # refresh the window - # except: - # print('* ERROR FINALIZING *') - # self.TKrootDestroyed = True - # Window.DecrementOpenCount() - return self - - def Refresh(self): - # self.QTApplication.processEvents() # refresh the window - return self - - def VisibilityChanged(self): - self.SizeChanged() - return self - - def Fill(self, values_dict): - FillFormWithValues(self, values_dict) - return self - - def FindElement(self, key, silent_on_error=False): - try: - element = self.AllKeysDict[key] - except KeyError: - element = None - # element = _FindElementFromKeyInSubForm(self, key) - if element is None: - if not silent_on_error: - print('*** WARNING = FindElement did not find the key. Please check your key\'s spelling ***') - PopupError( - 'Keyword error in FindElement Call', - 'Bad key = {}'.format(key), - 'Your bad line of code may resemble this:', - 'window.FindElement("{}")'.format(key), - ) - return ErrorElement(key=key) - else: - return False - return element - - Element = FindElement # shortcut function definition - - def BuildKeyDict(self): - dict = {} - self.AllKeysDict = self._BuildKeyDictForWindow(self, self, dict) - # print(f'keys built = {self.AllKeysDict}') - - def _BuildKeyDictForWindow(self, top_window, window, key_dict): - for row_num, row in enumerate(window.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Type == ELEM_TYPE_FRAME: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Type == ELEM_TYPE_TAB_GROUP: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Type == ELEM_TYPE_TAB: - key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) - if element.Key is None: # if no key has been assigned.... create one for input elements - if element.Type == ELEM_TYPE_BUTTON: - element.Key = element.ButtonText - if element.Type in ( - ELEM_TYPE_MENUBAR, - ELEM_TYPE_BUTTONMENU, - ELEM_TYPE_CANVAS, - ELEM_TYPE_INPUT_SLIDER, - ELEM_TYPE_GRAPH, - ELEM_TYPE_IMAGE, - ELEM_TYPE_INPUT_CHECKBOX, - ELEM_TYPE_INPUT_LISTBOX, - ELEM_TYPE_INPUT_COMBO, - ELEM_TYPE_INPUT_MULTILINE, - ELEM_TYPE_INPUT_OPTION_MENU, - ELEM_TYPE_INPUT_SPIN, - ELEM_TYPE_TABLE, - ELEM_TYPE_TREE, - ELEM_TYPE_INPUT_TEXT, - ): - element.Key = top_window.DictionaryKeyCounter - top_window.DictionaryKeyCounter += 1 - if element.Key is not None: - if element.Key in key_dict.keys(): - (print('*** Duplicate key found in your layout {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) - element.Key = element.Key + str(self.UniqueKeyCounter) - self.UniqueKeyCounter += 1 - (print('*** Replaced new key with {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) - key_dict[element.Key] = element - return key_dict - - def FindElementWithFocus(self): - return self.FocusElement - # element = _FindElementWithFocusInSubForm(self) - # return element - - def SaveToDisk(self, filename): - try: - results = BuildResults(self, False, self) - with open(filename, 'wb') as sf: - pickle.dump(results[1], sf) - except: - print('*** Error saving form to disk ***') - - def LoadFromDisk(self, filename): - try: - with open(filename, 'rb') as df: - self.Fill(pickle.load(df)) - except: - print('*** Error loading form to disk ***') - - def GetScreenDimensions(self): - size = wx.GetDisplaySize() - return size - - def Move(self, x, y): - self.MasterFrame.SetPosition((x, y)) - - def Minimize(self): - self.MasterFrame.Iconize() - - def Maximize(self): - self.MasterFrame.Maximize() - - def _Close(self): - try: - self.TKroot.update() - except: - pass - if not self.NonBlocking: - BuildResults(self, False, self) - if self.TKrootDestroyed: - return None - self.TKrootDestroyed = True - self.RootNeedsDestroying = True - # self.__del__() - return None - - def Close(self): - if self.TKrootDestroyed: - return - try: - self.MasterFrame.Close() - except: - print('error closing window') - - CloseNonBlockingForm = Close - CloseNonBlocking = Close - - def Disable(self): - self.MasterFrame.Enable(False) - - def Enable(self): - self.MasterFrame.Enable(True) - - def Hide(self): - self._Hidden = True - self.MasterFrame.Hide() - return - - def UnHide(self): - if self._Hidden: - self.MasterFrame.Show() - self._Hidden = False - - def Disappear(self): - self.MasterFrame.SetTransparent(0) - - def Reappear(self): - self.MasterFrame.SetTransparent(255) - - def SetAlpha(self, alpha): - ''' - Change the window's transparency - :param alpha: From 0 to 1 with 0 being completely transparent - :return: - ''' - self._AlphaChannel = alpha * 255 - if self._AlphaChannel is not None: - self.MasterFrame.SetTransparent(self._AlphaChannel) - - @property - def AlphaChannel(self): - return self._AlphaChannel - - @AlphaChannel.setter - def AlphaChannel(self, alpha): - self.SetAlpha(alpha) - - def BringToFront(self): - self.MasterFrame.ToggleWindowStyle(wx.STAY_ON_TOP) - - def CurrentLocation(self): - location = self.MasterFrame.GetPosition() - return location - - def OnClose(self, event): - # print(f'CLOSE EVENT! event = {event}') - if self.DisableClose: - return - # print('GOT A CLOSE EVENT!', event, self.Window.Title) - if not self.IgnoreClose: - self.LastButtonClicked = None - self.XFound = True - if not self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! - self.RootNeedsDestroying = True - else: - self.RootNeedsDestroying = True - self.App.ExitMainLoop() # kick the users out of the mainloop - # print('exiting mainloop') - - self.MasterFrame.Destroy() - # TODO - Sept - This is all new from prior release... comment out? - """ - timer = wx.Timer(self.App) - self.App.Bind(wx.EVT_TIMER, self.timer_timeout) - timer.Start(milliseconds=100, oneShot=wx.TIMER_ONE_SHOT) - # self.CurrentlyRunningMainloop = True - # print(f'In main {self.Title}') - ################################# CALL GUWxTextCtrlI MAINLOOP ############################ - - self.App.MainLoop() - # self.CurrentlyRunningMainloop = False - timer.Stop() - print('after mainloop in close') - # TODO end - """ - - self.TKrootDestroyed = True - self.RootNeedsDestroying = True - - @property - def Size(self): - size = self.MasterFrame.GetSize() - return size - - @Size.setter - def Size(self, size): - self.MasterFrame.SetSize(size[0], size[1]) - - def SizeChanged(self): - size = self.Size - self.Size = size[0] + 1, size[1] + 1 - self.Size = size - self.MasterFrame.SetSizer(self.OuterSizer) - self.OuterSizer.Fit(self.MasterFrame) - - def __getitem__(self, key): - """ - Returns Element that matches the passed in key. - This is "called" by writing code as thus: - window['element key'].Update - - :param key: (Any) The key to find - :return: Union[Element, None] The element found or None if no element was found - """ - try: - return self.Element(key) - except Exception as e: - print('The key you passed in is no good. Key = {}*'.format(key)) - return None - - def __call__(self, *args, **kwargs): - """ - Call window.Read but without having to type it out. - window() == window.Read() - window(timeout=50) == window.Read(timeout=50) - - :param args: - :param kwargs: - :return: Tuple[Any, Dict[Any:Any]] The famous event, values that Read returns. - """ - return self.Read(*args, **kwargs) - - add_row = AddRow - add_rows = AddRows - alpha_channel = AlphaChannel - bring_to_front = BringToFront - close = Close - current_location = CurrentLocation - disable = Disable - disappear = Disappear - element = Element - enable = Enable - fill = Fill - finalize = Finalize - find_element = FindElement - find_element_with_focus = FindElementWithFocus - get_screen_dimensions = GetScreenDimensions - hide = Hide - layout = Layout - load_from_disk = LoadFromDisk - maximize = Maximize - minimize = Minimize - move = Move - read = Read - reappear = Reappear - refresh = Refresh - save_to_disk = SaveToDisk - set_alpha = SetAlpha - set_icon = SetIcon - size = Size - size_changed = SizeChanged - un_hide = UnHide - visibility_changed = VisibilityChanged - - -FlexForm = Window - -# =========================================================================== # -# Stops the mainloop and sets the event information # -# =========================================================================== # - - -def element_callback_quit_mainloop(element): - if element.Key is not None: - element.ParentForm.LastButtonClicked = element.Key - else: - element.ParentForm.LastButtonClicked = '' - element.ParentForm.FormRemainedOpen = True - if element.ParentForm.CurrentlyRunningMainloop: - element.ParentForm.App.ExitMainLoop() # kick the users out of the mainloop - - -def quit_mainloop(window): - window.App.ExitMainLoop() - - -# =========================================================================== # -# Convert from characters to pixels # -# =========================================================================== # -# def convert_tkinter_size_to_Wx(size): -# """ -# Converts size in characters to size in pixels -# :param size: size in characters, rows -# :return: size in pixels, pixels -# """ -# qtsize = size -# if size[1] is not None and size[1] < DEFAULT_PIXEL_TO_CHARS_CUTOFF: # change from character based size to pixels (roughly) -# qtsize = size[0]*DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1]*DEFAULT_PIXELS_TO_CHARS_SCALING[1] -# return qtsize - - -# =========================================================================== # -# Convert from characters to pixels # -# =========================================================================== # -def _convert_tkinter_size_to_Wx(size, scaling=DEFAULT_PIXELS_TO_CHARS_SCALING, height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF): - """ - Converts size in characters to size in pixels - :param size: size in characters, rows - :return: size in pixels, pixels - """ - qtsize = size - if size[1] is not None and size[1] < height_cutoff: # change from character based size to pixels (roughly) - qtsize = size[0] * scaling[0], size[1] * scaling[1] - return qtsize - - -def font_to_wx_font(font): - """ - Convert from font string/tyuple into a Qt style sheet string - :param font: "Arial 10 Bold" or ('Arial', 10, 'Bold) - :return: style string that can be combined with other style strings - """ - - if font is None: - return '' - - if type(font) is str: - _font = font.split(' ') - else: - _font = font - name = _font[0] - family = _font[0] - point_size = int(_font[1]) - - # style = _font[2] - - underline = 'underline' in _font[2:] - bold = 'bold' in _font - - wxfont = wx.Font( - point_size, - wx.FONTFAMILY_DEFAULT, - wx.FONTSTYLE_NORMAL, - wx.FONTWEIGHT_BOLD if bold else wx.FONTWEIGHT_NORMAL, - underline, - faceName=family, - ) - - return wxfont - - -def preprocess_radio_elements(top_window, window): - for row in window.Rows: - for element in row: - if element.Type == ELEM_TYPE_INPUT_RADIO: - if element.WxRadioButton is None: - element.WxRadioButton = wx.RadioButton(top_window.MasterPanel, id=wx.ID_ANY, label=element.Text, style=wx.RB_GROUP) - create_wx_radio_buttons(top_window, top_window, element.GroupID) - if element.Type in (ELEM_TYPE_COLUMN, ELEM_TYPE_FRAME, ELEM_TYPE_TAB_GROUP, ELEM_TYPE_TAB): - preprocess_radio_elements(top_window, element) - - -def create_wx_radio_buttons(top_window, window, group_id): - for row in window.Rows: - for element in row: - if element.Type == ELEM_TYPE_INPUT_RADIO: - if element.GroupID == group_id and element.WxRadioButton is None: - element.WxRadioButton = wx.RadioButton(top_window.MasterPanel, id=wx.ID_ANY, label=element.Text) - if element.Type in (ELEM_TYPE_COLUMN, ELEM_TYPE_FRAME, ELEM_TYPE_TAB_GROUP, ELEM_TYPE_TAB): - create_wx_radio_buttons(top_window, element, group_id) - - -# ################################################################################ -# ################################################################################ -# END OF ELEMENT DEFINITIONS -# ################################################################################ -# ################################################################################ - - -# =========================================================================== # -# Button Lazy Functions so the caller doesn't have to define a bunch of stuff # -# =========================================================================== # - - -# ------------------------- FOLDER BROWSE Element lazy function ------------------------- # -def FolderBrowse( - button_text='Browse', - target=(ThisRow, -1), - initial_folder=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - change_submits=False, - font=None, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_BROWSE_FOLDER, - target=target, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - disabled=disabled, - button_color=button_color, - change_submits=change_submits, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- FILE BROWSE Element lazy function ------------------------- # -def FileBrowse( - button_text='Browse', - target=(ThisRow, -1), - file_types=(('ALL Files', '*.*'),), - initial_folder=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - font=None, - disabled=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_BROWSE_FILE, - target=target, - file_types=file_types, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - change_submits=change_submits, - disabled=disabled, - button_color=button_color, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- FILES BROWSE Element (Multiple file selection) lazy function ------------------------- # -def FilesBrowse( - button_text='Browse', - target=(ThisRow, -1), - file_types=(('ALL Files', '*.*'),), - disabled=False, - initial_folder=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - font=None, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_BROWSE_FILES, - target=target, - file_types=file_types, - initial_folder=initial_folder, - change_submits=change_submits, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - disabled=disabled, - button_color=button_color, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- FILE BROWSE Element lazy function ------------------------- # -def FileSaveAs( - button_text='Save As...', - target=(ThisRow, -1), - file_types=(('ALL Files', '*.*'),), - initial_folder=None, - disabled=False, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - font=None, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_SAVEAS_FILE, - target=target, - file_types=file_types, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - disabled=disabled, - auto_size_button=auto_size_button, - button_color=button_color, - change_submits=change_submits, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- SAVE AS Element lazy function ------------------------- # -def SaveAs( - button_text='Save As...', - target=(ThisRow, -1), - file_types=(('ALL Files', '*.*'),), - initial_folder=None, - disabled=False, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - change_submits=False, - font=None, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_SAVEAS_FILE, - target=target, - file_types=file_types, - initial_folder=initial_folder, - tooltip=tooltip, - size=size, - disabled=disabled, - auto_size_button=auto_size_button, - button_color=button_color, - change_submits=change_submits, - font=font, - pad=pad, - key=key, - ) - - -# ------------------------- SAVE BUTTON lazy function ------------------------- # -def Save( - button_text='Save', - size=(None, None), - auto_size_button=None, - button_color=None, - bind_return_key=True, - disabled=False, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- SUBMIT BUTTON lazy function ------------------------- # -def Submit( - button_text='Submit', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- OPEN BUTTON lazy function ------------------------- # -def Open( - button_text='Open', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- OK BUTTON lazy function ------------------------- # -def OK( - button_text='OK', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- YES BUTTON lazy function ------------------------- # -def Ok( - button_text='Ok', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - bind_return_key=True, - tooltip=None, - font=None, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- CANCEL BUTTON lazy function ------------------------- # -def Cancel( - button_text='Cancel', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- QUIT BUTTON lazy function ------------------------- # -def Quit( - button_text='Quit', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Exit BUTTON lazy function ------------------------- # -def Exit( - button_text='Exit', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- YES BUTTON lazy function ------------------------- # -def Yes( - button_text='Yes', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=True, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- NO BUTTON lazy function ------------------------- # -def No( - button_text='No', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - tooltip=None, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- NO BUTTON lazy function ------------------------- # -def Help( - button_text='Help', - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - font=None, - tooltip=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- GENERIC BUTTON lazy function ------------------------- # -def SimpleButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - disabled=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_CLOSES_WIN, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - disabled=disabled, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- CLOSE BUTTON lazy function ------------------------- # -def CloseButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - disabled=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_CLOSES_WIN, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - disabled=disabled, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -CButton = CloseButton - - -# ------------------------- GENERIC BUTTON lazy function ------------------------- # -def ReadButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - bind_return_key=False, - disabled=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_READ_FORM, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - disabled=disabled, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -ReadFormButton = ReadButton -RButton = ReadFormButton - - -# ------------------------- Realtime BUTTON lazy function ------------------------- # -def RealtimeButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - disabled=False, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_REALTIME, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - disabled=disabled, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Dummy BUTTON lazy function ------------------------- # -def DummyButton( - button_text, - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - border_width=None, - tooltip=None, - size=(None, None), - auto_size_button=None, - button_color=None, - font=None, - disabled=False, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_CLOSES_WIN_ONLY, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -# ------------------------- Calendar Chooser Button lazy function ------------------------- # -def CalendarButton( - button_text, - target=(None, None), - close_when_date_chosen=True, - default_date_m_d_y=(None, None, None), - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - tooltip=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - button = Button( - button_text=button_text, - button_type=BUTTON_TYPE_CALENDAR_CHOOSER, - target=target, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - button.CalendarCloseWhenChosen = close_when_date_chosen - button.DefaultDate_M_D_Y = default_date_m_d_y - return button - - -# ------------------------- Calendar Chooser Button lazy function ------------------------- # -def ColorChooserButton( - button_text, - target=(None, None), - image_filename=None, - image_data=None, - image_size=(None, None), - image_subsample=None, - tooltip=None, - border_width=None, - size=(None, None), - auto_size_button=None, - button_color=None, - disabled=False, - font=None, - bind_return_key=False, - focus=False, - pad=None, - key=None, -): - return Button( - button_text=button_text, - button_type=BUTTON_TYPE_COLOR_CHOOSER, - target=target, - image_filename=image_filename, - image_data=image_data, - image_size=image_size, - image_subsample=image_subsample, - border_width=border_width, - tooltip=tooltip, - size=size, - auto_size_button=auto_size_button, - button_color=button_color, - font=font, - disabled=disabled, - bind_return_key=bind_return_key, - focus=focus, - pad=pad, - key=key, - ) - - -##################################### ----- RESULTS ------ ################################################## - - -def AddToReturnDictionary(form, element, value): - form.ReturnValuesDictionary[element.Key] = value - return - if element.Key is None: - form.ReturnValuesDictionary[form.DictionaryKeyCounter] = value - element.Key = form.DictionaryKeyCounter - form.DictionaryKeyCounter += 1 - else: - form.ReturnValuesDictionary[element.Key] = value - - -def AddToReturnList(form, value): - form.ReturnValuesList.append(value) - - -# ----------------------------------------------------------------------------# -# ------- FUNCTION InitializeResults. Sets up form results matrix --------# -def InitializeResults(form): - BuildResults(form, True, form) - return - - -# ===== Radio Button RadVar encoding and decoding =====# -# ===== The value is simply the row * 1000 + col =====# -def DecodeRadioRowCol(RadValue): - row = RadValue // 1000 - col = RadValue % 1000 - return row, col - - -def EncodeRadioRowCol(row, col): - RadValue = row * 1000 + col - return RadValue - - -# ------- FUNCTION BuildResults. Form exiting so build the results to pass back ------- # -# format of return values is -# (Button Pressed, input_values) -def BuildResults(form, initialize_only, top_level_form): - # Results for elements are: - # TEXT - Nothing - # INPUT - Read value from TK - # Button - Button Text and position as a Tuple - - # Get the initialized results so we don't have to rebuild - form.DictionaryKeyCounter = 0 - form.ReturnValuesDictionary = {} - form.ReturnValuesList = [] - BuildResultsForSubform(form, initialize_only, top_level_form) - # try: - # BuildResultsForSubform(form, initialize_only, top_level_form) - # except: - # print('Error building return values') - if not top_level_form.LastButtonClickedWasRealtime: - top_level_form.LastButtonClicked = None - return form.ReturnValues - - -def BuildResultsForSubform(form, initialize_only, top_level_form): - button_pressed_text = top_level_form.LastButtonClicked - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Key is not None and WRITE_ONLY_KEY in str(element.Key): - continue - value = None - if element.Type == ELEM_TYPE_COLUMN: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_FRAME: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_TAB_GROUP: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_TAB: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if not initialize_only: - if element.Type == ELEM_TYPE_INPUT_TEXT: - value = element.WxTextCtrl.GetValue() - if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: - element.WxTextCtrl.SetValue('') - elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: - value = element.WxCheckbox.GetValue() - value = value != 0 - elif element.Type == ELEM_TYPE_INPUT_RADIO: - value = element.WxRadioButton.GetValue() - elif element.Type == ELEM_TYPE_BUTTON: - if top_level_form.LastButtonClicked == element.ButtonText: - button_pressed_text = top_level_form.LastButtonClicked - if element.BType != BUTTON_TYPE_REALTIME: # Do not clear realtime buttons - top_level_form.LastButtonClicked = None - if element.BType == BUTTON_TYPE_CALENDAR_CHOOSER: - try: - value = element.TKCal.selection - except: - value = None - else: - try: - value = element.TKStringVar.get() - except: - value = None - elif element.Type == ELEM_TYPE_INPUT_COMBO: - value = element.WxComboBox.GetValue() - elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: - value = element.TKStringVar.get() - elif element.Type == ELEM_TYPE_INPUT_LISTBOX: - try: - items = element.TKListbox.curselection() - value = [element.Values[int(item)] for item in items] - except: - value = '' - elif element.Type == ELEM_TYPE_INPUT_SPIN: - value = element.WxTextCtrl.GetValue() - # value = element.CurrentValue - elif element.Type == ELEM_TYPE_INPUT_SLIDER: - try: - value = element.TKIntVar.get() - except: - value = 0 - elif element.Type == ELEM_TYPE_INPUT_MULTILINE: - if element.WriteOnly: - continue - try: - value = element.WxTextCtrl.GetValue() - except: - pass - - if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: - element.WxTextCtrl.SetValue('') - elif element.Type == ELEM_TYPE_TAB_GROUP: - try: - value = element.TKNotebook.tab(element.TKNotebook.index('current'))['text'] - tab_key = element.FindKeyFromTabName(value) - if tab_key is not None: - value = tab_key - except: - value = None - elif element.Type == ELEM_TYPE_TABLE: - value = element.SelectedRows - elif element.Type == ELEM_TYPE_TREE: - value = element.SelectedRows - elif element.Type == ELEM_TYPE_GRAPH: - value = element.ClickPosition - else: - value = None - - # if an input type element, update the results - if element.Type != ELEM_TYPE_BUTTON and element.Type != ELEM_TYPE_TEXT and element.Type != ELEM_TYPE_IMAGE and element.Type != ELEM_TYPE_OUTPUT and element.Type != ELEM_TYPE_PROGRESS_BAR and element.Type != ELEM_TYPE_COLUMN and element.Type != ELEM_TYPE_FRAME and element.Type != ELEM_TYPE_TAB: - AddToReturnList(form, value) - AddToReturnDictionary(top_level_form, element, value) - elif ( - (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_CALENDAR_CHOOSER and element.Target == (None, None)) - or (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_COLOR_CHOOSER and element.Target == (None, None)) - or ( - element.Type == ELEM_TYPE_BUTTON - and element.Key is not None - and ( - element.BType - in ( - BUTTON_TYPE_SAVEAS_FILE, - BUTTON_TYPE_BROWSE_FILE, - BUTTON_TYPE_BROWSE_FILES, - BUTTON_TYPE_BROWSE_FOLDER, - ) - ) - ) - ): - AddToReturnList(form, value) - AddToReturnDictionary(top_level_form, element, value) - - # if this is a column, then will fail so need to wrap with tr - try: - if form.ReturnKeyboardEvents and form.LastKeyboardEvent is not None: - button_pressed_text = form.LastKeyboardEvent - form.LastKeyboardEvent = None - except: - pass - - try: - form.ReturnValuesDictionary.pop(None, None) # clean up dictionary include None was included - except: - pass - - if not form.UseDictionary: - form.ReturnValues = button_pressed_text, form.ReturnValuesList - else: - form.ReturnValues = button_pressed_text, form.ReturnValuesDictionary - - return form.ReturnValues - - -def FillFormWithValues(form, values_dict): - FillSubformWithValues(form, values_dict) - - -def FillSubformWithValues(form, values_dict): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - value = None - if element.Type == ELEM_TYPE_COLUMN: - FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_FRAME: - FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_TAB_GROUP: - FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_TAB: - FillSubformWithValues(element, values_dict) - try: - value = values_dict[element.Key] - except: - continue - if element.Type == ELEM_TYPE_INPUT_TEXT: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_RADIO: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_COMBO: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_LISTBOX: - element.SetValue(value) - elif element.Type == ELEM_TYPE_INPUT_SLIDER: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_MULTILINE: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_SPIN: - element.Update(value) - elif element.Type == ELEM_TYPE_BUTTON: - element.Update(value) - - -def _FindElementFromKeyInSubForm(form, key): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_FRAME: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB_GROUP: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Key == key: - return element - - -def _FindElementWithFocusInSubForm(form): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_FRAME: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB_GROUP: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB: - matching_elem = _FindElementWithFocusInSubForm(element) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_INPUT_TEXT: - if element.TKEntry is not None: - if element.TKEntry is element.TKEntry.focus_get(): - return element - if element.Type == ELEM_TYPE_INPUT_MULTILINE: - if element.TKText is not None: - if element.TKText is element.TKText.focus_get(): - return element - - -if sys.version_info[0] >= 3: - - def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): - return_val = None - if type(sub_menu_info) is str: - if not is_sub_menu and not skip: - # print(f'Adding command {sub_menu_info}') - pos = sub_menu_info.find('&') - if pos != -1: - if pos == 0 or sub_menu_info[pos - 1] != '\\': - sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos + 1 :] - if sub_menu_info == '---': - top_menu.Append(wx.ID_SEPARATOR) - else: - try: - item_without_key = sub_menu_info[: sub_menu_info.index(MENU_KEY_SEPARATOR)] - except: - item_without_key = sub_menu_info - - if item_without_key[0] == MENU_DISABLED_CHARACTER: - id = top_menu.Append(wx.ID_ANY, item_without_key[len(MENU_DISABLED_CHARACTER) :]) - element.id_to_text[id] = sub_menu_info[1:] - top_menu.Enable(id.Id, False) - else: - id = top_menu.Append(wx.ID_ANY, item_without_key) - element.id_to_text[id] = sub_menu_info - - else: - i = 0 - while i < (len(sub_menu_info)): - item = sub_menu_info[i] - if i != len(sub_menu_info) - 1: - if type(sub_menu_info[i + 1]) == list: - new_menu = wx.Menu() - return_val = new_menu - pos = sub_menu_info[i].find('&') - if pos != -1: - if pos == 0 or sub_menu_info[i][pos - 1] != '\\': - sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1 :] - if sub_menu_info[i][0] == MENU_DISABLED_CHARACTER: - id = top_menu.AppendSubMenu(new_menu, sub_menu_info[i][len(MENU_DISABLED_CHARACTER) :]) - top_menu.Enable(id.Id, False) - else: - top_menu.AppendSubMenu(new_menu, sub_menu_info[i]) - AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) - i += 1 # skip the next one - else: - AddMenuItem(top_menu, item, element) - else: - AddMenuItem(top_menu, item, element) - i += 1 - return return_val - - -if sys.version_info[0] >= 3: - - def AddMenuItem2(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): - if type(sub_menu_info) is str: - if not is_sub_menu and not skip: - # print(f'Adding command {sub_menu_info}') - pos = sub_menu_info.find('&') - if pos != -1: - if pos == 0 or sub_menu_info[pos - 1] != '\\': - sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos + 1 :] - if sub_menu_info == '---': - top_menu.Append(wx.ID_SEPARATOR) - else: - top_menu.Append(wx.ID_ANY, sub_menu_info) - else: - i = 0 - while i < (len(sub_menu_info)): - item = sub_menu_info[i] - if i != len(sub_menu_info) - 1: - if type(sub_menu_info[i + 1]) == list: - new_menu = wx.Menu() - pos = sub_menu_info[i].find('&') - if pos != -1: - if pos == 0 or sub_menu_info[i][pos - 1] != '\\': - sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1 :] - top_menu.AppendSubMenu(new_menu, sub_menu_info[i]) - AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) - i += 1 # skip the next one - else: - AddMenuItem(top_menu, item, element) - else: - AddMenuItem(top_menu, item, element) - i += 1 - -else: - - def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): - if isinstance(sub_menu_info, types.StringType): - if not is_sub_menu and not skip: - # print(f'Adding command {sub_menu_info}') - pos = sub_menu_info.find('&') - if pos != -1: - if pos == 0 or sub_menu_info[pos - 1] != '\\': - sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos + 1 :] - if sub_menu_info == '---': - top_menu.add('separator') - else: - top_menu.add_command( - label=sub_menu_info, - underline=pos, - command=lambda: Menu._MenuItemChosenCallback(element, sub_menu_info), - ) - else: - i = 0 - while i < (len(sub_menu_info)): - item = sub_menu_info[i] - if i != len(sub_menu_info) - 1: - if not isinstance(sub_menu_info[i + 1], types.StringType): - new_menu = tk.Menu(top_menu, tearoff=element.Tearoff) - pos = sub_menu_info[i].find('&') - if pos != -1: - if pos == 0 or sub_menu_info[i][pos - 1] != '\\': - sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1 :] - top_menu.add_cascade(label=sub_menu_info[i], menu=new_menu, underline=pos) - AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) - i += 1 # skip the next one - else: - AddMenuItem(top_menu, item, element) - else: - AddMenuItem(top_menu, item, element) - i += 1 - - -# # ###### -# # # # # # # # # ##### # # #### # # -# # # # # # # # # # # # # # ## # -# # # ## ###### # # ###### # # # # # -# # # ## # # # # # # # # # # -# # # # # # # # # # # # # ## -## ## # # # # # # # #### # # - -# My crappy WxPython code - -# ░░░░░░░░░░░█▀▀░░█░░░░░░ -# ░░░░░░▄▀▀▀▀░░░░░█▄▄░░░░ -# ░░░░░░█░█░░░░░░░░░░▐░░░ -# ░░░░░░▐▐░░░░░░░░░▄░▐░░░ -# ░░░░░░█░░░░░░░░▄▀▀░▐░░░ -# ░░░░▄▀░░░░░░░░▐░▄▄▀░░░░ -# ░░▄▀░░░▐░░░░░█▄▀░▐░░░░░ -# ░░█░░░▐░░░░░░░░▄░█░░░░░ -# ░░░█▄░░▀▄░░░░▄▀▐░█░░░░░ -# ░░░█▐▀▀▀░▀▀▀▀░░▐░█░░░░░ -# ░░▐█▐▄░░▀░░░░░░▐░█▄▄░░░ -# ░░░▀▀▄░░░░░░░░▄▐▄▄▄▀░░░ -# ░░░░░░░░░░░░░░░░░░░░░░░ - - -# ------------------------------------------------------------------------------------------------------------------ # -# ------------------------------------------------------------------------------------------------------------------ # -# ===================================== WxPython CODE STARTS HERE ================================================ # -# ------------------------------------------------------------------------------------------------------------------ # -# ------------------------------------------------------------------------------------------------------------------ # - - -def PackFormIntoFrame(container_elem, containing_frame, toplevel_form): - """ - - :param container_elem: - :type container_elem: Window or Column or Tab or Frame - :param containing_frame: - :type containing_frame: wx.BoxSizer - :param toplevel_form: - :type toplevel_form: Window - :return: - """ - - def pad_widget(widget): - lrsizer = wx.BoxSizer(wx.HORIZONTAL) - if full_element_pad[1] == full_element_pad[3]: # if right = left - lrsizer.Add(widget, 0, wx.LEFT | wx.RIGHT, border=full_element_pad[1]) - else: - sizer = wx.BoxSizer(wx.HORIZONTAL) - sizer.Add(widget, 0, wx.LEFT, border=full_element_pad[3]) - lrsizer.Add(sizer, 0, wx.RIGHT, border=full_element_pad[1]) - - top_bottom_sizer = wx.BoxSizer(wx.HORIZONTAL) - if full_element_pad[0] == full_element_pad[2]: # if top = bottom - top_bottom_sizer.Add(lrsizer, 0, wx.TOP | wx.BOTTOM, border=full_element_pad[0]) - else: - sizer = wx.BoxSizer(wx.HORIZONTAL) - sizer.Add(lrsizer, 0, wx.TOP, border=full_element_pad[0]) - top_bottom_sizer.Add(sizer, 0, wx.BOTTOM, border=full_element_pad[2]) - return top_bottom_sizer - - # - # font, text color, background color, size, disabled, visible, tooltip - # - def do_font_and_color(widget): - if font: - widget.SetFont(font_to_wx_font(font)) - if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): - widget.SetForegroundColour(element.TextColor) - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - widget.SetBackgroundColour(element.BackgroundColor) - widget.SetMinSize(element_size) - if element.Disabled: - widget.Enable(False) - if not element.Visible: - widget.Hide() - if element.Tooltip: - widget.SetToolTip(element.Tooltip) - - def CharWidthInPixels(): - return tkinter.font.Font().measure('A') # single character width - - border_depth = toplevel_form.BorderDepth if toplevel_form.BorderDepth is not None else DEFAULT_BORDER_WIDTH - # --------------------------------------------------------------------------- # - # **************** Use FlexForm to build the tkinter window ********** ----- # - # Building is done row by row. # - # --------------------------------------------------------------------------- # - focus_set = False - ######################### LOOP THROUGH ROWS ######################### - # *********** ------- Loop through ROWS ------- ***********# - for row_num, flex_row in enumerate(container_elem.Rows): - ######################### LOOP THROUGH ELEMENTS ON ROW ######################### - # *********** ------- Loop through ELEMENTS ------- ***********# - # *********** Make TK Row ***********# - hsizer = wx.BoxSizer(wx.HORIZONTAL) - for col_num, element in enumerate(flex_row): - element.ParentForm = toplevel_form # save the button's parent form object - if toplevel_form.Font and (element.Font == DEFAULT_FONT or not element.Font): - font = toplevel_form.Font - element.Font = font - elif element.Font is not None: - font = element.Font - else: - font = DEFAULT_FONT - # ------- Determine Auto-Size setting on a cascading basis ------- # - if element.AutoSizeText is not None: # if element overide - auto_size_text = element.AutoSizeText - elif toplevel_form.AutoSizeText is not None: # if form override - auto_size_text = toplevel_form.AutoSizeText - else: - auto_size_text = DEFAULT_AUTOSIZE_TEXT - element_type = element.Type - # Set foreground color - text_color = element.TextColor - # Determine Element size - element_size = element.Size - if element_size == (None, None) and element_type not in ( - ELEM_TYPE_BUTTON, - ELEM_TYPE_BUTTONMENU, - ): # user did not specify a size - element_size = toplevel_form.DefaultElementSize - elif element_size == (None, None) and element_type in (ELEM_TYPE_BUTTON, ELEM_TYPE_BUTTONMENU): - element_size = toplevel_form.DefaultButtonElementSize - else: - auto_size_text = False # if user has specified a size then it shouldn't autosize - full_element_pad = [0, 0, 0, 0] # Top, Right, Bottom, Left - elementpad = element.Pad if element.Pad is not None else toplevel_form.ElementPadding - if type(elementpad[0]) != tuple: # left and right - full_element_pad[1] = full_element_pad[3] = elementpad[0] - else: - full_element_pad[3], full_element_pad[1] = elementpad[0] - if type(elementpad[1]) != tuple: # top and bottom - full_element_pad[0] = full_element_pad[2] = elementpad[1] - else: - full_element_pad[0], full_element_pad[2] = elementpad[1] - - border_depth = toplevel_form.BorderDepth if toplevel_form.BorderDepth is not None else DEFAULT_BORDER_WIDTH - try: - if element.BorderWidth is not None: - border_depth = element.BorderWidth - except: - pass - - # ------------------------- COLUMN element ------------------------- # - if element_type == ELEM_TYPE_COLUMN: - element = element # type: Column - element.WxBoxSizer = vsizer = wx.BoxSizer(wx.VERTICAL) - element.WxHSizer = hsizer - # element.WxScrollBar = wx.ScrollBar(toplevel_form.MasterFrame, id=wx.ID_ANY, style=wx.SB_VERTICAL) - # vsizer.Add(element.WxScrollBar) - PackFormIntoFrame(element, vsizer, toplevel_form) - - hsizer.Add(pad_widget(vsizer), 0) - if not element.Visible: - hsizer.Hide(vsizer, recursive=True) - - # # column_widget = QWidget() - # column_widget = QGroupBox() - # element.QT_QGroupBox = column_widget - # # column_widget.setFrameShape(QtWidgets.QFrame.NoFrame) - # style = create_style_from_font(font) - # if element.BackgroundColor is not None: - # style = style_entry(background_color=element.BackgroundColor) - # style += 'background-color: %s;' % element.BackgroundColor - # style += style_entry(border='0px solid gray') - # # style += 'border: 0px solid gray; ' - # style = style_generate('QGroupBox', style) - # column_widget.setStyleSheet(style) - # - # column_layout = QFormLayout() - # column_vbox = QVBoxLayout() - # - # PackFormIntoFrame(element, column_layout, toplevel_win) - # - # column_vbox.addLayout(column_layout) - # column_widget.setLayout(column_vbox) - # - # # column_widget.setStyleSheet(style) - # if not element.Visible: - # column_widget.setVisible(False) - # - # qt_row_layout.addWidget(column_widget) - - # if element.Scrollable: - # col_frame = TkScrollableFrame(tk_row_frame, - # element.VerticalScrollOnly) # do not use yet! not working - # PackFormIntoFrame(element, col_frame.TKFrame, toplevel_form) - # col_frame.TKFrame.update() - # if element.Size == (None, None): # if no size specified, use column width x column height/2 - # col_frame.canvas.config(width=col_frame.TKFrame.winfo_reqwidth(), - # height=col_frame.TKFrame.winfo_reqheight() / 2) - # else: - # col_frame.canvas.config(width=element.Size[0], height=element.Size[1]) - # - # if not element.BackgroundColor in (None, COLOR_SYSTEM_DEFAULT): - # col_frame.canvas.config(background=element.BackgroundColor) - # col_frame.TKFrame.config(background=element.BackgroundColor, borderwidth=0, - # highlightthickness=0) - # col_frame.config(background=element.BackgroundColor, borderwidth=0, highlightthickness=0) - # else: - # col_frame = tk.Frame(tk_row_frame) - # PackFormIntoFrame(element, col_frame, toplevel_form) - # - # col_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') - # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: - # col_frame.configure(background=element.BackgroundColor, highlightbackground=element.BackgroundColor, - # highlightcolor=element.BackgroundColor) - # ------------------------- TEXT element ------------------------- # - elif element_type == ELEM_TYPE_TEXT: - element = element # type: Text - if element.Justification is not None: - justification = element.Justification - elif toplevel_form.TextJustification is not None: - justification = toplevel_form.TextJustification - else: - justification = DEFAULT_TEXT_JUSTIFICATION - style = wx.ALIGN_LEFT if justification.startswith('l') else wx.ALIGN_CENTER if justification.startswith('c') else wx.ALIGN_RIGHT - # print(border_depth, element.BorderWidth) - if border_depth: - if element.Relief: - if element.Relief in (RELIEF_SOLID, RELIEF_FLAT): - style |= wx.SIMPLE_BORDER - elif element.Relief == RELIEF_SUNKEN: - style |= wx.SUNKEN_BORDER - elif element.Relief in (RELIEF_RAISED, RELIEF_RIDGE): - style |= wx.RAISED_BORDER - elif element.Relief in (RELIEF_SUNKEN, RELIEF_SUNKEN): - style |= wx.SUNKEN_BORDER - statictext = element.WxStaticText = wx.StaticText(toplevel_form.MasterPanel, -1, element.DisplayText, style=style) - if font: - statictext.SetFont(font_to_wx_font(font)) - if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): - statictext.SetForegroundColour(element.TextColor) - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - statictext.SetBackgroundColour(element.BackgroundColor) - display_text = element.DisplayText # text to display - if auto_size_text is False: - width, height = element_size - else: - lines = display_text.split('\n') - max_line_len = max([len(l) for l in lines]) - num_lines = len(lines) - if max_line_len > element_size[0]: # if text exceeds element size, the will have to wrap - width = element_size[0] - else: - width = max_line_len - height = num_lines - - if element.ClickSubmits: # bind events - statictext.Bind(wx.EVT_LEFT_UP, element._WxCallbackKeyboard) - - hsizer.Add(pad_widget(element.WxStaticText), 0) - - if not auto_size_text: - statictext.SetMinSize((width, height)) - - if element.Tooltip: - statictext.SetToolTip(element.Tooltip) - if not element.Visible: - statictext.Hide() - - # Set wrap-length for text (in PIXELS) == PAIN IN THE ASS - # wraplen = tktext_label.winfo_reqwidth() + 40 # width of widget in Pixels - # if not auto_size_text and height == 1: - # wraplen = 0 - # ------------------------- BUTTON element ------------------------- # - elif element_type == ELEM_TYPE_BUTTON: - element = element # type: Button - element.WxButton = button = wx.Button(toplevel_form.MasterPanel, style=wx.NO_BORDER) - button.SetLabelText(element.ButtonText) - if font: - button.SetFont(font_to_wx_font(font)) - button.Bind(wx.EVT_BUTTON, element.ButtonCallBack) - - element.Location = (row_num, col_num) - if element.AutoSizeButton is not None: - auto_size = element.AutoSizeButton - else: - auto_size = toplevel_form.AutoSizeButtons - if auto_size is False or element.Size[0] is not None: - width, height = element_size - else: - width = 0 - height = toplevel_form.DefaultButtonElementSize[1] - - if auto_size: - element.WxButton.SetWindowStyleFlag(element.WxButton.GetWindowStyleFlag() | wx.BU_EXACTFIT) - else: - element.WxButton.SetMinSize(_convert_tkinter_size_to_Wx((width, height), DEFAULT_PIXEL_TO_CHARS_CUTOFF)) - if element.ButtonColor != (None, None) and element.ButtonColor != DEFAULT_BUTTON_COLOR: - bc = element.ButtonColor - elif toplevel_form.ButtonColor != (None, None) and toplevel_form.ButtonColor != DEFAULT_BUTTON_COLOR: - bc = toplevel_form.ButtonColor - else: - bc = DEFAULT_BUTTON_COLOR - - button.SetBackgroundColour(bc[1]) - button.SetForegroundColour(bc[0]) - - sizer = pad_widget(button) - hsizer.Add(sizer, 0) - - if not element.Visible: - button.Hide() - if element.Tooltip: - button.SetToolTip(element.Tooltip) - - # if btype != BUTTON_TYPE_REALTIME: - # tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height, - # command=element.ButtonCallBack, justify=tk.LEFT, bd=border_depth, font=font) - # else: - # tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height, justify=tk.LEFT, - # bd=border_depth, font=font) - # tkbutton.bind('', element.ButtonReleaseCallBack) - # tkbutton.bind('', element.ButtonPressCallBack) - # if element.ImageFilename: # if button has an image on it - # tkbutton.config(highlightthickness=0) - # photo = tk.PhotoImage(file=element.ImageFilename) - # if element.ImageSize != (None, None): - # width, height = element.ImageSize - # if element.ImageSubsample: - # photo = photo.subsample(element.ImageSubsample) - # else: - # width, height = photo.width(), photo.height() - # tkbutton.config(image=photo, compound=tk.CENTER, width=width, height=height) - # tkbutton.image = photo - # if element.ImageData: # if button has an image on it - # tkbutton.config(highlightthickness=0) - # photo = tk.PhotoImage(data=element.ImageData) - # if element.ImageSize != (None, None): - # width, height = element.ImageSize - # if element.ImageSubsample: - # photo = photo.subsample(element.ImageSubsample) - # else: - # width, height = photo.width(), photo.height() - # tkbutton.config(image=photo, compound=tk.CENTER, width=width, height=height) - # tkbutton.image = photo - # if width != 0: - # tkbutton.configure(wraplength=wraplen + 10) # set wrap to width of widget - # tkbutton.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.BindReturnKey: - # element.TKButton.bind('', element._ReturnKeyHandler) - # if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - # focus_set = True - # element.TKButton.bind('', element._ReturnKeyHandler) - # element.TKButton.focus_set() - # toplevel_form.TKroot.focus_force() - # if element.Disabled == True: - # element.TKButton['state'] = 'disabled' - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKButton, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - - # # ------------------------- INPUT element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_TEXT: - element = element # type: InputText - if element.Justification is not None: - justification = element.Justification - elif toplevel_form.TextJustification is not None: - justification = toplevel_form.TextJustification - else: - justification = DEFAULT_TEXT_JUSTIFICATION - justify = wx.ALIGN_LEFT if justification.startswith('l') else wx.ALIGN_CENTER_HORIZONTAL if justification.startswith('c') else wx.ALIGN_RIGHT - if element.PasswordCharacter: - justify |= wx.TE_PASSWORD - - element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=justify) - - if element.DefaultText: - text_ctrl.SetValue(element.DefaultText) - if font: - text_ctrl.SetFont(font_to_wx_font(font)) - if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): - text_ctrl.SetForegroundColour(element.TextColor) - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - text_ctrl.SetBackgroundColour(element.BackgroundColor) - text_ctrl.SetMinSize(element_size) - if element.Disabled: - text_ctrl.Enable(False) - if element.ChangeSubmits: - text_ctrl.Bind(wx.EVT_KEY_UP, element._WxCallbackKeyboard) - text_ctrl.Bind(wx.EVT_TEXT_ENTER, element._ReturnKeyHandler) - - sizer = pad_widget(text_ctrl) - - hsizer.Add(sizer, 0) - - if not element.Visible: - text_ctrl.Hide() - if element.Tooltip: - text_ctrl.SetToolTip(element.Tooltip) - - if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - focus_set = True - element.SetFocus() - - # ------------------------- COMBO BOX (Drop Down) element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_COMBO: - element = element # type: Combo - if element.Readonly: - element.WxComboBox = wx.Choice(toplevel_form.MasterPanel, id=wx.ID_ANY, choices=element.Values) - else: - element.WxComboBox = wx.ComboBox(toplevel_form.MasterPanel, id=wx.ID_ANY, choices=element.Values) - if element.DefaultValue: - element.WxComboBox.SetSelection(element.WxComboBox.FindString(element.DefaultValue)) - if element.Readonly: - element.WxComboBox.SetWindowStyle(wx.CB_READONLY) - - do_font_and_color(element.WxComboBox) - sizer = pad_widget(element.WxComboBox) - - if element.ChangeSubmits: - element.WxComboBox.Bind(wx.EVT_COMBOBOX, element._WxCallbackKeyboard) - - hsizer.Add(sizer, 0) - - # max_line_len = max([len(str(l)) for l in element.Values]) - # if auto_size_text is False: - # width = element_size[0] - # else: - # width = max_line_len - # element.TKStringVar = tk.StringVar() - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # combostyle = ttk.Style() - # try: - # combostyle.theme_create('combostyle', - # settings={'TCombobox': - # {'configure': - # {'selectbackground': element.BackgroundColor, - # 'fieldbackground': element.BackgroundColor, - # 'foreground': text_color, - # 'background': element.BackgroundColor} - # }}) - # except: - # try: - # combostyle.theme_settings('combostyle', - # settings={'TCombobox': - # {'configure': - # {'selectbackground': element.BackgroundColor, - # 'fieldbackground': element.BackgroundColor, - # 'foreground': text_color, - # 'background': element.BackgroundColor} - # }}) - # except: - # pass - # # ATTENTION: this applies the new style 'combostyle' to all ttk.Combobox - # combostyle.theme_use('combostyle') - # element.TKCombo = ttk.Combobox(tk_row_frame, width=width, textvariable=element.TKStringVar, font=font) - # if element.Size[1] != 1 and element.Size[1] is not None: - # element.TKCombo.configure(height=element.Size[1]) - # # element.TKCombo['state']='readonly' - # element.TKCombo['values'] = element.Values - # - # # if element.InitializeAsDisabled: - # # element.TKCombo['state'] = 'disabled' - # # if element.BackgroundColor is not None: - # # element.TKCombo.configure(background=element.BackgroundColor) - # element.TKCombo.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.DefaultValue: - # for i, v in enumerate(element.Values): - # if v == element.DefaultValue: - # element.TKCombo.current(i) - # break - # else: - # element.TKCombo.current(0) - # if element.ChangeSubmits: - # element.TKCombo.bind('<>', element.ComboboxSelectHandler) - # if element.Readonly: - # element.TKCombo['state'] = 'readonly' - # if element.Disabled is True: # note overrides readonly if disabled - # element.TKCombo['state'] = 'disabled' - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKCombo, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # # ------------------------- LISTBOX element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_LISTBOX: - pass - # max_line_len = max([len(str(l)) for l in element.Values]) if len(element.Values) != 0 else 0 - # if auto_size_text is False: - # width = element_size[0] - # else: - # width = max_line_len - # listbox_frame = tk.Frame(tk_row_frame) - # element.TKStringVar = tk.StringVar() - # element.TKListbox = tk.Listbox(listbox_frame, height=element_size[1], width=width, - # selectmode=element.SelectMode, font=font) - # for index, item in enumerate(element.Values): - # element.TKListbox.insert(tk.END, item) - # if element.DefaultValues is not None and item in element.DefaultValues: - # element.TKListbox.selection_set(index) - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element.TKListbox.configure(background=element.BackgroundColor) - # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - # element.TKListbox.configure(fg=text_color) - # if element.ChangeSubmits: - # element.TKListbox.bind('<>', element.ListboxSelectHandler) - # vsb = tk.Scrollbar(listbox_frame, orient="vertical", command=element.TKListbox.yview) - # element.TKListbox.configure(yscrollcommand=vsb.set) - # element.TKListbox.pack(side=tk.LEFT) - # vsb.pack(side=tk.LEFT, fill='y') - # listbox_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.BindReturnKey: - # element.TKListbox.bind('', element.ListboxSelectHandler) - # element.TKListbox.bind('', element.ListboxSelectHandler) - # if element.Disabled == True: - # element.TKListbox['state'] = 'disabled' - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKListbox, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- INPUT MULTILINE element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_MULTILINE: - element = element # type: Multiline - justify = 0 - if element.EnterSubmits: - justify |= wx.TE_PROCESS_ENTER - justify |= wx.TE_MULTILINE - element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=justify) - - if element.DefaultText: - text_ctrl.SetValue(element.DefaultText) - if font: - text_ctrl.SetFont(font_to_wx_font(font)) - if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): - text_ctrl.SetForegroundColour(element.TextColor) - if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - text_ctrl.SetBackgroundColour(element.BackgroundColor) - text_ctrl.SetMinSize(element_size) - if element.Disabled: - text_ctrl.Enable(False) - if element.ChangeSubmits: - text_ctrl.Bind(wx.EVT_KEY_UP, element._WxCallbackKeyboard) - if element.EnterSubmits: - text_ctrl.Bind(wx.EVT_TEXT_ENTER, element._ReturnKeyHandler) - - sizer = pad_widget(text_ctrl) - hsizer.Add(sizer, 0) - - if not element.Visible: - text_ctrl.Hide() - if element.Tooltip: - text_ctrl.SetToolTip(element.Tooltip) - - if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - focus_set = True - element.SetFocus() - # ------------------------- OUTPUT MULTILINE element ------------------------- # - elif element_type == ELEM_TYPE_MULTILINE_OUTPUT: - element = element # type: MultilineOutput - style = 0 - if element.EnterSubmits: - style |= wx.TE_PROCESS_ENTER - style |= wx.TE_MULTILINE | wx.TE_READONLY - element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=style) - if element.DefaultText: - text_ctrl.SetValue(element.DefaultText) - - do_font_and_color(element.WxTextCtrl) - - if element.ChangeSubmits: - text_ctrl.Bind(wx.EVT_KEY_UP, element._WxCallbackKeyboard) - if element.EnterSubmits: - text_ctrl.Bind(wx.EVT_TEXT_ENTER, element._ReturnKeyHandler) - - sizer = pad_widget(text_ctrl) - - hsizer.Add(sizer, 0) - - if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - focus_set = True - element.SetFocus() - # ------------------------- OUTPUT element -----------------fd-------- # - elif element_type == ELEM_TYPE_OUTPUT: - element = element # type: Output - style = 0 - style |= wx.TE_MULTILINE | wx.TE_READONLY - style = wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL - element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=style) - - do_font_and_color(element.WxTextCtrl) - - sizer = pad_widget(text_ctrl) - - hsizer.Add(sizer, 0) - - element._reroute_stdout() - # ------------------------- INPUT CHECKBOX element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_CHECKBOX: - element = element # type:Checkbox - element.WxCheckbox = widget = wx.CheckBox(toplevel_form.MasterPanel) - if element.Text: - widget.SetLabel(element.Text) - do_font_and_color(element.WxCheckbox) - sizer = pad_widget(widget) - - if element.ChangeSubmits: - widget.Bind(wx.EVT_CHECKBOX, element._WxCallbackKeyboard) - - hsizer.Add(sizer, 0) - - if element.InitialState: - widget.SetValue(True) - element.WxCheckbox = widget - - # # ------------------------- PROGRESS BAR element ------------------------- # - elif element_type == ELEM_TYPE_PROGRESS_BAR: - element = element # type: ProgressBar - style = wx.GA_HORIZONTAL if element.Orientation.startswith('h') else wx.GA_VERTICAL - element_size = element_size[::-1] if element.Orientation.startswith('v') else element_size - element_size = wx.Size((element_size[0], element_size[1])) - element.WxGauge = gauge = wx.Gauge(toplevel_form.MasterPanel, wx.ID_ANY, range=element.MaxValue, style=style, size=element_size) - if element.StartValue is not None: - gauge.SetValue(element.StartValue) - do_font_and_color(element.WxGauge) - sizer = pad_widget(gauge) - hsizer.Add(sizer, 0) - # ------------------------- INPUT RADIO BUTTON element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_RADIO: - element = element # type: Radio - widget = element.WxRadioButton # type: wx.RadioButton - do_font_and_color(element.WxRadioButton) - sizer = pad_widget(widget) - if element.ChangeSubmits: - widget.Bind(wx.EVT_RADIOBUTTON, element._WxCallbackKeyboard) - hsizer.Add(sizer, 0) - if element.InitialState: - widget.SetValue(True) - else: - widget.SetValue(False) - - # ------------------------- INPUT SPINNER element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_SPIN: - element = element # type:Spin - ######## First make an Input widget that will be used to display the text ######## - style = wx.ALIGN_RIGHT - if element.ReadOnly: - style |= wx.TE_READONLY - element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=style) - do_font_and_color(element.WxTextCtrl) - if element.ChangeSubmits: - text_ctrl.Bind(wx.EVT_KEY_UP, element._WxCallbackKeyboard) - text_ctrl.Bind(wx.EVT_TEXT_ENTER, element._ReturnKeyHandler) - if element.DefaultValue: - text_ctrl.SetValue(str(element.DefaultValue)) - element.CurrentValue = element.DefaultValue - saved_pad = full_element_pad - full_element_pad[3] = 0 # set right padding to 0 - hsizer.Add(pad_widget(text_ctrl), 0) - - full_element_pad = saved_pad - ######## Now make a "Spin Button" that has the arrows ######## - # element.WxSpinCtrl = widget = wx.SpinCtrl(toplevel_form.MasterPanel, style=wx.SP_WRAP|wx.SP_ARROW_KEYS) - element.WxSpinCtrl = widget = wx.SpinButton(toplevel_form.MasterPanel, style=wx.SP_WRAP | wx.SP_ARROW_KEYS) - do_font_and_color(element.WxSpinCtrl) - element.WxSpinCtrl.SetRange(0, len(element.Values) - 1) - if element.DefaultValue: - element.WxSpinCtrl.SetValue(element.Values.index(element.DefaultValue)) - widget.SetMinSize((25, 25)) - - widget.Bind(wx.EVT_SPIN, element._WxSpinCallback) - saved_pad = full_element_pad - full_element_pad[1] = 0 # trying to set left pad to 0 but doesn't seem to work - hsizer.Add(pad_widget(widget), 0) - full_element_pad = saved_pad - - # ------------------------- IMAGE element ------------------------- # - elif element_type == ELEM_TYPE_IMAGE: - pass - # if element.Filename is not None: - # photo = tk.PhotoImage(file=element.Filename) - # elif element.Data is not None: - # photo = tk.PhotoImage(data=element.Data) - # else: - # photo = None - # print('*ERROR laying out form.... Image Element has no image specified*') - # - # if photo is not None: - # if element_size == ( - # None, None) or element_size == None or element_size == toplevel_form.DefaultElementSize: - # width, height = photo.width(), photo.height() - # else: - # width, height = element_size - # if photo is not None: - # element.tktext_label = tk.Label(tk_row_frame, image=photo, width=width, height=height, - # bd=border_depth) - # else: - # element.tktext_label = tk.Label(tk_row_frame, width=width, height=height, bd=border_depth) - # if element.BackgroundColor is not None: - # element.tktext_label.config(background=element.BackgroundColor); - # - # element.tktext_label.image = photo - # # tktext_label.configure(anchor=tk.NW, image=photo) - # element.tktext_label.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.tktext_label, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Canvas element ------------------------- # - elif element_type == ELEM_TYPE_CANVAS: - pass - # width, height = element_size - # if element._TKCanvas is None: - # element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) - # else: - # element._TKCanvas.master = tk_row_frame - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) - # element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - - # ------------------------- Graph element ------------------------- # - elif element_type == ELEM_TYPE_GRAPH: - pass - # width, height = element_size - # if element._TKCanvas is None: - # element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) - # else: - # element._TKCanvas.master = tk_row_frame - # element._TKCanvas2 = tk.Canvas(element._TKCanvas, width=width, height=height, bd=border_depth) - # element._TKCanvas2.pack(side=tk.LEFT) - # element._TKCanvas2.addtag_all('mytag') - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # element._TKCanvas2.configure(background=element.BackgroundColor, highlightthickness=0) - # element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) - # element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # if element.ChangeSubmits: - # element._TKCanvas2.bind('', element.ButtonReleaseCallBack) - # element._TKCanvas2.bind('', element.ButtonPressCallBack) - # if element.DragSubmits: - # element._TKCanvas2.bind('', element.MotionCallBack) - # ------------------------- MENUBAR element ------------------------- # - elif element_type == ELEM_TYPE_MENUBAR: - pass - # menu_def = element.MenuDefinition - # element.TKMenu = tk.Menu(toplevel_form.TKroot, tearoff=element.Tearoff) # create the menubar - # menubar = element.TKMenu - # for menu_entry in menu_def: - # # print(f'Adding a Menubar ENTRY {menu_entry}') - # baritem = tk.Menu(menubar, tearoff=element.Tearoff) - # pos = menu_entry[0].find('&') - # # print(pos) - # if pos != -1: - # if pos == 0 or menu_entry[0][pos - 1] != "\\": - # menu_entry[0] = menu_entry[0][:pos] + menu_entry[0][pos + 1:] - # menubar.add_cascade(label=menu_entry[0], menu=baritem, underline=pos) - # if len(menu_entry) > 1: - # AddMenuItem(baritem, menu_entry[1], element) - # toplevel_form.TKroot.configure(menu=element.TKMenu) - # ------------------------- Frame element ------------------------- # - elif element_type == ELEM_TYPE_FRAME: - element = element # type: Frame - # ----- code from column as a pattern to follow ----- - # element = element # type: Column - # element.WxBoxSizer = vsizer = wx.BoxSizer(wx.VERTICAL) - # element.WxHSizer = hsizer - # PackFormIntoFrame(element, vsizer, toplevel_form) - # - # hsizer.Add(pad_widget(vsizer), 0) - # if not element.Visible: - # hsizer.Hide(vsizer, recursive=True) - # element.panel = panel = wx.Panel(toplevel_form.MasterFrame) - element.WxBoxSizer = vsizer = wx.StaticBoxSizer(orient=wx.VERTICAL, parent=toplevel_form.MasterFrame.panel, label=element.Title) - element.WxHSizer = hsizer - PackFormIntoFrame(element, vsizer, toplevel_form) - - hsizer.Add(pad_widget(vsizer), 0) - if not element.Visible: - hsizer.Hide(vsizer, recursive=True) - - # labeled_frame = tk.LabelFrame(tk_row_frame, text=element.Title, relief=element.Relief) - # PackFormIntoFrame(element, labeled_frame, toplevel_form) - # labeled_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: - # labeled_frame.configure(background=element.BackgroundColor, - # highlightbackground=element.BackgroundColor, - # highlightcolor=element.BackgroundColor) - # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: - # labeled_frame.configure(foreground=element.TextColor) - # if font is not None: - # labeled_frame.configure(font=font) - # if element.TitleLocation is not None: - # labeled_frame.configure(labelanchor=element.TitleLocation) - # if element.BorderWidth is not None: - # labeled_frame.configure(borderwidth=element.BorderWidth) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(labeled_frame, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Tab element ------------------------- # - elif element_type == ELEM_TYPE_TAB: - pass - # element.TKFrame = tk.Frame(form.TKNotebook) - # PackFormIntoFrame(element, element.TKFrame, toplevel_form) - # if element.Disabled: - # form.TKNotebook.add(element.TKFrame, text=element.Title, state='disabled') - # else: - # form.TKNotebook.add(element.TKFrame, text=element.Title) - # form.TKNotebook.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # element.ParentNotebook = form.TKNotebook - # element.TabID = form.TabCount - # form.TabCount += 1 - # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: - # element.TKFrame.configure(background=element.BackgroundColor, - # highlightbackground=element.BackgroundColor, - # highlightcolor=element.BackgroundColor) - # # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: - # # element.TKFrame.configure(foreground=element.TextColor) - # - # # ttk.Style().configure("TNotebook", background='red') - # # ttk.Style().map("TNotebook.Tab", background=[("selected", 'orange')], - # # foreground=[("selected", 'green')]) - # # ttk.Style().configure("TNotebook.Tab", background='blue', foreground='yellow') - # - # if element.BorderWidth is not None: - # element.TKFrame.configure(borderwidth=element.BorderWidth) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKFrame, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- TabGroup element ------------------------- # - elif element_type == ELEM_TYPE_TAB_GROUP: - pass - - # custom_style = str(element.Key) + 'customtab.TNotebook' - # style = ttk.Style(tk_row_frame) - # if element.Theme is not None: - # style.theme_use(element.Theme) - # if element.TabLocation is not None: - # position_dict = {'left': 'w', 'right': 'e', 'top': 'n', 'bottom': 's', 'lefttop': 'wn', - # 'leftbottom': 'ws', 'righttop': 'en', 'rightbottom': 'es', 'bottomleft': 'sw', - # 'bottomright': 'se', 'topleft': 'nw', 'topright': 'ne'} - # try: - # tab_position = position_dict[element.TabLocation] - # except: - # tab_position = position_dict['top'] - # style.configure(custom_style, tabposition=tab_position) - # - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # style.configure(custom_style, background=element.BackgroundColor, foreground='purple') - # - # # style.theme_create("yummy", parent="alt", settings={ - # # "TNotebook": {"configure": {"tabmargins": [2, 5, 2, 0]}}, - # # "TNotebook.Tab": { - # # "configure": {"padding": [5, 1], "background": mygreen}, - # # "map": {"background": [("selected", myred)], - # # "expand": [("selected", [1, 1, 1, 0])]}}}) - # - # # style.configure(custom_style+'.Tab', background='red') - # if element.SelectedTitleColor != None: - # style.map(custom_style + '.Tab', foreground=[("selected", element.SelectedTitleColor)]) - # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - # style.configure(custom_style + '.Tab', foreground=element.TextColor) - # # style.configure(custom_style, background='blue', foreground='yellow') - # - # element.TKNotebook = ttk.Notebook(tk_row_frame, style=custom_style) - # - # PackFormIntoFrame(element, toplevel_form.TKroot, toplevel_form) - # - # if element.ChangeSubmits: - # element.TKNotebook.bind('<>', element.TabGroupSelectHandler) - # if element.BorderWidth is not None: - # element.TKNotebook.configure(borderwidth=element.BorderWidth) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKNotebook, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- SLIDER Box element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_SLIDER: - pass - # slider_length = element_size[0] * CharWidthInPixels() - # slider_width = element_size[1] - # element.TKIntVar = tk.IntVar() - # element.TKIntVar.set(element.DefaultValue) - # if element.Orientation[0] == 'v': - # range_from = element.Range[1] - # range_to = element.Range[0] - # slider_length += DEFAULT_MARGINS[1] * (element_size[0] * 2) # add in the padding - # else: - # range_from = element.Range[0] - # range_to = element.Range[1] - # if element.ChangeSubmits: - # tkscale = tk.Scale(tk_row_frame, orient=element.Orientation, variable=element.TKIntVar, - # from_=range_from, to_=range_to, resolution=element.Resolution, - # length=slider_length, width=slider_width, bd=element.BorderWidth, - # relief=element.Relief, font=font, tickinterval=element.TickInterval, - # command=element.SliderChangedHandler) - # else: - # tkscale = tk.Scale(tk_row_frame, orient=element.Orientation, variable=element.TKIntVar, - # from_=range_from, to_=range_to, resolution=element.Resolution, - # length=slider_length, width=slider_width, bd=element.BorderWidth, - # relief=element.Relief, font=font, tickinterval=element.TickInterval) - # tkscale.config(highlightthickness=0) - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # tkscale.configure(background=element.BackgroundColor) - # if DEFAULT_SCROLLBAR_COLOR != COLOR_SYSTEM_DEFAULT: - # tkscale.config(troughcolor=DEFAULT_SCROLLBAR_COLOR) - # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - # tkscale.configure(fg=text_color) - # tkscale.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # element.TKScale = tkscale - # if element.Disabled == True: - # element.TKScale['state'] = 'disabled' - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKScale, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- TABLE element ------------------------- # - elif element_type == ELEM_TYPE_TABLE: - pass - # frame = tk.Frame(tk_row_frame) - # - # height = element.NumRows - # if element.Justification == 'left': - # anchor = tk.W - # elif element.Justification == 'right': - # anchor = tk.E - # else: - # anchor = tk.CENTER - # column_widths = {} - # for row in element.Values: - # for i, col in enumerate(row): - # col_width = min(len(str(col)), element.MaxColumnWidth) - # try: - # if col_width > column_widths[i]: - # column_widths[i] = col_width - # except: - # column_widths[i] = col_width - # if element.ColumnsToDisplay is None: - # displaycolumns = element.ColumnHeadings - # else: - # displaycolumns = [] - # for i, should_display in enumerate(element.ColumnsToDisplay): - # if should_display: - # displaycolumns.append(element.ColumnHeadings[i]) - # column_headings = element.ColumnHeadings - # if element.DisplayRowNumbers: # if display row number, tack on the numbers to front of columns - # displaycolumns = [element.RowHeaderText, ] + displaycolumns - # column_headings = [element.RowHeaderText, ] + element.ColumnHeadings - # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, - # displaycolumns=displaycolumns, show='headings', height=height, - # selectmode=element.SelectMode) - # treeview = element.TKTreeview - # if element.DisplayRowNumbers: - # treeview.heading(element.RowHeaderText, text=element.RowHeaderText) # make a dummy heading - # treeview.column(element.RowHeaderText, width=50, anchor=anchor) - # for i, heading in enumerate(element.ColumnHeadings): - # treeview.heading(heading, text=heading) - # if element.AutoSizeColumns: - # width = max(column_widths[i], len(heading)) - # else: - # try: - # width = element.ColumnWidths[i] - # except: - # width = element.DefaultColumnWidth - # - # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) - # # Insert values into the tree - # for i, value in enumerate(element.Values): - # if element.DisplayRowNumbers: - # value = [i + element.StartingRowNumber] + value - # id = treeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i % 2) - # if element.AlternatingRowColor is not None: - # treeview.tag_configure(1, background=element.AlternatingRowColor) - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", background=element.BackgroundColor, - # fieldbackground=element.BackgroundColor) - # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", foreground=element.TextColor) - # # scrollable_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') - # treeview.bind("<>", element.treeview_selected) - # if element.BindReturnKey: - # treeview.bind('', element.treeview_double_click) - # treeview.bind('', element.treeview_double_click) - # scrollbar = tk.Scrollbar(frame) - # scrollbar.pack(side=tk.RIGHT, fill='y') - # scrollbar.config(command=treeview.yview) - # treeview.configure(yscrollcommand=scrollbar.set) - # - # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') - # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) - # if element.Tooltip is not None: - # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Tree element ------------------------- # - elif element_type == ELEM_TYPE_TREE: - pass - # frame = tk.Frame(tk_row_frame) - # - # height = element.NumRows - # if element.Justification == 'left': # justification - # anchor = tk.W - # elif element.Justification == 'right': - # anchor = tk.E - # else: - # anchor = tk.CENTER - # - # if element.ColumnsToDisplay is None: # Which cols to display - # displaycolumns = element.ColumnHeadings - # else: - # displaycolumns = [] - # for i, should_display in enumerate(element.ColumnsToDisplay): - # if should_display: - # displaycolumns.append(element.ColumnHeadings[i]) - # column_headings = element.ColumnHeadings - # # ------------- GET THE TREEVIEW WIDGET ------------- - # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, - # displaycolumns=displaycolumns, show='tree headings', height=height, - # selectmode=element.SelectMode, ) - # treeview = element.TKTreeview - # for i, heading in enumerate(element.ColumnHeadings): # Configure cols + headings - # treeview.heading(heading, text=heading) - # if element.AutoSizeColumns: - # width = min(element.MaxColumnWidth, len(heading) + 1) - # else: - # try: - # width = element.ColumnWidths[i] - # except: - # width = element.DefaultColumnWidth - # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) - # - # def add_treeview_data(node): - # # print(f'Inserting {node.key} under parent {node.parent}') - # if node.key != '': - # treeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, - # open=element.ShowExpanded) - # for node in node.children: - # add_treeview_data(node) - # - # add_treeview_data(element.TreeData.root_node) - # treeview.column('#0', width=element.Col0Width * CharWidthInPixels(), anchor=anchor) - # # ----- configure colors ----- - # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", background=element.BackgroundColor, - # fieldbackground=element.BackgroundColor) - # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - # ttk.Style().configure("Treeview", foreground=element.TextColor) - # - # scrollbar = tk.Scrollbar(frame) - # scrollbar.pack(side=tk.RIGHT, fill='y') - # scrollbar.config(command=treeview.yview) - # treeview.configure(yscrollcommand=scrollbar.set) - # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') - # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) - # treeview.bind("<>", element.treeview_selected) - # if element.Tooltip is not None: # tooltip - # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, - # timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Separator element ------------------------- # - elif element_type == ELEM_TYPE_SEPARATOR: - element = element # type: VerticalSeparator - if element.Orientation.lower().startswith('v'): - element.WxStaticLine = static_line = wx.StaticLine(toplevel_form.MasterPanel, style=wx.LI_VERTICAL) - else: - element.WxStaticLine = static_line = wx.StaticLine(toplevel_form.MasterPanel, style=wx.LI_HORIZONTAL) - - do_font_and_color(element.WxStaticLine) - - sizer = pad_widget(static_line) - - hsizer.Add(sizer, 0) - - # .................DONE WITH ROW pack the row of widgets ..................# - - # Add the row to the layout. Justify the row depending on the settings for its container - if container_elem.ElementJustification.startswith('c'): - containing_frame.Add(hsizer, 0, wx.ALIGN_CENTER, border=0) - elif container_elem.ElementJustification.startswith('r'): - containing_frame.Add(hsizer, 0, wx.ALIGN_RIGHT, border=0) - else: - containing_frame.Add(hsizer, 0, wx.TOP | wx.BOTTOM, border=0) - - return - - -# ----====----====----====----====----==== STARTUP TK ====----====----====----====----====----# -def StartupTK(window: Window): - - ow = Window.NumOpenWindows - if Window.highest_level_app is None: - app = Window.highest_level_app = wx.App(False) - else: - app = Window.highest_level_app - Window.IncrementOpenCount() - - # -------- grab anywhere -------- - if window.GrabAnywhere: - frame = DragFrame(title=window.Title) - else: - frame = wx.Frame(None, title=window.Title) - - panel = wx.Panel(frame, -1, style=wx.TRANSPARENT_WINDOW) - # panel.SetTransparent(.5) - if window.GrabAnywhere: - panel.Bind(wx.EVT_MOTION, frame.on_mouse) - - window.App = app - window.MasterFrame = frame - window.MasterPanel = panel - window.MasterFrame.panel = panel - frame.Bind(wx.EVT_CLOSE, window.OnClose) - - # ----------------------------- Icon ----------------------------- - if window.WindowIcon: - if type(window.WindowIcon) is bytes: - icon = PyEmbeddedImage(window.WindowIcon).GetIcon() - else: - if os.path.exists(window.WindowIcon): - icon = wx.Icon(window.WindowIcon, wx.BITMAP_TYPE_ANY) - else: - icon = PyEmbeddedImage(DEFAULT_BASE64_ICON).GetIcon() - if icon: - frame.SetIcon(icon) - - # ----------------------------- Background ----------------------------- - if window.BackgroundColor is not None and window.BackgroundColor != COLOR_SYSTEM_DEFAULT: - panel.SetBackgroundColour(window.BackgroundColor) - - if window.BackgroundImage: - if type(window.BackgroundImage) is bytes: - pic = PyEmbeddedImage(window.BackgroundImage).GetBitmap() - else: - if os.path.exists(window.BackgroundImage): - pic = wx.Image(window.BackgroundImage, wx.BITMAP_TYPE_ANY).ConvertToBitmap() - else: - pic = PyEmbeddedImage(DEFAULT_BASE64_ICON).GetBitmap() - window.bitmap1 = wx.StaticBitmap(window.MasterPanel, -1, pic, (0, 0)) - - InitializeResults(window) - - # ----------------------------- ----------------------------- - # ----------------------------- ----------------------------- - # ----------------------------- handle settings using Style Flags ----------------------------- - style = 0 - if window.NoTitleBar: - style |= wx.BORDER_NONE - else: - style |= wx.BORDER_DEFAULT - if window.KeepOnTop: - style |= wx.STAY_ON_TOP - if style: - window.MasterFrame.SetWindowStyleFlag(style) - - if window.ReturnKeyboardEvents: - # style |= wx.WANTS_CHARS - window.App.Bind(wx.EVT_CHAR_HOOK, window.callback_keyboard_char) - window.App.Bind(wx.EVT_MOUSEWHEEL, window.callback_keyboard_char) - - # ----------------------------- Sizer creation and PACK FORM ----------------------------- - vsizer = wx.BoxSizer(wx.VERTICAL) - - preprocess_radio_elements(window, window) - - # ----------------------------- Do the packing of the elements ----------------------------- - - PackFormIntoFrame(window, vsizer, window) - - # ----------------------------- Sizers to create margins ----------------------------- - outersizer = wx.BoxSizer(wx.VERTICAL) - outersizer.Fit(window.MasterFrame) - outersizer.Add(vsizer, 1, wx.TOP | wx.BOTTOM | wx.EXPAND, border=DEFAULT_MARGINS[1]) - - window.OuterSizer = wx.BoxSizer(wx.VERTICAL) - window.OuterSizer.Fit(window.MasterFrame) - window.OuterSizer.Add(outersizer, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, border=DEFAULT_MARGINS[0]) - - window.MasterPanel.SetSizer(window.OuterSizer) - - window.OuterSizer.Fit(window.MasterFrame) - - # ----------------------------- window location, size and alpha ----------------------------- - if window.Location != (None, None): - window.MasterFrame.Move(window.Location[0], window.Location[1]) - else: - window.MasterFrame.Center(wx.BOTH) - - if window._Size != (None, None): - window.MasterFrame.SetSize(window._Size[0], window._Size[1]) - - if window._AlphaChannel is not None: - window.SetAlpha(window._AlphaChannel) - - # ----------------------------- DISPLAY the window ----------------------------- - window.MasterFrame.Show() - - # ....................................... DONE creating and laying out window ..........................# - if RUN_INSPECTION_TOOL: - wx.lib.inspection.InspectionTool().Show() - window.CurrentlyRunningMainloop = True - - if window.Timeout: - timer = wx.Timer(window.App) - window.App.Bind(wx.EVT_TIMER, window.timer_timeout) - timer.Start(milliseconds=window.Timeout, oneShot=wx.TIMER_ONE_SHOT) - else: - timer = None - - if window.AutoClose: - window.timer = wx.Timer(window.App, id=Window.NumOpenWindows) - window.App.Bind(wx.EVT_TIMER, lambda frame: window.autoclose_timer_callback(window.MasterFrame), id=Window.NumOpenWindows) - window.timer.Start(milliseconds=window.AutoCloseDuration * 1000, oneShot=wx.TIMER_ONE_SHOT) - # ------------------------------------ MAINLOOP ------------------------------------ - - if not window.NonBlocking: - window.App.MainLoop() - else: - window.non_block_timer = wx.Timer(window.App, id=5678) - window.App.Bind(wx.EVT_TIMER, window.non_block_timer_timeout, id=5678) - window.non_block_timer.Start(milliseconds=0, oneShot=wx.TIMER_ONE_SHOT) - window.App.MainLoop() - - if Window.stdout_is_rerouted: - sys.stdout = Window.stdout_location - window.CurrentlyRunningMainloop = False - if timer: - timer.Stop() - - # if not window.FormRemainedOpen: - # _my_windows.Decrement() - # if window.RootNeedsDestroying: - # window.TKroot.destroy() - # window.RootNeedsDestroying = False - return - - -# ==============================_GetNumLinesNeeded ==# -# Helper function for determining how to wrap text # -# ===================================================# -def _GetNumLinesNeeded(text, max_line_width): - if max_line_width == 0: - return 1 - lines = text.split('\n') - num_lines = len(lines) # number of original lines of text - max_line_len = max([len(l) for l in lines]) # longest line - lines_used = [] - for L in lines: - lines_used.append(len(L) // max_line_width + (len(L) % max_line_width > 0)) # fancy math to round up - total_lines_needed = sum(lines_used) - return total_lines_needed - - -# ============================== PROGRESS METER ========================================== # - - -def ConvertArgsToSingleString(*args): - ( - max_line_total, - width_used, - total_lines, - ) = ( - 0, - 0, - 0, - ) - single_line_message = '' - # loop through args and built a SINGLE string from them - for message in args: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = max(longest_line_len, width_used) - max_line_total = max(max_line_total, width_used) - lines_needed = _GetNumLinesNeeded(message, width_used) - total_lines += lines_needed - single_line_message += message + '\n' - return single_line_message, width_used, total_lines - - -METER_REASON_CANCELLED = 'cancelled' -METER_REASON_CLOSED = 'closed' -METER_REASON_REACHED_MAX = 'finished' -METER_OK = True -METER_STOPPED = False - - -class QuickMeter(object): - active_meters = {} - exit_reasons = {} - - def __init__( - self, - title, - current_value, - max_value, - key, - *args, - orientation='v', - bar_color=(None, None), - button_color=(None, None), - size=DEFAULT_PROGRESS_BAR_SIZE, - border_width=None, - grab_anywhere=False, - ): - self.start_time = datetime.datetime.utcnow() - self.key = key - self.orientation = orientation - self.bar_color = bar_color - self.size = size - self.grab_anywhere = grab_anywhere - self.button_color = button_color - self.border_width = border_width - self.title = title - self.current_value = current_value - self.max_value = max_value - self.close_reason = None - self.window = self.BuildWindow(*args) - - def BuildWindow(self, *args): - layout = [] - if self.orientation.lower().startswith('h'): - col = [] - col += [[T(''.join(map(lambda x: str(x) + '\n', args)), key='_OPTMSG_')]] ### convert all *args into one string that can be updated - col += [ - [T('', size=(25, 8), key='_STATS_')], - [ProgressBar(max_value=self.max_value, orientation='h', key='_PROG_', size=self.size)], - [Cancel(button_color=self.button_color), Stretch()], - ] - layout = [Column(col)] - else: - col = [[ProgressBar(max_value=self.max_value, orientation='v', key='_PROG_', size=self.size)]] - col2 = [] - col2 += [[T(''.join(map(lambda x: str(x) + '\n', args)), key='_OPTMSG_')]] ### convert all *args into one string that can be updated - col2 += [[T('', size=(25, 8), key='_STATS_')], [Cancel(button_color=self.button_color), Stretch()]] - layout = [Column(col), Column(col2)] - self.window = Window(self.title, grab_anywhere=self.grab_anywhere, border_depth=self.border_width) - self.window.Layout([layout]).Finalize() - - return self.window - - def UpdateMeter(self, current_value, max_value, *args): - self.current_value = current_value - self.max_value = max_value - self.window.Element('_PROG_').UpdateBar(self.current_value, self.max_value) - self.window.Element('_STATS_').Update('\n'.join(self.ComputeProgressStats())) - self.window.Element('_OPTMSG_').Update(value=''.join(map(lambda x: str(x) + '\n', args))) ### update the string with the args - event, values = self.window.Read(timeout=0) - if event in ('Cancel', None) or current_value >= max_value: - self.window.Close() - del QuickMeter.active_meters[self.key] - QuickMeter.exit_reasons[self.key] = METER_REASON_CANCELLED if event == 'Cancel' else METER_REASON_CLOSED if event is None else METER_REASON_REACHED_MAX - return QuickMeter.exit_reasons[self.key] - return METER_OK - - def ComputeProgressStats(self): - utc = datetime.datetime.utcnow() - time_delta = utc - self.start_time - total_seconds = time_delta.total_seconds() - if not total_seconds: - total_seconds = 1 - try: - time_per_item = total_seconds / self.current_value - except: - time_per_item = 1 - seconds_remaining = (self.max_value - self.current_value) * time_per_item - time_remaining = str(datetime.timedelta(seconds=seconds_remaining)) - time_remaining_short = (time_remaining).split('.')[0] - time_delta_short = str(time_delta).split('.')[0] - total_time = time_delta + datetime.timedelta(seconds=seconds_remaining) - total_time_short = str(total_time).split('.')[0] - self.stat_messages = [ - '{} of {}'.format(self.current_value, self.max_value), - '{} %'.format(100 * self.current_value // self.max_value), - '', - ' {:6.2f} Iterations per Second'.format(self.current_value / total_seconds), - ' {:6.2f} Seconds per Iteration'.format(total_seconds / (self.current_value if self.current_value else 1)), - '', - '{} Elapsed Time'.format(time_delta_short), - '{} Time Remaining'.format(time_remaining_short), - '{} Estimated Total Time'.format(total_time_short), - ] - return self.stat_messages - - -def OneLineProgressMeter( - title, - current_value, - max_value, - key='OK for 1 meter', - *args, - orientation='v', - bar_color=(None, None), - button_color=None, - size=DEFAULT_PROGRESS_BAR_SIZE, - border_width=None, - grab_anywhere=False, -): - if key not in QuickMeter.active_meters: - meter = QuickMeter( - title, - current_value, - max_value, - key, - *args, - orientation=orientation, - bar_color=bar_color, - button_color=button_color, - size=size, - border_width=border_width, - grab_anywhere=grab_anywhere, - ) - QuickMeter.active_meters[key] = meter - else: - meter = QuickMeter.active_meters[key] - - rc = meter.UpdateMeter(current_value, max_value, *args) - OneLineProgressMeter.exit_reasons = getattr(OneLineProgressMeter, 'exit_reasons', QuickMeter.exit_reasons) - return rc == METER_OK - - -def OneLineProgressMeterCancel(key='OK for 1 meter'): - try: - meter = QuickMeter.active_meters[key] - meter.window.Close() - del QuickMeter.active_meters[key] - QuickMeter.exit_reasons[key] = METER_REASON_CANCELLED - except: # meter is already deleted - return - - -# input is #RRGGBB -# output is #RRGGBB -def GetComplimentaryHex(color): - # strip the # from the beginning - color = color[1:] - # convert the string into hex - color = int(color, 16) - # invert the three bytes - # as good as substracting each of RGB component by 255(FF) - comp_color = 0xFFFFFF ^ color - # convert the color back to hex by prefixing a # - comp_color = '#%06X' % comp_color - return comp_color - - -# ======================== EasyPrint =====# -# ===================================================# - - -class DebugWin: - debug_window = None - - def __init__( - self, - size=(None, None), - location=(None, None), - font=None, - no_titlebar=False, - no_button=False, - grab_anywhere=False, - keep_on_top=False, - title=None, - do_not_reroute_stdout=False, - ): - # Show a form that's a running counter - self.size = size - self.location = location - self.font = font - self.no_titlebar = no_titlebar - self.no_button = no_button - self.grab_anywhere = grab_anywhere - self.keep_on_top = keep_on_top - self.do_not_reroute_stdout = do_not_reroute_stdout - - win_size = size if size != (None, None) else DEFAULT_DEBUG_WINDOW_SIZE - self.window = Window( - title=title or 'Debug Window', - no_titlebar=no_titlebar, - auto_size_text=True, - location=location, - font=font or ('Courier New', 10), - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - ) - self.output_element = MultilineOutput(size=win_size, key='_MULTILINE_') if do_not_reroute_stdout else Output(size=win_size) - - if no_button: - self.layout = [[self.output_element]] - else: - self.layout = [[self.output_element], [DummyButton('Quit'), Stretch()]] - self.window.AddRows(self.layout) - self.window.Read(timeout=0) # Show a non-blocking form, returns immediately - Window.active_popups[self.window] = 'debug window' - return - - def Print(self, *args, end=None, sep=None): - sepchar = sep if sep is not None else ' ' - endchar = end if end is not None else '\n' - - if self.window is None: # if window was destroyed already, just print - self.__init__( - size=self.size, - location=self.location, - font=self.font, - no_titlebar=self.no_titlebar, - no_button=self.no_button, - grab_anywhere=self.grab_anywhere, - keep_on_top=self.keep_on_top, - do_not_reroute_stdout=self.do_not_reroute_stdout, - ) - event, values = self.window.Read(timeout=0) - if event == 'Quit' or event is None: - self.Close() - self.__init__( - size=self.size, - location=self.location, - font=self.font, - no_titlebar=self.no_titlebar, - no_button=self.no_button, - grab_anywhere=self.grab_anywhere, - keep_on_top=self.keep_on_top, - do_not_reroute_stdout=self.do_not_reroute_stdout, - ) - if self.do_not_reroute_stdout: - outstring = '' - for arg in args: - outstring += str(arg) + sepchar - outstring += endchar - self.output_element.Update(outstring, append=True) - else: - print(*args, sep=sepchar, end=endchar) - - def Close(self): - self.window.Close() - self.window = None - - -def PrintClose(): - EasyPrintClose() - - -def EasyPrint( - *args, - size=(None, None), - end=None, - sep=None, - location=(None, None), - font=None, - no_titlebar=False, - no_button=False, - grab_anywhere=False, - keep_on_top=False, - do_not_reroute_stdout=True, -): - - if DebugWin.debug_window is None: - DebugWin.debug_window = DebugWin( - size=size, - location=location, - font=font, - no_titlebar=no_titlebar, - no_button=no_button, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - do_not_reroute_stdout=do_not_reroute_stdout, - ) - DebugWin.debug_window.Print(*args, end=end, sep=sep) - - -Print = EasyPrint -eprint = EasyPrint - - -def EasyPrintClose(): - if DebugWin.debug_window is not None: - DebugWin.debug_window.Close() - DebugWin.debug_window = None - - -# ======================== Scrolled Text Box =====# -# ===================================================# -def PopupScrolled(*args, button_color=None, yes_no=False, auto_close=False, auto_close_duration=None, size=(None, None)): - if not args: - return - width, height = size - width = width if width else MESSAGE_BOX_LINE_WIDTH - form = Window( - args[0], - auto_size_text=True, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - ) - max_line_total, max_line_width, total_lines, height_computed = 0, 0, 0, 0 - complete_output = '' - for message in args: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = min(longest_line_len, width) - max_line_total = max(max_line_total, width_used) - max_line_width = width - lines_needed = _GetNumLinesNeeded(message, width_used) - height_computed += lines_needed - complete_output += message + '\n' - total_lines += lines_needed - height_computed = MAX_SCROLLED_TEXT_BOX_HEIGHT if height_computed > MAX_SCROLLED_TEXT_BOX_HEIGHT else height_computed - if height: - height_computed = height - form.AddRow(Multiline(complete_output, size=(max_line_width, height_computed))) - pad = max_line_total - 15 if max_line_total > 15 else 1 - # show either an OK or Yes/No depending on paramater - if yes_no: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Yes(), No()) - button, values = form.Read() - return button - else: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Button('OK', size=(5, 1), button_color=button_color)) - button, values = form.Read() - return button - - -ScrolledTextBox = PopupScrolled - - -# ============================== SetGlobalIcon ======# -# Sets the icon to be used by default # -# ===================================================# -def SetGlobalIcon(icon): - if icon is not None: - Window.user_defined_icon = icon - return True - - -# ============================== SetOptions =========# -# Sets the icon to be used by default # -# ===================================================# -def SetOptions( - icon=None, - button_color=None, - element_size=(None, None), - button_element_size=(None, None), - margins=(None, None), - element_padding=(None, None), - auto_size_text=None, - auto_size_buttons=None, - font=None, - border_width=None, - slider_border_width=None, - slider_relief=None, - slider_orientation=None, - autoclose_time=None, - message_box_line_width=None, - progress_meter_border_depth=None, - progress_meter_style=None, - progress_meter_relief=None, - progress_meter_color=None, - progress_meter_size=None, - text_justification=None, - background_color=None, - element_background_color=None, - text_element_background_color=None, - input_elements_background_color=None, - input_text_color=None, - scrollbar_color=None, - text_color=None, - element_text_color=None, - debug_win_size=(None, None), - window_location=(None, None), - tooltip_time=None, -): - global DEFAULT_ELEMENT_SIZE - global DEFAULT_BUTTON_ELEMENT_SIZE - global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term - global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels - global DEFAULT_AUTOSIZE_TEXT - global DEFAULT_AUTOSIZE_BUTTONS - global DEFAULT_FONT - global DEFAULT_BORDER_WIDTH - global DEFAULT_AUTOCLOSE_TIME - global DEFAULT_BUTTON_COLOR - global MESSAGE_BOX_LINE_WIDTH - global DEFAULT_PROGRESS_BAR_BORDER_WIDTH - global DEFAULT_PROGRESS_BAR_STYLE - global DEFAULT_PROGRESS_BAR_RELIEF - global DEFAULT_PROGRESS_BAR_COLOR - global DEFAULT_PROGRESS_BAR_SIZE - global DEFAULT_TEXT_JUSTIFICATION - global DEFAULT_DEBUG_WINDOW_SIZE - global DEFAULT_SLIDER_BORDER_WIDTH - global DEFAULT_SLIDER_RELIEF - global DEFAULT_SLIDER_ORIENTATION - global DEFAULT_BACKGROUND_COLOR - global DEFAULT_INPUT_ELEMENTS_COLOR - global DEFAULT_ELEMENT_BACKGROUND_COLOR - global DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - global DEFAULT_SCROLLBAR_COLOR - global DEFAULT_TEXT_COLOR - global DEFAULT_WINDOW_LOCATION - global DEFAULT_ELEMENT_TEXT_COLOR - global DEFAULT_INPUT_TEXT_COLOR - global DEFAULT_TOOLTIP_TIME - - if icon is not None: - Window.user_defined_icon = icon - - if button_color != None: - DEFAULT_BUTTON_COLOR = button_color - - if element_size != (None, None): - DEFAULT_ELEMENT_SIZE = element_size - - if button_element_size != (None, None): - DEFAULT_BUTTON_ELEMENT_SIZE = button_element_size - - if margins != (None, None): - DEFAULT_MARGINS = margins - - if element_padding != (None, None): - DEFAULT_ELEMENT_PADDING = element_padding - - if auto_size_text != None: - DEFAULT_AUTOSIZE_TEXT = auto_size_text - - if auto_size_buttons != None: - DEFAULT_AUTOSIZE_BUTTONS = auto_size_buttons - - if font != None: - DEFAULT_FONT = font - - if border_width != None: - DEFAULT_BORDER_WIDTH = border_width - - if autoclose_time != None: - DEFAULT_AUTOCLOSE_TIME = autoclose_time - - if message_box_line_width != None: - MESSAGE_BOX_LINE_WIDTH = message_box_line_width - - if progress_meter_border_depth != None: - DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth - - if progress_meter_style != None: - DEFAULT_PROGRESS_BAR_STYLE = progress_meter_style - - if progress_meter_relief != None: - DEFAULT_PROGRESS_BAR_RELIEF = progress_meter_relief - - if progress_meter_color != None: - DEFAULT_PROGRESS_BAR_COLOR = progress_meter_color - - if progress_meter_size != None: - DEFAULT_PROGRESS_BAR_SIZE = progress_meter_size - - if slider_border_width != None: - DEFAULT_SLIDER_BORDER_WIDTH = slider_border_width - - if slider_orientation != None: - DEFAULT_SLIDER_ORIENTATION = slider_orientation - - if slider_relief != None: - DEFAULT_SLIDER_RELIEF = slider_relief - - if text_justification != None: - DEFAULT_TEXT_JUSTIFICATION = text_justification - - if background_color != None: - DEFAULT_BACKGROUND_COLOR = background_color - - if text_element_background_color != None: - DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = text_element_background_color - - if input_elements_background_color != None: - DEFAULT_INPUT_ELEMENTS_COLOR = input_elements_background_color - - if element_background_color != None: - DEFAULT_ELEMENT_BACKGROUND_COLOR = element_background_color - - if window_location != (None, None): - DEFAULT_WINDOW_LOCATION = window_location - - if debug_win_size != (None, None): - DEFAULT_DEBUG_WINDOW_SIZE = debug_win_size - - if text_color != None: - DEFAULT_TEXT_COLOR = text_color - - if scrollbar_color != None: - DEFAULT_SCROLLBAR_COLOR = scrollbar_color - - if element_text_color != None: - DEFAULT_ELEMENT_TEXT_COLOR = element_text_color - - if input_text_color is not None: - DEFAULT_INPUT_TEXT_COLOR = input_text_color - - if tooltip_time is not None: - DEFAULT_TOOLTIP_TIME = tooltip_time - - return True - - -# ----------------------------------------------------------------- # - -# .########.##.....##.########.##.....##.########..######. -# ....##....##.....##.##.......###...###.##.......##....## -# ....##....##.....##.##.......####.####.##.......##...... -# ....##....#########.######...##.###.##.######....######. -# ....##....##.....##.##.......##.....##.##.............## -# ....##....##.....##.##.......##.....##.##.......##....## -# ....##....##.....##.########.##.....##.########..######. - -# ----------------------------------------------------------------- # - -# The official Theme code - -#################### ChangeLookAndFeel ####################### -# Predefined settings that will change the colors and styles # -# of the elements. # -############################################################## -LOOK_AND_FEEL_TABLE = { - 'SystemDefault': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'SystemDefaultForReal': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': COLOR_SYSTEM_DEFAULT, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'SystemDefault1': { - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': COLOR_SYSTEM_DEFAULT, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'Material1': { - 'BACKGROUND': '#E3F2FD', - 'TEXT': '#000000', - 'INPUT': '#86A8FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#86A8FF', - 'BUTTON': ('#FFFFFF', '#5079D3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'Material2': { - 'BACKGROUND': '#FAFAFA', - 'TEXT': '#000000', - 'INPUT': '#004EA1', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#5EA7FF', - 'BUTTON': ('#FFFFFF', '#0079D3'), # based on Reddit color - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'Reddit': { - 'BACKGROUND': '#ffffff', - 'TEXT': '#1a1a1b', - 'INPUT': '#dae0e6', - 'TEXT_INPUT': '#222222', - 'SCROLL': '#a5a4a4', - 'BUTTON': ('white', '#0079d3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#ff5414', - 'ACCENT2': '#33a8ff', - 'ACCENT3': '#dbf0ff', - }, - 'Topanga': { - 'BACKGROUND': '#282923', - 'TEXT': '#E7DB74', - 'INPUT': '#393a32', - 'TEXT_INPUT': '#E7C855', - 'SCROLL': '#E7C855', - 'BUTTON': ('#E7C855', '#284B5A'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#c15226', - 'ACCENT2': '#7a4d5f', - 'ACCENT3': '#889743', - }, - 'GreenTan': { - 'BACKGROUND': '#9FB8AD', - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': '#F7F3EC', - 'TEXT_INPUT': 'black', - 'SCROLL': '#F7F3EC', - 'BUTTON': ('white', '#475841'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Dark': { - 'BACKGROUND': '#404040', - 'TEXT': 'white', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': 'white', - 'SCROLL': '#707070', - 'BUTTON': ('white', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen': { - 'BACKGROUND': '#B7CECE', - 'TEXT': 'black', - 'INPUT': '#FDFFF7', - 'TEXT_INPUT': 'black', - 'SCROLL': '#FDFFF7', - 'BUTTON': ('white', '#658268'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'ACCENT1': '#76506d', - 'ACCENT2': '#5148f1', - 'ACCENT3': '#0a1c84', - 'PROGRESS_DEPTH': 0, - }, - 'Dark2': { - 'BACKGROUND': '#404040', - 'TEXT': 'white', - 'INPUT': 'white', - 'TEXT_INPUT': 'black', - 'SCROLL': '#707070', - 'BUTTON': ('white', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Black': { - 'BACKGROUND': 'black', - 'TEXT': 'white', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': 'white', - 'SCROLL': '#707070', - 'BUTTON': ('black', 'white'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Tan': { - 'BACKGROUND': '#fdf6e3', - 'TEXT': '#268bd1', - 'INPUT': '#eee8d5', - 'TEXT_INPUT': '#6c71c3', - 'SCROLL': '#eee8d5', - 'BUTTON': ('white', '#063542'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'TanBlue': { - 'BACKGROUND': '#e5dece', - 'TEXT': '#063289', - 'INPUT': '#f9f8f4', - 'TEXT_INPUT': '#242834', - 'SCROLL': '#eee8d5', - 'BUTTON': ('white', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkTanBlue': { - 'BACKGROUND': '#242834', - 'TEXT': '#dfe6f8', - 'INPUT': '#97755c', - 'TEXT_INPUT': 'white', - 'SCROLL': '#a9afbb', - 'BUTTON': ('white', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkAmber': { - 'BACKGROUND': '#2c2825', - 'TEXT': '#fdcb52', - 'INPUT': '#705e52', - 'TEXT_INPUT': '#fdcb52', - 'SCROLL': '#705e52', - 'BUTTON': ('black', '#fdcb52'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue': { - 'BACKGROUND': '#1a2835', - 'TEXT': '#d1ecff', - 'INPUT': '#335267', - 'TEXT_INPUT': '#acc2d0', - 'SCROLL': '#1b6497', - 'BUTTON': ('black', '#fafaf8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Reds': { - 'BACKGROUND': '#280001', - 'TEXT': 'white', - 'INPUT': '#d8d584', - 'TEXT_INPUT': 'black', - 'SCROLL': '#763e00', - 'BUTTON': ('black', '#daad28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Green': { - 'BACKGROUND': '#82a459', - 'TEXT': 'black', - 'INPUT': '#d8d584', - 'TEXT_INPUT': 'black', - 'SCROLL': '#e3ecf3', - 'BUTTON': ('white', '#517239'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BluePurple': { - 'BACKGROUND': '#A5CADD', - 'TEXT': '#6E266E', - 'INPUT': '#E0F5FF', - 'TEXT_INPUT': 'black', - 'SCROLL': '#E0F5FF', - 'BUTTON': ('white', '#303952'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Purple': { - 'BACKGROUND': '#B0AAC2', - 'TEXT': 'black', - 'INPUT': '#F2EFE8', - 'SCROLL': '#F2EFE8', - 'TEXT_INPUT': 'black', - 'BUTTON': ('black', '#C2D4D8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BlueMono': { - 'BACKGROUND': '#AAB6D3', - 'TEXT': 'black', - 'INPUT': '#F1F4FC', - 'SCROLL': '#F1F4FC', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#7186C7'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'GreenMono': { - 'BACKGROUND': '#A8C1B4', - 'TEXT': 'black', - 'INPUT': '#DDE0DE', - 'SCROLL': '#E3E3E3', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#6D9F85'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BrownBlue': { - 'BACKGROUND': '#64778d', - 'TEXT': 'white', - 'INPUT': '#f0f3f7', - 'SCROLL': '#A6B2BE', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#283b5b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'BrightColors': { - 'BACKGROUND': '#b4ffb4', - 'TEXT': 'black', - 'INPUT': '#ffff64', - 'SCROLL': '#ffb482', - 'TEXT_INPUT': 'black', - 'BUTTON': ('black', '#ffa0dc'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'NeutralBlue': { - 'BACKGROUND': '#92aa9d', - 'TEXT': 'black', - 'INPUT': '#fcfff6', - 'SCROLL': '#fcfff6', - 'TEXT_INPUT': 'black', - 'BUTTON': ('black', '#d0dbbd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'Kayak': { - 'BACKGROUND': '#a7ad7f', - 'TEXT': 'black', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#5d907d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'SandyBeach': { - 'BACKGROUND': '#efeccb', - 'TEXT': '#012f2f', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#012f2f', - 'BUTTON': ('white', '#046380'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'TealMono': { - 'BACKGROUND': '#a8cfdd', - 'TEXT': 'black', - 'INPUT': '#dfedf2', - 'SCROLL': '#dfedf2', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#183440'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - ################################## Renamed Original Themes ################################## - 'Default': { # plain gray but blue buttons - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'Default1': { # everything is gray - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': COLOR_SYSTEM_DEFAULT, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'DefaultNoMoreNagging': { # a duplicate of "Default" for users that are tired of the nag screen - 'BACKGROUND': COLOR_SYSTEM_DEFAULT, - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': COLOR_SYSTEM_DEFAULT, - 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, - 'SCROLL': COLOR_SYSTEM_DEFAULT, - 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, - 'PROGRESS': COLOR_SYSTEM_DEFAULT, - 'BORDER': 1, - 'SLIDER_DEPTH': 1, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue': { - 'BACKGROUND': '#E3F2FD', - 'TEXT': '#000000', - 'INPUT': '#86A8FF', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#86A8FF', - 'BUTTON': ('#FFFFFF', '#5079D3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'LightGrey': { - 'BACKGROUND': '#FAFAFA', - 'TEXT': '#000000', - 'INPUT': '#004EA1', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#5EA7FF', - 'BUTTON': ('#FFFFFF', '#0079D3'), # based on Reddit color - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 0, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#FF0266', - 'ACCENT2': '#FF5C93', - 'ACCENT3': '#C5003C', - }, - 'LightGrey1': { - 'BACKGROUND': '#ffffff', - 'TEXT': '#1a1a1b', - 'INPUT': '#dae0e6', - 'TEXT_INPUT': '#222222', - 'SCROLL': '#a5a4a4', - 'BUTTON': ('white', '#0079d3'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#ff5414', - 'ACCENT2': '#33a8ff', - 'ACCENT3': '#dbf0ff', - }, - 'DarkBrown': { - 'BACKGROUND': '#282923', - 'TEXT': '#E7DB74', - 'INPUT': '#393a32', - 'TEXT_INPUT': '#E7C855', - 'SCROLL': '#E7C855', - 'BUTTON': ('#E7C855', '#284B5A'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'ACCENT1': '#c15226', - 'ACCENT2': '#7a4d5f', - 'ACCENT3': '#889743', - }, - 'LightGreen1': { - 'BACKGROUND': '#9FB8AD', - 'TEXT': COLOR_SYSTEM_DEFAULT, - 'INPUT': '#F7F3EC', - 'TEXT_INPUT': 'black', - 'SCROLL': '#F7F3EC', - 'BUTTON': ('white', '#475841'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey': { - 'BACKGROUND': '#404040', - 'TEXT': 'white', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': 'white', - 'SCROLL': '#707070', - 'BUTTON': ('white', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen2': { - 'BACKGROUND': '#B7CECE', - 'TEXT': 'black', - 'INPUT': '#FDFFF7', - 'TEXT_INPUT': 'black', - 'SCROLL': '#FDFFF7', - 'BUTTON': ('white', '#658268'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'ACCENT1': '#76506d', - 'ACCENT2': '#5148f1', - 'ACCENT3': '#0a1c84', - 'PROGRESS_DEPTH': 0, - }, - 'DarkGrey1': { - 'BACKGROUND': '#404040', - 'TEXT': 'white', - 'INPUT': 'white', - 'TEXT_INPUT': 'black', - 'SCROLL': '#707070', - 'BUTTON': ('white', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlack': { - 'BACKGROUND': 'black', - 'TEXT': 'white', - 'INPUT': '#4D4D4D', - 'TEXT_INPUT': 'white', - 'SCROLL': '#707070', - 'BUTTON': ('black', 'white'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown': { - 'BACKGROUND': '#fdf6e3', - 'TEXT': '#268bd1', - 'INPUT': '#eee8d5', - 'TEXT_INPUT': '#6c71c3', - 'SCROLL': '#eee8d5', - 'BUTTON': ('white', '#063542'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown1': { - 'BACKGROUND': '#e5dece', - 'TEXT': '#063289', - 'INPUT': '#f9f8f4', - 'TEXT_INPUT': '#242834', - 'SCROLL': '#eee8d5', - 'BUTTON': ('white', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue1': { - 'BACKGROUND': '#242834', - 'TEXT': '#dfe6f8', - 'INPUT': '#97755c', - 'TEXT_INPUT': 'white', - 'SCROLL': '#a9afbb', - 'BUTTON': ('white', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBrown1': { - 'BACKGROUND': '#2c2825', - 'TEXT': '#fdcb52', - 'INPUT': '#705e52', - 'TEXT_INPUT': '#fdcb52', - 'SCROLL': '#705e52', - 'BUTTON': ('black', '#fdcb52'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue2': { - 'BACKGROUND': '#1a2835', - 'TEXT': '#d1ecff', - 'INPUT': '#335267', - 'TEXT_INPUT': '#acc2d0', - 'SCROLL': '#1b6497', - 'BUTTON': ('black', '#fafaf8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBrown2': { - 'BACKGROUND': '#280001', - 'TEXT': 'white', - 'INPUT': '#d8d584', - 'TEXT_INPUT': 'black', - 'SCROLL': '#763e00', - 'BUTTON': ('black', '#daad28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkGreen': { - 'BACKGROUND': '#82a459', - 'TEXT': 'black', - 'INPUT': '#d8d584', - 'TEXT_INPUT': 'black', - 'SCROLL': '#e3ecf3', - 'BUTTON': ('white', '#517239'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue1': { - 'BACKGROUND': '#A5CADD', - 'TEXT': '#6E266E', - 'INPUT': '#E0F5FF', - 'TEXT_INPUT': 'black', - 'SCROLL': '#E0F5FF', - 'BUTTON': ('white', '#303952'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightPurple': { - 'BACKGROUND': '#B0AAC2', - 'TEXT': 'black', - 'INPUT': '#F2EFE8', - 'SCROLL': '#F2EFE8', - 'TEXT_INPUT': 'black', - 'BUTTON': ('black', '#C2D4D8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue2': { - 'BACKGROUND': '#AAB6D3', - 'TEXT': 'black', - 'INPUT': '#F1F4FC', - 'SCROLL': '#F1F4FC', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#7186C7'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen3': { - 'BACKGROUND': '#A8C1B4', - 'TEXT': 'black', - 'INPUT': '#DDE0DE', - 'SCROLL': '#E3E3E3', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#6D9F85'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'DarkBlue3': { - 'BACKGROUND': '#64778d', - 'TEXT': 'white', - 'INPUT': '#f0f3f7', - 'SCROLL': '#A6B2BE', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#283b5b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen4': { - 'BACKGROUND': '#b4ffb4', - 'TEXT': 'black', - 'INPUT': '#ffff64', - 'SCROLL': '#ffb482', - 'TEXT_INPUT': 'black', - 'BUTTON': ('black', '#ffa0dc'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightGreen5': { - 'BACKGROUND': '#92aa9d', - 'TEXT': 'black', - 'INPUT': '#fcfff6', - 'SCROLL': '#fcfff6', - 'TEXT_INPUT': 'black', - 'BUTTON': ('black', '#d0dbbd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown2': { - 'BACKGROUND': '#a7ad7f', - 'TEXT': 'black', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#5d907d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBrown3': { - 'BACKGROUND': '#efeccb', - 'TEXT': '#012f2f', - 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', - 'TEXT_INPUT': '#012f2f', - 'BUTTON': ('white', '#046380'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - 'LightBlue3': { - 'BACKGROUND': '#a8cfdd', - 'TEXT': 'black', - 'INPUT': '#dfedf2', - 'SCROLL': '#dfedf2', - 'TEXT_INPUT': 'black', - 'BUTTON': ('white', '#183440'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, - ################################## End Renamed Original Themes ################################## - # - 'LightBrown4': { - 'BACKGROUND': '#d7c79e', - 'TEXT': '#a35638', - 'INPUT': '#9dab86', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#a35638', - 'BUTTON': ('white', '#a35638'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#a35638', '#9dab86', '#e08f62', '#d7c79e'], - }, - 'DarkTeal': { - 'BACKGROUND': '#003f5c', - 'TEXT': '#fb5b5a', - 'INPUT': '#bc4873', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#bc4873', - 'BUTTON': ('white', '#fb5b5a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], - }, - 'DarkPurple': { - 'BACKGROUND': '#472b62', - 'TEXT': '#fb5b5a', - 'INPUT': '#bc4873', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#bc4873', - 'BUTTON': ('#FFFFFF', '#472b62'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], - }, - 'LightGreen6': { - 'BACKGROUND': '#eafbea', - 'TEXT': '#1f6650', - 'INPUT': '#6f9a8d', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#1f6650', - 'BUTTON': ('white', '#1f6650'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#1f6650', '#6f9a8d', '#ea5e5e', '#eafbea'], - }, - 'DarkGrey2': { - 'BACKGROUND': '#2b2b28', - 'TEXT': '#f8f8f8', - 'INPUT': '#f1d6ab', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f1d6ab', - 'BUTTON': ('#2b2b28', '#e3b04b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#2b2b28', '#e3b04b', '#f1d6ab', '#f8f8f8'], - }, - 'LightBrown6': { - 'BACKGROUND': '#f9b282', - 'TEXT': '#8f4426', - 'INPUT': '#de6b35', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#8f4426', - 'BUTTON': ('white', '#8f4426'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#8f4426', '#de6b35', '#64ccda', '#f9b282'], - }, - 'DarkTeal1': { - 'BACKGROUND': '#396362', - 'TEXT': '#ffe7d1', - 'INPUT': '#f6c89f', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f6c89f', - 'BUTTON': ('#ffe7d1', '#4b8e8d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], - }, - 'LightBrown7': { - 'BACKGROUND': '#f6c89f', - 'TEXT': '#396362', - 'INPUT': '#4b8e8d', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#396362', - 'BUTTON': ('white', '#396362'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], - }, - 'DarkPurple1': { - 'BACKGROUND': '#0c093c', - 'TEXT': '#fad6d6', - 'INPUT': '#eea5f6', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#eea5f6', - 'BUTTON': ('#FFFFFF', '#df42d1'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#0c093c', '#df42d1', '#eea5f6', '#fad6d6'], - }, - 'DarkGrey3': { - 'BACKGROUND': '#211717', - 'TEXT': '#dfddc7', - 'INPUT': '#f58b54', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f58b54', - 'BUTTON': ('#dfddc7', '#a34a28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], - }, - 'LightBrown8': { - 'BACKGROUND': '#dfddc7', - 'TEXT': '#211717', - 'INPUT': '#a34a28', - 'TEXT_INPUT': '#dfddc7', - 'SCROLL': '#211717', - 'BUTTON': ('#dfddc7', '#a34a28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], - }, - 'DarkBlue4': { - 'BACKGROUND': '#494ca2', - 'TEXT': '#e3e7f1', - 'INPUT': '#c6cbef', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#c6cbef', - 'BUTTON': ('#FFFFFF', '#8186d5'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#494ca2', '#8186d5', '#c6cbef', '#e3e7f1'], - }, - 'LightBlue4': { - 'BACKGROUND': '#5c94bd', - 'TEXT': '#470938', - 'INPUT': '#1a3e59', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#470938', - 'BUTTON': ('white', '#470938'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#470938', '#1a3e59', '#5c94bd', '#f2d6eb'], - }, - 'DarkTeal2': { - 'BACKGROUND': '#394a6d', - 'TEXT': '#c0ffb3', - 'INPUT': '#52de97', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#52de97', - 'BUTTON': ('#c0ffb3', '#394a6d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], - }, - 'DarkTeal3': { - 'BACKGROUND': '#3c9d9b', - 'TEXT': '#c0ffb3', - 'INPUT': '#52de97', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#52de97', - 'BUTTON': ('#c0ffb3', '#394a6d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], - }, - 'DarkPurple5': { - 'BACKGROUND': '#730068', - 'TEXT': '#f6f078', - 'INPUT': '#01d28e', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#01d28e', - 'BUTTON': ('#f6f078', '#730068'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#730068', '#434982', '#01d28e', '#f6f078'], - }, - 'DarkPurple2': { - 'BACKGROUND': '#202060', - 'TEXT': '#b030b0', - 'INPUT': '#602080', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#602080', - 'BUTTON': ('white', '#202040'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#202040', '#202060', '#602080', '#b030b0'], - }, - 'DarkBlue5': { - 'BACKGROUND': '#000272', - 'TEXT': '#ff6363', - 'INPUT': '#a32f80', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#a32f80', - 'BUTTON': ('#FFFFFF', '#341677'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#000272', '#341677', '#a32f80', '#ff6363'], - }, - 'LightGrey2': { - 'BACKGROUND': '#f6f6f6', - 'TEXT': '#420000', - 'INPUT': '#d4d7dd', - 'TEXT_INPUT': '#420000', - 'SCROLL': '#420000', - 'BUTTON': ('#420000', '#d4d7dd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], - }, - 'LightGrey3': { - 'BACKGROUND': '#eae9e9', - 'TEXT': '#420000', - 'INPUT': '#d4d7dd', - 'TEXT_INPUT': '#420000', - 'SCROLL': '#420000', - 'BUTTON': ('#420000', '#d4d7dd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], - }, - 'DarkBlue6': { - 'BACKGROUND': '#01024e', - 'TEXT': '#ff6464', - 'INPUT': '#8b4367', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#8b4367', - 'BUTTON': ('#FFFFFF', '#543864'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#01024e', '#543864', '#8b4367', '#ff6464'], - }, - 'DarkBlue7': { - 'BACKGROUND': '#241663', - 'TEXT': '#eae7af', - 'INPUT': '#a72693', - 'TEXT_INPUT': '#eae7af', - 'SCROLL': '#a72693', - 'BUTTON': ('#eae7af', '#160f30'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#160f30', '#241663', '#a72693', '#eae7af'], - }, - 'LightBrown9': { - 'BACKGROUND': '#f6d365', - 'TEXT': '#3a1f5d', - 'INPUT': '#c83660', - 'TEXT_INPUT': '#f6d365', - 'SCROLL': '#3a1f5d', - 'BUTTON': ('#f6d365', '#c83660'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3a1f5d', '#c83660', '#e15249', '#f6d365'], - }, - 'DarkPurple3': { - 'BACKGROUND': '#6e2142', - 'TEXT': '#ffd692', - 'INPUT': '#e16363', - 'TEXT_INPUT': '#ffd692', - 'SCROLL': '#e16363', - 'BUTTON': ('#ffd692', '#943855'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], - }, - 'LightBrown10': { - 'BACKGROUND': '#ffd692', - 'TEXT': '#6e2142', - 'INPUT': '#943855', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#6e2142', - 'BUTTON': ('white', '#6e2142'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], - }, - 'DarkPurple4': { - 'BACKGROUND': '#200f21', - 'TEXT': '#f638dc', - 'INPUT': '#5a3d5c', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#5a3d5c', - 'BUTTON': ('#FFFFFF', '#382039'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#200f21', '#382039', '#5a3d5c', '#f638dc'], - }, - 'LightBlue5': { - 'BACKGROUND': '#b2fcff', - 'TEXT': '#3e64ff', - 'INPUT': '#5edfff', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#3e64ff', - 'BUTTON': ('white', '#3e64ff'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3e64ff', '#5edfff', '#b2fcff', '#ecfcff'], - }, - 'DarkTeal4': { - 'BACKGROUND': '#464159', - 'TEXT': '#c7f0db', - 'INPUT': '#8bbabb', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#8bbabb', - 'BUTTON': ('#FFFFFF', '#6c7b95'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], - }, - 'LightTeal': { - 'BACKGROUND': '#c7f0db', - 'TEXT': '#464159', - 'INPUT': '#6c7b95', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#464159', - 'BUTTON': ('white', '#464159'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], - }, - 'DarkTeal5': { - 'BACKGROUND': '#8bbabb', - 'TEXT': '#464159', - 'INPUT': '#6c7b95', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#464159', - 'BUTTON': ('#c7f0db', '#6c7b95'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], - }, - 'LightGrey4': { - 'BACKGROUND': '#faf5ef', - 'TEXT': '#672f2f', - 'INPUT': '#99b19c', - 'TEXT_INPUT': '#672f2f', - 'SCROLL': '#672f2f', - 'BUTTON': ('#672f2f', '#99b19c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], - }, - 'LightGreen7': { - 'BACKGROUND': '#99b19c', - 'TEXT': '#faf5ef', - 'INPUT': '#d7d1c9', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#d7d1c9', - 'BUTTON': ('#FFFFFF', '#99b19c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], - }, - 'LightGrey5': { - 'BACKGROUND': '#d7d1c9', - 'TEXT': '#672f2f', - 'INPUT': '#99b19c', - 'TEXT_INPUT': '#672f2f', - 'SCROLL': '#672f2f', - 'BUTTON': ('white', '#672f2f'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], - }, - 'DarkBrown3': { - 'BACKGROUND': '#a0855b', - 'TEXT': '#f9f6f2', - 'INPUT': '#f1d6ab', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f1d6ab', - 'BUTTON': ('white', '#38470b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], - }, - 'LightBrown11': { - 'BACKGROUND': '#f1d6ab', - 'TEXT': '#38470b', - 'INPUT': '#a0855b', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#38470b', - 'BUTTON': ('#f9f6f2', '#a0855b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], - }, - 'DarkRed': { - 'BACKGROUND': '#83142c', - 'TEXT': '#f9d276', - 'INPUT': '#ad1d45', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#ad1d45', - 'BUTTON': ('#f9d276', '#ad1d45'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#44000d', '#83142c', '#ad1d45', '#f9d276'], - }, - 'DarkTeal6': { - 'BACKGROUND': '#204969', - 'TEXT': '#fff7f7', - 'INPUT': '#dadada', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#dadada', - 'BUTTON': ('black', '#fff7f7'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#204969', '#08ffc8', '#dadada', '#fff7f7'], - }, - 'DarkBrown4': { - 'BACKGROUND': '#252525', - 'TEXT': '#ff0000', - 'INPUT': '#af0404', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#af0404', - 'BUTTON': ('white', '#252525'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#252525', '#414141', '#af0404', '#ff0000'], - }, - 'LightYellow': { - 'BACKGROUND': '#f4ff61', - 'TEXT': '#27aa80', - 'INPUT': '#32ff6a', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#27aa80', - 'BUTTON': ('#f4ff61', '#27aa80'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#27aa80', '#32ff6a', '#a8ff3e', '#f4ff61'], - }, - 'DarkGreen1': { - 'BACKGROUND': '#2b580c', - 'TEXT': '#fdef96', - 'INPUT': '#f7b71d', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f7b71d', - 'BUTTON': ('#fdef96', '#2b580c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#2b580c', '#afa939', '#f7b71d', '#fdef96'], - }, - 'LightGreen8': { - 'BACKGROUND': '#c8dad3', - 'TEXT': '#63707e', - 'INPUT': '#93b5b3', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#63707e', - 'BUTTON': ('white', '#63707e'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#63707e', '#93b5b3', '#c8dad3', '#f2f6f5'], - }, - 'DarkTeal7': { - 'BACKGROUND': '#248ea9', - 'TEXT': '#fafdcb', - 'INPUT': '#aee7e8', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#aee7e8', - 'BUTTON': ('black', '#fafdcb'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#248ea9', '#28c3d4', '#aee7e8', '#fafdcb'], - }, - 'DarkBlue8': { - 'BACKGROUND': '#454d66', - 'TEXT': '#d9d872', - 'INPUT': '#58b368', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#58b368', - 'BUTTON': ('black', '#009975'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#009975', '#454d66', '#58b368', '#d9d872'], - }, - 'DarkBlue9': { - 'BACKGROUND': '#263859', - 'TEXT': '#ff6768', - 'INPUT': '#6b778d', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#6b778d', - 'BUTTON': ('#ff6768', '#263859'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#17223b', '#263859', '#6b778d', '#ff6768'], - }, - 'DarkBlue10': { - 'BACKGROUND': '#0028ff', - 'TEXT': '#f1f4df', - 'INPUT': '#10eaf0', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#10eaf0', - 'BUTTON': ('#f1f4df', '#24009c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#24009c', '#0028ff', '#10eaf0', '#f1f4df'], - }, - 'DarkBlue11': { - 'BACKGROUND': '#6384b3', - 'TEXT': '#e6f0b6', - 'INPUT': '#b8e9c0', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#b8e9c0', - 'BUTTON': ('#e6f0b6', '#684949'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#684949', '#6384b3', '#b8e9c0', '#e6f0b6'], - }, - 'DarkTeal8': { - 'BACKGROUND': '#71a0a5', - 'TEXT': '#212121', - 'INPUT': '#665c84', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#212121', - 'BUTTON': ('#fab95b', '#665c84'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#212121', '#665c84', '#71a0a5', '#fab95b'], - }, - 'DarkRed1': { - 'BACKGROUND': '#c10000', - 'TEXT': '#eeeeee', - 'INPUT': '#dedede', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#dedede', - 'BUTTON': ('#c10000', '#eeeeee'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#c10000', '#ff4949', '#dedede', '#eeeeee'], - }, - 'LightBrown5': { - 'BACKGROUND': '#fff591', - 'TEXT': '#e41749', - 'INPUT': '#f5587b', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e41749', - 'BUTTON': ('#fff591', '#e41749'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#e41749', '#f5587b', '#ff8a5c', '#fff591'], - }, - 'LightGreen9': { - 'BACKGROUND': '#f1edb3', - 'TEXT': '#3b503d', - 'INPUT': '#4a746e', - 'TEXT_INPUT': '#f1edb3', - 'SCROLL': '#3b503d', - 'BUTTON': ('#f1edb3', '#3b503d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], - 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], - }, - 'DarkGreen2': { - 'BACKGROUND': '#3b503d', - 'TEXT': '#f1edb3', - 'INPUT': '#c8cf94', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#c8cf94', - 'BUTTON': ('#f1edb3', '#3b503d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], - 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], - }, - 'LightGray1': { - 'BACKGROUND': '#f2f2f2', - 'TEXT': '#222831', - 'INPUT': '#393e46', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#222831', - 'BUTTON': ('#f2f2f2', '#222831'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#222831', '#393e46', '#f96d00', '#f2f2f2'], - 'DESCRIPTION': ['Black', 'Grey', 'Orange', 'Grey', 'Autumn'], - }, - 'DarkGrey4': { - 'BACKGROUND': '#52524e', - 'TEXT': '#e9e9e5', - 'INPUT': '#d4d6c8', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#d4d6c8', - 'BUTTON': ('#FFFFFF', '#9a9b94'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#52524e', '#9a9b94', '#d4d6c8', '#e9e9e5'], - 'DESCRIPTION': ['Grey', 'Pastel', 'Winter'], - }, - 'DarkBlue12': { - 'BACKGROUND': '#324e7b', - 'TEXT': '#f8f8f8', - 'INPUT': '#86a6df', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#86a6df', - 'BUTTON': ('#FFFFFF', '#5068a9'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#324e7b', '#5068a9', '#86a6df', '#f8f8f8'], - 'DESCRIPTION': ['Blue', 'Grey', 'Cold', 'Winter'], - }, - 'DarkPurple6': { - 'BACKGROUND': '#070739', - 'TEXT': '#e1e099', - 'INPUT': '#c327ab', - 'TEXT_INPUT': '#e1e099', - 'SCROLL': '#c327ab', - 'BUTTON': ('#e1e099', '#521477'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#070739', '#521477', '#c327ab', '#e1e099'], - 'DESCRIPTION': ['Black', 'Purple', 'Yellow', 'Dark'], - }, - 'DarkBlue13': { - 'BACKGROUND': '#203562', - 'TEXT': '#e3e8f8', - 'INPUT': '#c0c5cd', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#c0c5cd', - 'BUTTON': ('#FFFFFF', '#3e588f'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#203562', '#3e588f', '#c0c5cd', '#e3e8f8'], - 'DESCRIPTION': ['Blue', 'Grey', 'Wedding', 'Cold'], - }, - 'DarkBrown5': { - 'BACKGROUND': '#3c1b1f', - 'TEXT': '#f6e1b5', - 'INPUT': '#e2bf81', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e2bf81', - 'BUTTON': ('#3c1b1f', '#f6e1b5'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3c1b1f', '#b21e4b', '#e2bf81', '#f6e1b5'], - 'DESCRIPTION': ['Brown', 'Red', 'Yellow', 'Warm'], - }, - 'DarkGreen3': { - 'BACKGROUND': '#062121', - 'TEXT': '#eeeeee', - 'INPUT': '#e4dcad', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e4dcad', - 'BUTTON': ('#eeeeee', '#181810'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], - 'DESCRIPTION': ['Black', 'Black', 'Brown', 'Grey'], - }, - 'DarkBlack1': { - 'BACKGROUND': '#181810', - 'TEXT': '#eeeeee', - 'INPUT': '#e4dcad', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e4dcad', - 'BUTTON': ('white', '#062121'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], - 'DESCRIPTION': ['Black', 'Black', 'Brown', 'Grey'], - }, - 'DarkGrey5': { - 'BACKGROUND': '#343434', - 'TEXT': '#f3f3f3', - 'INPUT': '#e9dcbe', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e9dcbe', - 'BUTTON': ('#FFFFFF', '#8e8b82'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], - 'DESCRIPTION': ['Grey', 'Brown'], - }, - 'LightBrown12': { - 'BACKGROUND': '#8e8b82', - 'TEXT': '#f3f3f3', - 'INPUT': '#e9dcbe', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e9dcbe', - 'BUTTON': ('#f3f3f3', '#8e8b82'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], - 'DESCRIPTION': ['Grey', 'Brown'], - }, - 'DarkTeal9': { - 'BACKGROUND': '#13445a', - 'TEXT': '#fef4e8', - 'INPUT': '#446878', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#446878', - 'BUTTON': ('#fef4e8', '#446878'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#13445a', '#970747', '#446878', '#fef4e8'], - 'DESCRIPTION': ['Red', 'Grey', 'Blue', 'Wedding', 'Retro'], - }, - 'DarkBlue14': { - 'BACKGROUND': '#21273d', - 'TEXT': '#f1f6f8', - 'INPUT': '#b9d4f1', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#b9d4f1', - 'BUTTON': ('#FFFFFF', '#6a759b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], - 'DESCRIPTION': ['Blue', 'Black', 'Grey', 'Cold', 'Winter'], - }, - 'LightBlue6': { - 'BACKGROUND': '#f1f6f8', - 'TEXT': '#21273d', - 'INPUT': '#6a759b', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#21273d', - 'BUTTON': ('#f1f6f8', '#6a759b'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], - 'DESCRIPTION': ['Blue', 'Black', 'Grey', 'Cold', 'Winter'], - }, - 'DarkGreen4': { - 'BACKGROUND': '#044343', - 'TEXT': '#e4e4e4', - 'INPUT': '#045757', - 'TEXT_INPUT': '#e4e4e4', - 'SCROLL': '#045757', - 'BUTTON': ('#e4e4e4', '#045757'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#222222', '#044343', '#045757', '#e4e4e4'], - 'DESCRIPTION': ['Black', 'Turquoise', 'Grey', 'Dark'], - }, - 'DarkGreen5': { - 'BACKGROUND': '#1b4b36', - 'TEXT': '#e0e7f1', - 'INPUT': '#aebd77', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#aebd77', - 'BUTTON': ('#FFFFFF', '#538f6a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#1b4b36', '#538f6a', '#aebd77', '#e0e7f1'], - 'DESCRIPTION': ['Green', 'Grey'], - }, - 'DarkTeal10': { - 'BACKGROUND': '#0d3446', - 'TEXT': '#d8dfe2', - 'INPUT': '#71adb5', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#71adb5', - 'BUTTON': ('#FFFFFF', '#176d81'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#0d3446', '#176d81', '#71adb5', '#d8dfe2'], - 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter', 'Cold'], - }, - 'DarkGrey6': { - 'BACKGROUND': '#3e3e3e', - 'TEXT': '#ededed', - 'INPUT': '#68868c', - 'TEXT_INPUT': '#ededed', - 'SCROLL': '#68868c', - 'BUTTON': ('#FFFFFF', '#405559'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], - 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], - }, - 'DarkTeal11': { - 'BACKGROUND': '#405559', - 'TEXT': '#ededed', - 'INPUT': '#68868c', - 'TEXT_INPUT': '#ededed', - 'SCROLL': '#68868c', - 'BUTTON': ('#ededed', '#68868c'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], - 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], - }, - 'LightBlue7': { - 'BACKGROUND': '#9ed0e0', - 'TEXT': '#19483f', - 'INPUT': '#5c868e', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#19483f', - 'BUTTON': ('white', '#19483f'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#19483f', '#5c868e', '#ff6a38', '#9ed0e0'], - 'DESCRIPTION': ['Orange', 'Blue', 'Turquoise'], - }, - 'LightGreen10': { - 'BACKGROUND': '#d8ebb5', - 'TEXT': '#205d67', - 'INPUT': '#639a67', - 'TEXT_INPUT': '#FFFFFF', - 'SCROLL': '#205d67', - 'BUTTON': ('#d8ebb5', '#205d67'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#205d67', '#639a67', '#d9bf77', '#d8ebb5'], - 'DESCRIPTION': ['Blue', 'Green', 'Brown', 'Vintage'], - }, - 'DarkBlue15': { - 'BACKGROUND': '#151680', - 'TEXT': '#f1fea4', - 'INPUT': '#375fc0', - 'TEXT_INPUT': '#f1fea4', - 'SCROLL': '#375fc0', - 'BUTTON': ('#f1fea4', '#1c44ac'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], - 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], - }, - 'DarkBlue16': { - 'BACKGROUND': '#1c44ac', - 'TEXT': '#f1fea4', - 'INPUT': '#375fc0', - 'TEXT_INPUT': '#f1fea4', - 'SCROLL': '#375fc0', - 'BUTTON': ('#f1fea4', '#151680'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], - 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], - }, - 'DarkTeal12': { - 'BACKGROUND': '#004a7c', - 'TEXT': '#fafafa', - 'INPUT': '#e8f1f5', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#e8f1f5', - 'BUTTON': ('#fafafa', '#005691'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#004a7c', '#005691', '#e8f1f5', '#fafafa'], - 'DESCRIPTION': ['Grey', 'Blue', 'Cold', 'Winter'], - }, - 'LightBrown13': { - 'BACKGROUND': '#ebf5ee', - 'TEXT': '#921224', - 'INPUT': '#bdc6b8', - 'TEXT_INPUT': '#921224', - 'SCROLL': '#921224', - 'BUTTON': ('white', '#921224'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#921224', '#bdc6b8', '#bce0da', '#ebf5ee'], - 'DESCRIPTION': ['Red', 'Blue', 'Grey', 'Vintage', 'Wedding'], - }, - 'DarkBlue17': { - 'BACKGROUND': '#21294c', - 'TEXT': '#f9f2d7', - 'INPUT': '#f2dea8', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#f2dea8', - 'BUTTON': ('#f9f2d7', '#141829'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#141829', '#21294c', '#f2dea8', '#f9f2d7'], - 'DESCRIPTION': ['Black', 'Blue', 'Yellow'], - }, - 'DarkBrown6': { - 'BACKGROUND': '#785e4d', - 'TEXT': '#f2eee3', - 'INPUT': '#baaf92', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#baaf92', - 'BUTTON': ('white', '#785e4d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#785e4d', '#ff8426', '#baaf92', '#f2eee3'], - 'DESCRIPTION': ['Grey', 'Brown', 'Orange', 'Autumn'], - }, - 'DarkGreen6': { - 'BACKGROUND': '#5c715e', - 'TEXT': '#f2f9f1', - 'INPUT': '#ddeedf', - 'TEXT_INPUT': '#000000', - 'SCROLL': '#ddeedf', - 'BUTTON': ('#f2f9f1', '#5c715e'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#5c715e', '#b6cdbd', '#ddeedf', '#f2f9f1'], - 'DESCRIPTION': ['Grey', 'Green', 'Vintage'], - }, - 'DarkGrey7': { - 'BACKGROUND': '#4b586e', - 'TEXT': '#dddddd', - 'INPUT': '#574e6d', - 'TEXT_INPUT': '#dddddd', - 'SCROLL': '#574e6d', - 'BUTTON': ('#dddddd', '#43405d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#43405d', '#4b586e', '#574e6d', '#dddddd'], - 'DESCRIPTION': ['Grey', 'Winter', 'Cold'], - }, - 'DarkRed2': { - 'BACKGROUND': '#ab1212', - 'TEXT': '#f6e4b5', - 'INPUT': '#cd3131', - 'TEXT_INPUT': '#f6e4b5', - 'SCROLL': '#cd3131', - 'BUTTON': ('#f6e4b5', '#ab1212'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#ab1212', '#1fad9f', '#cd3131', '#f6e4b5'], - 'DESCRIPTION': ['Turquoise', 'Red', 'Yellow'], - }, - 'LightGrey6': { - 'BACKGROUND': '#e3e3e3', - 'TEXT': '#233142', - 'INPUT': '#455d7a', - 'TEXT_INPUT': '#e3e3e3', - 'SCROLL': '#233142', - 'BUTTON': ('#e3e3e3', '#455d7a'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - 'COLOR_LIST': ['#233142', '#455d7a', '#f95959', '#e3e3e3'], - 'DESCRIPTION': ['Black', 'Blue', 'Red', 'Grey'], - }, - 'HotDogStand': { - 'BACKGROUND': 'red', - 'TEXT': 'yellow', - 'INPUT': 'yellow', - 'TEXT_INPUT': 'black', - 'SCROLL': 'yellow', - 'BUTTON': ('red', 'yellow'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, - 'BORDER': 1, - 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0, - }, -} - - -def ListOfLookAndFeelValues(): - """ - Get a list of the valid values to pass into your call to change_look_and_feel - :return: List[str] - list of valid string values - """ - return sorted(list(LOOK_AND_FEEL_TABLE.keys())) - - -def theme(new_theme=None): - """ - Sets / Gets the current Theme. If none is specified then returns the current theme. - This call replaces the ChangeLookAndFeel / change_look_and_feel call which only sets the theme. - - :param new_theme: (str) the new theme name to use - :return: (str) the currently selected theme - """ - if new_theme is not None: - change_look_and_feel(new_theme) - return CURRENT_LOOK_AND_FEEL - - -def theme_background_color(color=None): - """ - Sets/Returns the background color currently in use - Used for Windows and containers (Column, Frame, Tab) and tables - - :return: (str) - color string of the background color currently in use - """ - if color is not None: - set_options(background_color=color) - return DEFAULT_BACKGROUND_COLOR - - -def theme_element_background_color(color=None): - """ - Sets/Returns the background color currently in use for all elements except containers - - :return: (str) - color string of the element background color currently in use - """ - if color is not None: - set_options(element_background_color=color) - return DEFAULT_ELEMENT_BACKGROUND_COLOR - - -def theme_text_color(color=None): - """ - Sets/Returns the text color currently in use - - :return: (str) - color string of the text color currently in use - """ - if color is not None: - set_options(text_color=color) - return DEFAULT_TEXT_COLOR - - -def theme_input_background_color(color=None): - """ - Sets/Returns the input element background color currently in use - - :return: (str) - color string of the input element background color currently in use - """ - if color is not None: - set_options(input_elements_background_color=color) - return DEFAULT_INPUT_ELEMENTS_COLOR - - -def theme_input_text_color(color=None): - """ - Sets/Returns the input element entry color (not the text but the thing that's displaying the text) - - :return: (str) - color string of the input element color currently in use - """ - if color is not None: - set_options(input_text_color=color) - return DEFAULT_INPUT_TEXT_COLOR - - -def theme_button_color(color=None): - """ - Sets/Returns the button color currently in use - - :return: Tuple[str, str] - TUPLE with color strings of the button color currently in use (button text color, button background color) - """ - if color is not None: - set_options(button_color=color) - return DEFAULT_BUTTON_COLOR - - -def theme_progress_bar_color(color=None): - """ - Sets/Returns the progress bar colors by the current color theme - - :return: Tuple[str, str] - TUPLE with color strings of the ProgressBar color currently in use(button text color, button background color) - """ - if color is not None: - set_options(progress_meter_color=color) - return DEFAULT_PROGRESS_BAR_COLOR - - -def theme_slider_color(color=None): - """ - Sets/Returns the slider color (used for sliders) - - :return: (str) - color string of the slider color currently in use - """ - if color is not None: - set_options(scrollbar_color=color) - return DEFAULT_SCROLLBAR_COLOR - - -def theme_border_width(border_width=None): - """ - Sets/Returns the border width currently in use - Used by non ttk elements at the moment - - :return: (int) - border width currently in use - """ - if border_width is not None: - set_options(border_width=border_width) - return DEFAULT_BORDER_WIDTH - - -def theme_slider_border_width(border_width=None): - """ - Sets/Returns the slider border width currently in use - - :return: (int) - border width currently in use - """ - if border_width is not None: - set_options(slider_border_width=border_width) - return DEFAULT_SLIDER_BORDER_WIDTH - - -def theme_progress_bar_border_width(border_width=None): - """ - Sets/Returns the progress meter border width currently in use - - :return: (int) - border width currently in use - """ - if border_width is not None: - set_options(progress_meter_border_depth=border_width) - return DEFAULT_PROGRESS_BAR_BORDER_WIDTH - - -def theme_element_text_color(color=None): - """ - Sets/Returns the text color used by elements that have text as part of their display (Tables, Trees and Sliders) - - :return: (str) - color string currently in use - """ - if color is not None: - set_options(element_text_color=color) - return DEFAULT_ELEMENT_TEXT_COLOR - - -def theme_list(): - """ - Returns a sorted list of the currently available color themes - - :return: List[str] - A sorted list of the currently available color themes - """ - return list_of_look_and_feel_values() - - -def theme_add_new(new_theme_name, new_theme_dict): - """ - Add a new theme to the dictionary of themes - - :param new_theme_name: text to display in element - :type new_theme_name: (str) - :param new_theme_dict: text to display in element - :type new_theme_dict: (dict) - """ - global LOOK_AND_FEEL_TABLE - try: - LOOK_AND_FEEL_TABLE[new_theme_name] = new_theme_dict - except Exception as e: - print('Exception during adding new theme {}'.format(e)) - - -def theme_previewer(columns=12): - """ - Show a window with all of the color themes - takes a while so be patient - - :param columns: (int) number of themes in a single row - """ - preview_all_look_and_feel_themes(columns) - - -def ChangeLookAndFeel(index, force=False): - """ - Change the "color scheme" of all future PySimpleGUI Windows. - The scheme are string names that specify a group of colors. Background colors, text colors, button colors. - There are 13 different color settings that are changed at one time using a single call to ChangeLookAndFeel - The look and feel table itself has these indexes into the dictionary LOOK_AND_FEEL_TABLE. - The original list was (prior to a major rework and renaming)... these names still work... - In Nov 2019 a new Theme Formula was devised to make choosing a theme easier: - The "Formula" is: - ["Dark" or "Light"] Color Number - Colors can be Blue Brown Grey Green Purple Red Teal Yellow Black - The number will vary for each pair. There are more DarkGrey entries than there are LightYellow for example. - Default = The default settings (only button color is different than system default) - Default1 = The full system default including the button (everything's gray... how sad... don't be all gray... please....) - :param index: (str) the name of the index into the Look and Feel table (does not have to be exact, can be "fuzzy") - :param force: (bool) no longer used - """ - - global CURRENT_LOOK_AND_FEEL - - # if sys.platform.startswith('darwin') and not force: - # print('*** Changing look and feel is not supported on Mac platform ***') - # return - - theme = index - # normalize available l&f values - lf_values = [item.lower() for item in list_of_look_and_feel_values()] - - # option 1 - opt1 = theme.replace(' ', '').lower() - - # option 2 (reverse lookup) - optx = theme.lower().split(' ') - optx.reverse() - opt2 = ''.join(optx) - - # search for valid l&f name - if opt1 in lf_values: - ix = lf_values.index(opt1) - elif opt2 in lf_values: - ix = lf_values.index(opt2) - else: - ix = randint(0, len(lf_values) - 1) - print('** Warning - {} Theme is not a valid theme. Change your theme call. **'.format(index)) - print('valid values are', list_of_look_and_feel_values()) - print('Instead, please enjoy a random Theme named {}'.format(list_of_look_and_feel_values()[ix])) - - selection = list_of_look_and_feel_values()[ix] - CURRENT_LOOK_AND_FEEL = selection - try: - colors = LOOK_AND_FEEL_TABLE[selection] - - # Color the progress bar using button background and input colors...unless they're the same - if colors['PROGRESS'] != COLOR_SYSTEM_DEFAULT: - if colors['BUTTON'][1] != colors['INPUT'] and colors['BUTTON'][1] != colors['BACKGROUND']: - colors['PROGRESS'] = colors['BUTTON'][1], colors['INPUT'] - else: # if the same, then use text input on top of input color - colors['PROGRESS'] = (colors['TEXT_INPUT'], colors['INPUT']) - else: - colors['PROGRESS'] = DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL - # call to change all the colors - SetOptions( - background_color=colors['BACKGROUND'], - text_element_background_color=colors['BACKGROUND'], - element_background_color=colors['BACKGROUND'], - text_color=colors['TEXT'], - input_elements_background_color=colors['INPUT'], - # button_color=colors['BUTTON'] if not sys.platform.startswith('darwin') else None, - button_color=colors['BUTTON'], - progress_meter_color=colors['PROGRESS'], - border_width=colors['BORDER'], - slider_border_width=colors['SLIDER_DEPTH'], - progress_meter_border_depth=colors['PROGRESS_DEPTH'], - scrollbar_color=(colors['SCROLL']), - element_text_color=colors['TEXT'], - input_text_color=colors['TEXT_INPUT'], - ) - except: # most likely an index out of range - print('** Warning - Theme value not valid. Change your theme call. **') - print('valid values are', list_of_look_and_feel_values()) - - -def preview_all_look_and_feel_themes(columns=12): - """ - Displays a "Quick Reference Window" showing all of the different Look and Feel settings that are available. - They are sorted alphabetically. The legacy color names are mixed in, but otherwise they are sorted into Dark and Light halves - :param columns: (int) The number of themes to display per row - """ - - # Show a "splash" type message so the user doesn't give up waiting - popup_quick_message( - 'Hang on for a moment, this will take a bit to create....', - background_color='red', - text_color='white', - auto_close=True, - non_blocking=True, - ) - - web = False - - win_bg = 'black' - - def sample_layout(): - return [ - [Text('Text element'), InputText('Input data here', size=(10, 1))], - [Button('Ok'), Button('Cancel'), Slider((1, 10), orientation='h', size=(5, 15))], - ] - - layout = [[Text('Here is a complete list of themes', font='Default 18', background_color=win_bg)]] - - names = list_of_look_and_feel_values() - names.sort() - row = [] - for count, theme in enumerate(names): - change_look_and_feel(theme) - if not count % columns: - layout += [row] - row = [] - row += [Frame(theme, sample_layout() if not web else [[T(theme)]] + sample_layout())] - if row: - layout += [row] - - window = Window('Preview of all Look and Feel choices', layout, background_color=win_bg) - window.read() - window.close() - - -# ============================== sprint ======# -# Is identical to the Scrolled Text Box # -# Provides a crude 'print' mechanism but in a # -# GUI environment # -# ============================================# -sprint = ScrolledTextBox - - -# Converts an object's contents into a nice printable string. Great for dumping debug data -def ObjToStringSingleObj(obj): - if obj is None: - return 'None' - return str(obj.__class__) + '\n' + '\n'.join((repr(item) + ' = ' + repr(obj.__dict__[item]) for item in sorted(obj.__dict__))) - - -def ObjToString(obj, extra=' '): - if obj is None: - return 'None' - return str(obj.__class__) + '\n' + '\n'.join((extra + (str(item) + ' = ' + (ObjToString(obj.__dict__[item], extra + ' ') if hasattr(obj.__dict__[item], '__dict__') else str(obj.__dict__[item]))) for item in sorted(obj.__dict__))) - - -# ------------------------------------------------------------------------------------------------------------------ # -# ===================================== Upper PySimpleGUI ======================================================== # -# Pre-built dialog boxes for all your needs These are the "high level API calls # -# ------------------------------------------------------------------------------------------------------------------ # - -###### -# # #### ##### # # ##### #### -# # # # # # # # # # # -###### # # # # # # # # #### -# # # ##### # # ##### # -# # # # # # # # # -# #### # #### # #### - - -# ----------------------------------- The mighty Popup! ------------------------------------------------------------ # - - -def Popup( - *args, - title=None, - button_color=None, - background_color=None, - text_color=None, - button_type=POPUP_BUTTONS_OK, - auto_close=False, - auto_close_duration=None, - custom_text=(None, None), - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Popup - Display a popup box with as many parms as you wish to include - :param args: - :param button_color: - :param background_color: - :param text_color: - :param button_type: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - if not args: - args_to_print = [''] - else: - args_to_print = args - if line_width != None: - local_line_width = line_width - else: - local_line_width = MESSAGE_BOX_LINE_WIDTH - _title = title if title is not None else args_to_print[0] - window = Window( - _title, - auto_size_text=True, - background_color=background_color, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - icon=icon, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - max_line_total, total_lines = 0, 0 - for message in args_to_print: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - if message.count('\n'): - message_wrapped = message - else: - message_wrapped = textwrap.fill(message, local_line_width) - message_wrapped_lines = message_wrapped.count('\n') + 1 - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = min(longest_line_len, local_line_width) - max_line_total = max(max_line_total, width_used) - # height = _GetNumLinesNeeded(message, width_used) - height = message_wrapped_lines - window.AddRow(Text(message_wrapped, auto_size_text=True, text_color=text_color, background_color=background_color)) - total_lines += height - - if non_blocking: - PopupButton = DummyButton # important to use or else button will close other windows too! - else: - PopupButton = Button - # show either an OK or Yes/No depending on paramater - if custom_text != (None, None): - if type(custom_text) is not tuple: - window.AddRow(PopupButton(custom_text, size=(len(custom_text), 1), button_color=button_color, focus=True, bind_return_key=True)) - elif custom_text[1] is None: - window.AddRow( - PopupButton( - custom_text[0], - size=(len(custom_text[0]), 1), - button_color=button_color, - focus=True, - bind_return_key=True, - ) - ) - else: - window.AddRow( - PopupButton( - custom_text[0], - button_color=button_color, - focus=True, - bind_return_key=True, - size=(len(custom_text[0]), 1), - ), - PopupButton(custom_text[1], button_color=button_color, size=(len(custom_text[0]), 1)), - ) - elif button_type is POPUP_BUTTONS_YES_NO: - window.AddRow( - PopupButton('Yes', button_color=button_color, focus=True, bind_return_key=True, pad=((20, 5), 3), size=(5, 1)), - PopupButton('No', button_color=button_color, size=(5, 1)), - ) - elif button_type is POPUP_BUTTONS_CANCELLED: - window.AddRow(PopupButton('Cancelled', button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) - elif button_type is POPUP_BUTTONS_ERROR: - window.AddRow(PopupButton('Error', size=(6, 1), button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) - elif button_type is POPUP_BUTTONS_OK_CANCEL: - window.AddRow( - PopupButton('OK', size=(6, 1), button_color=button_color, focus=True, bind_return_key=True), - PopupButton('Cancel', size=(6, 1), button_color=button_color), - ) - elif button_type is POPUP_BUTTONS_NO_BUTTONS: - pass - else: - window.AddRow(PopupButton('OK', size=(5, 1), button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) - - if non_blocking: - button, values = window.Read(timeout=0) - return button, window - else: - button, values = window.Read() - window.Close() - return button - - -# ============================== MsgBox============# -# Lazy function. Same as calling Popup with parms # -# This function WILL Disappear perhaps today # -# ==================================================# -# MsgBox is the legacy call and should not be used any longer -def MsgBox(*args): - raise DeprecationWarning('MsgBox is no longer supported... change your call to Popup') - - -# --------------------------- PopupNoButtons --------------------------- -def PopupNoButtons( - *args, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Show a Popup but without any buttons - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=POPUP_BUTTONS_NO_BUTTONS, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -# --------------------------- PopupNonBlocking --------------------------- -def PopupNonBlocking( - *args, - button_type=POPUP_BUTTONS_OK, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=True, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Show Popup box and immediately return (does not block) - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - return Popup( - *args, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -PopupNoWait = PopupNonBlocking - - -# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup --------------------------- -def PopupQuick( - *args, - button_type=POPUP_BUTTONS_OK, - button_color=None, - background_color=None, - text_color=None, - auto_close=True, - auto_close_duration=2, - non_blocking=True, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Show Popup box that doesn't block and closes itself - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup with no titlebar and no buttons --------------------------- -def PopupQuickMessage( - *args, - button_type=POPUP_BUTTONS_NO_BUTTONS, - button_color=None, - background_color=None, - text_color=None, - auto_close=True, - auto_close_duration=2, - non_blocking=True, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=True, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Show Popup box that doesn't block and closes itself - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -# --------------------------- PopupNoTitlebar --------------------------- -def PopupNoTitlebar( - *args, - button_type=POPUP_BUTTONS_OK, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - grab_anywhere=True, - keep_on_top=False, - location=(None, None), -): - """ - Display a Popup without a titlebar. Enables grab anywhere so you can move it - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=True, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -PopupNoFrame = PopupNoTitlebar -PopupNoBorder = PopupNoTitlebar -PopupAnnoying = PopupNoTitlebar - - -# --------------------------- PopupAutoClose --------------------------- -def PopupAutoClose( - *args, - button_type=POPUP_BUTTONS_OK, - button_color=None, - background_color=None, - text_color=None, - auto_close=True, - auto_close_duration=DEFAULT_AUTOCLOSE_TIME, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Popup that closes itself after some time period - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_color=button_color, - background_color=background_color, - text_color=text_color, - button_type=button_type, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -PopupTimed = PopupAutoClose - - -# --------------------------- PopupError --------------------------- -def PopupError( - *args, - button_color=DEFAULT_ERROR_BUTTON_COLOR, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Popup with colored button and 'Error' as button text - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_type=POPUP_BUTTONS_ERROR, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -# --------------------------- PopupCancel --------------------------- -def PopupCancel( - *args, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Display Popup with "cancelled" button text - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_type=POPUP_BUTTONS_CANCELLED, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -# --------------------------- PopupOK --------------------------- -def PopupOK( - *args, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Display Popup with OK button only - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup( - *args, - button_type=POPUP_BUTTONS_OK, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -# --------------------------- PopupOKCancel --------------------------- -def PopupOKCancel( - *args, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Display popup with OK and Cancel buttons - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: OK, Cancel or None - """ - return Popup( - *args, - button_type=POPUP_BUTTONS_OK_CANCEL, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -# --------------------------- PopupYesNo --------------------------- -def PopupYesNo( - *args, - button_color=None, - background_color=None, - text_color=None, - auto_close=False, - auto_close_duration=None, - non_blocking=False, - icon=DEFAULT_WINDOW_ICON, - line_width=None, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Display Popup with Yes and No buttons - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: Yes, No or None - """ - return Popup( - *args, - button_type=POPUP_BUTTONS_YES_NO, - background_color=background_color, - text_color=text_color, - non_blocking=non_blocking, - icon=icon, - line_width=line_width, - button_color=button_color, - auto_close=auto_close, - auto_close_duration=auto_close_duration, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - -############################################################################## -# The PopupGet_____ functions - Will return user input # -############################################################################## - - -# --------------------------- PopupGetFolder --------------------------- - - -def PopupGetFolder( - message, - title=None, - default_path='', - no_window=False, - size=(None, None), - button_color=None, - background_color=None, - text_color=None, - icon=DEFAULT_WINDOW_ICON, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - initial_folder=None, -): - """ - Display popup with text entry field and browse button. Browse for folder - :param message: - :param default_path: - :param no_window: - :param size: - :param button_color: - :param background_color: - :param text_color: - :param icon: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: Contents of text field. None if closed using X or cancelled - """ - - if no_window: - app = wx.App(False) - frame = wx.Frame() - - if initial_folder: - dialog = wx.DirDialog(frame, style=wx.FD_OPEN) - else: - dialog = wx.DirDialog(frame) - folder_name = '' - if dialog.ShowModal() == wx.ID_OK: - folder_name = dialog.GetPath() - return folder_name - - layout = [ - [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], - [InputText(default_text=default_path, size=size, key='_INPUT_'), FolderBrowse(initial_folder=initial_folder)], - [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], - ] - - _title = title if title is not None else message - window = Window( - title=_title, - layout=layout, - icon=icon, - auto_size_text=True, - button_color=button_color, - background_color=background_color, - font=font, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - button, values = window.Read() - window.Close() - if button != 'Ok': - return None - else: - path = values['_INPUT_'] - return path - - -# --------------------------- PopupGetFile --------------------------- - - -def PopupGetFile( - message, - title=None, - default_path='', - default_extension='', - save_as=False, - file_types=(('ALL Files', '*'),), - no_window=False, - size=(None, None), - button_color=None, - background_color=None, - text_color=None, - icon=DEFAULT_WINDOW_ICON, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), - initial_folder=None, -): - """ - Display popup with text entry field and browse button. Browse for file - :param message: - :param default_path: - :param default_extension: - :param save_as: - :param file_types: - :param no_window: - :param size: - :param button_color: - :param background_color: - :param text_color: - :param icon: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: string representing the path chosen, None if cancelled or window closed with X - """ - - if no_window: - app = wx.App(False) - frame = wx.Frame() - - qt_types = convert_tkinter_filetypes_to_wx(file_types) - style = wx.FD_SAVE if save_as else wx.FD_OPEN - if initial_folder: - dialog = wx.FileDialog(frame, defaultDir=initial_folder, wildcard=qt_types, style=style) - else: - dialog = wx.FileDialog(frame, wildcard=qt_types, style=style) - if dialog.ShowModal() == wx.ID_OK: - file_name = dialog.GetPath() - else: - file_name = '' - return file_name - - browse_button = SaveAs(file_types=file_types, initial_folder=initial_folder) if save_as else FileBrowse(file_types=file_types, initial_folder=initial_folder) - - layout = [ - [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], - [InputText(default_text=default_path, size=(30, 1), key='_INPUT_'), browse_button], - [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], - ] - - _title = title if title is not None else message - - window = Window( - title=_title, - layout=layout, - icon=icon, - auto_size_text=True, - button_color=button_color, - font=font, - background_color=background_color, - no_titlebar=no_titlebar, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - location=location, - ) - - button, values = window.Read() - window.Close() - if button != 'Ok': - return None - else: - path = values['_INPUT_'] - return path - - -# --------------------------- PopupGetText --------------------------- - - -def PopupGetText( - message, - title=None, - default_text='', - password_char='', - size=(None, None), - button_color=None, - background_color=None, - text_color=None, - icon=DEFAULT_WINDOW_ICON, - font=None, - no_titlebar=False, - grab_anywhere=False, - keep_on_top=False, - location=(None, None), -): - """ - Display Popup with text entry field - :param message: - :param default_text: - :param password_char: - :param size: - :param button_color: - :param background_color: - :param text_color: - :param icon: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: Text entered or None if window was closed - """ - - layout = [ - [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], - [InputText(default_text=default_text, size=size, key='_INPUT_', password_char=password_char)], - [CloseButton('Ok', size=(60, 20), bind_return_key=True), CloseButton('Cancel', size=(60, 20))], - ] - - _title = title if title is not None else message - - window = Window( - title=_title, - layout=layout, - icon=icon, - auto_size_text=True, - button_color=button_color, - no_titlebar=no_titlebar, - background_color=background_color, - grab_anywhere=grab_anywhere, - keep_on_top=keep_on_top, - font=font, - location=location, - ) - - button, values = window.Read() - window.Close() - if button != 'Ok': - return None - else: - path = values['_INPUT_'] - return path - - -# ------------------------ PEP8-ify The SDK ------------------------# -change_look_and_feel = ChangeLookAndFeel -easy_print = EasyPrint -easy_print_close = EasyPrintClose -fill_form_with_values = FillFormWithValues -get_complimentary_hex = GetComplimentaryHex -list_of_look_and_feel_values = ListOfLookAndFeelValues -obj_to_string = ObjToString -obj_to_string_single_obj = ObjToStringSingleObj -one_line_progress_meter = OneLineProgressMeter -one_line_progress_meter_cancel = OneLineProgressMeterCancel -popup = Popup -popup_annoying = PopupAnnoying -popup_auto_close = PopupAutoClose -popup_cancel = PopupCancel -popup_error = PopupError -popup_get_file = PopupGetFile -popup_get_folder = PopupGetFolder -popup_get_text = PopupGetText -popup_no_border = PopupNoBorder -popup_no_buttons = PopupNoButtons -popup_no_frame = PopupNoFrame -popup_no_titlebar = PopupNoTitlebar -popup_no_wait = PopupNoWait -popup_non_blocking = PopupNonBlocking -popup_ok = PopupOK -popup_ok_cancel = PopupOKCancel -popup_quick = PopupQuick -popup_quick_message = PopupQuickMessage -popup_scrolled = PopupScrolled -popup_timed = PopupTimed -popup_yes_no = PopupYesNo -print_close = PrintClose -scrolled_text_box = ScrolledTextBox -set_global_icon = SetGlobalIcon -set_options = SetOptions -timer_start = TimerStart -timer_stop = TimerStop - - -# ------------------------ Set the "Official PySimpleGUI Theme Colors" ------------------------ -theme(CURRENT_LOOK_AND_FEEL) - -# theme_previewer() - - -""" - d8b - Y8P - -88888b.d88b. 8888b. 888 88888b. -888 "888 "88b "88b 888 888 "88b -888 888 888 .d888888 888 888 888 -888 888 888 888 888 888 888 888 -888 888 888 "Y888888 888 888 888 - -""" - - -def main(): - - def VerLine(version, description, justification='r', size=(30, 1)): - return [ - T(version, justification=justification, font='Any 12', text_color='yellow', size=size), - T(description, font='Any 12'), - ] - - ver = version.split('\n')[0] - - # ChangeLookAndFeel('Light Brown 11') - frame_contents = [[T('Inside my frame')], [Input(size=(5, 1))], [Input()]] - layout = [ - [Text('Welcome to PySimpleGUI!', font='Arial 15', text_color='yellow')], - VerLine(ver, 'PySimpleGUI Version'), - VerLine(os.path.dirname(os.path.abspath(__file__)), 'PySimpleGUI Location', justification='l', size=(30, 2)), - VerLine(sys.version, 'Python Version', justification='l', size=(40, 2)), - [Text('You should be importing this module rather than running it', justification='l', size=(50, 1))], - [Text('Here is your sample input window....')], - [Frame('FRAME with Centered Contents', frame_contents, element_justification='c')], - [InputText('Source', focus=True, size_px=(200, 80)), FileBrowse()], - [InputText('Dest'), FolderBrowse()], - [Checkbox('Checkbox 1', size=(15, 1)), Checkbox('Checkbox 2')], - [Radio('Radio 1', 'group', size=(15, 1)), Radio('Radio 2', 'group')], - [Multiline('Multiline Input', do_not_clear=True, size=(40, 4), enable_events=True)], - [MultilineOutput('Multiline Output', size=(40, 5), text_color='blue')], - [ - Combo( - values=['Combo 1', 'Combo 2', 'Combo 3'], - default_value='Combo 2', - key='_COMBO_', - enable_events=True, - readonly=False, - tooltip='Combo box', - disabled=False, - font='Courier 18', - size=(12, 1), - ) - ], - [Spin(values=['Spin a', 'Spin b', 'Spin c'], font='ANY 15', key='_SPIN_', size=(10, 1), enable_events=True)], - [Button('Ok'), Button('Exit')], - ] - - window = Window( - 'Demo window..', - layout, - # default_element_size=(35,1), - auto_size_text=True, - auto_size_buttons=True, - no_titlebar=False, - disable_close=False, - disable_minimize=True, - grab_anywhere=True, - # element_justification='r' - ) - - while True: - event, values = window.Read() - print(event, values) - - if event in (None, 'Exit'): - break - window.Close() - - -if __name__ == '__main__': - - main() - sys.exit(69) diff --git a/FreeSimpleGUIWx/FreeSimpleGUIWx/__init__.py b/FreeSimpleGUIWx/FreeSimpleGUIWx/__init__.py index a9b1d8ba..5d50d274 100644 --- a/FreeSimpleGUIWx/FreeSimpleGUIWx/__init__.py +++ b/FreeSimpleGUIWx/FreeSimpleGUIWx/__init__.py @@ -1,2 +1,10564 @@ -from .FreeSimpleGUIWx import * -from .FreeSimpleGUIWx import __version__ +#!/usr/bin/python3 +version = __version__ = '0.17.1.5 Unreleased\n VSeparator added (spelling error), Radio.reset_group added and removed the clearing all when one cleared, added default key for one_line_progress_meter, auto-add keys to tables & trees, added theme_add_new' + +port = 'PySimpleGUIWx' + +import sys +import wx +import wx.adv +import wx.lib.inspection +from wx.lib.embeddedimage import PyEmbeddedImage +import wx.lib.scrolledpanel +import types +import datetime +import textwrap +import pickle +import os +import time +from random import randint + +RUN_INSPECTION_TOOL = False + + +###### ##### ##### # # ### # # +# # # # # # # # # ##### # ###### # # # # # # # # # # +# # # # # # ## ## # # # # # # # # # # # # # +###### # ##### # # ## # # # # ##### # #### # # # # # # ## +# # # # # # ##### # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # +# # ##### # # # # ###### ###### ##### ##### ### ## ## # # + +""" + + 21-Dec-2018 + Welcome to the "core" PySimpleGUIWx port! + +::: ::: ::: ::: ::::::::: ::: ::: ::::::::::: ::: ::: :::::::: :::: ::: +:+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:+: :+: ++:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ :+:+:+ +:+ ++#+ +:+ +#+ +#++:+ +#++:++#+ +#++: +#+ +#++:++#++ +#+ +:+ +#+ +:+ +#+ ++#+ +#+#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+#+# + #+#+# #+#+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+# + ### ### ### ### ### ### ### ### ### ######## ### #### + + + + This marks the 3rd port of the PySimpleGUI GUI SDK. Each port gets a little better than + the previous, in theory. + + It will take a while for this Wx port to be completed, but should be running with a fully selection + of widgets fairly quickly. The Qt port required 1 week to get to "Alpha" condition + + Enjoy! +""" + +g_time_start = 0 +g_time_end = 0 +g_time_delta = 0 + + +# Because looks matter... +DEFAULT_BASE64_ICON = b'iVBORw0KGgoAAAANSUhEUgAAACEAAAAgCAMAAACrZuH4AAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAMGmYMGqZMWqaMmubMmycM22dNGuZNm2bNm6bNG2dN26cNG6dNG6eNW+fN3CfOHCeOXGfNXCgNnGhN3KiOHOjOXSjOHSkOnWmOnamOnanPHSiPXakPnalO3eoPnimO3ioPHioPHmpPHmqPXqqPnurPnusPnytP3yuQHimQnurQn2sQH2uQX6uQH6vR32qRn+sSXujSHynTH2mTn+nSX6pQH6wTIGsTYKuTYSvQoCxQoCyRIK0R4S1RYS2Roa4SIe4SIe6SIi7Soq7SYm8SYq8Sou+TY2/UYStUYWvVIWtUYeyVoewUIi0VIizUI6+Vo+8WImxXJG5YI2xZI+xZ5CzZJC0ZpG1b5a3apW4aZm/cZi4dJ2/eJ69fJ+9XZfEZZnCZJzHaZ/Jdp/AeKTI/tM8/9Q7/9Q8/9Q9/9Q+/tQ//9VA/9ZA/9ZB/9ZC/9dD/9ZE/tdJ/9dK/9hF/9hG/9hH/9hI/9hJ/9hK/9lL/9pK/9pL/thO/9pM/9pN/9tO/9tP/9xP/tpR/9xQ/9xR/9xS/9xT/91U/91V/t1W/95W/95X/95Y/95Z/99a/99b/txf/txh/txk/t5l/t1q/t5v/+Bb/+Bc/+Bd/+Be/+Bf/+Bg/+Fh/+Fi/+Jh/+Ji/uJk/uJl/+Jm/+Rm/uJo/+Ro/+Rr/+Zr/+Vs/+Vu/+Zs/+Zu/uF0/uVw/+dw/+dz/+d2/uB5/uB6/uJ9/uR7/uR+/uV//+hx/+hy/+h0/+h2/+l4/+l7/+h8gKXDg6vLgazOhKzMiqrEj6/KhK/Qka/Hk7HJlLHJlLPMmLTLmbbOkLXSmLvXn77XoLrPpr/Tn8DaocLdpcHYrcjdssfZus/g/uOC/uOH/uaB/uWE/uaF/uWK/+qA/uqH/uqI/uuN/uyM/ueS/ueW/ueY/umQ/uqQ/uuS/uuW/uyU/uyX/uqa/uue/uye/uyf/u6f/uyq/u+r/u+t/vCm/vCp/vCu/vCy/vC2/vK2/vO8/vO/wtTjwtXlzdrl/vTA/vPQAAAAiNpY5gAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAAFw8AABcPASe7rwsAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAKUSURBVDhPhdB3WE1xHMdxt5JV0dANoUiyd8kqkey996xclUuTlEKidO3qVnTbhIyMW/bee5NskjJLmR/f3++cK/94vP76Ps/n/Zx7z6mE/6koJowcK154vvHOL/GsKCZXkUgkWlf4vWGWq5tsDz+JWIzSokAiqXGe7nWu3HxhEYof7fhOqp1GtptQuMruVhQdxZ05U5G47tYUHbQ4oah6Fg9Z4ubm7i57JhQjdHS0RSzUPoG17u6zZTKZh8c8XlytqW9YWUOH1LqFOZ6enl5ec+XybFb0rweM1tPTM6yuq6vLs0lYJJfLvb19fHwDWGF0jh5lYNAe4/QFemOwxtfXz8/fPyBgwVMqzAcCF4ybAZ2MRCexJGBhYGBQUHDw4u1UHDG1G2ZqB/Q1MTHmzAE+kpCwL1RghlTaBt/6SaXS2kx9YH1IaOjSZST8vfA9JtoDnSngGgL7wkg4WVkofA9mcF1Sx8zMzBK4v3wFiYiMVLxlEy9u21syFhYNmgN7IyJXEYViNZvEYoCVVWOmUVvgQVSUQqGIjolRFvOAFd8HWVs34VoA+6OjY2JjY5Vxm4BC1UuhGG5jY9OUaQXci1MqlfHx8YmqjyhOViW9ZsUN29akJRmPFwkJCZsTSXIpilJffXiTzorLXYgtcxRJKpUqKTklJQ0oSt9FP/EonxVdNY4jla1kK4q2ZB6mIr+AipvduzFUzMSOtLT09IyMzMxtJKug/F0u/6dTexAWDcXXLGEjapKjfsILOLKEuYiSnTQeYCt3UHhbwEHjGMrETfBJU5zq5dSTcXC8hLJccSWP2cgLXHPu7cQNAcpyxF1dyjehAKb0cSYUAOXCUw6V8OFPgevTXFymC+fPPLU677Nw/1X8A/AbfAKGulaqFlIAAAAASUVORK5CYII=' + + +def TimerStart(): + global g_time_start + + g_time_start = time.time() + + +def TimerStop(): + global g_time_delta, g_time_end + + g_time_end = time.time() + g_time_delta = g_time_end - g_time_start + print(g_time_delta) + + +# ----====----====----==== Constants the user CAN safely change ====----====----====----# +DEFAULT_WINDOW_ICON = 'default_icon.ico' +DEFAULT_ELEMENT_SIZE = (250, 26) # In pixels +DEFAULT_BUTTON_ELEMENT_SIZE = (10, 1) # In CHARACTERS +DEFAULT_MARGINS = (10, 5) # Margins for each LEFT/RIGHT margin is first term +DEFAULT_ELEMENT_PADDING = (3, 2) # Padding between elements (row, col) in pixels +DEFAULT_AUTOSIZE_TEXT = True +DEFAULT_AUTOSIZE_BUTTONS = True +DEFAULT_FONT = ('Helvetica', 10) +DEFAULT_TEXT_JUSTIFICATION = 'left' +DEFAULT_BORDER_WIDTH = 1 +DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form +DEFAULT_DEBUG_WINDOW_SIZE = (80, 20) +DEFAULT_WINDOW_LOCATION = (None, None) +MAX_SCROLLED_TEXT_BOX_HEIGHT = 50 +DEFAULT_TOOLTIP_TIME = 400 +DEFAULT_PIXELS_TO_CHARS_SCALING = (10, 26) # 1 character represents x by y pixels +DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT = (10, 20) # 1 character represents x by y pixels +DEFAULT_PIXEL_TO_CHARS_CUTOFF = 20 # number of chars that triggers using pixels instead of chars +DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE = 70 # number of chars that triggers using pixels instead of chars +MENU_DISABLED_CHARACTER = '!' +MENU_KEY_SEPARATOR = '::' + +#################### COLOR STUFF #################### +BLUES = ('#082567', '#0A37A3', '#00345B') +PURPLES = ('#480656', '#4F2398', '#380474') +GREENS = ('#01826B', '#40A860', '#96D2AB', '#00A949', '#003532') +YELLOWS = ('#F3FB62', '#F0F595') +TANS = ('#FFF9D5', '#F4EFCF', '#DDD8BA') +NICE_BUTTON_COLORS = ( + (GREENS[3], TANS[0]), + ('#000000', '#FFFFFF'), + ('#FFFFFF', '#000000'), + (YELLOWS[0], PURPLES[1]), + (YELLOWS[0], GREENS[3]), + (YELLOWS[0], BLUES[2]), +) + +COLOR_SYSTEM_DEFAULT = '1234567890' # Colors should never be this long +DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default +OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = ('white', BLUES[0]) # Colors should never be this long + + +CURRENT_LOOK_AND_FEEL = 'DarkBlue3' + + +DEFAULT_ERROR_BUTTON_COLOR = ('#FFFFFF', '#FF0000') +DEFAULT_BACKGROUND_COLOR = None +DEFAULT_ELEMENT_BACKGROUND_COLOR = None +DEFAULT_ELEMENT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = None +DEFAULT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_INPUT_ELEMENTS_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_INPUT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT +DEFAULT_SCROLLBAR_COLOR = None +# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[0]) # (Text, Background) or (Color "on", Color) as a way to remember +# DEFAULT_BUTTON_COLOR = (GREENS[3], TANS[0]) # Foreground, Background (None, None) == System Default +# DEFAULT_BUTTON_COLOR = (YELLOWS[0], GREENS[4]) # Foreground, Background (None, None) == System Default +# DEFAULT_BUTTON_COLOR = ('white', 'black') # Foreground, Background (None, None) == System Default +# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[2]) # Foreground, Background (None, None) == System Default +# DEFAULT_PROGRESS_BAR_COLOR = (GREENS[2], GREENS[0]) # a nice green progress bar +# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[1], BLUES[1]) # a nice green progress bar +# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[0], BLUES[0]) # a nice green progress bar +# DEFAULT_PROGRESS_BAR_COLOR = (PURPLES[1],PURPLES[0]) # a nice purple progress bar + +# A transparent button is simply one that matches the background +TRANSPARENT_BUTTON = 'This constant has been depricated. You must set your button background = background it is on for it to be transparent appearing' +# -------------------------------------------------------------------------------- +# Progress Bar Relief Choices +RELIEF_RAISED = 'raised' +RELIEF_SUNKEN = 'sunken' +RELIEF_FLAT = 'flat' +RELIEF_RIDGE = 'ridge' +RELIEF_GROOVE = 'groove' +RELIEF_SOLID = 'solid' + +DEFAULT_PROGRESS_BAR_COLOR = (GREENS[0], '#D0D0D0') # a nice green progress bar +DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL = (GREENS[0], '#D0D0D0') # a nice green progress bar +DEFAULT_PROGRESS_BAR_SIZE = (20, 20) # Size of Progress Bar (characters for length, pixels for width) +DEFAULT_PROGRESS_BAR_BORDER_WIDTH = 1 +DEFAULT_PROGRESS_BAR_RELIEF = RELIEF_GROOVE +PROGRESS_BAR_STYLES = ('default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative') +DEFAULT_PROGRESS_BAR_STYLE = 'default' +DEFAULT_METER_ORIENTATION = 'Horizontal' +DEFAULT_SLIDER_ORIENTATION = 'vertical' +DEFAULT_SLIDER_BORDER_WIDTH = 1 +DEFAULT_SLIDER_RELIEF = 00000 +DEFAULT_FRAME_RELIEF = 00000 + +DEFAULT_LISTBOX_SELECT_MODE = 00000 +SELECT_MODE_MULTIPLE = 00000 +LISTBOX_SELECT_MODE_MULTIPLE = 'multiple' +SELECT_MODE_BROWSE = 00000 +LISTBOX_SELECT_MODE_BROWSE = 'browse' +SELECT_MODE_EXTENDED = 00000 +LISTBOX_SELECT_MODE_EXTENDED = 'extended' +SELECT_MODE_SINGLE = 00000 +LISTBOX_SELECT_MODE_SINGLE = 'single' + +TABLE_SELECT_MODE_NONE = 00000 +TABLE_SELECT_MODE_BROWSE = 00000 +TABLE_SELECT_MODE_EXTENDED = 00000 +DEFAULT_TABLE_SECECT_MODE = TABLE_SELECT_MODE_EXTENDED + +TITLE_LOCATION_TOP = 00000 +TITLE_LOCATION_BOTTOM = 00000 +TITLE_LOCATION_LEFT = 00000 +TITLE_LOCATION_RIGHT = 00000 +TITLE_LOCATION_TOP_LEFT = 00000 +TITLE_LOCATION_TOP_RIGHT = 00000 +TITLE_LOCATION_BOTTOM_LEFT = 00000 +TITLE_LOCATION_BOTTOM_RIGHT = 00000 + +THEME_DEFAULT = 'default' +THEME_WINNATIVE = 'winnative' +THEME_CLAM = 'clam' +THEME_ALT = 'alt' +THEME_CLASSIC = 'classic' +THEME_VISTA = 'vista' +THEME_XPNATIVE = 'xpnative' + +# DEFAULT_METER_ORIENTATION = 'Vertical' +# ----====----====----==== Constants the user should NOT f-with ====----====----====----# +ThisRow = 555666777 # magic number + +# DEFAULT_WINDOW_ICON = '' +MESSAGE_BOX_LINE_WIDTH = 60 + +# "Special" Key Values.. reserved +# Key representing a Read timeout +EVENT_TIMEOUT = TIMEOUT_EVENT = TIMEOUT_KEY = '__TIMEOUT__' +# Window closed event (user closed with X or destroyed using OS) +WIN_CLOSED = WINDOW_CLOSED = None + +# Key indicating should not create any return values for element +WRITE_ONLY_KEY = '__WRITE ONLY__' +EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED = '__DOUBLE_CLICKED__' +EVENT_SYSTEM_TRAY_ICON_ACTIVATED = '__ACTIVATED__' +EVENT_SYSTEM_TRAY_ICON_RIGHT_CLICK = '__RIGHT_CLICK__' +EVENT_SYSTEM_TRAY_MESSAGE_CLICKED = '__MESSAGE_CLICKED__' + +# Icons for displaying system tray messages +SYSTEM_TRAY_MESSAGE_ICON_INFORMATION = wx.ICON_INFORMATION +SYSTEM_TRAY_MESSAGE_ICON_WARNING = wx.ICON_WARNING +SYSTEM_TRAY_MESSAGE_ICON_CRITICAL = wx.ICON_ERROR +SYSTEM_TRAY_MESSAGE_ICON_NOICON = None + +ICON_SCREEN_DEPTH = -1 + +ICON_STOP = 512 + + +# a shameful global variable. This represents the top-level window information. Needed because opening a second window is different than opening the first. +class MyWindows: + def __init__(self): + self.NumOpenWindows = 0 + self.user_defined_icon = None + self.hidden_master_root = None + + def Decrement(self): + self.NumOpenWindows -= 1 * (self.NumOpenWindows != 0) # decrement if not 0 + # print('---- DECREMENTING Num Open Windows = {} ---'.format(self.NumOpenWindows)) + + def Increment(self): + self.NumOpenWindows += 1 + # print('++++ INCREMENTING Num Open Windows = {} ++++'.format(self.NumOpenWindows)) + + +_my_windows = MyWindows() # terrible hack using globals... means need a class for collecing windows + + +# ====================================================================== # +# One-liner functions that are handy as f_ck # +# ====================================================================== # +def RGB(red, green, blue): + return '#%02x%02x%02x' % (red, green, blue) + + +# ====================================================================== # +# Enums for types # +# ====================================================================== # +# ------------------------- Button types ------------------------- # +# todo Consider removing the Submit, Cancel types... they are just 'RETURN' type in reality +# uncomment this line and indent to go back to using Enums +# class ButtonType(Enum): +BUTTON_TYPE_BROWSE_FOLDER = 1 +BUTTON_TYPE_BROWSE_FILE = 2 +BUTTON_TYPE_BROWSE_FILES = 21 +BUTTON_TYPE_SAVEAS_FILE = 3 +BUTTON_TYPE_CLOSES_WIN = 5 +BUTTON_TYPE_CLOSES_WIN_ONLY = 6 +BUTTON_TYPE_READ_FORM = 7 +BUTTON_TYPE_REALTIME = 9 +BUTTON_TYPE_CALENDAR_CHOOSER = 30 +BUTTON_TYPE_COLOR_CHOOSER = 40 + +BROWSE_FILES_DELIMITER = ';' # the delimeter to be used between each file in the returned string + +# ------------------------- Element types ------------------------- # +# class ElementType(Enum): +ELEM_TYPE_TEXT = 'text' +ELEM_TYPE_INPUT_TEXT = 'input' +ELEM_TYPE_INPUT_COMBO = 'combo' +ELEM_TYPE_INPUT_OPTION_MENU = 'option menu' +ELEM_TYPE_INPUT_RADIO = 'radio' +ELEM_TYPE_INPUT_MULTILINE = 'multiline' +ELEM_TYPE_MULTILINE_OUTPUT = 'multioutput' +ELEM_TYPE_INPUT_CHECKBOX = 'checkbox' +ELEM_TYPE_INPUT_SPIN = 'spin' +ELEM_TYPE_BUTTON = 'button' +ELEM_TYPE_BUTTONMENU = 'buttonmenu' +ELEM_TYPE_IMAGE = 'image' +ELEM_TYPE_CANVAS = 'canvas' +ELEM_TYPE_FRAME = 'frame' +ELEM_TYPE_GRAPH = 'graph' +ELEM_TYPE_TAB = 'tab' +ELEM_TYPE_TAB_GROUP = 'tabgroup' +ELEM_TYPE_INPUT_SLIDER = 'slider' +ELEM_TYPE_INPUT_LISTBOX = 'listbox' +ELEM_TYPE_OUTPUT = 'output' +ELEM_TYPE_COLUMN = 'column' +ELEM_TYPE_MENUBAR = 'menubar' +ELEM_TYPE_PROGRESS_BAR = 'progressbar' +ELEM_TYPE_BLANK = 'blank' +ELEM_TYPE_TABLE = 'table' +ELEM_TYPE_TREE = 'tree' +ELEM_TYPE_ERROR = 'error' +ELEM_TYPE_SEPARATOR = 'separator' + +# ------------------------- Popup Buttons Types ------------------------- # +POPUP_BUTTONS_YES_NO = 1 +POPUP_BUTTONS_CANCELLED = 2 +POPUP_BUTTONS_ERROR = 3 +POPUP_BUTTONS_OK_CANCEL = 4 +POPUP_BUTTONS_OK = 0 +POPUP_BUTTONS_NO_BUTTONS = 5 + + +# ---------------------------------------------------------------------- # +# Cascading structure.... Objects get larger # +# Button # +# Element # +# Row # +# Form # +# ---------------------------------------------------------------------- # +# ---------------------------------------------------------------------- # +# Element CLASS # +# ---------------------------------------------------------------------- # +class Element: + def __init__( + self, + elem_type, + size=(None, None), + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + + # if elem_type != ELEM_TYPE_GRAPH: + # self.Size = convert_tkinter_size_to_Wx(size) + # else: + # self.Size = size + self.Size = size_px + if size_px == (None, None) and size != (None, None): + if elem_type in ( + ELEM_TYPE_MULTILINE_OUTPUT, + ELEM_TYPE_INPUT_MULTILINE, + ELEM_TYPE_OUTPUT, + ELEM_TYPE_TABLE, + ELEM_TYPE_TREE, + ELEM_TYPE_TAB, + ELEM_TYPE_COLUMN, + ): + self.Size = _convert_tkinter_size_to_Wx(size, DEFAULT_PIXELS_TO_CHARS_SCALING_MULTILINE_TEXT, DEFAULT_PIXEL_TO_CHARS_CUTOFF_MULTILINE) + else: + self.Size = _convert_tkinter_size_to_Wx(size, DEFAULT_PIXELS_TO_CHARS_SCALING, DEFAULT_PIXEL_TO_CHARS_CUTOFF) + self.Type = elem_type + self.AutoSizeText = auto_size_text + # self.Pad = DEFAULT_ELEMENT_PADDING if pad is None else pad + self.Pad = pad + if font is not None and type(font) is not str: + self.Font = font + elif font is not None: + self.Font = font.split(' ') + else: + self.Font = font + + self.TKStringVar = None + self.TKIntVar = None + self.TKText = None + self.TKEntry = None + self.TKImage = None + + self.ParentForm = None # type: Window + self.ParentContainer = None # will be a Form, Column, or Frame element + self.TextInputDefault = None + self.Position = (0, 0) # Default position Row 0, Col 0 + self.BackgroundColor = background_color if background_color is not None else DEFAULT_ELEMENT_BACKGROUND_COLOR + self.TextColor = text_color if text_color is not None else DEFAULT_ELEMENT_TEXT_COLOR + self.Key = key # dictionary key for return values + self.Tooltip = tooltip + self.TooltipObject = None + self.Visible = visible + self.metadata = None # type: Any + + def FindReturnKeyBoundButton(self, form): + for row in form.Rows: + for element in row: + if element.Type == ELEM_TYPE_BUTTON: + if element.BindReturnKey: + return element + if element.Type == ELEM_TYPE_COLUMN: + rc = self.FindReturnKeyBoundButton(element) + if rc is not None: + return rc + if element.Type == ELEM_TYPE_FRAME: + rc = self.FindReturnKeyBoundButton(element) + if rc is not None: + return rc + if element.Type == ELEM_TYPE_TAB_GROUP: + rc = self.FindReturnKeyBoundButton(element) + if rc is not None: + return rc + if element.Type == ELEM_TYPE_TAB: + rc = self.FindReturnKeyBoundButton(element) + if rc is not None: + return rc + return None + + def _TextClickedHandler(self, event): + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = self.DisplayText + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop + + def _ReturnKeyHandler(self, event): + MyForm = self.ParentForm + button_element = self.FindReturnKeyBoundButton(MyForm) + if button_element is not None: + button_element.ButtonCallBack(event) + + def _ListboxSelectHandler(self, event): + # first, get the results table built + # modify the Results table in the parent FlexForm object + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '' + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop + + def _ComboboxSelectHandler(self, event): + # first, get the results table built + # modify the Results table in the parent FlexForm object + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '' + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop + + def _RadioHandler(self): + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '' + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() + + def _CheckboxHandler(self): + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '' + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() + + def _TabGroupSelectHandler(self, event): + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '' + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() + + def _KeyboardHandler(self, event): + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '' + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() + + def _WxCallbackKeyboard(self, value): + element_callback_quit_mainloop(self) + + def Update(self, widget, background_color=None, text_color=None, font=None, visible=None, disabled=None, tooltip=None): + if font: + widget.SetFont(font_to_wx_font(font)) + if text_color not in (None, COLOR_SYSTEM_DEFAULT): + widget.SetForegroundColour(text_color) + if background_color not in (None, COLOR_SYSTEM_DEFAULT): + widget.SetBackgroundColour(background_color) + if visible is True: + widget.Show() + self.ParentForm.VisibilityChanged() + elif visible is False: + widget.Hide() + self.ParentForm.VisibilityChanged() + if disabled: + widget.Enable(False) + elif disabled is False: + widget.Enable(True) + if tooltip is not None: + widget.SetToolTip(tooltip) + + def __call__(self, *args, **kwargs): + """ + Makes it possible to "call" an already existing element. When you do make the "call", it actually calls + the Update method for the element. + Example: If this text element was in yoiur layout: + sg.Text('foo', key='T') + Then you can call the Update method for that element by writing: + window.FindElement('T')('new text value') + + :param args: + :param kwargs: + :return: + """ + return self.Update(*args, **kwargs) + + update = Update + + +# ---------------------------------------------------------------------- # +# Input Class # +# ---------------------------------------------------------------------- # +class InputText(Element): + def __init__( + self, + default_text='', + size=(None, None), + disabled=False, + password_char='', + justification=None, + background_color=None, + text_color=None, + font=None, + tooltip=None, + change_submits=False, + enable_events=False, + do_not_clear=True, + key=None, + focus=False, + pad=None, + visible=True, + size_px=(None, None), + ): + ''' + Input a line of text Element + :param default_text: Default value to display + :param size: Size of field in characters + :param password_char: If non-blank, will display this character for every character typed + :param background_color: Color for Element. Text or RGB Hex + ''' + self.DefaultText = str(default_text) + self.PasswordCharacter = str(password_char) + bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Focus = focus + self.do_not_clear = do_not_clear + self.Justification = justification or 'left' + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + self.QT_QLineEdit = None + self.ValueWasChanged = False + self.WxTextCtrl = None + + super().__init__( + ELEM_TYPE_INPUT_TEXT, + size=size, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + font=font, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + + def Update(self, value=None, disabled=None, select=None, background_color=None, text_color=None, font=None, visible=None): + if disabled is True: + self.WxTextCtrl.Enable(True) + elif disabled is False: + self.WxTextCtrl.Enable(False) + if value is not None: + self.WxTextCtrl.SetValue(str(value)) + self.DefaultText = value + if select: + self.WxTextCtrl.SelectAll() + # if visible: + # self.WxTextCtrl.Show() + # self.ParentForm.VisibilityChanged() + # elif visible is False: + # self.WxTextCtrl.Hide() + super().Update(self.WxTextCtrl, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def Get(self): + return self.WxTextCtrl.GetValue() + + def SetFocus(self): + self.WxTextCtrl.SetFocus() + + get = Get + set_focus = SetFocus + update = Update + + +# ------------------------- INPUT TEXT Element lazy functions ------------------------- # +In = InputText +Input = InputText +I = InputText + + +# ---------------------------------------------------------------------- # +# Combo # +# ---------------------------------------------------------------------- # +class Combo(Element): + def __init__( + self, + values, + default_value=None, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + disabled=False, + key=None, + pad=None, + tooltip=None, + readonly=False, + visible_items=10, + font=None, + auto_complete=True, + visible=True, + size_px=(None, None), + ): + ''' + Input Combo Box Element (also called Dropdown box) + :param values: + :param size: Size of field in characters + :param auto_size_text: True if should shrink field to fit the default text + :param background_color: Color for Element. Text or RGB Hex + ''' + self.Values = values + self.DefaultValue = default_value + self.ChangeSubmits = change_submits or enable_events + # self.InitializeAsDisabled = disabled + self.Disabled = disabled + self.Readonly = readonly + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.VisibleItems = visible_items + self.AutoComplete = auto_complete + self.WxComboBox = None # type: wx.ComboBox + + super().__init__( + ELEM_TYPE_INPUT_COMBO, + size=size, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + font=font or DEFAULT_FONT, + visible=visible, + size_px=size_px, + ) + + def Update( + self, + value=None, + values=None, + set_to_index=None, + disabled=None, + readonly=None, + background_color=None, + text_color=None, + font=None, + visible=None, + ): + if values is not None: + self.WxComboBox.Set(values) + if value: + self.WxComboBox.SetSelection(self.WxComboBox.FindString(value)) + if set_to_index is not None: + self.WxComboBox.SetSelection(set_to_index) + if disabled is True: + self.WxComboBox.Enable(False) + elif disabled is False: + self.WxComboBox.Enable(True) + if readonly is not None: + self.WxComboBox.SetWindowStyle(wx.CB_READONLY) + + super().Update(self.WxComboBox, background_color=background_color, text_color=text_color, font=font, visible=visible) + + update = Update + + +# ------------------------- INPUT COMBO Element lazy functions ------------------------- # +InputCombo = Combo +DropDown = InputCombo +Drop = InputCombo + + +# ---------------------------------------------------------------------- # +# Option Menu # +# ---------------------------------------------------------------------- # +class OptionMenu(Element): + def __init__( + self, + values, + default_value=None, + size=(None, None), + disabled=False, + auto_size_text=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None, + ): + ''' + InputOptionMenu + :param values: + :param default_value: + :param size: + :param disabled: + :param auto_size_text: + :param background_color: + :param text_color: + :param key: + :param pad: + :param tooltip: + ''' + self.Values = values + self.DefaultValue = default_value + self.TKOptionMenu = None + self.Disabled = disabled + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + + super().__init__( + ELEM_TYPE_INPUT_OPTION_MENU, + size=size, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + ) + + def Update(self, value=None, values=None, disabled=None): + if values is not None: + self.Values = values + if self.Values is not None: + for index, v in enumerate(self.Values): + if v == value: + try: + self.TKStringVar.set(value) + except: + pass + self.DefaultValue = value + break + if disabled == True: + self.TKOptionMenu['state'] = 'disabled' + elif disabled == False: + self.TKOptionMenu['state'] = 'normal' + + update = Update + + +# ------------------------- OPTION MENU Element lazy functions ------------------------- # +InputOptionMenu = OptionMenu + + +# ---------------------------------------------------------------------- # +# Listbox # +# ---------------------------------------------------------------------- # +class Listbox(Element): + def __init__( + self, + values, + default_values=None, + select_mode=None, + change_submits=False, + bind_return_key=False, + size=(None, None), + disabled=False, + auto_size_text=None, + font=None, + background_color=None, + size_px=(None, None), + text_color=None, + key=None, + pad=None, + tooltip=None, + ): + ''' + Listbox Element + :param values: + :param default_values: + :param select_mode: + :param change_submits: + :param bind_return_key: + :param size: + :param disabled: + :param auto_size_text: + :param font: + :param background_color: + :param text_color: + :param key: + :param pad: + :param tooltip: + ''' + self.Values = values + self.DefaultValues = default_values + self.TKListbox = None + self.ChangeSubmits = change_submits + self.BindReturnKey = bind_return_key + self.Disabled = disabled + if select_mode == LISTBOX_SELECT_MODE_BROWSE: + self.SelectMode = SELECT_MODE_BROWSE + elif select_mode == LISTBOX_SELECT_MODE_EXTENDED: + self.SelectMode = SELECT_MODE_EXTENDED + elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE: + self.SelectMode = SELECT_MODE_MULTIPLE + elif select_mode == LISTBOX_SELECT_MODE_SINGLE: + self.SelectMode = SELECT_MODE_SINGLE + else: + self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + + super().__init__( + ELEM_TYPE_INPUT_LISTBOX, + size=size, + auto_size_text=auto_size_text, + font=font, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + size_px=size_px, + tooltip=tooltip, + ) + + def Update(self, values=None, disabled=None): + if disabled == True: + self.TKListbox.configure(state='disabled') + elif disabled == False: + self.TKListbox.configure(state='normal') + if values is not None: + self.TKListbox.delete(0, 'end') + for item in values: + self.TKListbox.insert(tk.END, item) + self.TKListbox.selection_set(0, 0) + self.Values = values + + def SetValue(self, values): + for index, item in enumerate(self.Values): + try: + if item in values: + self.TKListbox.selection_set(index) + else: + self.TKListbox.selection_clear(index) + except: + pass + self.DefaultValues = values + + def GetListValues(self): + return self.Values + + get_list_values = GetListValues + set_value = SetValue + update = Update + + +# ---------------------------------------------------------------------- # +# Radio # +# ---------------------------------------------------------------------- # +class Radio(Element): + def __init__( + self, + text, + group_id, + default=False, + disabled=False, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + font=None, + key=None, + pad=None, + tooltip=None, + change_submits=False, + enable_events=False, + visible=True, + size_px=(None, None), + ): + """ + + :param text: + :param group_id: + :param default: + :param disabled: + :param size: + :param auto_size_text: + :param background_color: + :param text_color: + :param font: + :param key: + :param pad: + :param tooltip: + :param change_submits: + :param enable_events: + :param visible: + :param size_px: + """ + self.InitialState = default + self.Text = text + self.GroupID = group_id + self.Value = None + self.Disabled = disabled + self.TextColor = text_color or DEFAULT_TEXT_COLOR + self.ChangeSubmits = change_submits or enable_events + self.WxRadioButton = None # type: wx.RadioButton + + super().__init__( + ELEM_TYPE_INPUT_RADIO, + size=size, + auto_size_text=auto_size_text, + font=font, + background_color=background_color, + text_color=self.TextColor, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + + def Update(self, value=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): + if value is True: + self.WxRadioButton.SetValue(True) + elif value is False: + self.WxRadioButton.SetValue(False) + super().Update(self.WxRadioButton, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def reset_group(self): + self.WxRadioButton.SetValue(True) + self.WxRadioButton.SetValue(False) + + update = Update + + +# ---------------------------------------------------------------------- # +# Checkbox # +# ---------------------------------------------------------------------- # +class Checkbox(Element): + def __init__( + self, + text, + default=False, + size=(None, None), + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + disabled=False, + key=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + ''' + Checkbox Element + :param text: + :param default: + :param size: + :param auto_size_text: + :param font: + :param background_color: + :param text_color: + :param change_submits: + :param disabled: + :param key: + :param pad: + :param tooltip: + ''' + self.Text = text + self.InitialState = default + self.WxCheckbox = None # type:wx.CheckBox + self.Disabled = disabled + self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR + self.ChangeSubmits = change_submits or enable_events + + super().__init__( + ELEM_TYPE_INPUT_CHECKBOX, + size=size, + auto_size_text=auto_size_text, + font=font, + background_color=background_color, + text_color=self.TextColor, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + + def Get(self): + return self.WxCheckbox.GetValue() + + def Update(self, value=None, disabled=None): + if value is not None: + try: + self.WxCheckbox.SetValue(value) + self.InitialState = value + except: + pass + if disabled == True: + self.WxCheckbox.Disable() + elif disabled == False: + self.WxCheckbox.Enable() + + get = Get + update = Update + + +# ------------------------- CHECKBOX Element lazy functions ------------------------- # +CB = Checkbox +CBox = Checkbox +Check = Checkbox + + +# ---------------------------------------------------------------------- # +# Spin # +# ---------------------------------------------------------------------- # + + +class Spin(Element): + # Values = None + # TKSpinBox = None + def __init__( + self, + values, + initial_value=None, + disabled=False, + change_submits=False, + enable_events=False, + size=(None, None), + readonly=True, + auto_size_text=None, + font=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + ''' + Spinner Element + :param values: + :param initial_value: + :param disabled: + :param change_submits: + :param size: + :param auto_size_text: + :param font: + :param background_color: + :param text_color: + :param key: + :param pad: + :param tooltip: + ''' + self.Values = values + self.DefaultValue = initial_value or values[0] + self.ChangeSubmits = change_submits or enable_events + self.Disabled = disabled + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.WxSpinCtrl: wx.SpinCtrl = None + self.WxTextCtrl = None # type : wx.TextCtrl + self.CurrentValue = self.DefaultValue + self.ReadOnly = readonly + + super().__init__( + ELEM_TYPE_INPUT_SPIN, + size=size, + auto_size_text=auto_size_text, + font=font, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + visible=visible, + size_px=size_px, + ) + return + + def _WxSpinCallback(self, event): + event = event # type:wx.SpinEvent + print(f'spin event {event.GetInt()} {self.WxSpinCtrl.GetValue()}') + offset = event.GetInt() + self.WxTextCtrl.SetValue(self.Values[offset]) + self.CurrentValue = self.Values[offset] + if self.ChangeSubmits: + element_callback_quit_mainloop(self) + + def Update(self, value=None, values=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): + if values != None: + self.Values = values + self.QT_Spinner.setStrings(values) + # self.QT_Spinner.setRange(self.Values[0], self.Values[1]) + if value is not None: + # self.QT_Spinner.setValue(value) + try: + self.QT_Spinner.setValue(self.QT_Spinner.valueFromText(value)) + self.DefaultValue = value + except: + pass + if disabled == True: + self.QT_Spinner.setDisabled(True) + elif disabled == False: + self.QT_Spinner.setDisabled(False) + super().Update(self.QT_Spinner, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def Get(self): + return self.WxSpinCtrl.GetValue() + + get = Get + update = Update + + +# ---------------------------------------------------------------------- # +# Multiline # +# ---------------------------------------------------------------------- # +class Multiline(Element): + def __init__( + self, + default_text='', + enter_submits=False, + disabled=False, + autoscroll=False, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + do_not_clear=True, + key=None, + write_only=False, + focus=False, + font=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + ''' + Multiline Element + :param default_text: + :param enter_submits: + :param disabled: + :param autoscroll: + :param size: + :param auto_size_text: + :param background_color: + :param text_color: + :param do_not_clear: + :param key: + :param focus: + :param pad: + :param tooltip: + :param font: + ''' + self.DefaultText = default_text + self.EnterSubmits = enter_submits + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + self.Focus = focus + self.do_not_clear = do_not_clear + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Autoscroll = autoscroll + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + self.WriteOnly = write_only + + self.Widget = self.WxTextCtrl = None + + super().__init__( + ELEM_TYPE_INPUT_MULTILINE, + size=size, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + font=font or DEFAULT_FONT, + visible=visible, + size_px=size_px, + ) + return + + def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None): + try: # added in case the widget has already been deleted for some readon. + if value is not None and not append: + self.WxTextCtrl.SetValue(value) + elif value is not None and append: + self.WxTextCtrl.AppendText(value) + if background_color is not None: + self.WxTextCtrl.SetBackgroundColour(background_color) + if text_color is not None: + self.WxTextCtrl.SetForegroundColour(text_color) + if font is not None: + self.WxTextCtrl.SetFont(font) + if disabled: + self.WxTextCtrl.Enable(True) + elif disabled is False: + self.WxTextCtrl.Enable(False) + except: + pass + + super().Update(self.WxTextCtrl, background_color=background_color, text_color=text_color, font=font, visible=visible) + + # + # def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None): + # if value is not None and not append: + # self.DefaultText = value + # self.QT_TextEdit.setText(str(value)) + # elif value is not None and append: + # self.DefaultText = value + # self.QT_TextEdit.setText(self.QT_TextEdit.toPlainText() + str(value)) + # if disabled == True: + # self.QT_TextEdit.setDisabled(True) + # elif disabled == False: + # self.QT_TextEdit.setDisabled(False) + # super().Update(self.QT_TextEdit, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def Get(self): + self.WxTextCtrl.GetValue() + + def SetFocus(self): + self.WxTextCtrl.SetFocus() + + get = Get + set_focus = SetFocus + update = Update + + +# ---------------------------------------------------------------------- # +# Multiline Output # +# ---------------------------------------------------------------------- # +class MultilineOutput(Element): + def __init__( + self, + default_text='', + enter_submits=False, + disabled=False, + autoscroll=False, + size=(None, None), + auto_size_text=None, + background_color=None, + text_color=None, + change_submits=False, + enable_events=False, + do_not_clear=True, + key=None, + focus=False, + font=None, + pad=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + ''' + Multiline Element + :param default_text: + :param enter_submits: + :param disabled: + :param autoscroll: + :param size: + :param auto_size_text: + :param background_color: + :param text_color: + :param do_not_clear: + :param key: + :param focus: + :param pad: + :param tooltip: + :param font: + ''' + self.DefaultText = default_text + self.EnterSubmits = enter_submits + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + self.Focus = focus + self.do_not_clear = do_not_clear + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.Autoscroll = autoscroll + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + + self.WxTextCtrl = None + + super().__init__( + ELEM_TYPE_MULTILINE_OUTPUT, + size=size, + auto_size_text=auto_size_text, + background_color=bg, + text_color=fg, + key=key, + pad=pad, + tooltip=tooltip, + font=font or DEFAULT_FONT, + visible=visible, + size_px=size_px, + ) + return + + def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None): + if value is not None and not append: + self.WxTextCtrl.SetLabel(value) + elif value is not None and append: + self.WxTextCtrl.AppendText(value) + if background_color is not None: + self.WxTextCtrl.SetBackgroundColour(background_color) + if text_color is not None: + self.WxTextCtrl.SetForegroundColour(text_color) + if font is not None: + self.WxTextCtrl.SetFont(font) + if disabled: + self.WxTextCtrl.Enable(True) + elif disabled is False: + self.WxTextCtrl.Enable(False) + super().Update(self.WxTextCtrl, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def Get(self): + self.WxTextCtrl.GetValue() + + def SetFocus(self): + self.WxTextCtrl.SetFocus() + + get = Get + set_focus = SetFocus + update = Update + + +# ---------------------------------------------------------------------- # +# Text # +# ---------------------------------------------------------------------- # +class Text(Element): + def __init__( + self, + text='', + size=(None, None), + auto_size_text=None, + click_submits=None, + enable_events=False, + relief=None, + border_width=None, + font=None, + text_color=None, + background_color=None, + justification=None, + pad=None, + margins=None, + key=None, + tooltip=None, + visible=True, + size_px=(None, None), + ): + """ + Text + :param text: + :param size: + :param auto_size_text: + :param click_submits: + :param enable_events: + :param relief: + :param font: + :param text_color: + :param background_color: + :param justification: + :param pad: + :param margins: + :param key: + :param tooltip: + :param visible: + :param size_px: + """ + self.DisplayText = str(text) + self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR + self.Justification = justification + self.Relief = relief + self.ClickSubmits = click_submits or enable_events + self.Margins = margins + self.size_px = size_px + if background_color is None: + bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR + else: + bg = background_color + pixelsize = size + if size[1] is not None and size[1] < 10: + pixelsize = size[0] * 10, size[1] * 20 + self.WxStaticText = None # type: wx.StaticText # wx.StaticText(form.MasterPanel, -1, element.DisplayText) + self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH + + super().__init__( + ELEM_TYPE_TEXT, + pixelsize, + auto_size_text, + background_color=bg, + font=font if font else DEFAULT_FONT, + text_color=self.TextColor, + pad=pad, + key=key, + tooltip=tooltip, + size_px=size_px, + visible=visible, + ) + return + + def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): + if self.ParentForm.TKrootDestroyed: + return + if value is not None: + self.WxStaticText.SetLabel(str(value)) + self.DisplayText = str(value) + if background_color is not None: + self.WxStaticText.SetBackgroundColour(background_color) + if text_color is not None: + self.WxStaticText.SetForegroundColour(text_color) + if font is not None: + self.WxStaticText.SetFont(font) + super().Update(self.WxStaticText, background_color=background_color, text_color=text_color, font=font, visible=visible) + + update = Update + + +# ------------------------- Text Element lazy functions ------------------------- # +Txt = Text +T = Text + + +class RedirectText(object): + def __init__(self, aWxTextCtrl): + self.out = aWxTextCtrl + + def write(self, string): + self.out.AppendText(string) + + def flush(self): + return + + +# ---------------------------------------------------------------------- # +# Output # +# Routes stdout, stderr to a scrolled window # +# ---------------------------------------------------------------------- # +class Output(Element): + def __init__( + self, + size=(None, None), + background_color=None, + text_color=None, + pad=None, + font=None, + tooltip=None, + key=None, + visible=True, + size_px=(None, None), + disabled=False, + ): + ''' + Output Element + :param size: + :param background_color: + :param text_color: + :param pad: + :param font: + :param tooltip: + :param key: + ''' + self._TKOut = None + bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.WxTextCtrl = None # type: wx.TextCtrl + self.redir = None + self.output = None + self.Disabled = disabled + + super().__init__( + ELEM_TYPE_OUTPUT, + size=size, + background_color=bg, + text_color=fg, + pad=pad, + font=font, + tooltip=tooltip, + key=key, + visible=visible, + size_px=size_px, + ) + + def _reroute_stdout(self): + self.my_stdout = sys.stdout + self.my_stderr = sys.stderr + self.redir = RedirectText(self.WxTextCtrl) + sys.stdout = self.redir + sys.stderr = self.redir + Window.stdout_is_rerouted = True + Window.stdout_location = self.redir + + def _reroute_again(self): + sys.stdout = self.redir + + def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): + if value is not None: + self.WxTextCtrl.AppendText(value) + super().Update(self.WxTextCtrl, background_color=background_color, text_color=text_color, font=font, visible=visible) + + def __del__(self): + try: + sys.stdout = self.my_stdout + sys.stderr = self.my_stderr + except: + pass + # super().__del__() + + update = Update + + +# ---------------------------------------------------------------------- # +# Button Class # +# ---------------------------------------------------------------------- # +class Button(Element): + def __init__( + self, + button_text='', + button_type=BUTTON_TYPE_READ_FORM, + target=(None, None), + tooltip=None, + file_types=(('ALL Files', '*'),), + initial_folder=None, + disabled=False, + change_submits=False, + enable_events=False, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, + visible=True, + size_px=(None, None), + ): + ''' + Button Element + :param button_text: + :param button_type: + :param target: + :param tooltip: + :param file_types: + :param initial_folder: + :param disabled: + :param image_filename: + :param image_size: + :param image_subsample: + :param border_width: + :param size: + :param auto_size_button: + :param button_color: + :param default_value: + :param font: + :param bind_return_key: + :param focus: + :param pad: + :param key: + ''' + self.AutoSizeButton = auto_size_button + self.BType = button_type + self.FileTypes = file_types + self.TKButton = None + self.Target = target + self.ButtonText = str(button_text) + self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR + self.TextColor = self.ButtonColor[0] + self.BackgroundColor = self.ButtonColor[1] + self.ImageFilename = image_filename + self.ImageData = image_data + self.ImageSize = image_size + self.ImageSubsample = image_subsample + self.UserData = None + self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH + self.BindReturnKey = bind_return_key + self.Focus = focus + self.TKCal = None + self.CalendarCloseWhenChosen = None + self.DefaultDate_M_D_Y = (None, None, None) + self.InitialFolder = initial_folder + self.Disabled = disabled + self.ChangeSubmits = change_submits or enable_events + self.QT_QPushButton = None + self.ColorChosen = None + self.Relief = None + self.WxButton = None # type: wx.Button + # self.temp_size = size if size != (NONE, NONE) else + + super().__init__( + ELEM_TYPE_BUTTON, + size=size, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + text_color=self.TextColor, + background_color=self.BackgroundColor, + visible=visible, + size_px=size_px, + ) + return + + # Realtime button release callback + def ButtonReleaseCallBack(self, parm): + self.LastButtonClickedWasRealtime = False + self.ParentForm.LastButtonClicked = None + + # Realtime button callback + def ButtonPressCallBack(self, parm): + self.ParentForm.LastButtonClickedWasRealtime = True + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = self.ButtonText + if self.ParentForm.CurrentlyRunningMainloop: + pass # kick out of loop if read was called + + # ------- Button Callback ------- # + def ButtonCallBack(self, event): + + # print('Button callback') + + # print(f'Parent = {self.ParentForm} Position = {self.Position}') + # Buttons modify targets or return from the form + # If modifying target, get the element object at the target and modify its StrVar + target = self.Target + target_element = None + if target[0] == ThisRow: + target = [self.Position[0], target[1]] + if target[1] < 0: + target[1] = self.Position[1] + target[1] + strvar = None + should_submit_window = False + if target == (None, None): + strvar = self.TKStringVar + else: + if not isinstance(target, str): + if target[0] < 0: + target = [self.Position[0] + target[0], target[1]] + target_element = self.ParentContainer._GetElementAtLocation(target) + else: + target_element = self.ParentForm.FindElement(target) + try: + strvar = target_element.TKStringVar + except: + pass + try: + if target_element.ChangeSubmits: + should_submit_window = True + except: + pass + filetypes = (('ALL Files', '*'),) if self.FileTypes is None else self.FileTypes + if self.BType == BUTTON_TYPE_BROWSE_FOLDER: # Browse Folder + wx_types = convert_tkinter_filetypes_to_wx(self.FileTypes) + if self.InitialFolder: + dialog = wx.DirDialog(self.ParentForm.MasterFrame, style=wx.FD_OPEN) + else: + dialog = wx.DirDialog(self.ParentForm.MasterFrame) + folder_name = '' + if dialog.ShowModal() == wx.ID_OK: + folder_name = dialog.GetPath() + if folder_name != '': + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = folder_name + else: + target_element.Update(folder_name) + elif self.BType == BUTTON_TYPE_BROWSE_FILE: # Browse File + qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) + if self.InitialFolder: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_OPEN) + else: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_OPEN) + file_name = '' + if dialog.ShowModal() == wx.ID_OK: + file_name = dialog.GetPath() + else: + file_name = '' + if file_name != '': + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = file_name + else: + target_element.Update(file_name) + elif self.BType == BUTTON_TYPE_BROWSE_FILES: # Browse Files + qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) + if self.InitialFolder: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_MULTIPLE) + else: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_MULTIPLE) + file_names = '' + if dialog.ShowModal() == wx.ID_OK: + file_names = dialog.GetPaths() + else: + file_names = '' + if file_names != '': + file_names = BROWSE_FILES_DELIMITER.join(file_names) + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = file_names + else: + target_element.Update(file_names) + elif self.BType == BUTTON_TYPE_SAVEAS_FILE: # Save As File + qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) + if self.InitialFolder: + dialog = wx.FileDialog( + self.ParentForm.MasterFrame, + defaultDir=self.InitialFolder, + wildcard=qt_types, + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, + ) + else: + dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + file_name = '' + if dialog.ShowModal() == wx.ID_OK: + file_name = dialog.GetPath() + else: + file_name = '' + if file_name != '': + if target_element.Type == ELEM_TYPE_BUTTON: + target_element.FileOrFolderName = file_name + else: + target_element.Update(file_name) + elif self.BType == BUTTON_TYPE_COLOR_CHOOSER: # Color Chooser + qcolor = QColorDialog.getColor() + rgb_color = qcolor.getRgb() + color = '#' + ''.join('%02x' % i for i in rgb_color[:3]) + if self.Target == (None, None): + self.FileOrFolderName = color + else: + target_element.Update(color) + elif self.BType == BUTTON_TYPE_CLOSES_WIN: # Closes Window + # first, get the results table built + # modify the Results table in the parent FlexForm object + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = self.ButtonText + self.ParentForm.FormRemainedOpen = False + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.App.ExitMainLoop() + self.ParentForm.IgnoreClose = True + self.ParentForm.MasterFrame.Close() + if self.ParentForm.NonBlocking: + Window.DecrementOpenCount() + self.ParentForm._Close() + elif self.BType == BUTTON_TYPE_READ_FORM: # Read Button + # first, get the results table built + # modify the Results table in the parent FlexForm object + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = self.ButtonText + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out + self.ParentForm.App.ExitMainLoop() + elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = self.ButtonText + if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out + self.ParentForm.App.ExitMainLoop() + self.ParentForm.IgnoreClose = True + self.ParentForm.MasterFrame.Close() + self.ParentForm._Close() + Window.DecrementOpenCount() + elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window + should_submit_window = False + + if should_submit_window: + self.ParentForm.LastButtonClicked = target_element.Key + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.App.ExitMainLoop() + return + + def Update( + self, + text=None, + button_color=(None, None), + disabled=None, + image_data=None, + image_filename=None, + font=None, + visible=None, + ): + if text is not None: + self.WxButton.SetLabelText(text) + self.ButtonText = text + fg = bg = None + if button_color != (None, None): + self.ButtonColor = button_color + fg, bg = button_color + super().Update(self.WxButton, background_color=bg, text_color=fg, font=font, visible=visible, disabled=disabled) + + def GetText(self): + return self.ButtonText + + def SetFocus(self): + self.QT_QPushButton.setFocus() + + get_text = GetText + set_focus = SetFocus + update = Update + + +def convert_tkinter_filetypes_to_wx(filetypes): + wx_filetypes = '' + for item in filetypes: + filetype = item[0] + ' (' + item[1] + ')|' + item[1] + wx_filetypes += filetype + return wx_filetypes + + +# ------------------------- Button lazy functions ------------------------- # +B = Button +Btn = Button + + +# ---------------------------------------------------------------------- # +# ProgreessBar # +# ---------------------------------------------------------------------- # +class ProgressBar(Element): + def __init__( + self, + max_value, + orientation=None, + size=(None, None), + start_value=0, + auto_size_text=None, + bar_color=(None, None), + style=None, + border_width=None, + relief=None, + key=None, + pad=None, + disabled=False, + visible=True, + size_px=(None, None), + ): + ''' + ProgressBar Element + :param max_value: + :param orientation: + :param size: + :param auto_size_text: + :param bar_color: + :param style: + :param border_width: + :param relief: + :param key: + :param pad: + ''' + self.MaxValue = max_value + self.TKProgressBar = None + self.Cancelled = False + self.NotRunning = True + self.Orientation = orientation if orientation else DEFAULT_METER_ORIENTATION + self.BarColor = bar_color if bar_color != (None, None) else DEFAULT_PROGRESS_BAR_COLOR + self.BarStyle = style if style else DEFAULT_PROGRESS_BAR_STYLE + self.BorderWidth = border_width if border_width is not None else DEFAULT_PROGRESS_BAR_BORDER_WIDTH + self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF + self.BarExpired = False + self.StartValue = start_value + self.Disabled = disabled + tsize = size + if size[0] is not None and size[0] < 100: + # tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] + tsize = size[0] * 10, size[1] + self.WxGauge = None # type: wx.Gauge + + super().__init__( + ELEM_TYPE_PROGRESS_BAR, + size=tsize, + auto_size_text=auto_size_text, + key=key, + pad=pad, + visible=visible, + size_px=size_px, + ) + + # returns False if update failed + def UpdateBar(self, current_count, max=None): + try: # Could havae been destroyed by user + if max is not None: + self.WxGauge.SetRange(max) + self.WxGauge.SetValue(current_count) + except: + pass + return True + + def Update(self, visible=None): + super().Update(self.WxGauge, visible=visible) + + update = Update + update_bar = UpdateBar + + +# ---------------------------------------------------------------------- # +# Image # +# ---------------------------------------------------------------------- # +class Image(Element): + def __init__(self, filename=None, data=None, background_color=None, size=(None, None), pad=None, key=None, tooltip=None): + ''' + Image Element + :param filename: + :param data: + :param background_color: + :param size: + :param pad: + :param key: + :param tooltip: + ''' + self.Filename = filename + self.Data = data + self.tktext_label = None + self.BackgroundColor = background_color + if data is None and filename is None: + print('* Warning... no image specified in Image Element! *') + super().__init__( + ELEM_TYPE_IMAGE, + size=size, + background_color=background_color, + pad=pad, + key=key, + tooltip=tooltip, + size_px=size, + ) + return + + def Update(self, filename=None, data=None, size=(None, None)): + if filename is not None: + image = tk.PhotoImage(file=filename) + elif data is not None: + # if type(data) is bytes: + try: + image = tk.PhotoImage(data=data) + except: + return # an error likely means the window has closed so exit + # else: + # image = data + else: + return + width, height = size[0] or image.width(), size[1] or image.height() + self.tktext_label.configure(image=image, width=width, height=height) + self.tktext_label.image = image + + update = Update + + +# ---------------------------------------------------------------------- # +# Canvas # +# ---------------------------------------------------------------------- # +class Canvas(Element): + def __init__(self, canvas=None, background_color=None, size=(None, None), pad=None, key=None, tooltip=None): + ''' + Canvas Element + :param canvas: + :param background_color: + :param size: + :param pad: + :param key: + :param tooltip: + ''' + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self._TKCanvas = canvas + + super().__init__(ELEM_TYPE_CANVAS, background_color=background_color, size=size, pad=pad, key=key, tooltip=tooltip) + return + + @property + def TKCanvas(self): + if self._TKCanvas is None: + print('*** Did you forget to call Finalize()? Your code should look something like: ***') + print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') + return self._TKCanvas + + +# ---------------------------------------------------------------------- # +# Graph # +# ---------------------------------------------------------------------- # +class Graph(Element): + def __init__( + self, + canvas_size, + graph_bottom_left, + graph_top_right, + background_color=None, + pad=None, + change_submits=False, + drag_submits=False, + key=None, + tooltip=None, + ): + ''' + Graph Element + :param canvas_size: + :param graph_bottom_left: + :param graph_top_right: + :param background_color: + :param pad: + :param key: + :param tooltip: + ''' + self.CanvasSize = canvas_size + self.BottomLeft = graph_bottom_left + self.TopRight = graph_top_right + self._TKCanvas = None + self._TKCanvas2 = None + self.ChangeSubmits = change_submits + self.DragSubmits = drag_submits + self.ClickPosition = (None, None) + self.MouseButtonDown = False + super().__init__(ELEM_TYPE_GRAPH, background_color=background_color, size_px=canvas_size, pad=pad, key=key, tooltip=tooltip) + return + + def _convert_xy_to_canvas_xy(self, x_in, y_in): + if None in (x_in, y_in): + return None, None + scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) + scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) + new_x = 0 + scale_x * (x_in - self.BottomLeft[0]) + new_y = self.CanvasSize[1] + scale_y * (y_in - self.BottomLeft[1]) + return new_x, new_y + + def _convert_canvas_xy_to_xy(self, x_in, y_in): + if None in (x_in, y_in): + return None, None + scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) + scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) + + new_x = x_in / scale_x + self.BottomLeft[0] + new_y = (y_in - self.CanvasSize[1]) / scale_y + self.BottomLeft[1] + return int(new_x), int(new_y) + + def DrawLine(self, point_from, point_to, color='black', width=1): + if point_from == (None, None): + return + converted_point_from = self._convert_xy_to_canvas_xy(point_from[0], point_from[1]) + converted_point_to = self._convert_xy_to_canvas_xy(point_to[0], point_to[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_line(converted_point_from, converted_point_to, width=width, fill=color) + + def DrawPoint(self, point, size=2, color='black'): + if point == (None, None): + return + converted_point = self._convert_xy_to_canvas_xy(point[0], point[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_oval( + converted_point[0] - size, + converted_point[1] - size, + converted_point[0] + size, + converted_point[1] + size, + fill=color, + outline=color, + ) + + def DrawCircle(self, center_location, radius, fill_color=None, line_color='black'): + if center_location == (None, None): + return + converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_oval( + converted_point[0] - radius, + converted_point[1] - radius, + converted_point[0] + radius, + converted_point[1] + radius, + fill=fill_color, + outline=line_color, + ) + + def DrawOval(self, top_left, bottom_right, fill_color=None, line_color=None): + converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_oval( + converted_top_left[0], + converted_top_left[1], + converted_bottom_right[0], + converted_bottom_right[1], + fill=fill_color, + outline=line_color, + ) + + def DrawArc(self, top_left, bottom_right, extent, start_angle, style=None, arc_color='black'): + converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + tkstyle = tk.PIESLICE if style is None else style + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_arc( + converted_top_left[0], + converted_top_left[1], + converted_bottom_right[0], + converted_bottom_right[1], + extent=extent, + start=start_angle, + style=tkstyle, + outline=arc_color, + ) + + def DrawRectangle(self, top_left, bottom_right, fill_color=None, line_color=None): + converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) + converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + return self._TKCanvas2.create_rectangle( + converted_top_left[0], + converted_top_left[1], + converted_bottom_right[0], + converted_bottom_right[1], + fill=fill_color, + outline=line_color, + ) + + def DrawText(self, text, location, color='black', font=None, angle=0): + if location == (None, None): + return + converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + text_id = self._TKCanvas2.create_text(converted_point[0], converted_point[1], text=text, font=font, fill=color, angle=angle) + return text_id + + def Erase(self): + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + self._TKCanvas2.delete('all') + + def Update(self, background_color): + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + self._TKCanvas2.configure(background=background_color) + + def Move(self, x_direction, y_direction): + zero_converted = self._convert_xy_to_canvas_xy(0, 0) + shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) + shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) + if self._TKCanvas2 is None: + print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') + print('Call Window.Finalize() prior to this operation') + return None + self._TKCanvas2.move('all', shift_amount[0], shift_amount[1]) + + def MoveFigure(self, figure, x_direction, y_direction): + zero_converted = self._convert_xy_to_canvas_xy(0, 0) + shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) + shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) + if figure is None: + print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') + print('Call Window.Finalize() prior to all graph operations') + return None + self._TKCanvas2.move(figure, shift_amount[0], shift_amount[1]) + + def change_coordinates(self, graph_bottom_left, graph_top_right): + """ + Changes the corrdinate system to a new one. The same 2 points in space are used to define the coorinate + system - the bottom left and the top right values of your graph. + + :param graph_bottom_left: Tuple[int, int] (x,y) The bottoms left corner of your coordinate system + :param graph_top_right: Tuple[int, int] (x,y) The top right corner of your coordinate system + """ + self.BottomLeft = graph_bottom_left + self.TopRight = graph_top_right + + @property + def TKCanvas(self): + if self._TKCanvas2 is None: + print('*** Did you forget to call Finalize()? Your code should look something like: ***') + print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') + return self._TKCanvas2 + + # Realtime button release callback + def ButtonReleaseCallBack(self, event): + self.ClickPosition = (None, None) + self.LastButtonClickedWasRealtime = not self.DragSubmits + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() + if self.DragSubmits: + self.ParentForm.LastButtonClicked = None + self.MouseButtonDown = False + + # Realtime button callback + def ButtonPressCallBack(self, event): + self.ClickPosition = self._convert_canvas_xy_to_xy(event.x, event.y) + self.ParentForm.LastButtonClickedWasRealtime = self.DragSubmits + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick out of loop if read was called + self.MouseButtonDown = True + + # Realtime button callback + def MotionCallBack(self, event): + if not self.MouseButtonDown: + return + self.ClickPosition = self._convert_canvas_xy_to_xy(event.x, event.y) + self.ParentForm.LastButtonClickedWasRealtime = self.DragSubmits + if self.Key is not None: + self.ParentForm.LastButtonClicked = self.Key + else: + self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick out of loop if read was called + + +# ---------------------------------------------------------------------- # +# Frame # +# ---------------------------------------------------------------------- # +class Frame(Element): + def __init__( + self, + title, + layout, + title_color=None, + background_color=None, + title_location=None, + element_justification='left', + relief=DEFAULT_FRAME_RELIEF, + size=(None, None), + size_px=(None, None), + font=None, + pad=None, + border_width=None, + key=None, + tooltip=None, + ): + ''' + Frame Element + :param title: + :param layout: + :param title_color: + :param background_color: + :param title_location: + :param relief: + :param size: + :param font: + :param pad: + :param border_width: + :param key: + :param tooltip: + ''' + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.Rows = [] + # self.ParentForm = None + self.TKFrame = None + self.Title = title + self.Relief = relief + self.TitleLocation = title_location + self.BorderWidth = border_width + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.ElementJustification = element_justification + + self._Layout(layout) + + super().__init__( + ELEM_TYPE_FRAME, + background_color=background_color, + text_color=title_color, + size=size, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + size_px=size_px, + ) + return + + def _AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def _Layout(self, rows): + for row in rows: + self._AddRow(*row) + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + +# ---------------------------------------------------------------------- # +# Separator # +# Routes stdout, stderr to a scrolled window # +# ---------------------------------------------------------------------- # +class VerticalSeparator(Element): + def __init__(self, size=(None, None), size_px=None, pad=None): + ''' + VerticalSeperator - A separator that spans only 1 row in a vertical fashion + :param pad: + ''' + self.Orientation = 'vertical' # for now only vertical works + self.Disabled = None + self.WxStaticLine = None # type: wx.StaticLine + super().__init__(ELEM_TYPE_SEPARATOR, pad=pad, size=size, size_px=size_px) + + +VSeparator = VerticalSeparator +VSeparator = VerticalSeparator +VSep = VerticalSeparator + + +# ---------------------------------------------------------------------- # +# Separator # +# ---------------------------------------------------------------------- # +class HorizontalSeparator(Element): + def __init__(self, pad=None, size=(None, None), size_px=(None, None)): + ''' + VerticalSeperator - A separator that spans only 1 row in a vertical fashion + :param pad: + ''' + self.Orientation = 'horizontal' # for now only vertical works + self.Disabled = None + self.WxStaticLine = None # type: wx.StaticLine + + super().__init__(ELEM_TYPE_SEPARATOR, pad=pad, size=size, size_px=size_px) + + +HSeperator = HorizontalSeparator +HSep = HorizontalSeparator + + +# ---------------------------------------------------------------------- # +# Tab # +# ---------------------------------------------------------------------- # +class Tab(Element): + def __init__( + self, + title, + layout, + title_color=None, + background_color=None, + font=None, + element_justification='left', + pad=None, + disabled=False, + border_width=None, + key=None, + tooltip=None, + ): + ''' + Tab Element + :param title: + :param layout: + :param title_color: + :param background_color: + :param font: + :param pad: + :param disabled: + :param border_width: + :param key: + :param tooltip: + ''' + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.Rows = [] + self.TKFrame = None + self.Title = title + self.BorderWidth = border_width + self.Disabled = disabled + self.ParentNotebook = None + self.TabID = None + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.ElementJustification = element_justification + + self._Layout(layout) + + super().__init__( + ELEM_TYPE_TAB, + background_color=background_color, + text_color=title_color, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + ) + return + + def _AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def _Layout(self, rows): + for row in rows: + self._AddRow(*row) + return self + + def Update(self, disabled=None): # TODO Disable / enable of tabs is not complete + if disabled is None: + return + self.Disabled = disabled + state = 'disabled' if disabled is True else 'normal' + self.ParentNotebook.tab(self.TabID, state=state) + return self + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + update = Update + + +# ---------------------------------------------------------------------- # +# TabGroup # +# ---------------------------------------------------------------------- # +class TabGroup(Element): + def __init__( + self, + layout, + tab_location=None, + title_color=None, + selected_title_color=None, + background_color=None, + font=None, + change_submits=False, + pad=None, + border_width=None, + theme=None, + key=None, + tooltip=None, + ): + ''' + TabGroup Element + :param layout: + :param tab_location: + :param title_color: + :param selected_title_color: + :param background_color: + :param font: + :param change_submits: + :param pad: + :param border_width: + :param theme: + :param key: + :param tooltip: + ''' + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.SelectedTitleColor = selected_title_color + self.Rows = [] + self.TKNotebook = None + self.TabCount = 0 + self.BorderWidth = border_width + self.Theme = theme + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.ChangeSubmits = change_submits + self.TabLocation = tab_location + + self._Layout(layout) + + super().__init__( + ELEM_TYPE_TAB_GROUP, + background_color=background_color, + text_color=title_color, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + ) + return + + def _AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def _Layout(self, rows): + for row in rows: + self._AddRow(*row) + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def FindKeyFromTabName(self, tab_name): + for row in self.Rows: + for element in row: + if element.Title == tab_name: + return element.Key + return None + + find_key_from_tab_name = FindKeyFromTabName + + +# ---------------------------------------------------------------------- # +# Slider # +# ---------------------------------------------------------------------- # +class Slider(Element): + def __init__( + self, + range=(None, None), + default_value=None, + resolution=None, + tick_interval=None, + orientation=None, + border_width=None, + relief=None, + change_submits=False, + disabled=False, + size=(None, None), + size_px=(None, None), + font=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None, + ): + ''' + Slider Element + :param range: + :param default_value: + :param resolution: + :param orientation: + :param border_width: + :param relief: + :param change_submits: + :param disabled: + :param size: + :param font: + :param background_color: + :param text_color: + :param key: + :param pad: + :param tooltip: + ''' + self.TKScale = None + self.Range = (1, 10) if range == (None, None) else range + self.DefaultValue = self.Range[0] if default_value is None else default_value + self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION + self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH + self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF + self.Resolution = 1 if resolution is None else resolution + self.ChangeSubmits = change_submits + self.Disabled = disabled + self.TickInterval = tick_interval + temp_size = size + if temp_size == (None, None): + temp_size = (20, 20) if orientation.startswith('h') else (8, 20) + + super().__init__( + ELEM_TYPE_INPUT_SLIDER, + size=temp_size, + font=font, + background_color=background_color, + text_color=text_color, + key=key, + pad=pad, + tooltip=tooltip, + size_px=size_px, + ) + return + + def Update(self, value=None, range=(None, None), disabled=None): + if value is not None: + try: + self.TKIntVar.set(value) + if range != (None, None): + self.TKScale.config(from_=range[0], to_=range[1]) + except: + pass + self.DefaultValue = value + if disabled == True: + self.TKScale['state'] = 'disabled' + elif disabled == False: + self.TKScale['state'] = 'normal' + + update = Update + + +# +# ---------------------------------------------------------------------- # +# Column # +# ---------------------------------------------------------------------- # +class Column(Element): + def __init__( + self, + layout, + background_color=None, + element_justification='left', + size=(None, None), + size_px=(None, None), + pad=None, + scrollable=False, + vertical_scroll_only=False, + right_click_menu=None, + key=None, + visible=True, + ): + ''' + Column Element + :param layout: + :param background_color: + :param size: + :param pad: + :param scrollable: + :param key: + ''' + self.UseDictionary = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.ParentWindow = None + self.Rows = [] + self.Scrollable = scrollable + self.VerticalScrollOnly = vertical_scroll_only + self.RightClickMenu = right_click_menu + bg = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.WxBoxSizer = None # type: wx.BoxSizer + self.WxHSizer = None # type: wx.BoxSizer + self._Layout(layout) + self.ElementJustification = element_justification + tsize = size_px if size_px != (None, None) else size + + super().__init__(ELEM_TYPE_COLUMN, background_color=background_color, size_px=tsize, pad=pad, key=key, visible=visible) + return + + def _AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + if element.Key is not None: + self.UseDictionary = True + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + def _Layout(self, rows): + for row in rows: + self._AddRow(*row) + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def Update(self, visible=None): + if visible: + self.WxHSizer.Show(self.WxBoxSizer, recursive=True) + self.ParentForm.VisibilityChanged() + elif visible is False: + self.WxHSizer.Hide(self.WxBoxSizer, recursive=True) + self.ParentForm.VisibilityChanged() + + update = Update + + +# ---------------------------------------------------------------------- # +# Menu # +# ---------------------------------------------------------------------- # +class Menu(Element): + def __init__(self, menu_definition, background_color=None, size=(None, None), tearoff=False, pad=None, key=None): + ''' + Menu Element + :param menu_definition: + :param background_color: + :param size: + :param tearoff: + :param pad: + :param key: + ''' + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.MenuDefinition = menu_definition + self.TKMenu = None + self.Tearoff = tearoff + + super().__init__(ELEM_TYPE_MENUBAR, background_color=background_color, size=size, pad=pad, key=key) + return + + def _MenuItemChosenCallback(self, item_chosen): + # print('IN MENU ITEM CALLBACK', item_chosen) + self.ParentForm.LastButtonClicked = item_chosen + self.ParentForm.FormRemainedOpen = True + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop + + +# ---------------------------------------------------------------------- # +# Table # +# ---------------------------------------------------------------------- # +class Table(Element): + def __init__( + self, + values, + headings=None, + visible_column_map=None, + col_widths=None, + def_col_width=10, + auto_size_columns=True, + max_col_width=20, + select_mode=None, + display_row_numbers=False, + num_rows=None, + font=None, + justification='right', + text_color=None, + background_color=None, + alternating_row_color=None, + size=(None, None), + change_submits=False, + bind_return_key=False, + pad=None, + key=None, + tooltip=None, + ): + ''' + Table Element + :param values: + :param headings: + :param visible_column_map: + :param col_widths: + :param def_col_width: + :param auto_size_columns: + :param max_col_width: + :param select_mode: + :param display_row_numbers: + :param font: + :param justification: + :param text_color: + :param background_color: + :param size: + :param pad: + :param key: + :param tooltip: + ''' + self.Values = values + self.ColumnHeadings = headings + self.ColumnsToDisplay = visible_column_map + self.ColumnWidths = col_widths + self.MaxColumnWidth = max_col_width + self.DefaultColumnWidth = def_col_width + self.AutoSizeColumns = auto_size_columns + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.TextColor = text_color + self.Justification = justification + self.InitialState = None + self.SelectMode = select_mode + self.DisplayRowNumbers = display_row_numbers + self.NumRows = num_rows if num_rows is not None else size[1] + self.TKTreeview = None + self.AlternatingRowColor = alternating_row_color + self.SelectedRows = [] + self.ChangeSubmits = change_submits + self.BindReturnKey = bind_return_key + self.StartingRowNumber = 0 # When displaying row numbers, where to start + self.RowHeaderText = 'Row' + super().__init__( + ELEM_TYPE_TABLE, + text_color=text_color, + background_color=background_color, + font=font, + size=size, + pad=pad, + key=key, + tooltip=tooltip, + ) + return + + def Update(self, values=None): + if values is not None: + children = self.TKTreeview.get_children() + for i in children: + self.TKTreeview.detach(i) + self.TKTreeview.delete(i) + children = self.TKTreeview.get_children() + # self.TKTreeview.delete(*self.TKTreeview.get_children()) + for i, value in enumerate(values): + if self.DisplayRowNumbers: + value = [i + self.StartingRowNumber] + value + id = self.TKTreeview.insert('', 'end', text=i, iid=i + 1, values=value, tag=i % 2) + if self.AlternatingRowColor is not None: + self.TKTreeview.tag_configure(1, background=self.AlternatingRowColor) + self.Values = values + self.SelectedRows = [] + + update = Update + + +# ---------------------------------------------------------------------- # +# Tree # +# ---------------------------------------------------------------------- # +class Tree(Element): + def __init__( + self, + data=None, + headings=None, + visible_column_map=None, + col_widths=None, + col0_width=10, + def_col_width=10, + auto_size_columns=True, + max_col_width=20, + select_mode=None, + show_expanded=False, + change_submits=False, + font=None, + justification='right', + text_color=None, + background_color=None, + num_rows=None, + pad=None, + key=None, + tooltip=None, + ): + ''' + Tree Element + :param headings: + :param visible_column_map: + :param col_widths: + :param def_col_width: + :param auto_size_columns: + :param max_col_width: + :param select_mode: + :param font: + :param justification: + :param text_color: + :param background_color: + :param num_rows: + :param pad: + :param key: + :param tooltip: + ''' + self.TreeData = data + self.ColumnHeadings = headings + self.ColumnsToDisplay = visible_column_map + self.ColumnWidths = col_widths + self.MaxColumnWidth = max_col_width + self.DefaultColumnWidth = def_col_width + self.AutoSizeColumns = auto_size_columns + self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR + self.TextColor = text_color + self.Justification = justification + self.InitialState = None + self.SelectMode = select_mode + self.ShowExpanded = show_expanded + self.NumRows = num_rows + self.Col0Width = col0_width + self.TKTreeview = None + self.SelectedRows = [] + self.ChangeSubmits = change_submits + + super().__init__( + ELEM_TYPE_TREE, + text_color=text_color, + background_color=background_color, + font=font, + pad=pad, + key=key, + tooltip=tooltip, + ) + return + + def add_treeview_data(self, node): + # print(f'Inserting {node.key} under parent {node.parent}') + if node.key != '': + self.TKTreeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, open=self.ShowExpanded) + for node in node.children: + self.add_treeview_data(node) + + def Update(self, values=None, key=None, value=None, text=None): + if values is not None: + children = self.TKTreeview.get_children() + for i in children: + self.TKTreeview.detach(i) + self.TKTreeview.delete(i) + children = self.TKTreeview.get_children() + self.TreeData = values + self.add_treeview_data(self.TreeData.root_node) + self.SelectedRows = [] + if key is not None: + item = self.TKTreeview.item(key) + if value is not None: + self.TKTreeview.item(key, values=value) + if text is not None: + self.TKTreeview.item(key, text=text) + item = self.TKTreeview.item(key) + return self + + update = Update + + +class TreeData(object): + class Node(object): + def __init__(self, parent, key, text, values): + self.parent = parent + self.children = [] + self.key = key + self.text = text + self.values = values + + def _Add(self, node): + self.children.append(node) + + def __init__(self): + self.tree_dict = {} + self.root_node = self.Node('', '', 'root', []) + self.tree_dict[''] = self.root_node + + def _AddNode(self, key, node): + self.tree_dict[key] = node + + def Insert(self, parent, key, text, values): + node = self.Node(parent, key, text, values) + self.tree_dict[key] = node + parent_node = self.tree_dict[parent] + parent_node._Add(node) + + def __repr__(self): + return self._NodeStr(self.root_node, 1) + + def _NodeStr(self, node, level): + return '\n'.join([str(node.key) + ' : ' + str(node.text)] + [' ' * 4 * level + self._NodeStr(child, level + 1) for child in node.children]) + + +# ---------------------------------------------------------------------- # +# Error Element # +# ---------------------------------------------------------------------- # +class ErrorElement(Element): + def __init__(self, key=None): + ''' + Error Element + :param key: + ''' + self.Key = key + + super().__init__(ELEM_TYPE_ERROR, key=key) + return + + def Update(self, *args, **kwargs): + PopupError( + 'Keyword error in Update', + 'You need to stop this madness and check your spelling', + 'Bad key = {}'.format(self.Key), + 'Your bad line of code may resemble this:', + 'window.FindElement("{}")'.format(self.Key), + ) + return self + + def Get(self): + return 'This is NOT a valid Element!\nSTOP trying to do things with it or I will have to crash at some point!' + + update = Update + get = Get + + +Stretch = ErrorElement + + +# ------------------------------------------------------------------------- # +# Tray CLASS # +# ------------------------------------------------------------------------- # +class SystemTray: + def __init__(self, menu=None, filename=None, data=None, data_base64=None, tooltip=None): + ''' + SystemTray - create an icon in the system tray + :param menu: Menu definition + :param filename: filename for icon + :param data: in-ram image for icon + :param data_base64: basee-64 data for icon + :param tooltip: tooltip string + ''' + self.Menu = menu + self.TrayIcon = None + self.Shown = False + self.MenuItemChosen = TIMEOUT_KEY + self.LastMessage = None + self.LastTitle = None + self.App = None + self.Filename = filename + self.timer = None + self.DataBase64 = data_base64 + if Window.highest_level_app is None: + self.App = Window.highest_level_app = wx.App(False) + # This could be a very dangerous thing to add! + # It was needed in order for an application to run the Tray in a Thread + self.App.SetAssertMode(wx.APP_ASSERT_SUPPRESS) + else: + self.App = Window.highest_level_app + self.Tooltip = tooltip + + frame = wx.Frame(None, title='Tray icon frame') + if filename: + self.icon = wx.Icon(filename, wx.BITMAP_TYPE_ANY) + elif data_base64: + self.icon = PyEmbeddedImage(data_base64).GetIcon() + else: + self.icon = PyEmbeddedImage(DEFAULT_BASE64_ICON).GetIcon() + self.TaskBarIcon = self.CustomTaskBarIcon(frame, self.App, self.Menu, self.icon, tooltip=tooltip) + + # self.App.MainLoop() + + class CustomTaskBarIcon(wx.adv.TaskBarIcon): + def __init__(self, frame, app, menu, icon, tooltip=None): + wx.adv.TaskBarIcon.__init__(self) + self.frame = frame + self.app = app + self.menu_item_chosen = None + self.menu = menu + self.id_to_text = {} + self.tooltip = tooltip or wx.EmptyString + + self.SetIcon(icon, tooltip=self.tooltip) + self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.OnTaskBarLeftClick) + self.Bind(wx.adv.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarLeftDoubleClick) + self.Bind(wx.adv.EVT_TASKBAR_RIGHT_DOWN, self.OnTaskBarRightClick) + self.Bind(wx.adv.EVT_TASKBAR_BALLOON_CLICK, self.OnTaskBarMessageClick) + self.Bind(wx.EVT_MENU, self.OnMenu) + + def OnTaskBarActivate(self, evt): + pass + + def OnTaskBarClose(self, evt): + self.frame.Close() + + def OnTaskBarLeftClick(self, evt): + # print('Got a LEFT click!') + self.menu_item_chosen = EVENT_SYSTEM_TRAY_ICON_ACTIVATED + self.app.ExitMainLoop() + + def OnTaskBarMessageClick(self, evt): + # print('Got a LEFT click!') + self.menu_item_chosen = EVENT_SYSTEM_TRAY_MESSAGE_CLICKED + self.app.ExitMainLoop() + + def OnTaskBarLeftDoubleClick(self, evt): + # print('Got a double click!') + self.menu_item_chosen = EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED + self.app.ExitMainLoop() + + def CreatePopupMenu(self): + # print(f'Popup menu = {self.menu}') + menu = wx.Menu() + AddMenuItem(menu, self.menu[1], self) + return menu + + def OnTaskBarRightClick(self, evt): + # print('Got a right click!') + self.menu_item_chosen = EVENT_SYSTEM_TRAY_ICON_RIGHT_CLICK + # self.app.ExitMainLoop() + + def OnMenu(self, event): + # print(f'On Menu {event}') + menu = event.EventObject + text = '' + item = menu.FindItemById(event.Id) + # for item in menu.MenuItems: + # if item.Id == event.Id: + # print('** FOUND MENU ENTRY! **') + # print(f'item = {item}') + text = self.id_to_text[item] + # text = self.id_to_text[item.Id] + self.menu_item_chosen = text + self.app.ExitMainLoop() + + def Read(self, timeout=None): + ''' + Reads the context menu + :param timeout: Optional. Any value other than None indicates a non-blocking read + :return: + ''' + # if not self.Shown: + # self.Shown = True + # self.TrayIcon.show() + timeout1 = timeout + # if timeout1 == 0: + # timeout1 = 1 + # if wx.GetApp(): + # wx.GetApp().ProcessPendingEvents() + # self.App.ProcessPendingEvents() + # self.App.ProcessIdle() + # return self.MenuItemChosen + if timeout1 is not None: + try: + self.timer = wx.Timer(self.TaskBarIcon) + self.TaskBarIcon.Bind(wx.EVT_TIMER, self.timer_timeout) + self.timer.Start(milliseconds=timeout1, oneShot=wx.TIMER_ONE_SHOT) + except: + print('*** Got error in Read ***') + self.RunningMainLoop = True + self.App.MainLoop() + self.RunningMainLoop = False + if self.timer: + self.timer.Stop() + self.TaskBarIcon.Unbind(wx.EVT_TIMER) + del self.timer + self.timer = None + self.MenuItemChosen = self.TaskBarIcon.menu_item_chosen + return self.MenuItemChosen + + def timer_timeout(self, event): + self.TaskBarIcon.Unbind(wx.EVT_TIMER) + del self.timer + self.timer = None + self.TaskBarIcon.menu_item_chosen = TIMEOUT_KEY + self.App.ExitMainLoop() + + def Hide(self): + self.TaskBarIcon.RemoveIcon() + + def UnHide(self): + self.TaskBarIcon.SetIcon(icon=self.TaskBarIcon.icon, tooltip=self.TaskBarIcon.tooltip) + + def ShowMessage(self, title, message, filename=None, data=None, data_base64=None, messageicon=None, time=10000): + ''' + Shows a balloon above icon in system tray + :param title: Title shown in balloon + :param message: Message to be displayed + :param filename: Optional icon filename + :param data: Optional in-ram icon + :param data_base64: Optional base64 icon + :param time: How long to display message in milliseconds + :return: + ''' + if messageicon is None: + self.TaskBarIcon.ShowBalloon(title, message, msec=time) + else: + self.TaskBarIcon.ShowBalloon(title, message, msec=time, flags=messageicon) + + return self + + def Close(self): + ''' + + :return: + ''' + self.Hide() + # Don't close app because windows could be depending on it + # self.App.quit() + + def _DisableAsserts(self): + wx.DisableAsserts() + + def Update( + self, + menu=None, + tooltip=None, + filename=None, + data=None, + data_base64=None, + ): + ''' + Updates the menu, tooltip or icon + :param menu: menu defintion + :param tooltip: string representing tooltip + :param filename: icon filename + :param data: icon raw image + :param data_base64: icon base 64 image + :return: + ''' + # Menu + if menu is not None: + self.TaskBarIcon.menu = menu + if filename: + self.icon = wx.Icon(filename, wx.BITMAP_TYPE_ANY) + elif data_base64: + self.icon = PyEmbeddedImage(data_base64).GetIcon() + elif not self.icon: + self.icon = PyEmbeddedImage(DEFAULT_BASE64_ICON).GetIcon() + if self.icon: + self.Tooltip = tooltip or self.Tooltip or self.TaskBarIcon.tooltip or wx.EmptyString + self.TaskBarIcon.SetIcon(self.icon, tooltip=self.Tooltip) + # Tooltip + # if tooltip is not None: + # self.TrayIcon.setToolTip(str(tooltip)) + # Icon + # qicon = None + # if filename is not None: + # icon = wx.Icon(filename, wx.BITMAP_TYPE_ICO) + # self.TaskBarIcon.SetIcon(icon, tooltip=tooltip) + # elif data is not None: + # ba = QtCore.QByteArray.fromRawData(data) + # pixmap = QtGui.QPixmap() + # pixmap.loadFromData(ba) + # qicon = QIcon(pixmap) + # elif data_base64 is not None: + # ico1 = base64.b64decode(data_base64) + # fout = open("zzztemp_icon.ico", "wb") + # fout.write(ico1) + # fout.close() + # icon = wx.Icon('zzztemp_icon.ico', wx.BITMAP_TYPE_ICO) + # self.TrayIcon.SetIcon(icon, tooltip=tooltip) + # os.remove("zzztemp_icon.ico") + # if qicon is not None: + # self.TrayIcon.setIcon(qicon) + + close = Close + hide = Hide + read = Read + show_message = ShowMessage + un_hide = UnHide + update = Update + + +class DragFrame(wx.Frame): + def __init__(self, title=''): + wx.Frame.__init__(self, None, title=title) + + def on_mouse(self, event): + ''' + implement dragging + ''' + # print('on_mouse') + if not event.Dragging(): + self._dragPos = None + return + # self.CaptureMouse() + if not self._dragPos: + self._dragPos = event.GetPosition() + else: + pos = event.GetPosition() + displacement = self._dragPos - pos + self.SetPosition(self.GetPosition() - displacement) + + +# ------------------------------------------------------------------------- # +# Window CLASS # +# ------------------------------------------------------------------------- # +class Window: + + NumOpenWindows = 0 + user_defined_icon = None + hidden_master_root = None + QTApplication = None + active_popups = {} + highest_level_app = None + stdout_is_rerouted = False + stdout_location = None + + def __init__( + self, + title, + layout=None, + default_element_size=DEFAULT_ELEMENT_SIZE, + default_button_element_size=(None, None), + auto_size_text=None, + auto_size_buttons=None, + location=(None, None), + size=(None, None), + element_padding=None, + button_color=None, + font=None, + progress_bar_color=(None, None), + background_color=None, + border_depth=None, + auto_close=False, + auto_close_duration=None, + icon=DEFAULT_BASE64_ICON, + force_toplevel=False, + element_justification='left', + alpha_channel=1, + return_keyboard_events=False, + use_default_focus=True, + text_justification=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + resizable=True, + disable_close=False, + disable_minimize=False, + background_image=None, + finalize=False, + ): + ''' + + :param title: + :param default_element_size: + :param default_button_element_size: + :param auto_size_text: + :param auto_size_buttons: + :param location: + :param size: + :param element_padding: + :param button_color: + :param font: + :param progress_bar_color: + :param background_color: + :param border_depth: + :param auto_close: + :param auto_close_duration: + :param icon: + :param force_toplevel: + :param alpha_channel: + :param return_keyboard_events: + :param use_default_focus: + :param text_justification: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param resizable: + :param disable_close: + :param background_image: + ''' + self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT + self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS + self.Title = title + self.Rows = [] # a list of ELEMENTS for this row + self.DefaultElementSize = _convert_tkinter_size_to_Wx(default_element_size) + self.DefaultButtonElementSize = _convert_tkinter_size_to_Wx(default_button_element_size) if default_button_element_size != (None, None) else _convert_tkinter_size_to_Wx(DEFAULT_BUTTON_ELEMENT_SIZE) + self.Location = location + self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR + self.BackgroundColor = background_color if background_color else DEFAULT_BACKGROUND_COLOR + self.ParentWindow = None + self.Font = font if font else DEFAULT_FONT + self.RadioDict = {} + self.BorderDepth = border_depth + self.WindowIcon = Window.user_defined_icon if Window.user_defined_icon is not None else icon if icon is not None else DEFAULT_WINDOW_ICON + self.AutoClose = auto_close + self.NonBlocking = False + self.TKroot = None + self.TKrootDestroyed = False + self.CurrentlyRunningMainloop = False + self.FormRemainedOpen = False + self.TKAfterID = None + self.ProgressBarColor = progress_bar_color + self.AutoCloseDuration = auto_close_duration + self.RootNeedsDestroying = False + self.Shown = False + self.ReturnValues = None + self.ReturnValuesList = [] + self.ReturnValuesDictionary = {} + self.DictionaryKeyCounter = 0 + self.LastButtonClicked = None + self.LastButtonClickedWasRealtime = False + self.UseDictionary = False + self.UseDefaultFocus = use_default_focus + self.ReturnKeyboardEvents = return_keyboard_events + self.LastKeyboardEvent = None + self.TextJustification = text_justification + self.NoTitleBar = no_titlebar + self.GrabAnywhere = grab_anywhere + self.KeepOnTop = keep_on_top + self.ForcefTopLevel = force_toplevel + self.Resizable = resizable + self._AlphaChannel = alpha_channel + self.Timeout = None + self.TimeoutKey = TIMEOUT_KEY + self.TimerCancelled = False + self.DisableClose = disable_close + self._Hidden = False + # self.QTApplication = None + # self.QT_QMainWindow = None + self._Size = size + self.ElementPadding = element_padding or DEFAULT_ELEMENT_PADDING + self.FocusElement = None + self.ElementJustification = element_justification + self.BackgroundImage = background_image + self.XFound = False + self.DisableMinimize = disable_minimize + self.App = None # type: wx.App + self.MasterFrame = None # type: wx.Frame + self.MasterPanel = None # type: wx.Panel + self.IgnoreClose = False + self.UniqueKeyCounter = 0 + self.AllKeysDict = {} # dictionary containing all the keys and elements in this window + + if layout is not None: + self.Layout(layout) + if finalize: + self.Finalize() + + @classmethod + def IncrementOpenCount(self): + self.NumOpenWindows += 1 + # print('+++++ INCREMENTING Num Open Windows = {} ---'.format(Window.NumOpenWindows)) + + @classmethod + def DecrementOpenCount(self): + self.NumOpenWindows -= 1 * (self.NumOpenWindows != 0) # decrement if not 0 + # print('----- DECREMENTING Num Open Windows = {} ---'.format(Window.NumOpenWindows)) + + # ------------------------- Add ONE Row to Form ------------------------- # + def AddRow(self, *args): + '''Parms are a variable number of Elements''' + NumRows = len(self.Rows) # number of existing rows is our row number + CurrentRowNumber = NumRows # this row's number + CurrentRow = [] # start with a blank row and build up + # ------------------------- Add the elements to a row ------------------------- # + for i, element in enumerate(args): # Loop through list of elements and add them to the row + element.Position = (CurrentRowNumber, i) + element.ParentContainer = self + CurrentRow.append(element) + # ------------------------- Append the row to list of Rows ------------------------- # + self.Rows.append(CurrentRow) + + # ------------------------- Add Multiple Rows to Form ------------------------- # + def AddRows(self, rows): + for row in rows: + self.AddRow(*row) + + def Layout(self, rows): + self.AddRows(rows) + self.BuildKeyDict() + return self + + def LayoutAndRead(self, rows, non_blocking=False): + raise DeprecationWarning('LayoutAndRead is no longer supported... change your call to window.Layout(layout).Read()') + # self.AddRows(rows) + # self.Show(non_blocking=non_blocking) + # return self.ReturnValues + + def LayoutAndShow(self, rows): + raise DeprecationWarning('LayoutAndShow is no longer supported... change your call to LayoutAndRead') + + # ------------------------- ShowForm THIS IS IT! ------------------------- # + def Show(self, non_blocking=False): + self.Shown = True + # Compute num rows & num cols (it'll come in handy debugging) + self.NumRows = len(self.Rows) + self.NumCols = max(len(row) for row in self.Rows) + self.NonBlocking = non_blocking + + # Search through entire form to see if any elements set the focus + # if not, then will set the focus to the first input element + found_focus = False + for row in self.Rows: + for element in row: + try: + if element.Focus: + found_focus = True + except: + pass + try: + if element.Key is not None: + self.UseDictionary = True + except: + pass + + if not found_focus and self.UseDefaultFocus: + self.UseDefaultFocus = True + else: + self.UseDefaultFocus = False + # -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ## + StartupTK(self) + # If a button or keyboard event happened but no results have been built, build the results + if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: + return BuildResults(self, False, self) + + return self.ReturnValues + + # ------------------------- SetIcon - set the window's fav icon ------------------------- # + def SetIcon(self, icon=None, pngbase64=None): + pass + + def _GetElementAtLocation(self, location): + (row_num, col_num) = location + row = self.Rows[row_num] + element = row[col_num] + return element + + def _GetDefaultElementSize(self): + return self.DefaultElementSize + + def _AutoCloseAlarmCallback(self): + try: + window = self + if window: + if window.NonBlocking: + self.CloseNonBlockingForm() + else: + window._Close() + if self.CurrentlyRunningMainloop: + self.QTApplication.exit() # kick the users out of the mainloop + self.RootNeedsDestroying = True + self.QT_QMainWindow.close() + + except: + pass + + def timer_timeout(self, event): + # first, get the results table built + # modify the Results table in the parent FlexForm object + # print('timer timeout') + if self.TimerCancelled: + return + self.LastButtonClicked = self.TimeoutKey + self.FormRemainedOpen = True + if self.CurrentlyRunningMainloop: + self.App.ExitMainLoop() + + def non_block_timer_timeout(self, event): + # print('non-blocking timer timeout') + self.App.ExitMainLoop() + + def autoclose_timer_callback(self, frame): + # print('*** AUTOCLOSE TIMEOUT CALLBACK ***', frame) + try: + frame.Close() + except: + pass # if user has already closed the frame will get an error + # TODO Sept - does this need adding back? + # if self.CurrentlyRunningMainloop: + # self.App.ExitMainLoop() + + def callback_keyboard_char(self, event): + event = event # type:wx.KeyEvent + self.LastButtonClicked = None + self.FormRemainedOpen = True + if event.ClassName == 'wxMouseEvent': + if event.WheelRotation < 0: + self.LastKeyboardEvent = 'MouseWheel:Down' + else: + self.LastKeyboardEvent = 'MouseWheel:Up' + else: + self.LastKeyboardEvent = event.GetKeyCode() + if not self.NonBlocking: + BuildResults(self, False, self) + if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! + self.App.ExitMainLoop() # kick the users out of the mainloop + if event.ClassName != 'wxMouseEvent': + event.DoAllowNextEvent() + + def Read(self, timeout=None, timeout_key=TIMEOUT_KEY, close=False): + """ + THE biggest deal method in the Window class! This is how you get all of your data from your Window. + Pass in a timeout (in milliseconds) to wait for a maximum of timeout milliseconds. Will return timeout_key + if no other GUI events happen first. + Use the close parameter to close the window after reading + + :param timeout: (int) Milliseconds to wait until the Read will return IF no other GUI events happen first + :param timeout_key: (Any) The value that will be returned from the call if the timer expired + :param close: (bool) if True the window will be closed prior to returning + :return: Tuple[(Any), Union[Dict[Any:Any]], List[Any], None] (event, values) + """ + results = self._read(timeout=timeout, timeout_key=timeout_key) + if close: + self.close() + + return results + + def _read(self, timeout=None, timeout_key=TIMEOUT_KEY): + if timeout == 0: # timeout of zero runs the old readnonblocking + event, values = self._ReadNonBlocking() + if event is None: + event = timeout_key + if values is None: + event = None + return event, values # make event None if values was None and return + # Read with a timeout + self.Timeout = timeout + self.TimeoutKey = timeout_key + self.NonBlocking = False + if not self.Shown: + self.Show() + else: + # if already have a button waiting, the return previously built results + if self.LastButtonClicked is not None and not self.LastButtonClickedWasRealtime: + # print(f'*** Found previous clicked saved {self.LastButtonClicked}') + results = BuildResults(self, False, self) + self.LastButtonClicked = None + return results + InitializeResults(self) + # if the last button clicked was realtime, emulate a read non-blocking + # the idea is to quickly return realtime buttons without any blocks until released + if self.LastButtonClickedWasRealtime: + # print(f'RTime down {self.LastButtonClicked}' ) + try: + rc = self.TKroot.update() + except: + self.TKrootDestroyed = True + Window.DecrementOpenCount() + results = BuildResults(self, False, self) + if results[0] != None and results[0] != timeout_key: + return results + else: + pass + + # else: + # print("** REALTIME PROBLEM FOUND **", results) + + # normal read blocking code.... + if timeout != None: + self.TimerCancelled = False + timer = wx.Timer(self.App) + self.App.Bind(wx.EVT_TIMER, self.timer_timeout) + timer.Start(milliseconds=timeout, oneShot=wx.TIMER_ONE_SHOT) + else: + timer = None + self.CurrentlyRunningMainloop = True + # print(f'In main {self.Title}') + ################################# CALL GUWxTextCtrlI MAINLOOP ############################ + self.App.MainLoop() + self.CurrentlyRunningMainloop = False + self.TimerCancelled = True + if timer: + timer.Stop() + if Window.stdout_is_rerouted: + sys.stdout = Window.stdout_location + if self.RootNeedsDestroying: + # self.LastButtonClicked = None + # self.App.Close() + try: + self.MasterFrame.Close() + except: + pass + Window.DecrementOpenCount() + # if form was closed with X + if self.LastButtonClicked is None and self.LastKeyboardEvent is None and self.ReturnValues[0] is None: + Window.DecrementOpenCount() + # Determine return values + if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: + results = BuildResults(self, False, self) + if not self.LastButtonClickedWasRealtime: + self.LastButtonClicked = None + return results + else: + if not self.XFound and self.Timeout != 0 and self.Timeout is not None and self.ReturnValues[0] is None: # Special Qt case because returning for no reason so fake timeout + self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout + elif not self.XFound and self.ReturnValues[0] is None: # TODO HIGHLY EXPERIMENTAL... added due to tray icon interaction + # print("*** Faking timeout ***") + self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout + return self.ReturnValues + + def _ReadNonBlocking(self): + if self.TKrootDestroyed: + return None, None + if not self.Shown: + self.Show(non_blocking=True) + else: + # event = wx.Event() + # self.App.QueueEvent(event) + timer = wx.Timer(self.App) + self.App.Bind(wx.EVT_TIMER, self.timer_timeout) + timer.Start(milliseconds=0, oneShot=wx.TIMER_ONE_SHOT) + self.CurrentlyRunningMainloop = True + # print(f'In main {self.Title}') + ################################# CALL GUWxTextCtrlI MAINLOOP ############################ + + self.App.MainLoop() + if Window.stdout_is_rerouted: + sys.stdout = Window.stdout_location + # self.LastButtonClicked = 'TEST' + self.CurrentlyRunningMainloop = False + timer.Stop() + # while self.App.HasPendingEvents(): + # self.App.ProcessPendingEvents() + return BuildResults(self, False, self) + + def Finalize(self): + if self.TKrootDestroyed: + return self + if not self.Shown: + self.Show(non_blocking=True) + # else: + # try: + # self.QTApplication.processEvents() # refresh the window + # except: + # print('* ERROR FINALIZING *') + # self.TKrootDestroyed = True + # Window.DecrementOpenCount() + return self + + def Refresh(self): + # self.QTApplication.processEvents() # refresh the window + return self + + def VisibilityChanged(self): + self.SizeChanged() + return self + + def Fill(self, values_dict): + FillFormWithValues(self, values_dict) + return self + + def FindElement(self, key, silent_on_error=False): + try: + element = self.AllKeysDict[key] + except KeyError: + element = None + # element = _FindElementFromKeyInSubForm(self, key) + if element is None: + if not silent_on_error: + print('*** WARNING = FindElement did not find the key. Please check your key\'s spelling ***') + PopupError( + 'Keyword error in FindElement Call', + 'Bad key = {}'.format(key), + 'Your bad line of code may resemble this:', + 'window.FindElement("{}")'.format(key), + ) + return ErrorElement(key=key) + else: + return False + return element + + Element = FindElement # shortcut function definition + + def BuildKeyDict(self): + dict = {} + self.AllKeysDict = self._BuildKeyDictForWindow(self, self, dict) + # print(f'keys built = {self.AllKeysDict}') + + def _BuildKeyDictForWindow(self, top_window, window, key_dict): + for row_num, row in enumerate(window.Rows): + for col_num, element in enumerate(row): + if element.Type == ELEM_TYPE_COLUMN: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Type == ELEM_TYPE_FRAME: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Type == ELEM_TYPE_TAB_GROUP: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Type == ELEM_TYPE_TAB: + key_dict = self._BuildKeyDictForWindow(top_window, element, key_dict) + if element.Key is None: # if no key has been assigned.... create one for input elements + if element.Type == ELEM_TYPE_BUTTON: + element.Key = element.ButtonText + if element.Type in ( + ELEM_TYPE_MENUBAR, + ELEM_TYPE_BUTTONMENU, + ELEM_TYPE_CANVAS, + ELEM_TYPE_INPUT_SLIDER, + ELEM_TYPE_GRAPH, + ELEM_TYPE_IMAGE, + ELEM_TYPE_INPUT_CHECKBOX, + ELEM_TYPE_INPUT_LISTBOX, + ELEM_TYPE_INPUT_COMBO, + ELEM_TYPE_INPUT_MULTILINE, + ELEM_TYPE_INPUT_OPTION_MENU, + ELEM_TYPE_INPUT_SPIN, + ELEM_TYPE_TABLE, + ELEM_TYPE_TREE, + ELEM_TYPE_INPUT_TEXT, + ): + element.Key = top_window.DictionaryKeyCounter + top_window.DictionaryKeyCounter += 1 + if element.Key is not None: + if element.Key in key_dict.keys(): + (print('*** Duplicate key found in your layout {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) + element.Key = element.Key + str(self.UniqueKeyCounter) + self.UniqueKeyCounter += 1 + (print('*** Replaced new key with {} ***'.format(element.Key)) if element.Type != ELEM_TYPE_BUTTON else None) + key_dict[element.Key] = element + return key_dict + + def FindElementWithFocus(self): + return self.FocusElement + # element = _FindElementWithFocusInSubForm(self) + # return element + + def SaveToDisk(self, filename): + try: + results = BuildResults(self, False, self) + with open(filename, 'wb') as sf: + pickle.dump(results[1], sf) + except: + print('*** Error saving form to disk ***') + + def LoadFromDisk(self, filename): + try: + with open(filename, 'rb') as df: + self.Fill(pickle.load(df)) + except: + print('*** Error loading form to disk ***') + + def GetScreenDimensions(self): + size = wx.GetDisplaySize() + return size + + def Move(self, x, y): + self.MasterFrame.SetPosition((x, y)) + + def Minimize(self): + self.MasterFrame.Iconize() + + def Maximize(self): + self.MasterFrame.Maximize() + + def _Close(self): + try: + self.TKroot.update() + except: + pass + if not self.NonBlocking: + BuildResults(self, False, self) + if self.TKrootDestroyed: + return None + self.TKrootDestroyed = True + self.RootNeedsDestroying = True + # self.__del__() + return None + + def Close(self): + if self.TKrootDestroyed: + return + try: + self.MasterFrame.Close() + except: + print('error closing window') + + CloseNonBlockingForm = Close + CloseNonBlocking = Close + + def Disable(self): + self.MasterFrame.Enable(False) + + def Enable(self): + self.MasterFrame.Enable(True) + + def Hide(self): + self._Hidden = True + self.MasterFrame.Hide() + return + + def UnHide(self): + if self._Hidden: + self.MasterFrame.Show() + self._Hidden = False + + def Disappear(self): + self.MasterFrame.SetTransparent(0) + + def Reappear(self): + self.MasterFrame.SetTransparent(255) + + def SetAlpha(self, alpha): + ''' + Change the window's transparency + :param alpha: From 0 to 1 with 0 being completely transparent + :return: + ''' + self._AlphaChannel = alpha * 255 + if self._AlphaChannel is not None: + self.MasterFrame.SetTransparent(self._AlphaChannel) + + @property + def AlphaChannel(self): + return self._AlphaChannel + + @AlphaChannel.setter + def AlphaChannel(self, alpha): + self.SetAlpha(alpha) + + def BringToFront(self): + self.MasterFrame.ToggleWindowStyle(wx.STAY_ON_TOP) + + def CurrentLocation(self): + location = self.MasterFrame.GetPosition() + return location + + def OnClose(self, event): + # print(f'CLOSE EVENT! event = {event}') + if self.DisableClose: + return + # print('GOT A CLOSE EVENT!', event, self.Window.Title) + if not self.IgnoreClose: + self.LastButtonClicked = None + self.XFound = True + if not self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! + self.RootNeedsDestroying = True + else: + self.RootNeedsDestroying = True + self.App.ExitMainLoop() # kick the users out of the mainloop + # print('exiting mainloop') + + self.MasterFrame.Destroy() + # TODO - Sept - This is all new from prior release... comment out? + """ + timer = wx.Timer(self.App) + self.App.Bind(wx.EVT_TIMER, self.timer_timeout) + timer.Start(milliseconds=100, oneShot=wx.TIMER_ONE_SHOT) + # self.CurrentlyRunningMainloop = True + # print(f'In main {self.Title}') + ################################# CALL GUWxTextCtrlI MAINLOOP ############################ + + self.App.MainLoop() + # self.CurrentlyRunningMainloop = False + timer.Stop() + print('after mainloop in close') + # TODO end + """ + + self.TKrootDestroyed = True + self.RootNeedsDestroying = True + + @property + def Size(self): + size = self.MasterFrame.GetSize() + return size + + @Size.setter + def Size(self, size): + self.MasterFrame.SetSize(size[0], size[1]) + + def SizeChanged(self): + size = self.Size + self.Size = size[0] + 1, size[1] + 1 + self.Size = size + self.MasterFrame.SetSizer(self.OuterSizer) + self.OuterSizer.Fit(self.MasterFrame) + + def __getitem__(self, key): + """ + Returns Element that matches the passed in key. + This is "called" by writing code as thus: + window['element key'].Update + + :param key: (Any) The key to find + :return: Union[Element, None] The element found or None if no element was found + """ + try: + return self.Element(key) + except Exception as e: + print('The key you passed in is no good. Key = {}*'.format(key)) + return None + + def __call__(self, *args, **kwargs): + """ + Call window.Read but without having to type it out. + window() == window.Read() + window(timeout=50) == window.Read(timeout=50) + + :param args: + :param kwargs: + :return: Tuple[Any, Dict[Any:Any]] The famous event, values that Read returns. + """ + return self.Read(*args, **kwargs) + + add_row = AddRow + add_rows = AddRows + alpha_channel = AlphaChannel + bring_to_front = BringToFront + close = Close + current_location = CurrentLocation + disable = Disable + disappear = Disappear + element = Element + enable = Enable + fill = Fill + finalize = Finalize + find_element = FindElement + find_element_with_focus = FindElementWithFocus + get_screen_dimensions = GetScreenDimensions + hide = Hide + layout = Layout + load_from_disk = LoadFromDisk + maximize = Maximize + minimize = Minimize + move = Move + read = Read + reappear = Reappear + refresh = Refresh + save_to_disk = SaveToDisk + set_alpha = SetAlpha + set_icon = SetIcon + size = Size + size_changed = SizeChanged + un_hide = UnHide + visibility_changed = VisibilityChanged + + +FlexForm = Window + +# =========================================================================== # +# Stops the mainloop and sets the event information # +# =========================================================================== # + + +def element_callback_quit_mainloop(element): + if element.Key is not None: + element.ParentForm.LastButtonClicked = element.Key + else: + element.ParentForm.LastButtonClicked = '' + element.ParentForm.FormRemainedOpen = True + if element.ParentForm.CurrentlyRunningMainloop: + element.ParentForm.App.ExitMainLoop() # kick the users out of the mainloop + + +def quit_mainloop(window): + window.App.ExitMainLoop() + + +# =========================================================================== # +# Convert from characters to pixels # +# =========================================================================== # +# def convert_tkinter_size_to_Wx(size): +# """ +# Converts size in characters to size in pixels +# :param size: size in characters, rows +# :return: size in pixels, pixels +# """ +# qtsize = size +# if size[1] is not None and size[1] < DEFAULT_PIXEL_TO_CHARS_CUTOFF: # change from character based size to pixels (roughly) +# qtsize = size[0]*DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1]*DEFAULT_PIXELS_TO_CHARS_SCALING[1] +# return qtsize + + +# =========================================================================== # +# Convert from characters to pixels # +# =========================================================================== # +def _convert_tkinter_size_to_Wx(size, scaling=DEFAULT_PIXELS_TO_CHARS_SCALING, height_cutoff=DEFAULT_PIXEL_TO_CHARS_CUTOFF): + """ + Converts size in characters to size in pixels + :param size: size in characters, rows + :return: size in pixels, pixels + """ + qtsize = size + if size[1] is not None and size[1] < height_cutoff: # change from character based size to pixels (roughly) + qtsize = size[0] * scaling[0], size[1] * scaling[1] + return qtsize + + +def font_to_wx_font(font): + """ + Convert from font string/tyuple into a Qt style sheet string + :param font: "Arial 10 Bold" or ('Arial', 10, 'Bold) + :return: style string that can be combined with other style strings + """ + + if font is None: + return '' + + if type(font) is str: + _font = font.split(' ') + else: + _font = font + name = _font[0] + family = _font[0] + point_size = int(_font[1]) + + # style = _font[2] + + underline = 'underline' in _font[2:] + bold = 'bold' in _font + + wxfont = wx.Font( + point_size, + wx.FONTFAMILY_DEFAULT, + wx.FONTSTYLE_NORMAL, + wx.FONTWEIGHT_BOLD if bold else wx.FONTWEIGHT_NORMAL, + underline, + faceName=family, + ) + + return wxfont + + +def preprocess_radio_elements(top_window, window): + for row in window.Rows: + for element in row: + if element.Type == ELEM_TYPE_INPUT_RADIO: + if element.WxRadioButton is None: + element.WxRadioButton = wx.RadioButton(top_window.MasterPanel, id=wx.ID_ANY, label=element.Text, style=wx.RB_GROUP) + create_wx_radio_buttons(top_window, top_window, element.GroupID) + if element.Type in (ELEM_TYPE_COLUMN, ELEM_TYPE_FRAME, ELEM_TYPE_TAB_GROUP, ELEM_TYPE_TAB): + preprocess_radio_elements(top_window, element) + + +def create_wx_radio_buttons(top_window, window, group_id): + for row in window.Rows: + for element in row: + if element.Type == ELEM_TYPE_INPUT_RADIO: + if element.GroupID == group_id and element.WxRadioButton is None: + element.WxRadioButton = wx.RadioButton(top_window.MasterPanel, id=wx.ID_ANY, label=element.Text) + if element.Type in (ELEM_TYPE_COLUMN, ELEM_TYPE_FRAME, ELEM_TYPE_TAB_GROUP, ELEM_TYPE_TAB): + create_wx_radio_buttons(top_window, element, group_id) + + +# ################################################################################ +# ################################################################################ +# END OF ELEMENT DEFINITIONS +# ################################################################################ +# ################################################################################ + + +# =========================================================================== # +# Button Lazy Functions so the caller doesn't have to define a bunch of stuff # +# =========================================================================== # + + +# ------------------------- FOLDER BROWSE Element lazy function ------------------------- # +def FolderBrowse( + button_text='Browse', + target=(ThisRow, -1), + initial_folder=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + change_submits=False, + font=None, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_BROWSE_FOLDER, + target=target, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + disabled=disabled, + button_color=button_color, + change_submits=change_submits, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- FILE BROWSE Element lazy function ------------------------- # +def FileBrowse( + button_text='Browse', + target=(ThisRow, -1), + file_types=(('ALL Files', '*.*'),), + initial_folder=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + font=None, + disabled=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_BROWSE_FILE, + target=target, + file_types=file_types, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + change_submits=change_submits, + disabled=disabled, + button_color=button_color, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- FILES BROWSE Element (Multiple file selection) lazy function ------------------------- # +def FilesBrowse( + button_text='Browse', + target=(ThisRow, -1), + file_types=(('ALL Files', '*.*'),), + disabled=False, + initial_folder=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + font=None, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_BROWSE_FILES, + target=target, + file_types=file_types, + initial_folder=initial_folder, + change_submits=change_submits, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + disabled=disabled, + button_color=button_color, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- FILE BROWSE Element lazy function ------------------------- # +def FileSaveAs( + button_text='Save As...', + target=(ThisRow, -1), + file_types=(('ALL Files', '*.*'),), + initial_folder=None, + disabled=False, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + font=None, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_SAVEAS_FILE, + target=target, + file_types=file_types, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + disabled=disabled, + auto_size_button=auto_size_button, + button_color=button_color, + change_submits=change_submits, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- SAVE AS Element lazy function ------------------------- # +def SaveAs( + button_text='Save As...', + target=(ThisRow, -1), + file_types=(('ALL Files', '*.*'),), + initial_folder=None, + disabled=False, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + change_submits=False, + font=None, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_SAVEAS_FILE, + target=target, + file_types=file_types, + initial_folder=initial_folder, + tooltip=tooltip, + size=size, + disabled=disabled, + auto_size_button=auto_size_button, + button_color=button_color, + change_submits=change_submits, + font=font, + pad=pad, + key=key, + ) + + +# ------------------------- SAVE BUTTON lazy function ------------------------- # +def Save( + button_text='Save', + size=(None, None), + auto_size_button=None, + button_color=None, + bind_return_key=True, + disabled=False, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- SUBMIT BUTTON lazy function ------------------------- # +def Submit( + button_text='Submit', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- OPEN BUTTON lazy function ------------------------- # +def Open( + button_text='Open', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- OK BUTTON lazy function ------------------------- # +def OK( + button_text='OK', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- YES BUTTON lazy function ------------------------- # +def Ok( + button_text='Ok', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + bind_return_key=True, + tooltip=None, + font=None, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- CANCEL BUTTON lazy function ------------------------- # +def Cancel( + button_text='Cancel', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- QUIT BUTTON lazy function ------------------------- # +def Quit( + button_text='Quit', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Exit BUTTON lazy function ------------------------- # +def Exit( + button_text='Exit', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- YES BUTTON lazy function ------------------------- # +def Yes( + button_text='Yes', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=True, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- NO BUTTON lazy function ------------------------- # +def No( + button_text='No', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + tooltip=None, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- NO BUTTON lazy function ------------------------- # +def Help( + button_text='Help', + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + font=None, + tooltip=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- GENERIC BUTTON lazy function ------------------------- # +def SimpleButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + disabled=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_CLOSES_WIN, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + disabled=disabled, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- CLOSE BUTTON lazy function ------------------------- # +def CloseButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + disabled=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_CLOSES_WIN, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + disabled=disabled, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +CButton = CloseButton + + +# ------------------------- GENERIC BUTTON lazy function ------------------------- # +def ReadButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + bind_return_key=False, + disabled=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_READ_FORM, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + disabled=disabled, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +ReadFormButton = ReadButton +RButton = ReadFormButton + + +# ------------------------- Realtime BUTTON lazy function ------------------------- # +def RealtimeButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + disabled=False, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_REALTIME, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + disabled=disabled, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Dummy BUTTON lazy function ------------------------- # +def DummyButton( + button_text, + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + border_width=None, + tooltip=None, + size=(None, None), + auto_size_button=None, + button_color=None, + font=None, + disabled=False, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_CLOSES_WIN_ONLY, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +# ------------------------- Calendar Chooser Button lazy function ------------------------- # +def CalendarButton( + button_text, + target=(None, None), + close_when_date_chosen=True, + default_date_m_d_y=(None, None, None), + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + tooltip=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + button = Button( + button_text=button_text, + button_type=BUTTON_TYPE_CALENDAR_CHOOSER, + target=target, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + button.CalendarCloseWhenChosen = close_when_date_chosen + button.DefaultDate_M_D_Y = default_date_m_d_y + return button + + +# ------------------------- Calendar Chooser Button lazy function ------------------------- # +def ColorChooserButton( + button_text, + target=(None, None), + image_filename=None, + image_data=None, + image_size=(None, None), + image_subsample=None, + tooltip=None, + border_width=None, + size=(None, None), + auto_size_button=None, + button_color=None, + disabled=False, + font=None, + bind_return_key=False, + focus=False, + pad=None, + key=None, +): + return Button( + button_text=button_text, + button_type=BUTTON_TYPE_COLOR_CHOOSER, + target=target, + image_filename=image_filename, + image_data=image_data, + image_size=image_size, + image_subsample=image_subsample, + border_width=border_width, + tooltip=tooltip, + size=size, + auto_size_button=auto_size_button, + button_color=button_color, + font=font, + disabled=disabled, + bind_return_key=bind_return_key, + focus=focus, + pad=pad, + key=key, + ) + + +##################################### ----- RESULTS ------ ################################################## + + +def AddToReturnDictionary(form, element, value): + form.ReturnValuesDictionary[element.Key] = value + return + if element.Key is None: + form.ReturnValuesDictionary[form.DictionaryKeyCounter] = value + element.Key = form.DictionaryKeyCounter + form.DictionaryKeyCounter += 1 + else: + form.ReturnValuesDictionary[element.Key] = value + + +def AddToReturnList(form, value): + form.ReturnValuesList.append(value) + + +# ----------------------------------------------------------------------------# +# ------- FUNCTION InitializeResults. Sets up form results matrix --------# +def InitializeResults(form): + BuildResults(form, True, form) + return + + +# ===== Radio Button RadVar encoding and decoding =====# +# ===== The value is simply the row * 1000 + col =====# +def DecodeRadioRowCol(RadValue): + row = RadValue // 1000 + col = RadValue % 1000 + return row, col + + +def EncodeRadioRowCol(row, col): + RadValue = row * 1000 + col + return RadValue + + +# ------- FUNCTION BuildResults. Form exiting so build the results to pass back ------- # +# format of return values is +# (Button Pressed, input_values) +def BuildResults(form, initialize_only, top_level_form): + # Results for elements are: + # TEXT - Nothing + # INPUT - Read value from TK + # Button - Button Text and position as a Tuple + + # Get the initialized results so we don't have to rebuild + form.DictionaryKeyCounter = 0 + form.ReturnValuesDictionary = {} + form.ReturnValuesList = [] + BuildResultsForSubform(form, initialize_only, top_level_form) + # try: + # BuildResultsForSubform(form, initialize_only, top_level_form) + # except: + # print('Error building return values') + if not top_level_form.LastButtonClickedWasRealtime: + top_level_form.LastButtonClicked = None + return form.ReturnValues + + +def BuildResultsForSubform(form, initialize_only, top_level_form): + button_pressed_text = top_level_form.LastButtonClicked + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + if element.Key is not None and WRITE_ONLY_KEY in str(element.Key): + continue + value = None + if element.Type == ELEM_TYPE_COLUMN: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if element.Type == ELEM_TYPE_FRAME: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if element.Type == ELEM_TYPE_TAB_GROUP: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if element.Type == ELEM_TYPE_TAB: + element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter + element.ReturnValuesList = [] + element.ReturnValuesDictionary = {} + BuildResultsForSubform(element, initialize_only, top_level_form) + for item in element.ReturnValuesList: + AddToReturnList(top_level_form, item) + if element.UseDictionary: + top_level_form.UseDictionary = True + if element.ReturnValues[0] is not None: # if a button was clicked + button_pressed_text = element.ReturnValues[0] + + if not initialize_only: + if element.Type == ELEM_TYPE_INPUT_TEXT: + value = element.WxTextCtrl.GetValue() + if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: + element.WxTextCtrl.SetValue('') + elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: + value = element.WxCheckbox.GetValue() + value = value != 0 + elif element.Type == ELEM_TYPE_INPUT_RADIO: + value = element.WxRadioButton.GetValue() + elif element.Type == ELEM_TYPE_BUTTON: + if top_level_form.LastButtonClicked == element.ButtonText: + button_pressed_text = top_level_form.LastButtonClicked + if element.BType != BUTTON_TYPE_REALTIME: # Do not clear realtime buttons + top_level_form.LastButtonClicked = None + if element.BType == BUTTON_TYPE_CALENDAR_CHOOSER: + try: + value = element.TKCal.selection + except: + value = None + else: + try: + value = element.TKStringVar.get() + except: + value = None + elif element.Type == ELEM_TYPE_INPUT_COMBO: + value = element.WxComboBox.GetValue() + elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: + value = element.TKStringVar.get() + elif element.Type == ELEM_TYPE_INPUT_LISTBOX: + try: + items = element.TKListbox.curselection() + value = [element.Values[int(item)] for item in items] + except: + value = '' + elif element.Type == ELEM_TYPE_INPUT_SPIN: + value = element.WxTextCtrl.GetValue() + # value = element.CurrentValue + elif element.Type == ELEM_TYPE_INPUT_SLIDER: + try: + value = element.TKIntVar.get() + except: + value = 0 + elif element.Type == ELEM_TYPE_INPUT_MULTILINE: + if element.WriteOnly: + continue + try: + value = element.WxTextCtrl.GetValue() + except: + pass + + if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: + element.WxTextCtrl.SetValue('') + elif element.Type == ELEM_TYPE_TAB_GROUP: + try: + value = element.TKNotebook.tab(element.TKNotebook.index('current'))['text'] + tab_key = element.FindKeyFromTabName(value) + if tab_key is not None: + value = tab_key + except: + value = None + elif element.Type == ELEM_TYPE_TABLE: + value = element.SelectedRows + elif element.Type == ELEM_TYPE_TREE: + value = element.SelectedRows + elif element.Type == ELEM_TYPE_GRAPH: + value = element.ClickPosition + else: + value = None + + # if an input type element, update the results + if element.Type != ELEM_TYPE_BUTTON and element.Type != ELEM_TYPE_TEXT and element.Type != ELEM_TYPE_IMAGE and element.Type != ELEM_TYPE_OUTPUT and element.Type != ELEM_TYPE_PROGRESS_BAR and element.Type != ELEM_TYPE_COLUMN and element.Type != ELEM_TYPE_FRAME and element.Type != ELEM_TYPE_TAB: + AddToReturnList(form, value) + AddToReturnDictionary(top_level_form, element, value) + elif ( + (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_CALENDAR_CHOOSER and element.Target == (None, None)) + or (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_COLOR_CHOOSER and element.Target == (None, None)) + or ( + element.Type == ELEM_TYPE_BUTTON + and element.Key is not None + and ( + element.BType + in ( + BUTTON_TYPE_SAVEAS_FILE, + BUTTON_TYPE_BROWSE_FILE, + BUTTON_TYPE_BROWSE_FILES, + BUTTON_TYPE_BROWSE_FOLDER, + ) + ) + ) + ): + AddToReturnList(form, value) + AddToReturnDictionary(top_level_form, element, value) + + # if this is a column, then will fail so need to wrap with tr + try: + if form.ReturnKeyboardEvents and form.LastKeyboardEvent is not None: + button_pressed_text = form.LastKeyboardEvent + form.LastKeyboardEvent = None + except: + pass + + try: + form.ReturnValuesDictionary.pop(None, None) # clean up dictionary include None was included + except: + pass + + if not form.UseDictionary: + form.ReturnValues = button_pressed_text, form.ReturnValuesList + else: + form.ReturnValues = button_pressed_text, form.ReturnValuesDictionary + + return form.ReturnValues + + +def FillFormWithValues(form, values_dict): + FillSubformWithValues(form, values_dict) + + +def FillSubformWithValues(form, values_dict): + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + value = None + if element.Type == ELEM_TYPE_COLUMN: + FillSubformWithValues(element, values_dict) + if element.Type == ELEM_TYPE_FRAME: + FillSubformWithValues(element, values_dict) + if element.Type == ELEM_TYPE_TAB_GROUP: + FillSubformWithValues(element, values_dict) + if element.Type == ELEM_TYPE_TAB: + FillSubformWithValues(element, values_dict) + try: + value = values_dict[element.Key] + except: + continue + if element.Type == ELEM_TYPE_INPUT_TEXT: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_RADIO: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_COMBO: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_LISTBOX: + element.SetValue(value) + elif element.Type == ELEM_TYPE_INPUT_SLIDER: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_MULTILINE: + element.Update(value) + elif element.Type == ELEM_TYPE_INPUT_SPIN: + element.Update(value) + elif element.Type == ELEM_TYPE_BUTTON: + element.Update(value) + + +def _FindElementFromKeyInSubForm(form, key): + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + if element.Type == ELEM_TYPE_COLUMN: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_FRAME: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB_GROUP: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB: + matching_elem = _FindElementFromKeyInSubForm(element, key) + if matching_elem is not None: + return matching_elem + if element.Key == key: + return element + + +def _FindElementWithFocusInSubForm(form): + for row_num, row in enumerate(form.Rows): + for col_num, element in enumerate(row): + if element.Type == ELEM_TYPE_COLUMN: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_FRAME: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB_GROUP: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_TAB: + matching_elem = _FindElementWithFocusInSubForm(element) + if matching_elem is not None: + return matching_elem + if element.Type == ELEM_TYPE_INPUT_TEXT: + if element.TKEntry is not None: + if element.TKEntry is element.TKEntry.focus_get(): + return element + if element.Type == ELEM_TYPE_INPUT_MULTILINE: + if element.TKText is not None: + if element.TKText is element.TKText.focus_get(): + return element + + +if sys.version_info[0] >= 3: + + def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): + return_val = None + if type(sub_menu_info) is str: + if not is_sub_menu and not skip: + # print(f'Adding command {sub_menu_info}') + pos = sub_menu_info.find('&') + if pos != -1: + if pos == 0 or sub_menu_info[pos - 1] != '\\': + sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos + 1 :] + if sub_menu_info == '---': + top_menu.Append(wx.ID_SEPARATOR) + else: + try: + item_without_key = sub_menu_info[: sub_menu_info.index(MENU_KEY_SEPARATOR)] + except: + item_without_key = sub_menu_info + + if item_without_key[0] == MENU_DISABLED_CHARACTER: + id = top_menu.Append(wx.ID_ANY, item_without_key[len(MENU_DISABLED_CHARACTER) :]) + element.id_to_text[id] = sub_menu_info[1:] + top_menu.Enable(id.Id, False) + else: + id = top_menu.Append(wx.ID_ANY, item_without_key) + element.id_to_text[id] = sub_menu_info + + else: + i = 0 + while i < (len(sub_menu_info)): + item = sub_menu_info[i] + if i != len(sub_menu_info) - 1: + if type(sub_menu_info[i + 1]) == list: + new_menu = wx.Menu() + return_val = new_menu + pos = sub_menu_info[i].find('&') + if pos != -1: + if pos == 0 or sub_menu_info[i][pos - 1] != '\\': + sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1 :] + if sub_menu_info[i][0] == MENU_DISABLED_CHARACTER: + id = top_menu.AppendSubMenu(new_menu, sub_menu_info[i][len(MENU_DISABLED_CHARACTER) :]) + top_menu.Enable(id.Id, False) + else: + top_menu.AppendSubMenu(new_menu, sub_menu_info[i]) + AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) + i += 1 # skip the next one + else: + AddMenuItem(top_menu, item, element) + else: + AddMenuItem(top_menu, item, element) + i += 1 + return return_val + + +if sys.version_info[0] >= 3: + + def AddMenuItem2(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): + if type(sub_menu_info) is str: + if not is_sub_menu and not skip: + # print(f'Adding command {sub_menu_info}') + pos = sub_menu_info.find('&') + if pos != -1: + if pos == 0 or sub_menu_info[pos - 1] != '\\': + sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos + 1 :] + if sub_menu_info == '---': + top_menu.Append(wx.ID_SEPARATOR) + else: + top_menu.Append(wx.ID_ANY, sub_menu_info) + else: + i = 0 + while i < (len(sub_menu_info)): + item = sub_menu_info[i] + if i != len(sub_menu_info) - 1: + if type(sub_menu_info[i + 1]) == list: + new_menu = wx.Menu() + pos = sub_menu_info[i].find('&') + if pos != -1: + if pos == 0 or sub_menu_info[i][pos - 1] != '\\': + sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1 :] + top_menu.AppendSubMenu(new_menu, sub_menu_info[i]) + AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) + i += 1 # skip the next one + else: + AddMenuItem(top_menu, item, element) + else: + AddMenuItem(top_menu, item, element) + i += 1 + +else: + + def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): + if isinstance(sub_menu_info, types.StringType): + if not is_sub_menu and not skip: + # print(f'Adding command {sub_menu_info}') + pos = sub_menu_info.find('&') + if pos != -1: + if pos == 0 or sub_menu_info[pos - 1] != '\\': + sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos + 1 :] + if sub_menu_info == '---': + top_menu.add('separator') + else: + top_menu.add_command( + label=sub_menu_info, + underline=pos, + command=lambda: Menu._MenuItemChosenCallback(element, sub_menu_info), + ) + else: + i = 0 + while i < (len(sub_menu_info)): + item = sub_menu_info[i] + if i != len(sub_menu_info) - 1: + if not isinstance(sub_menu_info[i + 1], types.StringType): + new_menu = tk.Menu(top_menu, tearoff=element.Tearoff) + pos = sub_menu_info[i].find('&') + if pos != -1: + if pos == 0 or sub_menu_info[i][pos - 1] != '\\': + sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1 :] + top_menu.add_cascade(label=sub_menu_info[i], menu=new_menu, underline=pos) + AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True) + i += 1 # skip the next one + else: + AddMenuItem(top_menu, item, element) + else: + AddMenuItem(top_menu, item, element) + i += 1 + + +# # ###### +# # # # # # # # # ##### # # #### # # +# # # # # # # # # # # # # # ## # +# # # ## ###### # # ###### # # # # # +# # # ## # # # # # # # # # # +# # # # # # # # # # # # # ## +## ## # # # # # # # #### # # + +# My crappy WxPython code + +# ░░░░░░░░░░░█▀▀░░█░░░░░░ +# ░░░░░░▄▀▀▀▀░░░░░█▄▄░░░░ +# ░░░░░░█░█░░░░░░░░░░▐░░░ +# ░░░░░░▐▐░░░░░░░░░▄░▐░░░ +# ░░░░░░█░░░░░░░░▄▀▀░▐░░░ +# ░░░░▄▀░░░░░░░░▐░▄▄▀░░░░ +# ░░▄▀░░░▐░░░░░█▄▀░▐░░░░░ +# ░░█░░░▐░░░░░░░░▄░█░░░░░ +# ░░░█▄░░▀▄░░░░▄▀▐░█░░░░░ +# ░░░█▐▀▀▀░▀▀▀▀░░▐░█░░░░░ +# ░░▐█▐▄░░▀░░░░░░▐░█▄▄░░░ +# ░░░▀▀▄░░░░░░░░▄▐▄▄▄▀░░░ +# ░░░░░░░░░░░░░░░░░░░░░░░ + + +# ------------------------------------------------------------------------------------------------------------------ # +# ------------------------------------------------------------------------------------------------------------------ # +# ===================================== WxPython CODE STARTS HERE ================================================ # +# ------------------------------------------------------------------------------------------------------------------ # +# ------------------------------------------------------------------------------------------------------------------ # + + +def PackFormIntoFrame(container_elem, containing_frame, toplevel_form): + """ + + :param container_elem: + :type container_elem: Window or Column or Tab or Frame + :param containing_frame: + :type containing_frame: wx.BoxSizer + :param toplevel_form: + :type toplevel_form: Window + :return: + """ + + def pad_widget(widget): + lrsizer = wx.BoxSizer(wx.HORIZONTAL) + if full_element_pad[1] == full_element_pad[3]: # if right = left + lrsizer.Add(widget, 0, wx.LEFT | wx.RIGHT, border=full_element_pad[1]) + else: + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(widget, 0, wx.LEFT, border=full_element_pad[3]) + lrsizer.Add(sizer, 0, wx.RIGHT, border=full_element_pad[1]) + + top_bottom_sizer = wx.BoxSizer(wx.HORIZONTAL) + if full_element_pad[0] == full_element_pad[2]: # if top = bottom + top_bottom_sizer.Add(lrsizer, 0, wx.TOP | wx.BOTTOM, border=full_element_pad[0]) + else: + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(lrsizer, 0, wx.TOP, border=full_element_pad[0]) + top_bottom_sizer.Add(sizer, 0, wx.BOTTOM, border=full_element_pad[2]) + return top_bottom_sizer + + # + # font, text color, background color, size, disabled, visible, tooltip + # + def do_font_and_color(widget): + if font: + widget.SetFont(font_to_wx_font(font)) + if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): + widget.SetForegroundColour(element.TextColor) + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + widget.SetBackgroundColour(element.BackgroundColor) + widget.SetMinSize(element_size) + if element.Disabled: + widget.Enable(False) + if not element.Visible: + widget.Hide() + if element.Tooltip: + widget.SetToolTip(element.Tooltip) + + def CharWidthInPixels(): + return tkinter.font.Font().measure('A') # single character width + + border_depth = toplevel_form.BorderDepth if toplevel_form.BorderDepth is not None else DEFAULT_BORDER_WIDTH + # --------------------------------------------------------------------------- # + # **************** Use FlexForm to build the tkinter window ********** ----- # + # Building is done row by row. # + # --------------------------------------------------------------------------- # + focus_set = False + ######################### LOOP THROUGH ROWS ######################### + # *********** ------- Loop through ROWS ------- ***********# + for row_num, flex_row in enumerate(container_elem.Rows): + ######################### LOOP THROUGH ELEMENTS ON ROW ######################### + # *********** ------- Loop through ELEMENTS ------- ***********# + # *********** Make TK Row ***********# + hsizer = wx.BoxSizer(wx.HORIZONTAL) + for col_num, element in enumerate(flex_row): + element.ParentForm = toplevel_form # save the button's parent form object + if toplevel_form.Font and (element.Font == DEFAULT_FONT or not element.Font): + font = toplevel_form.Font + element.Font = font + elif element.Font is not None: + font = element.Font + else: + font = DEFAULT_FONT + # ------- Determine Auto-Size setting on a cascading basis ------- # + if element.AutoSizeText is not None: # if element overide + auto_size_text = element.AutoSizeText + elif toplevel_form.AutoSizeText is not None: # if form override + auto_size_text = toplevel_form.AutoSizeText + else: + auto_size_text = DEFAULT_AUTOSIZE_TEXT + element_type = element.Type + # Set foreground color + text_color = element.TextColor + # Determine Element size + element_size = element.Size + if element_size == (None, None) and element_type not in ( + ELEM_TYPE_BUTTON, + ELEM_TYPE_BUTTONMENU, + ): # user did not specify a size + element_size = toplevel_form.DefaultElementSize + elif element_size == (None, None) and element_type in (ELEM_TYPE_BUTTON, ELEM_TYPE_BUTTONMENU): + element_size = toplevel_form.DefaultButtonElementSize + else: + auto_size_text = False # if user has specified a size then it shouldn't autosize + full_element_pad = [0, 0, 0, 0] # Top, Right, Bottom, Left + elementpad = element.Pad if element.Pad is not None else toplevel_form.ElementPadding + if type(elementpad[0]) != tuple: # left and right + full_element_pad[1] = full_element_pad[3] = elementpad[0] + else: + full_element_pad[3], full_element_pad[1] = elementpad[0] + if type(elementpad[1]) != tuple: # top and bottom + full_element_pad[0] = full_element_pad[2] = elementpad[1] + else: + full_element_pad[0], full_element_pad[2] = elementpad[1] + + border_depth = toplevel_form.BorderDepth if toplevel_form.BorderDepth is not None else DEFAULT_BORDER_WIDTH + try: + if element.BorderWidth is not None: + border_depth = element.BorderWidth + except: + pass + + # ------------------------- COLUMN element ------------------------- # + if element_type == ELEM_TYPE_COLUMN: + element = element # type: Column + element.WxBoxSizer = vsizer = wx.BoxSizer(wx.VERTICAL) + element.WxHSizer = hsizer + # element.WxScrollBar = wx.ScrollBar(toplevel_form.MasterFrame, id=wx.ID_ANY, style=wx.SB_VERTICAL) + # vsizer.Add(element.WxScrollBar) + PackFormIntoFrame(element, vsizer, toplevel_form) + + hsizer.Add(pad_widget(vsizer), 0) + if not element.Visible: + hsizer.Hide(vsizer, recursive=True) + + # # column_widget = QWidget() + # column_widget = QGroupBox() + # element.QT_QGroupBox = column_widget + # # column_widget.setFrameShape(QtWidgets.QFrame.NoFrame) + # style = create_style_from_font(font) + # if element.BackgroundColor is not None: + # style = style_entry(background_color=element.BackgroundColor) + # style += 'background-color: %s;' % element.BackgroundColor + # style += style_entry(border='0px solid gray') + # # style += 'border: 0px solid gray; ' + # style = style_generate('QGroupBox', style) + # column_widget.setStyleSheet(style) + # + # column_layout = QFormLayout() + # column_vbox = QVBoxLayout() + # + # PackFormIntoFrame(element, column_layout, toplevel_win) + # + # column_vbox.addLayout(column_layout) + # column_widget.setLayout(column_vbox) + # + # # column_widget.setStyleSheet(style) + # if not element.Visible: + # column_widget.setVisible(False) + # + # qt_row_layout.addWidget(column_widget) + + # if element.Scrollable: + # col_frame = TkScrollableFrame(tk_row_frame, + # element.VerticalScrollOnly) # do not use yet! not working + # PackFormIntoFrame(element, col_frame.TKFrame, toplevel_form) + # col_frame.TKFrame.update() + # if element.Size == (None, None): # if no size specified, use column width x column height/2 + # col_frame.canvas.config(width=col_frame.TKFrame.winfo_reqwidth(), + # height=col_frame.TKFrame.winfo_reqheight() / 2) + # else: + # col_frame.canvas.config(width=element.Size[0], height=element.Size[1]) + # + # if not element.BackgroundColor in (None, COLOR_SYSTEM_DEFAULT): + # col_frame.canvas.config(background=element.BackgroundColor) + # col_frame.TKFrame.config(background=element.BackgroundColor, borderwidth=0, + # highlightthickness=0) + # col_frame.config(background=element.BackgroundColor, borderwidth=0, highlightthickness=0) + # else: + # col_frame = tk.Frame(tk_row_frame) + # PackFormIntoFrame(element, col_frame, toplevel_form) + # + # col_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') + # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: + # col_frame.configure(background=element.BackgroundColor, highlightbackground=element.BackgroundColor, + # highlightcolor=element.BackgroundColor) + # ------------------------- TEXT element ------------------------- # + elif element_type == ELEM_TYPE_TEXT: + element = element # type: Text + if element.Justification is not None: + justification = element.Justification + elif toplevel_form.TextJustification is not None: + justification = toplevel_form.TextJustification + else: + justification = DEFAULT_TEXT_JUSTIFICATION + style = wx.ALIGN_LEFT if justification.startswith('l') else wx.ALIGN_CENTER if justification.startswith('c') else wx.ALIGN_RIGHT + # print(border_depth, element.BorderWidth) + if border_depth: + if element.Relief: + if element.Relief in (RELIEF_SOLID, RELIEF_FLAT): + style |= wx.SIMPLE_BORDER + elif element.Relief == RELIEF_SUNKEN: + style |= wx.SUNKEN_BORDER + elif element.Relief in (RELIEF_RAISED, RELIEF_RIDGE): + style |= wx.RAISED_BORDER + elif element.Relief in (RELIEF_SUNKEN, RELIEF_SUNKEN): + style |= wx.SUNKEN_BORDER + statictext = element.WxStaticText = wx.StaticText(toplevel_form.MasterPanel, -1, element.DisplayText, style=style) + if font: + statictext.SetFont(font_to_wx_font(font)) + if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): + statictext.SetForegroundColour(element.TextColor) + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + statictext.SetBackgroundColour(element.BackgroundColor) + display_text = element.DisplayText # text to display + if auto_size_text is False: + width, height = element_size + else: + lines = display_text.split('\n') + max_line_len = max([len(l) for l in lines]) + num_lines = len(lines) + if max_line_len > element_size[0]: # if text exceeds element size, the will have to wrap + width = element_size[0] + else: + width = max_line_len + height = num_lines + + if element.ClickSubmits: # bind events + statictext.Bind(wx.EVT_LEFT_UP, element._WxCallbackKeyboard) + + hsizer.Add(pad_widget(element.WxStaticText), 0) + + if not auto_size_text: + statictext.SetMinSize((width, height)) + + if element.Tooltip: + statictext.SetToolTip(element.Tooltip) + if not element.Visible: + statictext.Hide() + + # Set wrap-length for text (in PIXELS) == PAIN IN THE ASS + # wraplen = tktext_label.winfo_reqwidth() + 40 # width of widget in Pixels + # if not auto_size_text and height == 1: + # wraplen = 0 + # ------------------------- BUTTON element ------------------------- # + elif element_type == ELEM_TYPE_BUTTON: + element = element # type: Button + element.WxButton = button = wx.Button(toplevel_form.MasterPanel, style=wx.NO_BORDER) + button.SetLabelText(element.ButtonText) + if font: + button.SetFont(font_to_wx_font(font)) + button.Bind(wx.EVT_BUTTON, element.ButtonCallBack) + + element.Location = (row_num, col_num) + if element.AutoSizeButton is not None: + auto_size = element.AutoSizeButton + else: + auto_size = toplevel_form.AutoSizeButtons + if auto_size is False or element.Size[0] is not None: + width, height = element_size + else: + width = 0 + height = toplevel_form.DefaultButtonElementSize[1] + + if auto_size: + element.WxButton.SetWindowStyleFlag(element.WxButton.GetWindowStyleFlag() | wx.BU_EXACTFIT) + else: + element.WxButton.SetMinSize(_convert_tkinter_size_to_Wx((width, height), DEFAULT_PIXEL_TO_CHARS_CUTOFF)) + if element.ButtonColor != (None, None) and element.ButtonColor != DEFAULT_BUTTON_COLOR: + bc = element.ButtonColor + elif toplevel_form.ButtonColor != (None, None) and toplevel_form.ButtonColor != DEFAULT_BUTTON_COLOR: + bc = toplevel_form.ButtonColor + else: + bc = DEFAULT_BUTTON_COLOR + + button.SetBackgroundColour(bc[1]) + button.SetForegroundColour(bc[0]) + + sizer = pad_widget(button) + hsizer.Add(sizer, 0) + + if not element.Visible: + button.Hide() + if element.Tooltip: + button.SetToolTip(element.Tooltip) + + # if btype != BUTTON_TYPE_REALTIME: + # tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height, + # command=element.ButtonCallBack, justify=tk.LEFT, bd=border_depth, font=font) + # else: + # tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height, justify=tk.LEFT, + # bd=border_depth, font=font) + # tkbutton.bind('', element.ButtonReleaseCallBack) + # tkbutton.bind('', element.ButtonPressCallBack) + # if element.ImageFilename: # if button has an image on it + # tkbutton.config(highlightthickness=0) + # photo = tk.PhotoImage(file=element.ImageFilename) + # if element.ImageSize != (None, None): + # width, height = element.ImageSize + # if element.ImageSubsample: + # photo = photo.subsample(element.ImageSubsample) + # else: + # width, height = photo.width(), photo.height() + # tkbutton.config(image=photo, compound=tk.CENTER, width=width, height=height) + # tkbutton.image = photo + # if element.ImageData: # if button has an image on it + # tkbutton.config(highlightthickness=0) + # photo = tk.PhotoImage(data=element.ImageData) + # if element.ImageSize != (None, None): + # width, height = element.ImageSize + # if element.ImageSubsample: + # photo = photo.subsample(element.ImageSubsample) + # else: + # width, height = photo.width(), photo.height() + # tkbutton.config(image=photo, compound=tk.CENTER, width=width, height=height) + # tkbutton.image = photo + # if width != 0: + # tkbutton.configure(wraplength=wraplen + 10) # set wrap to width of widget + # tkbutton.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.BindReturnKey: + # element.TKButton.bind('', element._ReturnKeyHandler) + # if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): + # focus_set = True + # element.TKButton.bind('', element._ReturnKeyHandler) + # element.TKButton.focus_set() + # toplevel_form.TKroot.focus_force() + # if element.Disabled == True: + # element.TKButton['state'] = 'disabled' + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKButton, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + + # # ------------------------- INPUT element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_TEXT: + element = element # type: InputText + if element.Justification is not None: + justification = element.Justification + elif toplevel_form.TextJustification is not None: + justification = toplevel_form.TextJustification + else: + justification = DEFAULT_TEXT_JUSTIFICATION + justify = wx.ALIGN_LEFT if justification.startswith('l') else wx.ALIGN_CENTER_HORIZONTAL if justification.startswith('c') else wx.ALIGN_RIGHT + if element.PasswordCharacter: + justify |= wx.TE_PASSWORD + + element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=justify) + + if element.DefaultText: + text_ctrl.SetValue(element.DefaultText) + if font: + text_ctrl.SetFont(font_to_wx_font(font)) + if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): + text_ctrl.SetForegroundColour(element.TextColor) + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + text_ctrl.SetBackgroundColour(element.BackgroundColor) + text_ctrl.SetMinSize(element_size) + if element.Disabled: + text_ctrl.Enable(False) + if element.ChangeSubmits: + text_ctrl.Bind(wx.EVT_KEY_UP, element._WxCallbackKeyboard) + text_ctrl.Bind(wx.EVT_TEXT_ENTER, element._ReturnKeyHandler) + + sizer = pad_widget(text_ctrl) + + hsizer.Add(sizer, 0) + + if not element.Visible: + text_ctrl.Hide() + if element.Tooltip: + text_ctrl.SetToolTip(element.Tooltip) + + if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): + focus_set = True + element.SetFocus() + + # ------------------------- COMBO BOX (Drop Down) element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_COMBO: + element = element # type: Combo + if element.Readonly: + element.WxComboBox = wx.Choice(toplevel_form.MasterPanel, id=wx.ID_ANY, choices=element.Values) + else: + element.WxComboBox = wx.ComboBox(toplevel_form.MasterPanel, id=wx.ID_ANY, choices=element.Values) + if element.DefaultValue: + element.WxComboBox.SetSelection(element.WxComboBox.FindString(element.DefaultValue)) + if element.Readonly: + element.WxComboBox.SetWindowStyle(wx.CB_READONLY) + + do_font_and_color(element.WxComboBox) + sizer = pad_widget(element.WxComboBox) + + if element.ChangeSubmits: + element.WxComboBox.Bind(wx.EVT_COMBOBOX, element._WxCallbackKeyboard) + + hsizer.Add(sizer, 0) + + # max_line_len = max([len(str(l)) for l in element.Values]) + # if auto_size_text is False: + # width = element_size[0] + # else: + # width = max_line_len + # element.TKStringVar = tk.StringVar() + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # combostyle = ttk.Style() + # try: + # combostyle.theme_create('combostyle', + # settings={'TCombobox': + # {'configure': + # {'selectbackground': element.BackgroundColor, + # 'fieldbackground': element.BackgroundColor, + # 'foreground': text_color, + # 'background': element.BackgroundColor} + # }}) + # except: + # try: + # combostyle.theme_settings('combostyle', + # settings={'TCombobox': + # {'configure': + # {'selectbackground': element.BackgroundColor, + # 'fieldbackground': element.BackgroundColor, + # 'foreground': text_color, + # 'background': element.BackgroundColor} + # }}) + # except: + # pass + # # ATTENTION: this applies the new style 'combostyle' to all ttk.Combobox + # combostyle.theme_use('combostyle') + # element.TKCombo = ttk.Combobox(tk_row_frame, width=width, textvariable=element.TKStringVar, font=font) + # if element.Size[1] != 1 and element.Size[1] is not None: + # element.TKCombo.configure(height=element.Size[1]) + # # element.TKCombo['state']='readonly' + # element.TKCombo['values'] = element.Values + # + # # if element.InitializeAsDisabled: + # # element.TKCombo['state'] = 'disabled' + # # if element.BackgroundColor is not None: + # # element.TKCombo.configure(background=element.BackgroundColor) + # element.TKCombo.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.DefaultValue: + # for i, v in enumerate(element.Values): + # if v == element.DefaultValue: + # element.TKCombo.current(i) + # break + # else: + # element.TKCombo.current(0) + # if element.ChangeSubmits: + # element.TKCombo.bind('<>', element.ComboboxSelectHandler) + # if element.Readonly: + # element.TKCombo['state'] = 'readonly' + # if element.Disabled is True: # note overrides readonly if disabled + # element.TKCombo['state'] = 'disabled' + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKCombo, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) + # # ------------------------- LISTBOX element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_LISTBOX: + pass + # max_line_len = max([len(str(l)) for l in element.Values]) if len(element.Values) != 0 else 0 + # if auto_size_text is False: + # width = element_size[0] + # else: + # width = max_line_len + # listbox_frame = tk.Frame(tk_row_frame) + # element.TKStringVar = tk.StringVar() + # element.TKListbox = tk.Listbox(listbox_frame, height=element_size[1], width=width, + # selectmode=element.SelectMode, font=font) + # for index, item in enumerate(element.Values): + # element.TKListbox.insert(tk.END, item) + # if element.DefaultValues is not None and item in element.DefaultValues: + # element.TKListbox.selection_set(index) + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element.TKListbox.configure(background=element.BackgroundColor) + # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: + # element.TKListbox.configure(fg=text_color) + # if element.ChangeSubmits: + # element.TKListbox.bind('<>', element.ListboxSelectHandler) + # vsb = tk.Scrollbar(listbox_frame, orient="vertical", command=element.TKListbox.yview) + # element.TKListbox.configure(yscrollcommand=vsb.set) + # element.TKListbox.pack(side=tk.LEFT) + # vsb.pack(side=tk.LEFT, fill='y') + # listbox_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.BindReturnKey: + # element.TKListbox.bind('', element.ListboxSelectHandler) + # element.TKListbox.bind('', element.ListboxSelectHandler) + # if element.Disabled == True: + # element.TKListbox['state'] = 'disabled' + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKListbox, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- INPUT MULTILINE element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_MULTILINE: + element = element # type: Multiline + justify = 0 + if element.EnterSubmits: + justify |= wx.TE_PROCESS_ENTER + justify |= wx.TE_MULTILINE + element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=justify) + + if element.DefaultText: + text_ctrl.SetValue(element.DefaultText) + if font: + text_ctrl.SetFont(font_to_wx_font(font)) + if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT): + text_ctrl.SetForegroundColour(element.TextColor) + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): + text_ctrl.SetBackgroundColour(element.BackgroundColor) + text_ctrl.SetMinSize(element_size) + if element.Disabled: + text_ctrl.Enable(False) + if element.ChangeSubmits: + text_ctrl.Bind(wx.EVT_KEY_UP, element._WxCallbackKeyboard) + if element.EnterSubmits: + text_ctrl.Bind(wx.EVT_TEXT_ENTER, element._ReturnKeyHandler) + + sizer = pad_widget(text_ctrl) + hsizer.Add(sizer, 0) + + if not element.Visible: + text_ctrl.Hide() + if element.Tooltip: + text_ctrl.SetToolTip(element.Tooltip) + + if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): + focus_set = True + element.SetFocus() + # ------------------------- OUTPUT MULTILINE element ------------------------- # + elif element_type == ELEM_TYPE_MULTILINE_OUTPUT: + element = element # type: MultilineOutput + style = 0 + if element.EnterSubmits: + style |= wx.TE_PROCESS_ENTER + style |= wx.TE_MULTILINE | wx.TE_READONLY + element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=style) + if element.DefaultText: + text_ctrl.SetValue(element.DefaultText) + + do_font_and_color(element.WxTextCtrl) + + if element.ChangeSubmits: + text_ctrl.Bind(wx.EVT_KEY_UP, element._WxCallbackKeyboard) + if element.EnterSubmits: + text_ctrl.Bind(wx.EVT_TEXT_ENTER, element._ReturnKeyHandler) + + sizer = pad_widget(text_ctrl) + + hsizer.Add(sizer, 0) + + if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): + focus_set = True + element.SetFocus() + # ------------------------- OUTPUT element -----------------fd-------- # + elif element_type == ELEM_TYPE_OUTPUT: + element = element # type: Output + style = 0 + style |= wx.TE_MULTILINE | wx.TE_READONLY + style = wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL + element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=style) + + do_font_and_color(element.WxTextCtrl) + + sizer = pad_widget(text_ctrl) + + hsizer.Add(sizer, 0) + + element._reroute_stdout() + # ------------------------- INPUT CHECKBOX element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_CHECKBOX: + element = element # type:Checkbox + element.WxCheckbox = widget = wx.CheckBox(toplevel_form.MasterPanel) + if element.Text: + widget.SetLabel(element.Text) + do_font_and_color(element.WxCheckbox) + sizer = pad_widget(widget) + + if element.ChangeSubmits: + widget.Bind(wx.EVT_CHECKBOX, element._WxCallbackKeyboard) + + hsizer.Add(sizer, 0) + + if element.InitialState: + widget.SetValue(True) + element.WxCheckbox = widget + + # # ------------------------- PROGRESS BAR element ------------------------- # + elif element_type == ELEM_TYPE_PROGRESS_BAR: + element = element # type: ProgressBar + style = wx.GA_HORIZONTAL if element.Orientation.startswith('h') else wx.GA_VERTICAL + element_size = element_size[::-1] if element.Orientation.startswith('v') else element_size + element_size = wx.Size((element_size[0], element_size[1])) + element.WxGauge = gauge = wx.Gauge(toplevel_form.MasterPanel, wx.ID_ANY, range=element.MaxValue, style=style, size=element_size) + if element.StartValue is not None: + gauge.SetValue(element.StartValue) + do_font_and_color(element.WxGauge) + sizer = pad_widget(gauge) + hsizer.Add(sizer, 0) + # ------------------------- INPUT RADIO BUTTON element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_RADIO: + element = element # type: Radio + widget = element.WxRadioButton # type: wx.RadioButton + do_font_and_color(element.WxRadioButton) + sizer = pad_widget(widget) + if element.ChangeSubmits: + widget.Bind(wx.EVT_RADIOBUTTON, element._WxCallbackKeyboard) + hsizer.Add(sizer, 0) + if element.InitialState: + widget.SetValue(True) + else: + widget.SetValue(False) + + # ------------------------- INPUT SPINNER element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_SPIN: + element = element # type:Spin + ######## First make an Input widget that will be used to display the text ######## + style = wx.ALIGN_RIGHT + if element.ReadOnly: + style |= wx.TE_READONLY + element.WxTextCtrl = text_ctrl = wx.TextCtrl(toplevel_form.MasterPanel, style=style) + do_font_and_color(element.WxTextCtrl) + if element.ChangeSubmits: + text_ctrl.Bind(wx.EVT_KEY_UP, element._WxCallbackKeyboard) + text_ctrl.Bind(wx.EVT_TEXT_ENTER, element._ReturnKeyHandler) + if element.DefaultValue: + text_ctrl.SetValue(str(element.DefaultValue)) + element.CurrentValue = element.DefaultValue + saved_pad = full_element_pad + full_element_pad[3] = 0 # set right padding to 0 + hsizer.Add(pad_widget(text_ctrl), 0) + + full_element_pad = saved_pad + ######## Now make a "Spin Button" that has the arrows ######## + # element.WxSpinCtrl = widget = wx.SpinCtrl(toplevel_form.MasterPanel, style=wx.SP_WRAP|wx.SP_ARROW_KEYS) + element.WxSpinCtrl = widget = wx.SpinButton(toplevel_form.MasterPanel, style=wx.SP_WRAP | wx.SP_ARROW_KEYS) + do_font_and_color(element.WxSpinCtrl) + element.WxSpinCtrl.SetRange(0, len(element.Values) - 1) + if element.DefaultValue: + element.WxSpinCtrl.SetValue(element.Values.index(element.DefaultValue)) + widget.SetMinSize((25, 25)) + + widget.Bind(wx.EVT_SPIN, element._WxSpinCallback) + saved_pad = full_element_pad + full_element_pad[1] = 0 # trying to set left pad to 0 but doesn't seem to work + hsizer.Add(pad_widget(widget), 0) + full_element_pad = saved_pad + + # ------------------------- IMAGE element ------------------------- # + elif element_type == ELEM_TYPE_IMAGE: + pass + # if element.Filename is not None: + # photo = tk.PhotoImage(file=element.Filename) + # elif element.Data is not None: + # photo = tk.PhotoImage(data=element.Data) + # else: + # photo = None + # print('*ERROR laying out form.... Image Element has no image specified*') + # + # if photo is not None: + # if element_size == ( + # None, None) or element_size == None or element_size == toplevel_form.DefaultElementSize: + # width, height = photo.width(), photo.height() + # else: + # width, height = element_size + # if photo is not None: + # element.tktext_label = tk.Label(tk_row_frame, image=photo, width=width, height=height, + # bd=border_depth) + # else: + # element.tktext_label = tk.Label(tk_row_frame, width=width, height=height, bd=border_depth) + # if element.BackgroundColor is not None: + # element.tktext_label.config(background=element.BackgroundColor); + # + # element.tktext_label.image = photo + # # tktext_label.configure(anchor=tk.NW, image=photo) + # element.tktext_label.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.tktext_label, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- Canvas element ------------------------- # + elif element_type == ELEM_TYPE_CANVAS: + pass + # width, height = element_size + # if element._TKCanvas is None: + # element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) + # else: + # element._TKCanvas.master = tk_row_frame + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) + # element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + + # ------------------------- Graph element ------------------------- # + elif element_type == ELEM_TYPE_GRAPH: + pass + # width, height = element_size + # if element._TKCanvas is None: + # element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) + # else: + # element._TKCanvas.master = tk_row_frame + # element._TKCanvas2 = tk.Canvas(element._TKCanvas, width=width, height=height, bd=border_depth) + # element._TKCanvas2.pack(side=tk.LEFT) + # element._TKCanvas2.addtag_all('mytag') + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # element._TKCanvas2.configure(background=element.BackgroundColor, highlightthickness=0) + # element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) + # element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # if element.ChangeSubmits: + # element._TKCanvas2.bind('', element.ButtonReleaseCallBack) + # element._TKCanvas2.bind('', element.ButtonPressCallBack) + # if element.DragSubmits: + # element._TKCanvas2.bind('', element.MotionCallBack) + # ------------------------- MENUBAR element ------------------------- # + elif element_type == ELEM_TYPE_MENUBAR: + pass + # menu_def = element.MenuDefinition + # element.TKMenu = tk.Menu(toplevel_form.TKroot, tearoff=element.Tearoff) # create the menubar + # menubar = element.TKMenu + # for menu_entry in menu_def: + # # print(f'Adding a Menubar ENTRY {menu_entry}') + # baritem = tk.Menu(menubar, tearoff=element.Tearoff) + # pos = menu_entry[0].find('&') + # # print(pos) + # if pos != -1: + # if pos == 0 or menu_entry[0][pos - 1] != "\\": + # menu_entry[0] = menu_entry[0][:pos] + menu_entry[0][pos + 1:] + # menubar.add_cascade(label=menu_entry[0], menu=baritem, underline=pos) + # if len(menu_entry) > 1: + # AddMenuItem(baritem, menu_entry[1], element) + # toplevel_form.TKroot.configure(menu=element.TKMenu) + # ------------------------- Frame element ------------------------- # + elif element_type == ELEM_TYPE_FRAME: + element = element # type: Frame + # ----- code from column as a pattern to follow ----- + # element = element # type: Column + # element.WxBoxSizer = vsizer = wx.BoxSizer(wx.VERTICAL) + # element.WxHSizer = hsizer + # PackFormIntoFrame(element, vsizer, toplevel_form) + # + # hsizer.Add(pad_widget(vsizer), 0) + # if not element.Visible: + # hsizer.Hide(vsizer, recursive=True) + # element.panel = panel = wx.Panel(toplevel_form.MasterFrame) + element.WxBoxSizer = vsizer = wx.StaticBoxSizer(orient=wx.VERTICAL, parent=toplevel_form.MasterFrame.panel, label=element.Title) + element.WxHSizer = hsizer + PackFormIntoFrame(element, vsizer, toplevel_form) + + hsizer.Add(pad_widget(vsizer), 0) + if not element.Visible: + hsizer.Hide(vsizer, recursive=True) + + # labeled_frame = tk.LabelFrame(tk_row_frame, text=element.Title, relief=element.Relief) + # PackFormIntoFrame(element, labeled_frame, toplevel_form) + # labeled_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: + # labeled_frame.configure(background=element.BackgroundColor, + # highlightbackground=element.BackgroundColor, + # highlightcolor=element.BackgroundColor) + # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: + # labeled_frame.configure(foreground=element.TextColor) + # if font is not None: + # labeled_frame.configure(font=font) + # if element.TitleLocation is not None: + # labeled_frame.configure(labelanchor=element.TitleLocation) + # if element.BorderWidth is not None: + # labeled_frame.configure(borderwidth=element.BorderWidth) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(labeled_frame, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- Tab element ------------------------- # + elif element_type == ELEM_TYPE_TAB: + pass + # element.TKFrame = tk.Frame(form.TKNotebook) + # PackFormIntoFrame(element, element.TKFrame, toplevel_form) + # if element.Disabled: + # form.TKNotebook.add(element.TKFrame, text=element.Title, state='disabled') + # else: + # form.TKNotebook.add(element.TKFrame, text=element.Title) + # form.TKNotebook.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # element.ParentNotebook = form.TKNotebook + # element.TabID = form.TabCount + # form.TabCount += 1 + # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: + # element.TKFrame.configure(background=element.BackgroundColor, + # highlightbackground=element.BackgroundColor, + # highlightcolor=element.BackgroundColor) + # # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: + # # element.TKFrame.configure(foreground=element.TextColor) + # + # # ttk.Style().configure("TNotebook", background='red') + # # ttk.Style().map("TNotebook.Tab", background=[("selected", 'orange')], + # # foreground=[("selected", 'green')]) + # # ttk.Style().configure("TNotebook.Tab", background='blue', foreground='yellow') + # + # if element.BorderWidth is not None: + # element.TKFrame.configure(borderwidth=element.BorderWidth) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKFrame, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- TabGroup element ------------------------- # + elif element_type == ELEM_TYPE_TAB_GROUP: + pass + + # custom_style = str(element.Key) + 'customtab.TNotebook' + # style = ttk.Style(tk_row_frame) + # if element.Theme is not None: + # style.theme_use(element.Theme) + # if element.TabLocation is not None: + # position_dict = {'left': 'w', 'right': 'e', 'top': 'n', 'bottom': 's', 'lefttop': 'wn', + # 'leftbottom': 'ws', 'righttop': 'en', 'rightbottom': 'es', 'bottomleft': 'sw', + # 'bottomright': 'se', 'topleft': 'nw', 'topright': 'ne'} + # try: + # tab_position = position_dict[element.TabLocation] + # except: + # tab_position = position_dict['top'] + # style.configure(custom_style, tabposition=tab_position) + # + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # style.configure(custom_style, background=element.BackgroundColor, foreground='purple') + # + # # style.theme_create("yummy", parent="alt", settings={ + # # "TNotebook": {"configure": {"tabmargins": [2, 5, 2, 0]}}, + # # "TNotebook.Tab": { + # # "configure": {"padding": [5, 1], "background": mygreen}, + # # "map": {"background": [("selected", myred)], + # # "expand": [("selected", [1, 1, 1, 0])]}}}) + # + # # style.configure(custom_style+'.Tab', background='red') + # if element.SelectedTitleColor != None: + # style.map(custom_style + '.Tab', foreground=[("selected", element.SelectedTitleColor)]) + # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + # style.configure(custom_style + '.Tab', foreground=element.TextColor) + # # style.configure(custom_style, background='blue', foreground='yellow') + # + # element.TKNotebook = ttk.Notebook(tk_row_frame, style=custom_style) + # + # PackFormIntoFrame(element, toplevel_form.TKroot, toplevel_form) + # + # if element.ChangeSubmits: + # element.TKNotebook.bind('<>', element.TabGroupSelectHandler) + # if element.BorderWidth is not None: + # element.TKNotebook.configure(borderwidth=element.BorderWidth) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKNotebook, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- SLIDER Box element ------------------------- # + elif element_type == ELEM_TYPE_INPUT_SLIDER: + pass + # slider_length = element_size[0] * CharWidthInPixels() + # slider_width = element_size[1] + # element.TKIntVar = tk.IntVar() + # element.TKIntVar.set(element.DefaultValue) + # if element.Orientation[0] == 'v': + # range_from = element.Range[1] + # range_to = element.Range[0] + # slider_length += DEFAULT_MARGINS[1] * (element_size[0] * 2) # add in the padding + # else: + # range_from = element.Range[0] + # range_to = element.Range[1] + # if element.ChangeSubmits: + # tkscale = tk.Scale(tk_row_frame, orient=element.Orientation, variable=element.TKIntVar, + # from_=range_from, to_=range_to, resolution=element.Resolution, + # length=slider_length, width=slider_width, bd=element.BorderWidth, + # relief=element.Relief, font=font, tickinterval=element.TickInterval, + # command=element.SliderChangedHandler) + # else: + # tkscale = tk.Scale(tk_row_frame, orient=element.Orientation, variable=element.TKIntVar, + # from_=range_from, to_=range_to, resolution=element.Resolution, + # length=slider_length, width=slider_width, bd=element.BorderWidth, + # relief=element.Relief, font=font, tickinterval=element.TickInterval) + # tkscale.config(highlightthickness=0) + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # tkscale.configure(background=element.BackgroundColor) + # if DEFAULT_SCROLLBAR_COLOR != COLOR_SYSTEM_DEFAULT: + # tkscale.config(troughcolor=DEFAULT_SCROLLBAR_COLOR) + # if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: + # tkscale.configure(fg=text_color) + # tkscale.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) + # element.TKScale = tkscale + # if element.Disabled == True: + # element.TKScale['state'] = 'disabled' + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKScale, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- TABLE element ------------------------- # + elif element_type == ELEM_TYPE_TABLE: + pass + # frame = tk.Frame(tk_row_frame) + # + # height = element.NumRows + # if element.Justification == 'left': + # anchor = tk.W + # elif element.Justification == 'right': + # anchor = tk.E + # else: + # anchor = tk.CENTER + # column_widths = {} + # for row in element.Values: + # for i, col in enumerate(row): + # col_width = min(len(str(col)), element.MaxColumnWidth) + # try: + # if col_width > column_widths[i]: + # column_widths[i] = col_width + # except: + # column_widths[i] = col_width + # if element.ColumnsToDisplay is None: + # displaycolumns = element.ColumnHeadings + # else: + # displaycolumns = [] + # for i, should_display in enumerate(element.ColumnsToDisplay): + # if should_display: + # displaycolumns.append(element.ColumnHeadings[i]) + # column_headings = element.ColumnHeadings + # if element.DisplayRowNumbers: # if display row number, tack on the numbers to front of columns + # displaycolumns = [element.RowHeaderText, ] + displaycolumns + # column_headings = [element.RowHeaderText, ] + element.ColumnHeadings + # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, + # displaycolumns=displaycolumns, show='headings', height=height, + # selectmode=element.SelectMode) + # treeview = element.TKTreeview + # if element.DisplayRowNumbers: + # treeview.heading(element.RowHeaderText, text=element.RowHeaderText) # make a dummy heading + # treeview.column(element.RowHeaderText, width=50, anchor=anchor) + # for i, heading in enumerate(element.ColumnHeadings): + # treeview.heading(heading, text=heading) + # if element.AutoSizeColumns: + # width = max(column_widths[i], len(heading)) + # else: + # try: + # width = element.ColumnWidths[i] + # except: + # width = element.DefaultColumnWidth + # + # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) + # # Insert values into the tree + # for i, value in enumerate(element.Values): + # if element.DisplayRowNumbers: + # value = [i + element.StartingRowNumber] + value + # id = treeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i % 2) + # if element.AlternatingRowColor is not None: + # treeview.tag_configure(1, background=element.AlternatingRowColor) + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", background=element.BackgroundColor, + # fieldbackground=element.BackgroundColor) + # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", foreground=element.TextColor) + # # scrollable_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') + # treeview.bind("<>", element.treeview_selected) + # if element.BindReturnKey: + # treeview.bind('', element.treeview_double_click) + # treeview.bind('', element.treeview_double_click) + # scrollbar = tk.Scrollbar(frame) + # scrollbar.pack(side=tk.RIGHT, fill='y') + # scrollbar.config(command=treeview.yview) + # treeview.configure(yscrollcommand=scrollbar.set) + # + # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') + # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) + # if element.Tooltip is not None: + # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- Tree element ------------------------- # + elif element_type == ELEM_TYPE_TREE: + pass + # frame = tk.Frame(tk_row_frame) + # + # height = element.NumRows + # if element.Justification == 'left': # justification + # anchor = tk.W + # elif element.Justification == 'right': + # anchor = tk.E + # else: + # anchor = tk.CENTER + # + # if element.ColumnsToDisplay is None: # Which cols to display + # displaycolumns = element.ColumnHeadings + # else: + # displaycolumns = [] + # for i, should_display in enumerate(element.ColumnsToDisplay): + # if should_display: + # displaycolumns.append(element.ColumnHeadings[i]) + # column_headings = element.ColumnHeadings + # # ------------- GET THE TREEVIEW WIDGET ------------- + # element.TKTreeview = ttk.Treeview(frame, columns=column_headings, + # displaycolumns=displaycolumns, show='tree headings', height=height, + # selectmode=element.SelectMode, ) + # treeview = element.TKTreeview + # for i, heading in enumerate(element.ColumnHeadings): # Configure cols + headings + # treeview.heading(heading, text=heading) + # if element.AutoSizeColumns: + # width = min(element.MaxColumnWidth, len(heading) + 1) + # else: + # try: + # width = element.ColumnWidths[i] + # except: + # width = element.DefaultColumnWidth + # treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) + # + # def add_treeview_data(node): + # # print(f'Inserting {node.key} under parent {node.parent}') + # if node.key != '': + # treeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, + # open=element.ShowExpanded) + # for node in node.children: + # add_treeview_data(node) + # + # add_treeview_data(element.TreeData.root_node) + # treeview.column('#0', width=element.Col0Width * CharWidthInPixels(), anchor=anchor) + # # ----- configure colors ----- + # if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", background=element.BackgroundColor, + # fieldbackground=element.BackgroundColor) + # if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: + # ttk.Style().configure("Treeview", foreground=element.TextColor) + # + # scrollbar = tk.Scrollbar(frame) + # scrollbar.pack(side=tk.RIGHT, fill='y') + # scrollbar.config(command=treeview.yview) + # treeview.configure(yscrollcommand=scrollbar.set) + # element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') + # frame.pack(side=tk.LEFT, expand=True, padx=0, pady=0) + # treeview.bind("<>", element.treeview_selected) + # if element.Tooltip is not None: # tooltip + # element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, + # timeout=DEFAULT_TOOLTIP_TIME) + # ------------------------- Separator element ------------------------- # + elif element_type == ELEM_TYPE_SEPARATOR: + element = element # type: VerticalSeparator + if element.Orientation.lower().startswith('v'): + element.WxStaticLine = static_line = wx.StaticLine(toplevel_form.MasterPanel, style=wx.LI_VERTICAL) + else: + element.WxStaticLine = static_line = wx.StaticLine(toplevel_form.MasterPanel, style=wx.LI_HORIZONTAL) + + do_font_and_color(element.WxStaticLine) + + sizer = pad_widget(static_line) + + hsizer.Add(sizer, 0) + + # .................DONE WITH ROW pack the row of widgets ..................# + + # Add the row to the layout. Justify the row depending on the settings for its container + if container_elem.ElementJustification.startswith('c'): + containing_frame.Add(hsizer, 0, wx.ALIGN_CENTER, border=0) + elif container_elem.ElementJustification.startswith('r'): + containing_frame.Add(hsizer, 0, wx.ALIGN_RIGHT, border=0) + else: + containing_frame.Add(hsizer, 0, wx.TOP | wx.BOTTOM, border=0) + + return + + +# ----====----====----====----====----==== STARTUP TK ====----====----====----====----====----# +def StartupTK(window: Window): + + ow = Window.NumOpenWindows + if Window.highest_level_app is None: + app = Window.highest_level_app = wx.App(False) + else: + app = Window.highest_level_app + Window.IncrementOpenCount() + + # -------- grab anywhere -------- + if window.GrabAnywhere: + frame = DragFrame(title=window.Title) + else: + frame = wx.Frame(None, title=window.Title) + + panel = wx.Panel(frame, -1, style=wx.TRANSPARENT_WINDOW) + # panel.SetTransparent(.5) + if window.GrabAnywhere: + panel.Bind(wx.EVT_MOTION, frame.on_mouse) + + window.App = app + window.MasterFrame = frame + window.MasterPanel = panel + window.MasterFrame.panel = panel + frame.Bind(wx.EVT_CLOSE, window.OnClose) + + # ----------------------------- Icon ----------------------------- + if window.WindowIcon: + if type(window.WindowIcon) is bytes: + icon = PyEmbeddedImage(window.WindowIcon).GetIcon() + else: + if os.path.exists(window.WindowIcon): + icon = wx.Icon(window.WindowIcon, wx.BITMAP_TYPE_ANY) + else: + icon = PyEmbeddedImage(DEFAULT_BASE64_ICON).GetIcon() + if icon: + frame.SetIcon(icon) + + # ----------------------------- Background ----------------------------- + if window.BackgroundColor is not None and window.BackgroundColor != COLOR_SYSTEM_DEFAULT: + panel.SetBackgroundColour(window.BackgroundColor) + + if window.BackgroundImage: + if type(window.BackgroundImage) is bytes: + pic = PyEmbeddedImage(window.BackgroundImage).GetBitmap() + else: + if os.path.exists(window.BackgroundImage): + pic = wx.Image(window.BackgroundImage, wx.BITMAP_TYPE_ANY).ConvertToBitmap() + else: + pic = PyEmbeddedImage(DEFAULT_BASE64_ICON).GetBitmap() + window.bitmap1 = wx.StaticBitmap(window.MasterPanel, -1, pic, (0, 0)) + + InitializeResults(window) + + # ----------------------------- ----------------------------- + # ----------------------------- ----------------------------- + # ----------------------------- handle settings using Style Flags ----------------------------- + style = 0 + if window.NoTitleBar: + style |= wx.BORDER_NONE + else: + style |= wx.BORDER_DEFAULT + if window.KeepOnTop: + style |= wx.STAY_ON_TOP + if style: + window.MasterFrame.SetWindowStyleFlag(style) + + if window.ReturnKeyboardEvents: + # style |= wx.WANTS_CHARS + window.App.Bind(wx.EVT_CHAR_HOOK, window.callback_keyboard_char) + window.App.Bind(wx.EVT_MOUSEWHEEL, window.callback_keyboard_char) + + # ----------------------------- Sizer creation and PACK FORM ----------------------------- + vsizer = wx.BoxSizer(wx.VERTICAL) + + preprocess_radio_elements(window, window) + + # ----------------------------- Do the packing of the elements ----------------------------- + + PackFormIntoFrame(window, vsizer, window) + + # ----------------------------- Sizers to create margins ----------------------------- + outersizer = wx.BoxSizer(wx.VERTICAL) + outersizer.Fit(window.MasterFrame) + outersizer.Add(vsizer, 1, wx.TOP | wx.BOTTOM | wx.EXPAND, border=DEFAULT_MARGINS[1]) + + window.OuterSizer = wx.BoxSizer(wx.VERTICAL) + window.OuterSizer.Fit(window.MasterFrame) + window.OuterSizer.Add(outersizer, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, border=DEFAULT_MARGINS[0]) + + window.MasterPanel.SetSizer(window.OuterSizer) + + window.OuterSizer.Fit(window.MasterFrame) + + # ----------------------------- window location, size and alpha ----------------------------- + if window.Location != (None, None): + window.MasterFrame.Move(window.Location[0], window.Location[1]) + else: + window.MasterFrame.Center(wx.BOTH) + + if window._Size != (None, None): + window.MasterFrame.SetSize(window._Size[0], window._Size[1]) + + if window._AlphaChannel is not None: + window.SetAlpha(window._AlphaChannel) + + # ----------------------------- DISPLAY the window ----------------------------- + window.MasterFrame.Show() + + # ....................................... DONE creating and laying out window ..........................# + if RUN_INSPECTION_TOOL: + wx.lib.inspection.InspectionTool().Show() + window.CurrentlyRunningMainloop = True + + if window.Timeout: + timer = wx.Timer(window.App) + window.App.Bind(wx.EVT_TIMER, window.timer_timeout) + timer.Start(milliseconds=window.Timeout, oneShot=wx.TIMER_ONE_SHOT) + else: + timer = None + + if window.AutoClose: + window.timer = wx.Timer(window.App, id=Window.NumOpenWindows) + window.App.Bind(wx.EVT_TIMER, lambda frame: window.autoclose_timer_callback(window.MasterFrame), id=Window.NumOpenWindows) + window.timer.Start(milliseconds=window.AutoCloseDuration * 1000, oneShot=wx.TIMER_ONE_SHOT) + # ------------------------------------ MAINLOOP ------------------------------------ + + if not window.NonBlocking: + window.App.MainLoop() + else: + window.non_block_timer = wx.Timer(window.App, id=5678) + window.App.Bind(wx.EVT_TIMER, window.non_block_timer_timeout, id=5678) + window.non_block_timer.Start(milliseconds=0, oneShot=wx.TIMER_ONE_SHOT) + window.App.MainLoop() + + if Window.stdout_is_rerouted: + sys.stdout = Window.stdout_location + window.CurrentlyRunningMainloop = False + if timer: + timer.Stop() + + # if not window.FormRemainedOpen: + # _my_windows.Decrement() + # if window.RootNeedsDestroying: + # window.TKroot.destroy() + # window.RootNeedsDestroying = False + return + + +# ==============================_GetNumLinesNeeded ==# +# Helper function for determining how to wrap text # +# ===================================================# +def _GetNumLinesNeeded(text, max_line_width): + if max_line_width == 0: + return 1 + lines = text.split('\n') + num_lines = len(lines) # number of original lines of text + max_line_len = max([len(l) for l in lines]) # longest line + lines_used = [] + for L in lines: + lines_used.append(len(L) // max_line_width + (len(L) % max_line_width > 0)) # fancy math to round up + total_lines_needed = sum(lines_used) + return total_lines_needed + + +# ============================== PROGRESS METER ========================================== # + + +def ConvertArgsToSingleString(*args): + ( + max_line_total, + width_used, + total_lines, + ) = ( + 0, + 0, + 0, + ) + single_line_message = '' + # loop through args and built a SINGLE string from them + for message in args: + # fancy code to check if string and convert if not is not need. Just always convert to string :-) + # if not isinstance(message, str): message = str(message) + message = str(message) + longest_line_len = max([len(l) for l in message.split('\n')]) + width_used = max(longest_line_len, width_used) + max_line_total = max(max_line_total, width_used) + lines_needed = _GetNumLinesNeeded(message, width_used) + total_lines += lines_needed + single_line_message += message + '\n' + return single_line_message, width_used, total_lines + + +METER_REASON_CANCELLED = 'cancelled' +METER_REASON_CLOSED = 'closed' +METER_REASON_REACHED_MAX = 'finished' +METER_OK = True +METER_STOPPED = False + + +class QuickMeter(object): + active_meters = {} + exit_reasons = {} + + def __init__( + self, + title, + current_value, + max_value, + key, + *args, + orientation='v', + bar_color=(None, None), + button_color=(None, None), + size=DEFAULT_PROGRESS_BAR_SIZE, + border_width=None, + grab_anywhere=False, + ): + self.start_time = datetime.datetime.utcnow() + self.key = key + self.orientation = orientation + self.bar_color = bar_color + self.size = size + self.grab_anywhere = grab_anywhere + self.button_color = button_color + self.border_width = border_width + self.title = title + self.current_value = current_value + self.max_value = max_value + self.close_reason = None + self.window = self.BuildWindow(*args) + + def BuildWindow(self, *args): + layout = [] + if self.orientation.lower().startswith('h'): + col = [] + col += [[T(''.join(map(lambda x: str(x) + '\n', args)), key='_OPTMSG_')]] ### convert all *args into one string that can be updated + col += [ + [T('', size=(25, 8), key='_STATS_')], + [ProgressBar(max_value=self.max_value, orientation='h', key='_PROG_', size=self.size)], + [Cancel(button_color=self.button_color), Stretch()], + ] + layout = [Column(col)] + else: + col = [[ProgressBar(max_value=self.max_value, orientation='v', key='_PROG_', size=self.size)]] + col2 = [] + col2 += [[T(''.join(map(lambda x: str(x) + '\n', args)), key='_OPTMSG_')]] ### convert all *args into one string that can be updated + col2 += [[T('', size=(25, 8), key='_STATS_')], [Cancel(button_color=self.button_color), Stretch()]] + layout = [Column(col), Column(col2)] + self.window = Window(self.title, grab_anywhere=self.grab_anywhere, border_depth=self.border_width) + self.window.Layout([layout]).Finalize() + + return self.window + + def UpdateMeter(self, current_value, max_value, *args): + self.current_value = current_value + self.max_value = max_value + self.window.Element('_PROG_').UpdateBar(self.current_value, self.max_value) + self.window.Element('_STATS_').Update('\n'.join(self.ComputeProgressStats())) + self.window.Element('_OPTMSG_').Update(value=''.join(map(lambda x: str(x) + '\n', args))) ### update the string with the args + event, values = self.window.Read(timeout=0) + if event in ('Cancel', None) or current_value >= max_value: + self.window.Close() + del QuickMeter.active_meters[self.key] + QuickMeter.exit_reasons[self.key] = METER_REASON_CANCELLED if event == 'Cancel' else METER_REASON_CLOSED if event is None else METER_REASON_REACHED_MAX + return QuickMeter.exit_reasons[self.key] + return METER_OK + + def ComputeProgressStats(self): + utc = datetime.datetime.utcnow() + time_delta = utc - self.start_time + total_seconds = time_delta.total_seconds() + if not total_seconds: + total_seconds = 1 + try: + time_per_item = total_seconds / self.current_value + except: + time_per_item = 1 + seconds_remaining = (self.max_value - self.current_value) * time_per_item + time_remaining = str(datetime.timedelta(seconds=seconds_remaining)) + time_remaining_short = (time_remaining).split('.')[0] + time_delta_short = str(time_delta).split('.')[0] + total_time = time_delta + datetime.timedelta(seconds=seconds_remaining) + total_time_short = str(total_time).split('.')[0] + self.stat_messages = [ + '{} of {}'.format(self.current_value, self.max_value), + '{} %'.format(100 * self.current_value // self.max_value), + '', + ' {:6.2f} Iterations per Second'.format(self.current_value / total_seconds), + ' {:6.2f} Seconds per Iteration'.format(total_seconds / (self.current_value if self.current_value else 1)), + '', + '{} Elapsed Time'.format(time_delta_short), + '{} Time Remaining'.format(time_remaining_short), + '{} Estimated Total Time'.format(total_time_short), + ] + return self.stat_messages + + +def OneLineProgressMeter( + title, + current_value, + max_value, + key='OK for 1 meter', + *args, + orientation='v', + bar_color=(None, None), + button_color=None, + size=DEFAULT_PROGRESS_BAR_SIZE, + border_width=None, + grab_anywhere=False, +): + if key not in QuickMeter.active_meters: + meter = QuickMeter( + title, + current_value, + max_value, + key, + *args, + orientation=orientation, + bar_color=bar_color, + button_color=button_color, + size=size, + border_width=border_width, + grab_anywhere=grab_anywhere, + ) + QuickMeter.active_meters[key] = meter + else: + meter = QuickMeter.active_meters[key] + + rc = meter.UpdateMeter(current_value, max_value, *args) + OneLineProgressMeter.exit_reasons = getattr(OneLineProgressMeter, 'exit_reasons', QuickMeter.exit_reasons) + return rc == METER_OK + + +def OneLineProgressMeterCancel(key='OK for 1 meter'): + try: + meter = QuickMeter.active_meters[key] + meter.window.Close() + del QuickMeter.active_meters[key] + QuickMeter.exit_reasons[key] = METER_REASON_CANCELLED + except: # meter is already deleted + return + + +# input is #RRGGBB +# output is #RRGGBB +def GetComplimentaryHex(color): + # strip the # from the beginning + color = color[1:] + # convert the string into hex + color = int(color, 16) + # invert the three bytes + # as good as substracting each of RGB component by 255(FF) + comp_color = 0xFFFFFF ^ color + # convert the color back to hex by prefixing a # + comp_color = '#%06X' % comp_color + return comp_color + + +# ======================== EasyPrint =====# +# ===================================================# + + +class DebugWin: + debug_window = None + + def __init__( + self, + size=(None, None), + location=(None, None), + font=None, + no_titlebar=False, + no_button=False, + grab_anywhere=False, + keep_on_top=False, + title=None, + do_not_reroute_stdout=False, + ): + # Show a form that's a running counter + self.size = size + self.location = location + self.font = font + self.no_titlebar = no_titlebar + self.no_button = no_button + self.grab_anywhere = grab_anywhere + self.keep_on_top = keep_on_top + self.do_not_reroute_stdout = do_not_reroute_stdout + + win_size = size if size != (None, None) else DEFAULT_DEBUG_WINDOW_SIZE + self.window = Window( + title=title or 'Debug Window', + no_titlebar=no_titlebar, + auto_size_text=True, + location=location, + font=font or ('Courier New', 10), + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + ) + self.output_element = MultilineOutput(size=win_size, key='_MULTILINE_') if do_not_reroute_stdout else Output(size=win_size) + + if no_button: + self.layout = [[self.output_element]] + else: + self.layout = [[self.output_element], [DummyButton('Quit'), Stretch()]] + self.window.AddRows(self.layout) + self.window.Read(timeout=0) # Show a non-blocking form, returns immediately + Window.active_popups[self.window] = 'debug window' + return + + def Print(self, *args, end=None, sep=None): + sepchar = sep if sep is not None else ' ' + endchar = end if end is not None else '\n' + + if self.window is None: # if window was destroyed already, just print + self.__init__( + size=self.size, + location=self.location, + font=self.font, + no_titlebar=self.no_titlebar, + no_button=self.no_button, + grab_anywhere=self.grab_anywhere, + keep_on_top=self.keep_on_top, + do_not_reroute_stdout=self.do_not_reroute_stdout, + ) + event, values = self.window.Read(timeout=0) + if event == 'Quit' or event is None: + self.Close() + self.__init__( + size=self.size, + location=self.location, + font=self.font, + no_titlebar=self.no_titlebar, + no_button=self.no_button, + grab_anywhere=self.grab_anywhere, + keep_on_top=self.keep_on_top, + do_not_reroute_stdout=self.do_not_reroute_stdout, + ) + if self.do_not_reroute_stdout: + outstring = '' + for arg in args: + outstring += str(arg) + sepchar + outstring += endchar + self.output_element.Update(outstring, append=True) + else: + print(*args, sep=sepchar, end=endchar) + + def Close(self): + self.window.Close() + self.window = None + + +def PrintClose(): + EasyPrintClose() + + +def EasyPrint( + *args, + size=(None, None), + end=None, + sep=None, + location=(None, None), + font=None, + no_titlebar=False, + no_button=False, + grab_anywhere=False, + keep_on_top=False, + do_not_reroute_stdout=True, +): + + if DebugWin.debug_window is None: + DebugWin.debug_window = DebugWin( + size=size, + location=location, + font=font, + no_titlebar=no_titlebar, + no_button=no_button, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + do_not_reroute_stdout=do_not_reroute_stdout, + ) + DebugWin.debug_window.Print(*args, end=end, sep=sep) + + +Print = EasyPrint +eprint = EasyPrint + + +def EasyPrintClose(): + if DebugWin.debug_window is not None: + DebugWin.debug_window.Close() + DebugWin.debug_window = None + + +# ======================== Scrolled Text Box =====# +# ===================================================# +def PopupScrolled(*args, button_color=None, yes_no=False, auto_close=False, auto_close_duration=None, size=(None, None)): + if not args: + return + width, height = size + width = width if width else MESSAGE_BOX_LINE_WIDTH + form = Window( + args[0], + auto_size_text=True, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + ) + max_line_total, max_line_width, total_lines, height_computed = 0, 0, 0, 0 + complete_output = '' + for message in args: + # fancy code to check if string and convert if not is not need. Just always convert to string :-) + # if not isinstance(message, str): message = str(message) + message = str(message) + longest_line_len = max([len(l) for l in message.split('\n')]) + width_used = min(longest_line_len, width) + max_line_total = max(max_line_total, width_used) + max_line_width = width + lines_needed = _GetNumLinesNeeded(message, width_used) + height_computed += lines_needed + complete_output += message + '\n' + total_lines += lines_needed + height_computed = MAX_SCROLLED_TEXT_BOX_HEIGHT if height_computed > MAX_SCROLLED_TEXT_BOX_HEIGHT else height_computed + if height: + height_computed = height + form.AddRow(Multiline(complete_output, size=(max_line_width, height_computed))) + pad = max_line_total - 15 if max_line_total > 15 else 1 + # show either an OK or Yes/No depending on paramater + if yes_no: + form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Yes(), No()) + button, values = form.Read() + return button + else: + form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Button('OK', size=(5, 1), button_color=button_color)) + button, values = form.Read() + return button + + +ScrolledTextBox = PopupScrolled + + +# ============================== SetGlobalIcon ======# +# Sets the icon to be used by default # +# ===================================================# +def SetGlobalIcon(icon): + if icon is not None: + Window.user_defined_icon = icon + return True + + +# ============================== SetOptions =========# +# Sets the icon to be used by default # +# ===================================================# +def SetOptions( + icon=None, + button_color=None, + element_size=(None, None), + button_element_size=(None, None), + margins=(None, None), + element_padding=(None, None), + auto_size_text=None, + auto_size_buttons=None, + font=None, + border_width=None, + slider_border_width=None, + slider_relief=None, + slider_orientation=None, + autoclose_time=None, + message_box_line_width=None, + progress_meter_border_depth=None, + progress_meter_style=None, + progress_meter_relief=None, + progress_meter_color=None, + progress_meter_size=None, + text_justification=None, + background_color=None, + element_background_color=None, + text_element_background_color=None, + input_elements_background_color=None, + input_text_color=None, + scrollbar_color=None, + text_color=None, + element_text_color=None, + debug_win_size=(None, None), + window_location=(None, None), + tooltip_time=None, +): + global DEFAULT_ELEMENT_SIZE + global DEFAULT_BUTTON_ELEMENT_SIZE + global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term + global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels + global DEFAULT_AUTOSIZE_TEXT + global DEFAULT_AUTOSIZE_BUTTONS + global DEFAULT_FONT + global DEFAULT_BORDER_WIDTH + global DEFAULT_AUTOCLOSE_TIME + global DEFAULT_BUTTON_COLOR + global MESSAGE_BOX_LINE_WIDTH + global DEFAULT_PROGRESS_BAR_BORDER_WIDTH + global DEFAULT_PROGRESS_BAR_STYLE + global DEFAULT_PROGRESS_BAR_RELIEF + global DEFAULT_PROGRESS_BAR_COLOR + global DEFAULT_PROGRESS_BAR_SIZE + global DEFAULT_TEXT_JUSTIFICATION + global DEFAULT_DEBUG_WINDOW_SIZE + global DEFAULT_SLIDER_BORDER_WIDTH + global DEFAULT_SLIDER_RELIEF + global DEFAULT_SLIDER_ORIENTATION + global DEFAULT_BACKGROUND_COLOR + global DEFAULT_INPUT_ELEMENTS_COLOR + global DEFAULT_ELEMENT_BACKGROUND_COLOR + global DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR + global DEFAULT_SCROLLBAR_COLOR + global DEFAULT_TEXT_COLOR + global DEFAULT_WINDOW_LOCATION + global DEFAULT_ELEMENT_TEXT_COLOR + global DEFAULT_INPUT_TEXT_COLOR + global DEFAULT_TOOLTIP_TIME + + if icon is not None: + Window.user_defined_icon = icon + + if button_color != None: + DEFAULT_BUTTON_COLOR = button_color + + if element_size != (None, None): + DEFAULT_ELEMENT_SIZE = element_size + + if button_element_size != (None, None): + DEFAULT_BUTTON_ELEMENT_SIZE = button_element_size + + if margins != (None, None): + DEFAULT_MARGINS = margins + + if element_padding != (None, None): + DEFAULT_ELEMENT_PADDING = element_padding + + if auto_size_text != None: + DEFAULT_AUTOSIZE_TEXT = auto_size_text + + if auto_size_buttons != None: + DEFAULT_AUTOSIZE_BUTTONS = auto_size_buttons + + if font != None: + DEFAULT_FONT = font + + if border_width != None: + DEFAULT_BORDER_WIDTH = border_width + + if autoclose_time != None: + DEFAULT_AUTOCLOSE_TIME = autoclose_time + + if message_box_line_width != None: + MESSAGE_BOX_LINE_WIDTH = message_box_line_width + + if progress_meter_border_depth != None: + DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth + + if progress_meter_style != None: + DEFAULT_PROGRESS_BAR_STYLE = progress_meter_style + + if progress_meter_relief != None: + DEFAULT_PROGRESS_BAR_RELIEF = progress_meter_relief + + if progress_meter_color != None: + DEFAULT_PROGRESS_BAR_COLOR = progress_meter_color + + if progress_meter_size != None: + DEFAULT_PROGRESS_BAR_SIZE = progress_meter_size + + if slider_border_width != None: + DEFAULT_SLIDER_BORDER_WIDTH = slider_border_width + + if slider_orientation != None: + DEFAULT_SLIDER_ORIENTATION = slider_orientation + + if slider_relief != None: + DEFAULT_SLIDER_RELIEF = slider_relief + + if text_justification != None: + DEFAULT_TEXT_JUSTIFICATION = text_justification + + if background_color != None: + DEFAULT_BACKGROUND_COLOR = background_color + + if text_element_background_color != None: + DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = text_element_background_color + + if input_elements_background_color != None: + DEFAULT_INPUT_ELEMENTS_COLOR = input_elements_background_color + + if element_background_color != None: + DEFAULT_ELEMENT_BACKGROUND_COLOR = element_background_color + + if window_location != (None, None): + DEFAULT_WINDOW_LOCATION = window_location + + if debug_win_size != (None, None): + DEFAULT_DEBUG_WINDOW_SIZE = debug_win_size + + if text_color != None: + DEFAULT_TEXT_COLOR = text_color + + if scrollbar_color != None: + DEFAULT_SCROLLBAR_COLOR = scrollbar_color + + if element_text_color != None: + DEFAULT_ELEMENT_TEXT_COLOR = element_text_color + + if input_text_color is not None: + DEFAULT_INPUT_TEXT_COLOR = input_text_color + + if tooltip_time is not None: + DEFAULT_TOOLTIP_TIME = tooltip_time + + return True + + +# ----------------------------------------------------------------- # + +# .########.##.....##.########.##.....##.########..######. +# ....##....##.....##.##.......###...###.##.......##....## +# ....##....##.....##.##.......####.####.##.......##...... +# ....##....#########.######...##.###.##.######....######. +# ....##....##.....##.##.......##.....##.##.............## +# ....##....##.....##.##.......##.....##.##.......##....## +# ....##....##.....##.########.##.....##.########..######. + +# ----------------------------------------------------------------- # + +# The official Theme code + +#################### ChangeLookAndFeel ####################### +# Predefined settings that will change the colors and styles # +# of the elements. # +############################################################## +LOOK_AND_FEEL_TABLE = { + 'SystemDefault': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'SystemDefaultForReal': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': COLOR_SYSTEM_DEFAULT, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'SystemDefault1': { + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': COLOR_SYSTEM_DEFAULT, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'Material1': { + 'BACKGROUND': '#E3F2FD', + 'TEXT': '#000000', + 'INPUT': '#86A8FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#86A8FF', + 'BUTTON': ('#FFFFFF', '#5079D3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'Material2': { + 'BACKGROUND': '#FAFAFA', + 'TEXT': '#000000', + 'INPUT': '#004EA1', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#5EA7FF', + 'BUTTON': ('#FFFFFF', '#0079D3'), # based on Reddit color + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'Reddit': { + 'BACKGROUND': '#ffffff', + 'TEXT': '#1a1a1b', + 'INPUT': '#dae0e6', + 'TEXT_INPUT': '#222222', + 'SCROLL': '#a5a4a4', + 'BUTTON': ('white', '#0079d3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#ff5414', + 'ACCENT2': '#33a8ff', + 'ACCENT3': '#dbf0ff', + }, + 'Topanga': { + 'BACKGROUND': '#282923', + 'TEXT': '#E7DB74', + 'INPUT': '#393a32', + 'TEXT_INPUT': '#E7C855', + 'SCROLL': '#E7C855', + 'BUTTON': ('#E7C855', '#284B5A'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#c15226', + 'ACCENT2': '#7a4d5f', + 'ACCENT3': '#889743', + }, + 'GreenTan': { + 'BACKGROUND': '#9FB8AD', + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': '#F7F3EC', + 'TEXT_INPUT': 'black', + 'SCROLL': '#F7F3EC', + 'BUTTON': ('white', '#475841'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Dark': { + 'BACKGROUND': '#404040', + 'TEXT': 'white', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': 'white', + 'SCROLL': '#707070', + 'BUTTON': ('white', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen': { + 'BACKGROUND': '#B7CECE', + 'TEXT': 'black', + 'INPUT': '#FDFFF7', + 'TEXT_INPUT': 'black', + 'SCROLL': '#FDFFF7', + 'BUTTON': ('white', '#658268'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'ACCENT1': '#76506d', + 'ACCENT2': '#5148f1', + 'ACCENT3': '#0a1c84', + 'PROGRESS_DEPTH': 0, + }, + 'Dark2': { + 'BACKGROUND': '#404040', + 'TEXT': 'white', + 'INPUT': 'white', + 'TEXT_INPUT': 'black', + 'SCROLL': '#707070', + 'BUTTON': ('white', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Black': { + 'BACKGROUND': 'black', + 'TEXT': 'white', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': 'white', + 'SCROLL': '#707070', + 'BUTTON': ('black', 'white'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Tan': { + 'BACKGROUND': '#fdf6e3', + 'TEXT': '#268bd1', + 'INPUT': '#eee8d5', + 'TEXT_INPUT': '#6c71c3', + 'SCROLL': '#eee8d5', + 'BUTTON': ('white', '#063542'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'TanBlue': { + 'BACKGROUND': '#e5dece', + 'TEXT': '#063289', + 'INPUT': '#f9f8f4', + 'TEXT_INPUT': '#242834', + 'SCROLL': '#eee8d5', + 'BUTTON': ('white', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkTanBlue': { + 'BACKGROUND': '#242834', + 'TEXT': '#dfe6f8', + 'INPUT': '#97755c', + 'TEXT_INPUT': 'white', + 'SCROLL': '#a9afbb', + 'BUTTON': ('white', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkAmber': { + 'BACKGROUND': '#2c2825', + 'TEXT': '#fdcb52', + 'INPUT': '#705e52', + 'TEXT_INPUT': '#fdcb52', + 'SCROLL': '#705e52', + 'BUTTON': ('black', '#fdcb52'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue': { + 'BACKGROUND': '#1a2835', + 'TEXT': '#d1ecff', + 'INPUT': '#335267', + 'TEXT_INPUT': '#acc2d0', + 'SCROLL': '#1b6497', + 'BUTTON': ('black', '#fafaf8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Reds': { + 'BACKGROUND': '#280001', + 'TEXT': 'white', + 'INPUT': '#d8d584', + 'TEXT_INPUT': 'black', + 'SCROLL': '#763e00', + 'BUTTON': ('black', '#daad28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Green': { + 'BACKGROUND': '#82a459', + 'TEXT': 'black', + 'INPUT': '#d8d584', + 'TEXT_INPUT': 'black', + 'SCROLL': '#e3ecf3', + 'BUTTON': ('white', '#517239'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BluePurple': { + 'BACKGROUND': '#A5CADD', + 'TEXT': '#6E266E', + 'INPUT': '#E0F5FF', + 'TEXT_INPUT': 'black', + 'SCROLL': '#E0F5FF', + 'BUTTON': ('white', '#303952'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Purple': { + 'BACKGROUND': '#B0AAC2', + 'TEXT': 'black', + 'INPUT': '#F2EFE8', + 'SCROLL': '#F2EFE8', + 'TEXT_INPUT': 'black', + 'BUTTON': ('black', '#C2D4D8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BlueMono': { + 'BACKGROUND': '#AAB6D3', + 'TEXT': 'black', + 'INPUT': '#F1F4FC', + 'SCROLL': '#F1F4FC', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#7186C7'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'GreenMono': { + 'BACKGROUND': '#A8C1B4', + 'TEXT': 'black', + 'INPUT': '#DDE0DE', + 'SCROLL': '#E3E3E3', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#6D9F85'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BrownBlue': { + 'BACKGROUND': '#64778d', + 'TEXT': 'white', + 'INPUT': '#f0f3f7', + 'SCROLL': '#A6B2BE', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#283b5b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'BrightColors': { + 'BACKGROUND': '#b4ffb4', + 'TEXT': 'black', + 'INPUT': '#ffff64', + 'SCROLL': '#ffb482', + 'TEXT_INPUT': 'black', + 'BUTTON': ('black', '#ffa0dc'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'NeutralBlue': { + 'BACKGROUND': '#92aa9d', + 'TEXT': 'black', + 'INPUT': '#fcfff6', + 'SCROLL': '#fcfff6', + 'TEXT_INPUT': 'black', + 'BUTTON': ('black', '#d0dbbd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'Kayak': { + 'BACKGROUND': '#a7ad7f', + 'TEXT': 'black', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#5d907d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'SandyBeach': { + 'BACKGROUND': '#efeccb', + 'TEXT': '#012f2f', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#012f2f', + 'BUTTON': ('white', '#046380'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'TealMono': { + 'BACKGROUND': '#a8cfdd', + 'TEXT': 'black', + 'INPUT': '#dfedf2', + 'SCROLL': '#dfedf2', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#183440'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + ################################## Renamed Original Themes ################################## + 'Default': { # plain gray but blue buttons + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'Default1': { # everything is gray + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': COLOR_SYSTEM_DEFAULT, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'DefaultNoMoreNagging': { # a duplicate of "Default" for users that are tired of the nag screen + 'BACKGROUND': COLOR_SYSTEM_DEFAULT, + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': COLOR_SYSTEM_DEFAULT, + 'TEXT_INPUT': COLOR_SYSTEM_DEFAULT, + 'SCROLL': COLOR_SYSTEM_DEFAULT, + 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, + 'PROGRESS': COLOR_SYSTEM_DEFAULT, + 'BORDER': 1, + 'SLIDER_DEPTH': 1, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue': { + 'BACKGROUND': '#E3F2FD', + 'TEXT': '#000000', + 'INPUT': '#86A8FF', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#86A8FF', + 'BUTTON': ('#FFFFFF', '#5079D3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'LightGrey': { + 'BACKGROUND': '#FAFAFA', + 'TEXT': '#000000', + 'INPUT': '#004EA1', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#5EA7FF', + 'BUTTON': ('#FFFFFF', '#0079D3'), # based on Reddit color + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 0, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#FF0266', + 'ACCENT2': '#FF5C93', + 'ACCENT3': '#C5003C', + }, + 'LightGrey1': { + 'BACKGROUND': '#ffffff', + 'TEXT': '#1a1a1b', + 'INPUT': '#dae0e6', + 'TEXT_INPUT': '#222222', + 'SCROLL': '#a5a4a4', + 'BUTTON': ('white', '#0079d3'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#ff5414', + 'ACCENT2': '#33a8ff', + 'ACCENT3': '#dbf0ff', + }, + 'DarkBrown': { + 'BACKGROUND': '#282923', + 'TEXT': '#E7DB74', + 'INPUT': '#393a32', + 'TEXT_INPUT': '#E7C855', + 'SCROLL': '#E7C855', + 'BUTTON': ('#E7C855', '#284B5A'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'ACCENT1': '#c15226', + 'ACCENT2': '#7a4d5f', + 'ACCENT3': '#889743', + }, + 'LightGreen1': { + 'BACKGROUND': '#9FB8AD', + 'TEXT': COLOR_SYSTEM_DEFAULT, + 'INPUT': '#F7F3EC', + 'TEXT_INPUT': 'black', + 'SCROLL': '#F7F3EC', + 'BUTTON': ('white', '#475841'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey': { + 'BACKGROUND': '#404040', + 'TEXT': 'white', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': 'white', + 'SCROLL': '#707070', + 'BUTTON': ('white', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen2': { + 'BACKGROUND': '#B7CECE', + 'TEXT': 'black', + 'INPUT': '#FDFFF7', + 'TEXT_INPUT': 'black', + 'SCROLL': '#FDFFF7', + 'BUTTON': ('white', '#658268'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'ACCENT1': '#76506d', + 'ACCENT2': '#5148f1', + 'ACCENT3': '#0a1c84', + 'PROGRESS_DEPTH': 0, + }, + 'DarkGrey1': { + 'BACKGROUND': '#404040', + 'TEXT': 'white', + 'INPUT': 'white', + 'TEXT_INPUT': 'black', + 'SCROLL': '#707070', + 'BUTTON': ('white', '#004F00'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlack': { + 'BACKGROUND': 'black', + 'TEXT': 'white', + 'INPUT': '#4D4D4D', + 'TEXT_INPUT': 'white', + 'SCROLL': '#707070', + 'BUTTON': ('black', 'white'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown': { + 'BACKGROUND': '#fdf6e3', + 'TEXT': '#268bd1', + 'INPUT': '#eee8d5', + 'TEXT_INPUT': '#6c71c3', + 'SCROLL': '#eee8d5', + 'BUTTON': ('white', '#063542'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown1': { + 'BACKGROUND': '#e5dece', + 'TEXT': '#063289', + 'INPUT': '#f9f8f4', + 'TEXT_INPUT': '#242834', + 'SCROLL': '#eee8d5', + 'BUTTON': ('white', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue1': { + 'BACKGROUND': '#242834', + 'TEXT': '#dfe6f8', + 'INPUT': '#97755c', + 'TEXT_INPUT': 'white', + 'SCROLL': '#a9afbb', + 'BUTTON': ('white', '#063289'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBrown1': { + 'BACKGROUND': '#2c2825', + 'TEXT': '#fdcb52', + 'INPUT': '#705e52', + 'TEXT_INPUT': '#fdcb52', + 'SCROLL': '#705e52', + 'BUTTON': ('black', '#fdcb52'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue2': { + 'BACKGROUND': '#1a2835', + 'TEXT': '#d1ecff', + 'INPUT': '#335267', + 'TEXT_INPUT': '#acc2d0', + 'SCROLL': '#1b6497', + 'BUTTON': ('black', '#fafaf8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBrown2': { + 'BACKGROUND': '#280001', + 'TEXT': 'white', + 'INPUT': '#d8d584', + 'TEXT_INPUT': 'black', + 'SCROLL': '#763e00', + 'BUTTON': ('black', '#daad28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkGreen': { + 'BACKGROUND': '#82a459', + 'TEXT': 'black', + 'INPUT': '#d8d584', + 'TEXT_INPUT': 'black', + 'SCROLL': '#e3ecf3', + 'BUTTON': ('white', '#517239'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue1': { + 'BACKGROUND': '#A5CADD', + 'TEXT': '#6E266E', + 'INPUT': '#E0F5FF', + 'TEXT_INPUT': 'black', + 'SCROLL': '#E0F5FF', + 'BUTTON': ('white', '#303952'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightPurple': { + 'BACKGROUND': '#B0AAC2', + 'TEXT': 'black', + 'INPUT': '#F2EFE8', + 'SCROLL': '#F2EFE8', + 'TEXT_INPUT': 'black', + 'BUTTON': ('black', '#C2D4D8'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue2': { + 'BACKGROUND': '#AAB6D3', + 'TEXT': 'black', + 'INPUT': '#F1F4FC', + 'SCROLL': '#F1F4FC', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#7186C7'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen3': { + 'BACKGROUND': '#A8C1B4', + 'TEXT': 'black', + 'INPUT': '#DDE0DE', + 'SCROLL': '#E3E3E3', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#6D9F85'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'DarkBlue3': { + 'BACKGROUND': '#64778d', + 'TEXT': 'white', + 'INPUT': '#f0f3f7', + 'SCROLL': '#A6B2BE', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#283b5b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen4': { + 'BACKGROUND': '#b4ffb4', + 'TEXT': 'black', + 'INPUT': '#ffff64', + 'SCROLL': '#ffb482', + 'TEXT_INPUT': 'black', + 'BUTTON': ('black', '#ffa0dc'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightGreen5': { + 'BACKGROUND': '#92aa9d', + 'TEXT': 'black', + 'INPUT': '#fcfff6', + 'SCROLL': '#fcfff6', + 'TEXT_INPUT': 'black', + 'BUTTON': ('black', '#d0dbbd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown2': { + 'BACKGROUND': '#a7ad7f', + 'TEXT': 'black', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#5d907d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBrown3': { + 'BACKGROUND': '#efeccb', + 'TEXT': '#012f2f', + 'INPUT': '#e6d3a8', + 'SCROLL': '#e6d3a8', + 'TEXT_INPUT': '#012f2f', + 'BUTTON': ('white', '#046380'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + 'LightBlue3': { + 'BACKGROUND': '#a8cfdd', + 'TEXT': 'black', + 'INPUT': '#dfedf2', + 'SCROLL': '#dfedf2', + 'TEXT_INPUT': 'black', + 'BUTTON': ('white', '#183440'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, + ################################## End Renamed Original Themes ################################## + # + 'LightBrown4': { + 'BACKGROUND': '#d7c79e', + 'TEXT': '#a35638', + 'INPUT': '#9dab86', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#a35638', + 'BUTTON': ('white', '#a35638'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#a35638', '#9dab86', '#e08f62', '#d7c79e'], + }, + 'DarkTeal': { + 'BACKGROUND': '#003f5c', + 'TEXT': '#fb5b5a', + 'INPUT': '#bc4873', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#bc4873', + 'BUTTON': ('white', '#fb5b5a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], + }, + 'DarkPurple': { + 'BACKGROUND': '#472b62', + 'TEXT': '#fb5b5a', + 'INPUT': '#bc4873', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#bc4873', + 'BUTTON': ('#FFFFFF', '#472b62'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#003f5c', '#472b62', '#bc4873', '#fb5b5a'], + }, + 'LightGreen6': { + 'BACKGROUND': '#eafbea', + 'TEXT': '#1f6650', + 'INPUT': '#6f9a8d', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#1f6650', + 'BUTTON': ('white', '#1f6650'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#1f6650', '#6f9a8d', '#ea5e5e', '#eafbea'], + }, + 'DarkGrey2': { + 'BACKGROUND': '#2b2b28', + 'TEXT': '#f8f8f8', + 'INPUT': '#f1d6ab', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f1d6ab', + 'BUTTON': ('#2b2b28', '#e3b04b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#2b2b28', '#e3b04b', '#f1d6ab', '#f8f8f8'], + }, + 'LightBrown6': { + 'BACKGROUND': '#f9b282', + 'TEXT': '#8f4426', + 'INPUT': '#de6b35', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#8f4426', + 'BUTTON': ('white', '#8f4426'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#8f4426', '#de6b35', '#64ccda', '#f9b282'], + }, + 'DarkTeal1': { + 'BACKGROUND': '#396362', + 'TEXT': '#ffe7d1', + 'INPUT': '#f6c89f', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f6c89f', + 'BUTTON': ('#ffe7d1', '#4b8e8d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], + }, + 'LightBrown7': { + 'BACKGROUND': '#f6c89f', + 'TEXT': '#396362', + 'INPUT': '#4b8e8d', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#396362', + 'BUTTON': ('white', '#396362'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#396362', '#4b8e8d', '#f6c89f', '#ffe7d1'], + }, + 'DarkPurple1': { + 'BACKGROUND': '#0c093c', + 'TEXT': '#fad6d6', + 'INPUT': '#eea5f6', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#eea5f6', + 'BUTTON': ('#FFFFFF', '#df42d1'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#0c093c', '#df42d1', '#eea5f6', '#fad6d6'], + }, + 'DarkGrey3': { + 'BACKGROUND': '#211717', + 'TEXT': '#dfddc7', + 'INPUT': '#f58b54', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f58b54', + 'BUTTON': ('#dfddc7', '#a34a28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], + }, + 'LightBrown8': { + 'BACKGROUND': '#dfddc7', + 'TEXT': '#211717', + 'INPUT': '#a34a28', + 'TEXT_INPUT': '#dfddc7', + 'SCROLL': '#211717', + 'BUTTON': ('#dfddc7', '#a34a28'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#211717', '#a34a28', '#f58b54', '#dfddc7'], + }, + 'DarkBlue4': { + 'BACKGROUND': '#494ca2', + 'TEXT': '#e3e7f1', + 'INPUT': '#c6cbef', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#c6cbef', + 'BUTTON': ('#FFFFFF', '#8186d5'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#494ca2', '#8186d5', '#c6cbef', '#e3e7f1'], + }, + 'LightBlue4': { + 'BACKGROUND': '#5c94bd', + 'TEXT': '#470938', + 'INPUT': '#1a3e59', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#470938', + 'BUTTON': ('white', '#470938'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#470938', '#1a3e59', '#5c94bd', '#f2d6eb'], + }, + 'DarkTeal2': { + 'BACKGROUND': '#394a6d', + 'TEXT': '#c0ffb3', + 'INPUT': '#52de97', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#52de97', + 'BUTTON': ('#c0ffb3', '#394a6d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], + }, + 'DarkTeal3': { + 'BACKGROUND': '#3c9d9b', + 'TEXT': '#c0ffb3', + 'INPUT': '#52de97', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#52de97', + 'BUTTON': ('#c0ffb3', '#394a6d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#394a6d', '#3c9d9b', '#52de97', '#c0ffb3'], + }, + 'DarkPurple5': { + 'BACKGROUND': '#730068', + 'TEXT': '#f6f078', + 'INPUT': '#01d28e', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#01d28e', + 'BUTTON': ('#f6f078', '#730068'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#730068', '#434982', '#01d28e', '#f6f078'], + }, + 'DarkPurple2': { + 'BACKGROUND': '#202060', + 'TEXT': '#b030b0', + 'INPUT': '#602080', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#602080', + 'BUTTON': ('white', '#202040'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#202040', '#202060', '#602080', '#b030b0'], + }, + 'DarkBlue5': { + 'BACKGROUND': '#000272', + 'TEXT': '#ff6363', + 'INPUT': '#a32f80', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#a32f80', + 'BUTTON': ('#FFFFFF', '#341677'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#000272', '#341677', '#a32f80', '#ff6363'], + }, + 'LightGrey2': { + 'BACKGROUND': '#f6f6f6', + 'TEXT': '#420000', + 'INPUT': '#d4d7dd', + 'TEXT_INPUT': '#420000', + 'SCROLL': '#420000', + 'BUTTON': ('#420000', '#d4d7dd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], + }, + 'LightGrey3': { + 'BACKGROUND': '#eae9e9', + 'TEXT': '#420000', + 'INPUT': '#d4d7dd', + 'TEXT_INPUT': '#420000', + 'SCROLL': '#420000', + 'BUTTON': ('#420000', '#d4d7dd'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#420000', '#d4d7dd', '#eae9e9', '#f6f6f6'], + }, + 'DarkBlue6': { + 'BACKGROUND': '#01024e', + 'TEXT': '#ff6464', + 'INPUT': '#8b4367', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#8b4367', + 'BUTTON': ('#FFFFFF', '#543864'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#01024e', '#543864', '#8b4367', '#ff6464'], + }, + 'DarkBlue7': { + 'BACKGROUND': '#241663', + 'TEXT': '#eae7af', + 'INPUT': '#a72693', + 'TEXT_INPUT': '#eae7af', + 'SCROLL': '#a72693', + 'BUTTON': ('#eae7af', '#160f30'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#160f30', '#241663', '#a72693', '#eae7af'], + }, + 'LightBrown9': { + 'BACKGROUND': '#f6d365', + 'TEXT': '#3a1f5d', + 'INPUT': '#c83660', + 'TEXT_INPUT': '#f6d365', + 'SCROLL': '#3a1f5d', + 'BUTTON': ('#f6d365', '#c83660'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3a1f5d', '#c83660', '#e15249', '#f6d365'], + }, + 'DarkPurple3': { + 'BACKGROUND': '#6e2142', + 'TEXT': '#ffd692', + 'INPUT': '#e16363', + 'TEXT_INPUT': '#ffd692', + 'SCROLL': '#e16363', + 'BUTTON': ('#ffd692', '#943855'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], + }, + 'LightBrown10': { + 'BACKGROUND': '#ffd692', + 'TEXT': '#6e2142', + 'INPUT': '#943855', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#6e2142', + 'BUTTON': ('white', '#6e2142'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#6e2142', '#943855', '#e16363', '#ffd692'], + }, + 'DarkPurple4': { + 'BACKGROUND': '#200f21', + 'TEXT': '#f638dc', + 'INPUT': '#5a3d5c', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#5a3d5c', + 'BUTTON': ('#FFFFFF', '#382039'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#200f21', '#382039', '#5a3d5c', '#f638dc'], + }, + 'LightBlue5': { + 'BACKGROUND': '#b2fcff', + 'TEXT': '#3e64ff', + 'INPUT': '#5edfff', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#3e64ff', + 'BUTTON': ('white', '#3e64ff'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3e64ff', '#5edfff', '#b2fcff', '#ecfcff'], + }, + 'DarkTeal4': { + 'BACKGROUND': '#464159', + 'TEXT': '#c7f0db', + 'INPUT': '#8bbabb', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#8bbabb', + 'BUTTON': ('#FFFFFF', '#6c7b95'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], + }, + 'LightTeal': { + 'BACKGROUND': '#c7f0db', + 'TEXT': '#464159', + 'INPUT': '#6c7b95', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#464159', + 'BUTTON': ('white', '#464159'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], + }, + 'DarkTeal5': { + 'BACKGROUND': '#8bbabb', + 'TEXT': '#464159', + 'INPUT': '#6c7b95', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#464159', + 'BUTTON': ('#c7f0db', '#6c7b95'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#464159', '#6c7b95', '#8bbabb', '#c7f0db'], + }, + 'LightGrey4': { + 'BACKGROUND': '#faf5ef', + 'TEXT': '#672f2f', + 'INPUT': '#99b19c', + 'TEXT_INPUT': '#672f2f', + 'SCROLL': '#672f2f', + 'BUTTON': ('#672f2f', '#99b19c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], + }, + 'LightGreen7': { + 'BACKGROUND': '#99b19c', + 'TEXT': '#faf5ef', + 'INPUT': '#d7d1c9', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#d7d1c9', + 'BUTTON': ('#FFFFFF', '#99b19c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], + }, + 'LightGrey5': { + 'BACKGROUND': '#d7d1c9', + 'TEXT': '#672f2f', + 'INPUT': '#99b19c', + 'TEXT_INPUT': '#672f2f', + 'SCROLL': '#672f2f', + 'BUTTON': ('white', '#672f2f'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#672f2f', '#99b19c', '#d7d1c9', '#faf5ef'], + }, + 'DarkBrown3': { + 'BACKGROUND': '#a0855b', + 'TEXT': '#f9f6f2', + 'INPUT': '#f1d6ab', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f1d6ab', + 'BUTTON': ('white', '#38470b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], + }, + 'LightBrown11': { + 'BACKGROUND': '#f1d6ab', + 'TEXT': '#38470b', + 'INPUT': '#a0855b', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#38470b', + 'BUTTON': ('#f9f6f2', '#a0855b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#38470b', '#a0855b', '#f1d6ab', '#f9f6f2'], + }, + 'DarkRed': { + 'BACKGROUND': '#83142c', + 'TEXT': '#f9d276', + 'INPUT': '#ad1d45', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#ad1d45', + 'BUTTON': ('#f9d276', '#ad1d45'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#44000d', '#83142c', '#ad1d45', '#f9d276'], + }, + 'DarkTeal6': { + 'BACKGROUND': '#204969', + 'TEXT': '#fff7f7', + 'INPUT': '#dadada', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#dadada', + 'BUTTON': ('black', '#fff7f7'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#204969', '#08ffc8', '#dadada', '#fff7f7'], + }, + 'DarkBrown4': { + 'BACKGROUND': '#252525', + 'TEXT': '#ff0000', + 'INPUT': '#af0404', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#af0404', + 'BUTTON': ('white', '#252525'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#252525', '#414141', '#af0404', '#ff0000'], + }, + 'LightYellow': { + 'BACKGROUND': '#f4ff61', + 'TEXT': '#27aa80', + 'INPUT': '#32ff6a', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#27aa80', + 'BUTTON': ('#f4ff61', '#27aa80'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#27aa80', '#32ff6a', '#a8ff3e', '#f4ff61'], + }, + 'DarkGreen1': { + 'BACKGROUND': '#2b580c', + 'TEXT': '#fdef96', + 'INPUT': '#f7b71d', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f7b71d', + 'BUTTON': ('#fdef96', '#2b580c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#2b580c', '#afa939', '#f7b71d', '#fdef96'], + }, + 'LightGreen8': { + 'BACKGROUND': '#c8dad3', + 'TEXT': '#63707e', + 'INPUT': '#93b5b3', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#63707e', + 'BUTTON': ('white', '#63707e'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#63707e', '#93b5b3', '#c8dad3', '#f2f6f5'], + }, + 'DarkTeal7': { + 'BACKGROUND': '#248ea9', + 'TEXT': '#fafdcb', + 'INPUT': '#aee7e8', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#aee7e8', + 'BUTTON': ('black', '#fafdcb'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#248ea9', '#28c3d4', '#aee7e8', '#fafdcb'], + }, + 'DarkBlue8': { + 'BACKGROUND': '#454d66', + 'TEXT': '#d9d872', + 'INPUT': '#58b368', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#58b368', + 'BUTTON': ('black', '#009975'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#009975', '#454d66', '#58b368', '#d9d872'], + }, + 'DarkBlue9': { + 'BACKGROUND': '#263859', + 'TEXT': '#ff6768', + 'INPUT': '#6b778d', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#6b778d', + 'BUTTON': ('#ff6768', '#263859'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#17223b', '#263859', '#6b778d', '#ff6768'], + }, + 'DarkBlue10': { + 'BACKGROUND': '#0028ff', + 'TEXT': '#f1f4df', + 'INPUT': '#10eaf0', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#10eaf0', + 'BUTTON': ('#f1f4df', '#24009c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#24009c', '#0028ff', '#10eaf0', '#f1f4df'], + }, + 'DarkBlue11': { + 'BACKGROUND': '#6384b3', + 'TEXT': '#e6f0b6', + 'INPUT': '#b8e9c0', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#b8e9c0', + 'BUTTON': ('#e6f0b6', '#684949'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#684949', '#6384b3', '#b8e9c0', '#e6f0b6'], + }, + 'DarkTeal8': { + 'BACKGROUND': '#71a0a5', + 'TEXT': '#212121', + 'INPUT': '#665c84', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#212121', + 'BUTTON': ('#fab95b', '#665c84'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#212121', '#665c84', '#71a0a5', '#fab95b'], + }, + 'DarkRed1': { + 'BACKGROUND': '#c10000', + 'TEXT': '#eeeeee', + 'INPUT': '#dedede', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#dedede', + 'BUTTON': ('#c10000', '#eeeeee'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#c10000', '#ff4949', '#dedede', '#eeeeee'], + }, + 'LightBrown5': { + 'BACKGROUND': '#fff591', + 'TEXT': '#e41749', + 'INPUT': '#f5587b', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e41749', + 'BUTTON': ('#fff591', '#e41749'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#e41749', '#f5587b', '#ff8a5c', '#fff591'], + }, + 'LightGreen9': { + 'BACKGROUND': '#f1edb3', + 'TEXT': '#3b503d', + 'INPUT': '#4a746e', + 'TEXT_INPUT': '#f1edb3', + 'SCROLL': '#3b503d', + 'BUTTON': ('#f1edb3', '#3b503d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], + 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], + }, + 'DarkGreen2': { + 'BACKGROUND': '#3b503d', + 'TEXT': '#f1edb3', + 'INPUT': '#c8cf94', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#c8cf94', + 'BUTTON': ('#f1edb3', '#3b503d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3b503d', '#4a746e', '#c8cf94', '#f1edb3'], + 'DESCRIPTION': ['Green', 'Turquoise', 'Yellow'], + }, + 'LightGray1': { + 'BACKGROUND': '#f2f2f2', + 'TEXT': '#222831', + 'INPUT': '#393e46', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#222831', + 'BUTTON': ('#f2f2f2', '#222831'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#222831', '#393e46', '#f96d00', '#f2f2f2'], + 'DESCRIPTION': ['Black', 'Grey', 'Orange', 'Grey', 'Autumn'], + }, + 'DarkGrey4': { + 'BACKGROUND': '#52524e', + 'TEXT': '#e9e9e5', + 'INPUT': '#d4d6c8', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#d4d6c8', + 'BUTTON': ('#FFFFFF', '#9a9b94'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#52524e', '#9a9b94', '#d4d6c8', '#e9e9e5'], + 'DESCRIPTION': ['Grey', 'Pastel', 'Winter'], + }, + 'DarkBlue12': { + 'BACKGROUND': '#324e7b', + 'TEXT': '#f8f8f8', + 'INPUT': '#86a6df', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#86a6df', + 'BUTTON': ('#FFFFFF', '#5068a9'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#324e7b', '#5068a9', '#86a6df', '#f8f8f8'], + 'DESCRIPTION': ['Blue', 'Grey', 'Cold', 'Winter'], + }, + 'DarkPurple6': { + 'BACKGROUND': '#070739', + 'TEXT': '#e1e099', + 'INPUT': '#c327ab', + 'TEXT_INPUT': '#e1e099', + 'SCROLL': '#c327ab', + 'BUTTON': ('#e1e099', '#521477'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#070739', '#521477', '#c327ab', '#e1e099'], + 'DESCRIPTION': ['Black', 'Purple', 'Yellow', 'Dark'], + }, + 'DarkBlue13': { + 'BACKGROUND': '#203562', + 'TEXT': '#e3e8f8', + 'INPUT': '#c0c5cd', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#c0c5cd', + 'BUTTON': ('#FFFFFF', '#3e588f'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#203562', '#3e588f', '#c0c5cd', '#e3e8f8'], + 'DESCRIPTION': ['Blue', 'Grey', 'Wedding', 'Cold'], + }, + 'DarkBrown5': { + 'BACKGROUND': '#3c1b1f', + 'TEXT': '#f6e1b5', + 'INPUT': '#e2bf81', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e2bf81', + 'BUTTON': ('#3c1b1f', '#f6e1b5'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3c1b1f', '#b21e4b', '#e2bf81', '#f6e1b5'], + 'DESCRIPTION': ['Brown', 'Red', 'Yellow', 'Warm'], + }, + 'DarkGreen3': { + 'BACKGROUND': '#062121', + 'TEXT': '#eeeeee', + 'INPUT': '#e4dcad', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e4dcad', + 'BUTTON': ('#eeeeee', '#181810'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], + 'DESCRIPTION': ['Black', 'Black', 'Brown', 'Grey'], + }, + 'DarkBlack1': { + 'BACKGROUND': '#181810', + 'TEXT': '#eeeeee', + 'INPUT': '#e4dcad', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e4dcad', + 'BUTTON': ('white', '#062121'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#062121', '#181810', '#e4dcad', '#eeeeee'], + 'DESCRIPTION': ['Black', 'Black', 'Brown', 'Grey'], + }, + 'DarkGrey5': { + 'BACKGROUND': '#343434', + 'TEXT': '#f3f3f3', + 'INPUT': '#e9dcbe', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e9dcbe', + 'BUTTON': ('#FFFFFF', '#8e8b82'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], + 'DESCRIPTION': ['Grey', 'Brown'], + }, + 'LightBrown12': { + 'BACKGROUND': '#8e8b82', + 'TEXT': '#f3f3f3', + 'INPUT': '#e9dcbe', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e9dcbe', + 'BUTTON': ('#f3f3f3', '#8e8b82'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#343434', '#8e8b82', '#e9dcbe', '#f3f3f3'], + 'DESCRIPTION': ['Grey', 'Brown'], + }, + 'DarkTeal9': { + 'BACKGROUND': '#13445a', + 'TEXT': '#fef4e8', + 'INPUT': '#446878', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#446878', + 'BUTTON': ('#fef4e8', '#446878'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#13445a', '#970747', '#446878', '#fef4e8'], + 'DESCRIPTION': ['Red', 'Grey', 'Blue', 'Wedding', 'Retro'], + }, + 'DarkBlue14': { + 'BACKGROUND': '#21273d', + 'TEXT': '#f1f6f8', + 'INPUT': '#b9d4f1', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#b9d4f1', + 'BUTTON': ('#FFFFFF', '#6a759b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], + 'DESCRIPTION': ['Blue', 'Black', 'Grey', 'Cold', 'Winter'], + }, + 'LightBlue6': { + 'BACKGROUND': '#f1f6f8', + 'TEXT': '#21273d', + 'INPUT': '#6a759b', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#21273d', + 'BUTTON': ('#f1f6f8', '#6a759b'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#21273d', '#6a759b', '#b9d4f1', '#f1f6f8'], + 'DESCRIPTION': ['Blue', 'Black', 'Grey', 'Cold', 'Winter'], + }, + 'DarkGreen4': { + 'BACKGROUND': '#044343', + 'TEXT': '#e4e4e4', + 'INPUT': '#045757', + 'TEXT_INPUT': '#e4e4e4', + 'SCROLL': '#045757', + 'BUTTON': ('#e4e4e4', '#045757'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#222222', '#044343', '#045757', '#e4e4e4'], + 'DESCRIPTION': ['Black', 'Turquoise', 'Grey', 'Dark'], + }, + 'DarkGreen5': { + 'BACKGROUND': '#1b4b36', + 'TEXT': '#e0e7f1', + 'INPUT': '#aebd77', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#aebd77', + 'BUTTON': ('#FFFFFF', '#538f6a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#1b4b36', '#538f6a', '#aebd77', '#e0e7f1'], + 'DESCRIPTION': ['Green', 'Grey'], + }, + 'DarkTeal10': { + 'BACKGROUND': '#0d3446', + 'TEXT': '#d8dfe2', + 'INPUT': '#71adb5', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#71adb5', + 'BUTTON': ('#FFFFFF', '#176d81'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#0d3446', '#176d81', '#71adb5', '#d8dfe2'], + 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter', 'Cold'], + }, + 'DarkGrey6': { + 'BACKGROUND': '#3e3e3e', + 'TEXT': '#ededed', + 'INPUT': '#68868c', + 'TEXT_INPUT': '#ededed', + 'SCROLL': '#68868c', + 'BUTTON': ('#FFFFFF', '#405559'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], + 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], + }, + 'DarkTeal11': { + 'BACKGROUND': '#405559', + 'TEXT': '#ededed', + 'INPUT': '#68868c', + 'TEXT_INPUT': '#ededed', + 'SCROLL': '#68868c', + 'BUTTON': ('#ededed', '#68868c'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#3e3e3e', '#405559', '#68868c', '#ededed'], + 'DESCRIPTION': ['Grey', 'Turquoise', 'Winter'], + }, + 'LightBlue7': { + 'BACKGROUND': '#9ed0e0', + 'TEXT': '#19483f', + 'INPUT': '#5c868e', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#19483f', + 'BUTTON': ('white', '#19483f'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#19483f', '#5c868e', '#ff6a38', '#9ed0e0'], + 'DESCRIPTION': ['Orange', 'Blue', 'Turquoise'], + }, + 'LightGreen10': { + 'BACKGROUND': '#d8ebb5', + 'TEXT': '#205d67', + 'INPUT': '#639a67', + 'TEXT_INPUT': '#FFFFFF', + 'SCROLL': '#205d67', + 'BUTTON': ('#d8ebb5', '#205d67'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#205d67', '#639a67', '#d9bf77', '#d8ebb5'], + 'DESCRIPTION': ['Blue', 'Green', 'Brown', 'Vintage'], + }, + 'DarkBlue15': { + 'BACKGROUND': '#151680', + 'TEXT': '#f1fea4', + 'INPUT': '#375fc0', + 'TEXT_INPUT': '#f1fea4', + 'SCROLL': '#375fc0', + 'BUTTON': ('#f1fea4', '#1c44ac'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], + 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], + }, + 'DarkBlue16': { + 'BACKGROUND': '#1c44ac', + 'TEXT': '#f1fea4', + 'INPUT': '#375fc0', + 'TEXT_INPUT': '#f1fea4', + 'SCROLL': '#375fc0', + 'BUTTON': ('#f1fea4', '#151680'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#151680', '#1c44ac', '#375fc0', '#f1fea4'], + 'DESCRIPTION': ['Blue', 'Yellow', 'Cold'], + }, + 'DarkTeal12': { + 'BACKGROUND': '#004a7c', + 'TEXT': '#fafafa', + 'INPUT': '#e8f1f5', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#e8f1f5', + 'BUTTON': ('#fafafa', '#005691'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#004a7c', '#005691', '#e8f1f5', '#fafafa'], + 'DESCRIPTION': ['Grey', 'Blue', 'Cold', 'Winter'], + }, + 'LightBrown13': { + 'BACKGROUND': '#ebf5ee', + 'TEXT': '#921224', + 'INPUT': '#bdc6b8', + 'TEXT_INPUT': '#921224', + 'SCROLL': '#921224', + 'BUTTON': ('white', '#921224'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#921224', '#bdc6b8', '#bce0da', '#ebf5ee'], + 'DESCRIPTION': ['Red', 'Blue', 'Grey', 'Vintage', 'Wedding'], + }, + 'DarkBlue17': { + 'BACKGROUND': '#21294c', + 'TEXT': '#f9f2d7', + 'INPUT': '#f2dea8', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#f2dea8', + 'BUTTON': ('#f9f2d7', '#141829'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#141829', '#21294c', '#f2dea8', '#f9f2d7'], + 'DESCRIPTION': ['Black', 'Blue', 'Yellow'], + }, + 'DarkBrown6': { + 'BACKGROUND': '#785e4d', + 'TEXT': '#f2eee3', + 'INPUT': '#baaf92', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#baaf92', + 'BUTTON': ('white', '#785e4d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#785e4d', '#ff8426', '#baaf92', '#f2eee3'], + 'DESCRIPTION': ['Grey', 'Brown', 'Orange', 'Autumn'], + }, + 'DarkGreen6': { + 'BACKGROUND': '#5c715e', + 'TEXT': '#f2f9f1', + 'INPUT': '#ddeedf', + 'TEXT_INPUT': '#000000', + 'SCROLL': '#ddeedf', + 'BUTTON': ('#f2f9f1', '#5c715e'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#5c715e', '#b6cdbd', '#ddeedf', '#f2f9f1'], + 'DESCRIPTION': ['Grey', 'Green', 'Vintage'], + }, + 'DarkGrey7': { + 'BACKGROUND': '#4b586e', + 'TEXT': '#dddddd', + 'INPUT': '#574e6d', + 'TEXT_INPUT': '#dddddd', + 'SCROLL': '#574e6d', + 'BUTTON': ('#dddddd', '#43405d'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#43405d', '#4b586e', '#574e6d', '#dddddd'], + 'DESCRIPTION': ['Grey', 'Winter', 'Cold'], + }, + 'DarkRed2': { + 'BACKGROUND': '#ab1212', + 'TEXT': '#f6e4b5', + 'INPUT': '#cd3131', + 'TEXT_INPUT': '#f6e4b5', + 'SCROLL': '#cd3131', + 'BUTTON': ('#f6e4b5', '#ab1212'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#ab1212', '#1fad9f', '#cd3131', '#f6e4b5'], + 'DESCRIPTION': ['Turquoise', 'Red', 'Yellow'], + }, + 'LightGrey6': { + 'BACKGROUND': '#e3e3e3', + 'TEXT': '#233142', + 'INPUT': '#455d7a', + 'TEXT_INPUT': '#e3e3e3', + 'SCROLL': '#233142', + 'BUTTON': ('#e3e3e3', '#455d7a'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + 'COLOR_LIST': ['#233142', '#455d7a', '#f95959', '#e3e3e3'], + 'DESCRIPTION': ['Black', 'Blue', 'Red', 'Grey'], + }, + 'HotDogStand': { + 'BACKGROUND': 'red', + 'TEXT': 'yellow', + 'INPUT': 'yellow', + 'TEXT_INPUT': 'black', + 'SCROLL': 'yellow', + 'BUTTON': ('red', 'yellow'), + 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, + 'BORDER': 1, + 'SLIDER_DEPTH': 0, + 'PROGRESS_DEPTH': 0, + }, +} + + +def ListOfLookAndFeelValues(): + """ + Get a list of the valid values to pass into your call to change_look_and_feel + :return: List[str] - list of valid string values + """ + return sorted(list(LOOK_AND_FEEL_TABLE.keys())) + + +def theme(new_theme=None): + """ + Sets / Gets the current Theme. If none is specified then returns the current theme. + This call replaces the ChangeLookAndFeel / change_look_and_feel call which only sets the theme. + + :param new_theme: (str) the new theme name to use + :return: (str) the currently selected theme + """ + if new_theme is not None: + change_look_and_feel(new_theme) + return CURRENT_LOOK_AND_FEEL + + +def theme_background_color(color=None): + """ + Sets/Returns the background color currently in use + Used for Windows and containers (Column, Frame, Tab) and tables + + :return: (str) - color string of the background color currently in use + """ + if color is not None: + set_options(background_color=color) + return DEFAULT_BACKGROUND_COLOR + + +def theme_element_background_color(color=None): + """ + Sets/Returns the background color currently in use for all elements except containers + + :return: (str) - color string of the element background color currently in use + """ + if color is not None: + set_options(element_background_color=color) + return DEFAULT_ELEMENT_BACKGROUND_COLOR + + +def theme_text_color(color=None): + """ + Sets/Returns the text color currently in use + + :return: (str) - color string of the text color currently in use + """ + if color is not None: + set_options(text_color=color) + return DEFAULT_TEXT_COLOR + + +def theme_input_background_color(color=None): + """ + Sets/Returns the input element background color currently in use + + :return: (str) - color string of the input element background color currently in use + """ + if color is not None: + set_options(input_elements_background_color=color) + return DEFAULT_INPUT_ELEMENTS_COLOR + + +def theme_input_text_color(color=None): + """ + Sets/Returns the input element entry color (not the text but the thing that's displaying the text) + + :return: (str) - color string of the input element color currently in use + """ + if color is not None: + set_options(input_text_color=color) + return DEFAULT_INPUT_TEXT_COLOR + + +def theme_button_color(color=None): + """ + Sets/Returns the button color currently in use + + :return: Tuple[str, str] - TUPLE with color strings of the button color currently in use (button text color, button background color) + """ + if color is not None: + set_options(button_color=color) + return DEFAULT_BUTTON_COLOR + + +def theme_progress_bar_color(color=None): + """ + Sets/Returns the progress bar colors by the current color theme + + :return: Tuple[str, str] - TUPLE with color strings of the ProgressBar color currently in use(button text color, button background color) + """ + if color is not None: + set_options(progress_meter_color=color) + return DEFAULT_PROGRESS_BAR_COLOR + + +def theme_slider_color(color=None): + """ + Sets/Returns the slider color (used for sliders) + + :return: (str) - color string of the slider color currently in use + """ + if color is not None: + set_options(scrollbar_color=color) + return DEFAULT_SCROLLBAR_COLOR + + +def theme_border_width(border_width=None): + """ + Sets/Returns the border width currently in use + Used by non ttk elements at the moment + + :return: (int) - border width currently in use + """ + if border_width is not None: + set_options(border_width=border_width) + return DEFAULT_BORDER_WIDTH + + +def theme_slider_border_width(border_width=None): + """ + Sets/Returns the slider border width currently in use + + :return: (int) - border width currently in use + """ + if border_width is not None: + set_options(slider_border_width=border_width) + return DEFAULT_SLIDER_BORDER_WIDTH + + +def theme_progress_bar_border_width(border_width=None): + """ + Sets/Returns the progress meter border width currently in use + + :return: (int) - border width currently in use + """ + if border_width is not None: + set_options(progress_meter_border_depth=border_width) + return DEFAULT_PROGRESS_BAR_BORDER_WIDTH + + +def theme_element_text_color(color=None): + """ + Sets/Returns the text color used by elements that have text as part of their display (Tables, Trees and Sliders) + + :return: (str) - color string currently in use + """ + if color is not None: + set_options(element_text_color=color) + return DEFAULT_ELEMENT_TEXT_COLOR + + +def theme_list(): + """ + Returns a sorted list of the currently available color themes + + :return: List[str] - A sorted list of the currently available color themes + """ + return list_of_look_and_feel_values() + + +def theme_add_new(new_theme_name, new_theme_dict): + """ + Add a new theme to the dictionary of themes + + :param new_theme_name: text to display in element + :type new_theme_name: (str) + :param new_theme_dict: text to display in element + :type new_theme_dict: (dict) + """ + global LOOK_AND_FEEL_TABLE + try: + LOOK_AND_FEEL_TABLE[new_theme_name] = new_theme_dict + except Exception as e: + print('Exception during adding new theme {}'.format(e)) + + +def theme_previewer(columns=12): + """ + Show a window with all of the color themes - takes a while so be patient + + :param columns: (int) number of themes in a single row + """ + preview_all_look_and_feel_themes(columns) + + +def ChangeLookAndFeel(index, force=False): + """ + Change the "color scheme" of all future PySimpleGUI Windows. + The scheme are string names that specify a group of colors. Background colors, text colors, button colors. + There are 13 different color settings that are changed at one time using a single call to ChangeLookAndFeel + The look and feel table itself has these indexes into the dictionary LOOK_AND_FEEL_TABLE. + The original list was (prior to a major rework and renaming)... these names still work... + In Nov 2019 a new Theme Formula was devised to make choosing a theme easier: + The "Formula" is: + ["Dark" or "Light"] Color Number + Colors can be Blue Brown Grey Green Purple Red Teal Yellow Black + The number will vary for each pair. There are more DarkGrey entries than there are LightYellow for example. + Default = The default settings (only button color is different than system default) + Default1 = The full system default including the button (everything's gray... how sad... don't be all gray... please....) + :param index: (str) the name of the index into the Look and Feel table (does not have to be exact, can be "fuzzy") + :param force: (bool) no longer used + """ + + global CURRENT_LOOK_AND_FEEL + + # if sys.platform.startswith('darwin') and not force: + # print('*** Changing look and feel is not supported on Mac platform ***') + # return + + theme = index + # normalize available l&f values + lf_values = [item.lower() for item in list_of_look_and_feel_values()] + + # option 1 + opt1 = theme.replace(' ', '').lower() + + # option 2 (reverse lookup) + optx = theme.lower().split(' ') + optx.reverse() + opt2 = ''.join(optx) + + # search for valid l&f name + if opt1 in lf_values: + ix = lf_values.index(opt1) + elif opt2 in lf_values: + ix = lf_values.index(opt2) + else: + ix = randint(0, len(lf_values) - 1) + print('** Warning - {} Theme is not a valid theme. Change your theme call. **'.format(index)) + print('valid values are', list_of_look_and_feel_values()) + print('Instead, please enjoy a random Theme named {}'.format(list_of_look_and_feel_values()[ix])) + + selection = list_of_look_and_feel_values()[ix] + CURRENT_LOOK_AND_FEEL = selection + try: + colors = LOOK_AND_FEEL_TABLE[selection] + + # Color the progress bar using button background and input colors...unless they're the same + if colors['PROGRESS'] != COLOR_SYSTEM_DEFAULT: + if colors['BUTTON'][1] != colors['INPUT'] and colors['BUTTON'][1] != colors['BACKGROUND']: + colors['PROGRESS'] = colors['BUTTON'][1], colors['INPUT'] + else: # if the same, then use text input on top of input color + colors['PROGRESS'] = (colors['TEXT_INPUT'], colors['INPUT']) + else: + colors['PROGRESS'] = DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL + # call to change all the colors + SetOptions( + background_color=colors['BACKGROUND'], + text_element_background_color=colors['BACKGROUND'], + element_background_color=colors['BACKGROUND'], + text_color=colors['TEXT'], + input_elements_background_color=colors['INPUT'], + # button_color=colors['BUTTON'] if not sys.platform.startswith('darwin') else None, + button_color=colors['BUTTON'], + progress_meter_color=colors['PROGRESS'], + border_width=colors['BORDER'], + slider_border_width=colors['SLIDER_DEPTH'], + progress_meter_border_depth=colors['PROGRESS_DEPTH'], + scrollbar_color=(colors['SCROLL']), + element_text_color=colors['TEXT'], + input_text_color=colors['TEXT_INPUT'], + ) + except: # most likely an index out of range + print('** Warning - Theme value not valid. Change your theme call. **') + print('valid values are', list_of_look_and_feel_values()) + + +def preview_all_look_and_feel_themes(columns=12): + """ + Displays a "Quick Reference Window" showing all of the different Look and Feel settings that are available. + They are sorted alphabetically. The legacy color names are mixed in, but otherwise they are sorted into Dark and Light halves + :param columns: (int) The number of themes to display per row + """ + + # Show a "splash" type message so the user doesn't give up waiting + popup_quick_message( + 'Hang on for a moment, this will take a bit to create....', + background_color='red', + text_color='white', + auto_close=True, + non_blocking=True, + ) + + web = False + + win_bg = 'black' + + def sample_layout(): + return [ + [Text('Text element'), InputText('Input data here', size=(10, 1))], + [Button('Ok'), Button('Cancel'), Slider((1, 10), orientation='h', size=(5, 15))], + ] + + layout = [[Text('Here is a complete list of themes', font='Default 18', background_color=win_bg)]] + + names = list_of_look_and_feel_values() + names.sort() + row = [] + for count, theme in enumerate(names): + change_look_and_feel(theme) + if not count % columns: + layout += [row] + row = [] + row += [Frame(theme, sample_layout() if not web else [[T(theme)]] + sample_layout())] + if row: + layout += [row] + + window = Window('Preview of all Look and Feel choices', layout, background_color=win_bg) + window.read() + window.close() + + +# ============================== sprint ======# +# Is identical to the Scrolled Text Box # +# Provides a crude 'print' mechanism but in a # +# GUI environment # +# ============================================# +sprint = ScrolledTextBox + + +# Converts an object's contents into a nice printable string. Great for dumping debug data +def ObjToStringSingleObj(obj): + if obj is None: + return 'None' + return str(obj.__class__) + '\n' + '\n'.join((repr(item) + ' = ' + repr(obj.__dict__[item]) for item in sorted(obj.__dict__))) + + +def ObjToString(obj, extra=' '): + if obj is None: + return 'None' + return str(obj.__class__) + '\n' + '\n'.join((extra + (str(item) + ' = ' + (ObjToString(obj.__dict__[item], extra + ' ') if hasattr(obj.__dict__[item], '__dict__') else str(obj.__dict__[item]))) for item in sorted(obj.__dict__))) + + +# ------------------------------------------------------------------------------------------------------------------ # +# ===================================== Upper PySimpleGUI ======================================================== # +# Pre-built dialog boxes for all your needs These are the "high level API calls # +# ------------------------------------------------------------------------------------------------------------------ # + +###### +# # #### ##### # # ##### #### +# # # # # # # # # # # +###### # # # # # # # # #### +# # # ##### # # ##### # +# # # # # # # # # +# #### # #### # #### + + +# ----------------------------------- The mighty Popup! ------------------------------------------------------------ # + + +def Popup( + *args, + title=None, + button_color=None, + background_color=None, + text_color=None, + button_type=POPUP_BUTTONS_OK, + auto_close=False, + auto_close_duration=None, + custom_text=(None, None), + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Popup - Display a popup box with as many parms as you wish to include + :param args: + :param button_color: + :param background_color: + :param text_color: + :param button_type: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + if not args: + args_to_print = [''] + else: + args_to_print = args + if line_width != None: + local_line_width = line_width + else: + local_line_width = MESSAGE_BOX_LINE_WIDTH + _title = title if title is not None else args_to_print[0] + window = Window( + _title, + auto_size_text=True, + background_color=background_color, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + icon=icon, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + max_line_total, total_lines = 0, 0 + for message in args_to_print: + # fancy code to check if string and convert if not is not need. Just always convert to string :-) + # if not isinstance(message, str): message = str(message) + message = str(message) + if message.count('\n'): + message_wrapped = message + else: + message_wrapped = textwrap.fill(message, local_line_width) + message_wrapped_lines = message_wrapped.count('\n') + 1 + longest_line_len = max([len(l) for l in message.split('\n')]) + width_used = min(longest_line_len, local_line_width) + max_line_total = max(max_line_total, width_used) + # height = _GetNumLinesNeeded(message, width_used) + height = message_wrapped_lines + window.AddRow(Text(message_wrapped, auto_size_text=True, text_color=text_color, background_color=background_color)) + total_lines += height + + if non_blocking: + PopupButton = DummyButton # important to use or else button will close other windows too! + else: + PopupButton = Button + # show either an OK or Yes/No depending on paramater + if custom_text != (None, None): + if type(custom_text) is not tuple: + window.AddRow(PopupButton(custom_text, size=(len(custom_text), 1), button_color=button_color, focus=True, bind_return_key=True)) + elif custom_text[1] is None: + window.AddRow( + PopupButton( + custom_text[0], + size=(len(custom_text[0]), 1), + button_color=button_color, + focus=True, + bind_return_key=True, + ) + ) + else: + window.AddRow( + PopupButton( + custom_text[0], + button_color=button_color, + focus=True, + bind_return_key=True, + size=(len(custom_text[0]), 1), + ), + PopupButton(custom_text[1], button_color=button_color, size=(len(custom_text[0]), 1)), + ) + elif button_type is POPUP_BUTTONS_YES_NO: + window.AddRow( + PopupButton('Yes', button_color=button_color, focus=True, bind_return_key=True, pad=((20, 5), 3), size=(5, 1)), + PopupButton('No', button_color=button_color, size=(5, 1)), + ) + elif button_type is POPUP_BUTTONS_CANCELLED: + window.AddRow(PopupButton('Cancelled', button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) + elif button_type is POPUP_BUTTONS_ERROR: + window.AddRow(PopupButton('Error', size=(6, 1), button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) + elif button_type is POPUP_BUTTONS_OK_CANCEL: + window.AddRow( + PopupButton('OK', size=(6, 1), button_color=button_color, focus=True, bind_return_key=True), + PopupButton('Cancel', size=(6, 1), button_color=button_color), + ) + elif button_type is POPUP_BUTTONS_NO_BUTTONS: + pass + else: + window.AddRow(PopupButton('OK', size=(5, 1), button_color=button_color, focus=True, bind_return_key=True, pad=((20, 0), 3))) + + if non_blocking: + button, values = window.Read(timeout=0) + return button, window + else: + button, values = window.Read() + window.Close() + return button + + +# ============================== MsgBox============# +# Lazy function. Same as calling Popup with parms # +# This function WILL Disappear perhaps today # +# ==================================================# +# MsgBox is the legacy call and should not be used any longer +def MsgBox(*args): + raise DeprecationWarning('MsgBox is no longer supported... change your call to Popup') + + +# --------------------------- PopupNoButtons --------------------------- +def PopupNoButtons( + *args, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Show a Popup but without any buttons + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=POPUP_BUTTONS_NO_BUTTONS, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +# --------------------------- PopupNonBlocking --------------------------- +def PopupNonBlocking( + *args, + button_type=POPUP_BUTTONS_OK, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=True, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Show Popup box and immediately return (does not block) + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + return Popup( + *args, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +PopupNoWait = PopupNonBlocking + + +# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup --------------------------- +def PopupQuick( + *args, + button_type=POPUP_BUTTONS_OK, + button_color=None, + background_color=None, + text_color=None, + auto_close=True, + auto_close_duration=2, + non_blocking=True, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Show Popup box that doesn't block and closes itself + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +# --------------------------- PopupQuick - a NonBlocking, Self-closing Popup with no titlebar and no buttons --------------------------- +def PopupQuickMessage( + *args, + button_type=POPUP_BUTTONS_NO_BUTTONS, + button_color=None, + background_color=None, + text_color=None, + auto_close=True, + auto_close_duration=2, + non_blocking=True, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=True, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Show Popup box that doesn't block and closes itself + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +# --------------------------- PopupNoTitlebar --------------------------- +def PopupNoTitlebar( + *args, + button_type=POPUP_BUTTONS_OK, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + grab_anywhere=True, + keep_on_top=False, + location=(None, None), +): + """ + Display a Popup without a titlebar. Enables grab anywhere so you can move it + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=True, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +PopupNoFrame = PopupNoTitlebar +PopupNoBorder = PopupNoTitlebar +PopupAnnoying = PopupNoTitlebar + + +# --------------------------- PopupAutoClose --------------------------- +def PopupAutoClose( + *args, + button_type=POPUP_BUTTONS_OK, + button_color=None, + background_color=None, + text_color=None, + auto_close=True, + auto_close_duration=DEFAULT_AUTOCLOSE_TIME, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Popup that closes itself after some time period + :param args: + :param button_type: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_color=button_color, + background_color=background_color, + text_color=text_color, + button_type=button_type, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +PopupTimed = PopupAutoClose + + +# --------------------------- PopupError --------------------------- +def PopupError( + *args, + button_color=DEFAULT_ERROR_BUTTON_COLOR, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Popup with colored button and 'Error' as button text + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_type=POPUP_BUTTONS_ERROR, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +# --------------------------- PopupCancel --------------------------- +def PopupCancel( + *args, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Display Popup with "cancelled" button text + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_type=POPUP_BUTTONS_CANCELLED, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +# --------------------------- PopupOK --------------------------- +def PopupOK( + *args, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Display Popup with OK button only + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: + """ + Popup( + *args, + button_type=POPUP_BUTTONS_OK, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +# --------------------------- PopupOKCancel --------------------------- +def PopupOKCancel( + *args, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Display popup with OK and Cancel buttons + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: OK, Cancel or None + """ + return Popup( + *args, + button_type=POPUP_BUTTONS_OK_CANCEL, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +# --------------------------- PopupYesNo --------------------------- +def PopupYesNo( + *args, + button_color=None, + background_color=None, + text_color=None, + auto_close=False, + auto_close_duration=None, + non_blocking=False, + icon=DEFAULT_WINDOW_ICON, + line_width=None, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Display Popup with Yes and No buttons + :param args: + :param button_color: + :param background_color: + :param text_color: + :param auto_close: + :param auto_close_duration: + :param non_blocking: + :param icon: + :param line_width: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: Yes, No or None + """ + return Popup( + *args, + button_type=POPUP_BUTTONS_YES_NO, + background_color=background_color, + text_color=text_color, + non_blocking=non_blocking, + icon=icon, + line_width=line_width, + button_color=button_color, + auto_close=auto_close, + auto_close_duration=auto_close_duration, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + +############################################################################## +# The PopupGet_____ functions - Will return user input # +############################################################################## + + +# --------------------------- PopupGetFolder --------------------------- + + +def PopupGetFolder( + message, + title=None, + default_path='', + no_window=False, + size=(None, None), + button_color=None, + background_color=None, + text_color=None, + icon=DEFAULT_WINDOW_ICON, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + initial_folder=None, +): + """ + Display popup with text entry field and browse button. Browse for folder + :param message: + :param default_path: + :param no_window: + :param size: + :param button_color: + :param background_color: + :param text_color: + :param icon: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: Contents of text field. None if closed using X or cancelled + """ + + if no_window: + app = wx.App(False) + frame = wx.Frame() + + if initial_folder: + dialog = wx.DirDialog(frame, style=wx.FD_OPEN) + else: + dialog = wx.DirDialog(frame) + folder_name = '' + if dialog.ShowModal() == wx.ID_OK: + folder_name = dialog.GetPath() + return folder_name + + layout = [ + [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], + [InputText(default_text=default_path, size=size, key='_INPUT_'), FolderBrowse(initial_folder=initial_folder)], + [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], + ] + + _title = title if title is not None else message + window = Window( + title=_title, + layout=layout, + icon=icon, + auto_size_text=True, + button_color=button_color, + background_color=background_color, + font=font, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + button, values = window.Read() + window.Close() + if button != 'Ok': + return None + else: + path = values['_INPUT_'] + return path + + +# --------------------------- PopupGetFile --------------------------- + + +def PopupGetFile( + message, + title=None, + default_path='', + default_extension='', + save_as=False, + file_types=(('ALL Files', '*'),), + no_window=False, + size=(None, None), + button_color=None, + background_color=None, + text_color=None, + icon=DEFAULT_WINDOW_ICON, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), + initial_folder=None, +): + """ + Display popup with text entry field and browse button. Browse for file + :param message: + :param default_path: + :param default_extension: + :param save_as: + :param file_types: + :param no_window: + :param size: + :param button_color: + :param background_color: + :param text_color: + :param icon: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: string representing the path chosen, None if cancelled or window closed with X + """ + + if no_window: + app = wx.App(False) + frame = wx.Frame() + + qt_types = convert_tkinter_filetypes_to_wx(file_types) + style = wx.FD_SAVE if save_as else wx.FD_OPEN + if initial_folder: + dialog = wx.FileDialog(frame, defaultDir=initial_folder, wildcard=qt_types, style=style) + else: + dialog = wx.FileDialog(frame, wildcard=qt_types, style=style) + if dialog.ShowModal() == wx.ID_OK: + file_name = dialog.GetPath() + else: + file_name = '' + return file_name + + browse_button = SaveAs(file_types=file_types, initial_folder=initial_folder) if save_as else FileBrowse(file_types=file_types, initial_folder=initial_folder) + + layout = [ + [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], + [InputText(default_text=default_path, size=(30, 1), key='_INPUT_'), browse_button], + [Button('Ok', size=(60, 20), bind_return_key=True), Button('Cancel', size=(60, 20))], + ] + + _title = title if title is not None else message + + window = Window( + title=_title, + layout=layout, + icon=icon, + auto_size_text=True, + button_color=button_color, + font=font, + background_color=background_color, + no_titlebar=no_titlebar, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + location=location, + ) + + button, values = window.Read() + window.Close() + if button != 'Ok': + return None + else: + path = values['_INPUT_'] + return path + + +# --------------------------- PopupGetText --------------------------- + + +def PopupGetText( + message, + title=None, + default_text='', + password_char='', + size=(None, None), + button_color=None, + background_color=None, + text_color=None, + icon=DEFAULT_WINDOW_ICON, + font=None, + no_titlebar=False, + grab_anywhere=False, + keep_on_top=False, + location=(None, None), +): + """ + Display Popup with text entry field + :param message: + :param default_text: + :param password_char: + :param size: + :param button_color: + :param background_color: + :param text_color: + :param icon: + :param font: + :param no_titlebar: + :param grab_anywhere: + :param keep_on_top: + :param location: + :return: Text entered or None if window was closed + """ + + layout = [ + [Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], + [InputText(default_text=default_text, size=size, key='_INPUT_', password_char=password_char)], + [CloseButton('Ok', size=(60, 20), bind_return_key=True), CloseButton('Cancel', size=(60, 20))], + ] + + _title = title if title is not None else message + + window = Window( + title=_title, + layout=layout, + icon=icon, + auto_size_text=True, + button_color=button_color, + no_titlebar=no_titlebar, + background_color=background_color, + grab_anywhere=grab_anywhere, + keep_on_top=keep_on_top, + font=font, + location=location, + ) + + button, values = window.Read() + window.Close() + if button != 'Ok': + return None + else: + path = values['_INPUT_'] + return path + + +# ------------------------ PEP8-ify The SDK ------------------------# +change_look_and_feel = ChangeLookAndFeel +easy_print = EasyPrint +easy_print_close = EasyPrintClose +fill_form_with_values = FillFormWithValues +get_complimentary_hex = GetComplimentaryHex +list_of_look_and_feel_values = ListOfLookAndFeelValues +obj_to_string = ObjToString +obj_to_string_single_obj = ObjToStringSingleObj +one_line_progress_meter = OneLineProgressMeter +one_line_progress_meter_cancel = OneLineProgressMeterCancel +popup = Popup +popup_annoying = PopupAnnoying +popup_auto_close = PopupAutoClose +popup_cancel = PopupCancel +popup_error = PopupError +popup_get_file = PopupGetFile +popup_get_folder = PopupGetFolder +popup_get_text = PopupGetText +popup_no_border = PopupNoBorder +popup_no_buttons = PopupNoButtons +popup_no_frame = PopupNoFrame +popup_no_titlebar = PopupNoTitlebar +popup_no_wait = PopupNoWait +popup_non_blocking = PopupNonBlocking +popup_ok = PopupOK +popup_ok_cancel = PopupOKCancel +popup_quick = PopupQuick +popup_quick_message = PopupQuickMessage +popup_scrolled = PopupScrolled +popup_timed = PopupTimed +popup_yes_no = PopupYesNo +print_close = PrintClose +scrolled_text_box = ScrolledTextBox +set_global_icon = SetGlobalIcon +set_options = SetOptions +timer_start = TimerStart +timer_stop = TimerStop + + +# ------------------------ Set the "Official PySimpleGUI Theme Colors" ------------------------ +theme(CURRENT_LOOK_AND_FEEL) + +# theme_previewer() + + +""" + d8b + Y8P + +88888b.d88b. 8888b. 888 88888b. +888 "888 "88b "88b 888 888 "88b +888 888 888 .d888888 888 888 888 +888 888 888 888 888 888 888 888 +888 888 888 "Y888888 888 888 888 + +""" + + +def main(): + + def VerLine(version, description, justification='r', size=(30, 1)): + return [ + T(version, justification=justification, font='Any 12', text_color='yellow', size=size), + T(description, font='Any 12'), + ] + + ver = version.split('\n')[0] + + # ChangeLookAndFeel('Light Brown 11') + frame_contents = [[T('Inside my frame')], [Input(size=(5, 1))], [Input()]] + layout = [ + [Text('Welcome to PySimpleGUI!', font='Arial 15', text_color='yellow')], + VerLine(ver, 'PySimpleGUI Version'), + VerLine(os.path.dirname(os.path.abspath(__file__)), 'PySimpleGUI Location', justification='l', size=(30, 2)), + VerLine(sys.version, 'Python Version', justification='l', size=(40, 2)), + [Text('You should be importing this module rather than running it', justification='l', size=(50, 1))], + [Text('Here is your sample input window....')], + [Frame('FRAME with Centered Contents', frame_contents, element_justification='c')], + [InputText('Source', focus=True, size_px=(200, 80)), FileBrowse()], + [InputText('Dest'), FolderBrowse()], + [Checkbox('Checkbox 1', size=(15, 1)), Checkbox('Checkbox 2')], + [Radio('Radio 1', 'group', size=(15, 1)), Radio('Radio 2', 'group')], + [Multiline('Multiline Input', do_not_clear=True, size=(40, 4), enable_events=True)], + [MultilineOutput('Multiline Output', size=(40, 5), text_color='blue')], + [ + Combo( + values=['Combo 1', 'Combo 2', 'Combo 3'], + default_value='Combo 2', + key='_COMBO_', + enable_events=True, + readonly=False, + tooltip='Combo box', + disabled=False, + font='Courier 18', + size=(12, 1), + ) + ], + [Spin(values=['Spin a', 'Spin b', 'Spin c'], font='ANY 15', key='_SPIN_', size=(10, 1), enable_events=True)], + [Button('Ok'), Button('Exit')], + ] + + window = Window( + 'Demo window..', + layout, + # default_element_size=(35,1), + auto_size_text=True, + auto_size_buttons=True, + no_titlebar=False, + disable_close=False, + disable_minimize=True, + grab_anywhere=True, + # element_justification='r' + ) + + while True: + event, values = window.Read() + print(event, values) + + if event in (None, 'Exit'): + break + window.Close() + + +if __name__ == '__main__': + + main() + sys.exit(69) diff --git a/FreeSimpleGUIWx/setup.cfg b/FreeSimpleGUIWx/setup.cfg index b8238e9e..6b18596a 100644 --- a/FreeSimpleGUIWx/setup.cfg +++ b/FreeSimpleGUIWx/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = FreeSimpleGUIWx -version = 1.0.0 +version = 1.1.0 maintainer = Spencer Phillip Young maintainer_email = spencer.young@spyoung.com description = The free-forever Python GUI framework.