commit beda40595324a4d3976fe13be8766f4721c146c7 Author: root Date: Thu Feb 19 08:49:56 2026 +0000 first commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6a745fd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.11-slim + +WORKDIR /usr/src/app + +COPY requirements.txt ./ +RUN apt-get update -y && apt-get install -y tk tcl +RUN pip install --force-reinstall -r requirements.txt + +COPY . . + +CMD [ "python", "./Shannon.py" ] diff --git a/Dockerfile.save b/Dockerfile.save new file mode 100644 index 0000000..04353fc --- /dev/null +++ b/Dockerfile.save @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +WORKDIR /usr/src/app + +COPY requirements.txt ./ +echo "http://nova.clouds.archive.ubuntu.com/ubuntu jammy/main amd64 Packages" > +RUN apt install tk/jammy +RUN pip install --force-reinstall -r requirements.txt + +COPY . . + +CMD [ "python", "./Shannon.py" ] diff --git a/PySimpleGUIWeb.py b/PySimpleGUIWeb.py new file mode 100644 index 0000000..e89619b --- /dev/null +++ b/PySimpleGUIWeb.py @@ -0,0 +1,8199 @@ +#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=.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 .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/README.md b/README.md new file mode 100644 index 0000000..d77a194 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Shannon-for-Dummies + +Educational application + +Exploration from Claude's Shannon initial theory to its practical application to satellite communications. + +The application is using PysimpleGUI / PySimpleGUIWeb and runs either in local windows or in web pages. + +The Web version via localhost is fully functional although not as convenient as the windowed version (only 1 plot open). +The look of the web version differs significantly from the local version (PySimpleGUIWeb is still beta). + +The Web version in remote does work for a single user (all users connected can send commands but see the same page). +This mode is experimental and doesn't behave as a web server : users are not managed, the app closes when the user closes the window ... + +The value of the application is essentially in the background information accessed by clicking labels of all inputs / outputs. + +A Knowledge DB is coupled to the application for collaborative contributions on the subject opening technical discussions. At this stage the DB is local. + diff --git a/Satellite.png b/Satellite.png new file mode 100644 index 0000000..111ad3a Binary files /dev/null and b/Satellite.png differ diff --git a/Shannon.png b/Shannon.png new file mode 100644 index 0000000..b37da4b Binary files /dev/null and b/Shannon.png differ diff --git a/Shannon.py b/Shannon.py new file mode 100644 index 0000000..b46244b --- /dev/null +++ b/Shannon.py @@ -0,0 +1,1028 @@ +''' ---------------------------------------------------------------------------------------------------------------- + +Shannon Equation for Dummies - JPC Feb 2021 + +Educational Application + +Exploration from Claude's Shannon initial theory to its practical application to satellite communications + +The application Runs either in local windows or in web pages + +The Web version via localhost is fully functional although not as convenient as the windowed version (only 1 plot open) + +The Web version in remote does work for a single user (all users connected can send command but see the same page) + +--------------------------------------------------------------------------------------------------------------------''' + +''' ------------------------------------------ Imports --------------------------------------------------- + +The GUI has been designed to be compatible with both PySimpleGUIWeb and PySimpleGUI + +The PySimpleGUi version takes full benefit of the matplotlib windowing whereas the Web version is constrained to use +a web compatible method with only one graph at a time + +''' +from math import * +import matplotlib.pyplot as plt +import numpy as np +import webbrowser +import Shannon_Dict as Shd +import sqlite3 +import os +import binascii +import itur +from astropy.units import imperial +# Python Program to Get IP Address +import socket +hostname = socket.gethostname() +IPAddr = socket.gethostbyname(hostname) + +Web_Version=True # +Web_Remote=True # +imperial.enable() + +if Web_Version : + import PySimpleGUIWeb as sg + from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, FigureCanvasAgg + import io + def draw_matfig(fig, element): + canv = FigureCanvasAgg(fig) + buf = io.BytesIO() + canv.print_figure(buf, format='png') + if buf is None: + return None + buf.seek(0) + data = buf.read() + element.update(data=data) + def window_matfig(fig, title_fig, title_win): + matlayout = [[sg.T(title_fig, font='Any 20')], + [sg.Image(key='-IMAGE-')], + [sg.B('Exit')]] + winmat = sg.Window(title_win, matlayout, finalize=True) + draw_matfig(fig, winmat['-IMAGE-']) + while True: + event, values = winmat.read() + if event == 'Exit' or event == sg.WIN_CLOSED: + break + plt.close() + winmat.close() +else: + import PySimpleGUI as sg + +if Web_Version and Web_Remote : + web_cfg={"web_ip":IPAddr,"web_port":8080,"web_start_browser":False} +else: + web_cfg={} + +''' ------------------------------------------ Core Functions --------------------------------------------------- + +These functions are mainly application of formulas given in the first panel and formatting functions + +''' + +def Combine_CNR(*CNR): + ''' Combination of Carrier to Noise Ratio ''' + NCR_l = 0 + for CNR_dB in CNR: + NCR_l += 10 ** (-CNR_dB / 10) # Summation of normalized noise variances + return -10 * log(NCR_l, 10) + +def Shannon(BW=36.0, CNR=10.0, Penalty=0.0): + ''' Shannon Limit, returns Bit Rate ''' + CNR_l = 10 ** ((CNR - Penalty) / 10) + return BW * log(1 + CNR_l, 2) + +def BR_Multiplier(BW_mul=1.0, P_mul=2.0, CNR=10.0): + ''' Returns BR multiplier ''' + CNR_l = 10 ** (CNR / 10) + return BW_mul * log(1 + CNR_l * P_mul / BW_mul, 2) / log(1 + CNR_l, 2) + +def Shannon_Points(BW=36.0, CNR=10.0): + ''' Returns CNR_l, BR_inf, C/N0 and BR(BW,CNR) ''' + CNR_l = 10 ** (CNR / 10) + C_N0_l = CNR_l * BW + BR_infinity = C_N0_l / log(2) + BR_constrained = Shannon(BW, CNR) + return CNR_l, BR_infinity, C_N0_l, BR_constrained + +def Shannon_Sp_Eff(Sp_Eff=0.5, BW=36.0, CNR=10.0): + ''' Returns values at required Spe : CNR, BW, BR ''' + C_N0_l = 10 ** (CNR / 10) * BW + BW_Spe_1 = C_N0_l + BW_Spe = C_N0_l / (2 ** Sp_Eff - 1) + BR_Spe = BW_Spe * Sp_Eff + CNR_Spe = 10 * log(BW_Spe_1 / BW_Spe, 10) + return CNR_Spe, BW_Spe, BR_Spe + +def BR_Format(BR=100): + return "{:.1f}".format(BR) + ' Mbps' + +def Power_Format(Pow=100): + Pow_dB=10*log(Pow,10) + if Pow > 1 and Pow < 1e4 : + return "{:.1f}".format(Pow) + ' W .. ' + "{:.1f}".format(Pow_dB) + ' dBW' + elif Pow <= 1 and Pow > 1e-3 : + return "{:.4f}".format(Pow) + ' W .. ' + "{:.1f}".format(Pow_dB) + ' dBW' + else : + return "{:.1e}".format(Pow) + ' W .. ' + "{:.1f}".format(Pow_dB) + ' dBW' + +def PFD_Format(Pow=1): # PSD in W per m2 + Pow_dB=10*log(Pow,10) + return "{:.1e}".format(Pow) + ' W/m\N{SUPERSCRIPT TWO} .. ' + "{:.1f}".format(Pow_dB) + ' dBW/m\N{SUPERSCRIPT TWO}' + +def PSD_Format(Pow=1): # PSD in W per MHz + Pow_dB=10*log(Pow,10) + return "{:.1e}".format(Pow) + ' W/MHz .. ' + "{:.1f}".format(Pow_dB) + ' dBW/MHz' + + +def Gain_Format(Gain=1000): + Gain_dB=10*log(Gain,10) + return "{:.1f}".format(Gain) + ' .. ' + "{:.1f}".format(Gain_dB) + ' dBi' + +def PLoss_Format(Loss=10): + Loss_dB=10*log(Loss,10) + return "{:.2}".format(Loss) + ' m\N{SUPERSCRIPT TWO} .. ' + "{:.1f}".format(Loss_dB) + ' dBm\N{SUPERSCRIPT TWO}' + + +''' ------------------------------------------ Database Functions --------------------------------------------------- + +Functions for management of users' contributions + +''' + +def Contribution_Write (DB_File) : + + Sh_DB=sqlite3.connect(DB_File) + Sh_DB_c=Sh_DB.cursor() + + Sh_DB_c.execute("CREATE TABLE IF NOT EXISTS contributions " + "(num INTEGER, name TEXT, title TEXT, keywords TEXT, text TEXT, date TEXT, password TEXT)") + Sh_DB.commit() + + Sh_DB_c.execute("SELECT MAX(num) FROM contributions") + ID_Contrib=Sh_DB_c.fetchall()[0][0] + + if ID_Contrib == None: + ID_Contrib = 0 + + print (ID_Contrib) + + layout3 = [ [sg.Text('Initials / name',size=(20,1), justification='center'), + sg.Input('',size=(45,1), key='-Name-')], + [sg.Text('Title ', size=(20, 1), justification='center'), + sg.Input('', size=(45, 1), key='-Title-')], + [sg.Text('Keywords ',size=(20,1), justification='center'), + sg.Input('', size=(45,1), key='-Keywords-')], + [sg.Text('Password ', size=(20, 1), justification='center'), + sg.Input('', size=(45, 1), key='-Password-')], + [sg.Frame('Write your text here', + [[sg.Multiline('', size=(80, 15), key='-Text-')], + [sg.Button('Validate'), + sg.Button('Exit'), + sg.Button('Help')]], + element_justification='center')] + ] + window3 = sg.Window('Write Contribution', layout3, finalize=True) + + while True: + event3, values = window3.read() + print(event3) + + if event3 == sg.WIN_CLOSED or event3 == 'Exit' : # if user closes window + break + + elif event3 == 'Validate' : + + ID_Contrib += 1 + + Sh_DB_c.execute("INSERT INTO contributions VALUES ( " + str (ID_Contrib) + ", '" + values['-Name-'] + + "', '" + values['-Title-'] + "', '" + values['-Keywords-'] + "', '" + values['-Text-'] + + "', " + "Date('now')" + ", '" + values['-Password-'] + "' )") + Sh_DB.commit() + + window3['-Text-'].Update('Thank you, your contribution has been successfully stored, ID #'+str(ID_Contrib)) + + elif event3 == 'Help': + window3['-Text-'].Update('Write your contribution here as a free text. Edit it in the text window until you' + ' are happy with the result and then Validate. \n\nContributions should be candid observations' + ' about the technical subject, references to relevant material (ideally as web pointers), open ' + ' discussion items about adjacent subjects, suggestion for improvement... \n\nYou can retrieve' + ' your contribution via a search from the Read Panel and delete them there with the relevant ' + 'password. Keeping the "no password" default will allow anyone to delete.') + + Sh_DB.close() + + window3.close() + + return ID_Contrib + +def Contribution_Read (DB_File) : + + layout4 = [ [sg.Text('Filter on Name',size=(20,1), justification='center'), + sg.Input('',size=(45,1), key='-Name-')], + [sg.Text('Filter on Title ', size=(20, 1), justification='center'), + sg.Input('', size=(45, 1), key='-Title-')], + [sg.Text('Filter on Keywords ',size=(20,1), justification='center'), + sg.Input('', size=(45,1), key='-Keywords-')], + [sg.Text('Filter on Content ', size=(20, 1), justification='center'), + sg.Input('', size=(45, 1), key='-Content-')], + [sg.Frame('Contribution', + [[sg.Multiline('', size=(80, 15), key='-Text-')], + [sg.Button('Load / Filter', key='-Filter-'), + sg.Button('Read Next', key='-Next-'), + sg.Button('Delete', key='-Del-'), + sg.Button('Exit'), + sg.Button('Help')]], + element_justification='center')] + ] + + if os.path.isfile(DB_File): + Sh_DB = sqlite3.connect(DB_File) + Sh_DB_c = Sh_DB.cursor() + else: + sg.popup('No Database Found', keep_on_top=True) + return 0 + + window4 = sg.Window('Read Contributions', layout4) + DB_ind = 0 + + while True: + + event4, values = window4.read() + + print(event4) + + if event4 == sg.WIN_CLOSED or event4 == 'Exit': # if user closes window + break + + elif event4 == '-Filter-': + + Name = "'%" + values['-Name-'] + "%'" + Title = "'%" + values['-Title-'] + "%'" + Keywords = "'%" + values['-Keywords-'] + "%'" + Content = "'%" + values['-Content-'] + "%'" + Sh_DB_c.execute("SELECT num, name, title, keywords, text, date, password FROM contributions WHERE " + + "name LIKE " + Name + + " AND title LIKE " + Title + + " AND keywords LIKE " + Keywords + + " AND text LIKE " + Content + + " ORDER BY num DESC " + " LIMIT 50 ") + DB_Extract = Sh_DB_c.fetchall() + + print(len(DB_Extract)) + + if len(DB_Extract) > 0 : # First Contribution read automatically + + window4['-Text-'].Update( DB_Extract[0][2] + ' , ' + DB_Extract[0][1] + + '\n\n' + DB_Extract[0][4] + '\n\n Keywords : ' + DB_Extract[0][3] + + '\n\n ID #' + str(DB_Extract[0][0]) + ' - ' + str(DB_Extract[0][5]) ) + DB_ind = 1 + + elif event4 == '-Next-' and DB_ind>0 : + + if DB_ind < len(DB_Extract): + window4['-Text-'].Update(DB_Extract[DB_ind][2] + ' , ' + DB_Extract[DB_ind][1] + + '\n\n' + DB_Extract[DB_ind][4] + '\n\n Keywords : ' + DB_Extract[DB_ind][3] + + '\n\n ID #' + str(DB_Extract[DB_ind][0]) + ' - ' + str(DB_Extract[DB_ind][5])) + DB_ind +=1 + + elif event4 == '-Del-': + Password = sg.popup_get_text('Enter Item\'s Password') + print (Password) + DB_ind -= 1 + if DB_Extract[DB_ind][6] == Password : + Item_num=DB_Extract[DB_ind][0] + Sh_DB_c.execute("DELETE FROM contributions WHERE num = " + str(Item_num)) + window4['-Text-'].Update('Item ID# '+ str(Item_num) + ' deleted') + else: + window4['-Text-'].Update('Incorrect password, item unaffected') + + elif event4 == 'Help': + window4['-Text-'].Update('To read contributions you have to load them first. If the search fields are empty' + ', all contributions will be loaded. You should only enter 1 item per search field.' + '\n\n You can delete items if you know the associated password. Items are not encrypted' + ' in the Database and can be removed by the admin.') + + Sh_DB.commit() + Sh_DB.close() + window4.close() + + return DB_ind + + +''' ------------------------------------------ GUI Functions--------------------------------------------------- + +LEDs from PySimpleGUI demos + +''' + + +def LEDIndicator(key=None, radius=30): + return sg.Graph(canvas_size=(radius, radius), + graph_bottom_left=(-radius, -radius), + graph_top_right=(radius, radius), + pad=(0, 0), key=key) + +def SetLED(window, key, color): + graph = window[key] + graph.erase() + graph.draw_circle((0, 0), 12, fill_color=color, line_color='black') + + +''' ------------------------------------------ Main Program --------------------------------------------------- + +The program has 2 main panels associated with events collection loops + +As the loops are build, when the second panel is open, events are only collected for this panel + +In the windowed version, matplotlib plots don't interfere with the event loops : as many as desired can be open + +''' + +# ---------------- First Panel : Shannon's Equation ------------------- + +form_i = {"size":(22,1),"justification":'left',"enable_events":True} # input label format +form_iv = {"size":(8,1),"justification":'center'} # imput value format +form_ov = {"size":(20,1),"justification":'center'} # output value format +form_o = {"size":(65,1),"justification":'right',"enable_events":True} # output label format, also using input elements +form_CRC = {"size":(8,1),"justification":'center',"enable_events":True} # CRC Format + +sg.set_options(auto_size_buttons=False, button_element_size=(14,1)) + +col1=sg.Column([[sg.Frame('Theoretical Exploration', + [[sg.Text(' Reference C/N [dB] ', **form_i,key='-iCNR-'),sg.Input('12', **form_iv, key='-CNR-')], + [sg.Text(' Reference BW [MHz] ', **form_i,key='-iBW-'),sg.Input('36', **form_iv, key='-BW-'), + sg.Text('',size=(34,1)),sg.Text('', **form_CRC, key='-CRC-'), LEDIndicator('-OK-')], + [sg.Text('Carrier Power to Noise Power Density Ratio : C/N\N{SUBSCRIPT ZERO}', **form_o,key='-iC_N0-'), + sg.Input(**form_ov,key='-C_N0-')], + [sg.Text('Theoretical BR at infinite BW : 1.44 C/N\N{SUBSCRIPT ZERO}', **form_o,key='-iBRinf-'), + sg.Input(**form_ov,key='-BRinf-')], + [sg.Text('Theoretical BR at Spectral Efficiency = 1 : C/N\N{SUBSCRIPT ZERO}', **form_o,key='-iBRunit-'), + sg.Input(**form_ov,key='-BRunit-')], + [sg.Text('Theoretical BR at Reference (BW,C/N)', **form_o,key='-iBRbw-'), + sg.Input(**form_ov, key='-BRbw-')], + [sg.Text('Carrier to Noise Ratio : C / N = C / (N\N{SUBSCRIPT ZERO}.B)', **form_o,key='-iCNRlin-'), + sg.Input(**form_ov, key='-CNRlin-')], + [sg.Text(' BW Increase Factor ',**form_i,key='-iBWmul-'), sg.Input('1', **form_iv, key='-BWmul-')], + [sg.Text(' Power Increase Factor ',**form_i,key='-iCmul-'), sg.Input('2', **form_iv, key='-Cmul-')], + [sg.Text('Bit Rate Increase Factor', **form_o,key='-iBRmul-'), sg.Input(**form_ov, key='-BRmul-')], + [sg.Button('Evaluation', visible=False, key='-Evaluation-', bind_return_key = True), + sg.Button('BW Sensitivity', pad=((60,5),(2,2)), key='-BW_Graph-'), + sg.Button('Power Sensitivity',key='-Pow_Graph-'), + sg.Button('BR Factor Map', key='-Map-'), + sg.Button('Go to Real World', key='-Real-')]])]]) + +col2=sg.Column([[sg.Frame('Background Information (click on items)', + [[sg.Multiline('Click on parameter\'s label to get information',size=(80,15),key='-Dialog-')], + [sg.Button('Advanced'), sg.Button('Write Contribution', key='-Write_Ct-'), + sg.Button('Read Contributions',key='-Read_Ct-'),sg.Button('Help')]], element_justification='center')]]) + +layout=[ + [sg.Button('Wiki : Claude_Shannon',size=(25,1), key='-Wiki-')], + [sg.Image(filename='Shannon.png',key='-iShannon-',enable_events=True, background_color='black')], + [col1,col2] + ] + +window = sg.Window('Shannon\'s Equation for Dummies', layout, disable_close=True, finalize=True, element_justification='center', **web_cfg) + +# Needed for the Web version (on a finalized window) +window['-CNR-'].Update('12') +window['-BW-'].Update('36') +window['-BWmul-'].Update('1') +window['-Cmul-'].Update('2') + +File_CRC=hex(binascii.crc32(open('Shannon.py', 'rb').read()) & 0xFFFFFFFF)[2:].upper() +print("File's CRC : ", File_CRC) + +Win_time_out = 500 # Time out approach required for enter capture in the Web version +First_Pass = True +Err_msg = False + +while True: + + event, values = window.read(timeout=Win_time_out, timeout_key='__TIMEOUT__') + + if event != '__TIMEOUT__' : print (event) + + try: + + if event == sg.WIN_CLOSED: # if user closes window + break + + elif event == '-Evaluation-' or event == '__TIMEOUT__' : + + if First_Pass : + window['-Dialog-'].Update(Shd.Help['-iShannon-']) + First_Pass = False + + CNR_List=values['-CNR-'] # CNR can be a combination of comma separated CNRs + CNR_Nyq=Combine_CNR(*[float(val) for val in CNR_List.split(',')]) + + BW_Nyq=float(values['-BW-']) + + CNR_l, BRinf, C_N0_l, BRbw = Shannon_Points(BW_Nyq,CNR_Nyq) + + BRunit = C_N0_l # Mbps / MHz : Sp Eff =1 + + window['-C_N0-'].Update("{:.1f}".format(C_N0_l) + ' MHz') + window['-BRinf-'].Update(BR_Format(BRinf)) + window['-BRunit-'].Update(BR_Format(BRunit)) + window['-BRbw-'].Update(BR_Format(BRbw)) + window['-CNRlin-'].Update("{:.1f}".format(CNR_Nyq)+" [dB] .. "+"{:.1f}".format(CNR_l)+' [Linear]') + + BWmul=float(values['-BWmul-']) + Cmul=float(values['-Cmul-']) + + BRmul=BR_Multiplier(BWmul,Cmul,CNR_Nyq) + window['-BRmul-'].Update("{:.2f}".format(BRmul)) + + Params = str(CNR_Nyq)+','+str(BW_Nyq)+','+str(BWmul)+','+str(Cmul) + Params_CRC = (hex(binascii.crc32(Params.encode('ascii')) & 0xFFFFFFFF)[2:].upper()) # Params' CRC + + elif event == '-Wiki-': + webbrowser.open('https://en.wikipedia.org/wiki/Claude_Shannon') + + elif event in ('-iC_N0-','-iCNR-','-iBRinf-','-iBRunit-','-iBRbw-','-iCNRlin-','-iBRmul-','-iCmul-', + '-iBWmul-','-iBW-','-iShannon-', '-CRC-', 'Advanced','Help' ): + window['-Dialog-'].Update(Shd.Help[event]) + + elif event == '-BW_Graph-': + BW = np.zeros(20) + BR = np.zeros(20) + CNR = np.zeros(20) + CNR[0] = CNR_Nyq + 10 * log(8, 10) + BW[0] = BW_Nyq / 8 + BR[0] = Shannon(BW[0], CNR[0]) + for i in range(1, 20): + BW[i] = BW[i - 1] * 2 ** (1 / 3) + CNR[i] = CNR[i - 1] - 10 * log(BW[i] / BW[i - 1], 10) + BR[i] = Shannon(BW[i], CNR[i]) + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + plt.plot(BW, BR, 'b') + Mark = ('D', 's', 'p', 'h', 'x') + for i in range(5): + ind = 3 * (i + 1) + BR_norm = BR[ind] / BR[9] + plt.plot(BW[ind], BR[ind], Mark[i] + 'b', label="{:.1f}".format(BW[ind]) + " MHz" + + " , {:.1f}".format(BR[ind]) + " Mbps" + " : {:.0%}".format(BR_norm)) + plt.title('Theoretical Bit Rate at Constant Power\nC/N\N{SUBSCRIPT ZERO} = ' + + "{:.1f}".format(C_N0_l) + " MHz" ) + plt.xlabel('Bandwidth [MHz]') + plt.ylabel('Bit Rate [Mbps]') + plt.grid(True) + plt.legend(loc='lower right') + + if Web_Version: + window_matfig(fig, title_fig='Bandwidth Sensitivity', title_win='Shannon for Dummies') + else: + plt.show(block=False) + + elif event == '-Pow_Graph-': + P_mul = np.zeros(20) + BR = np.zeros(20) + CNR = np.zeros(20) + P_mul[0] = 1 / 8 + CNR[0] = CNR_Nyq - 10 * log(8, 10) + BR[0] = Shannon(BW_Nyq, CNR[0]) + for i in range(1, 20): + P_mul[i] = P_mul[i-1] * 2 ** ( 1 / 3 ) + CNR[i] = CNR[i - 1] + 10 * log( 2 ** ( 1 / 3 ), 10 ) + BR[i] = Shannon(BW_Nyq, CNR[i]) + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + plt.plot(P_mul, BR, 'b') + Mark = ('D', 's', 'p', 'h', 'x') + for i in range(5): + ind = 3 * (i + 1) + BR_norm = BR[ind] / BR[9] + plt.plot(P_mul[ind], BR[ind], Mark[i] + 'b', label='{:.2f}'.format(P_mul[ind]) + + 'x , {:.1f}'.format(BR[ind]) + ' Mbps' + ' : {:.0%}'.format(BR_norm)) + plt.title('Theoretical Bit Rate at Constant Bandwidth : ' + '{:.1f}'.format(BW_Nyq) + ' MHz \n' + 'Reference : C/N = {:.1f}'.format(CNR_l) + ' [Linear Format]') + plt.xlabel('Power Multiplying Factor') + plt.ylabel('Bit Rate [Mbps]') + plt.grid(True) + plt.legend(loc='lower right') + + if Web_Version: + window_matfig(fig, title_fig='Power Sensitivity', title_win='Shannon for Dummies') + else: + plt.show(block=False) + + elif event == '-Map-' : + BR_mul=np.zeros((21,21)) + BW_mul=np.zeros((21,21)) + P_mul=np.zeros((21,21)) + for i in range(21): + for j in range(21): + BW_mul[i, j] = (i + 1)/4 + P_mul[i, j] = (j + 1)/4 + BR_mul[i, j] = BR_Multiplier(BW_mul[i, j], P_mul[i, j], CNR_Nyq) + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + Map = plt.contour(BW_mul,P_mul,BR_mul, 20) + plt.clabel(Map, inline=1, fontsize=8,fmt='%.2f') + plt.title('Bit Rate Multiplying Factor, \n Reference : C/N = {:.1f}'.format(CNR_Nyq) + ' dB, BW = ' + + '{:.1f}'.format(BW_Nyq) + ' MHz , C/N\N{SUBSCRIPT ZERO} = ' + '{:.1f}'.format(C_N0_l) + + ' MHz, BR = {:.1f}'.format(BRbw) + ' Mbps', fontsize=10) + plt.xlabel('Bandwidth Multiplying Factor') + plt.ylabel('Power Multiplying Factor') + plt.grid(True) + + if Web_Version: + window_matfig(fig, title_fig='Multiplying Factors Map', title_win='Shannon for Dummies') + else: + plt.show(block=False) + + elif event == '-Write_Ct-' : + Contribution_Write('Shannon_Theory.db') + + elif event == '-Read_Ct-' : + Contribution_Read('Shannon_Theory.db') + + elif event == '-Real-': # ---------------- Second Panel : Real World ------------------- + + P_i1, P_i2, P_i3 = '', '', '' + + fr1 = sg.Frame('Satellite Link',[ + [sg.Text('Satellite Altitude [km] ​', **form_i, key='-iSatAlt-'), + sg.Input('35786', **form_iv, key='-SatAlt-'), + sg.Text('Satellite Lat, Long ​[\N{DEGREE SIGN}]', **form_i, key='-iSatLatLong-'), + sg.Input('0.0, 19.2', **form_iv, key='-SatLatLong-'), + sg.Text('Ground Station Lat, Long ​[\N{DEGREE SIGN}]', **form_i, key='-iGSLatLong-'), + sg.Input('49.7, 6.3', **form_iv, key='-GSLatLong-')], + [sg.Text('HPA Output Power [W]',**form_i,key='-iHPA-'), + sg.Input('120', **form_iv, key='-HPA_P-'), + sg.Text('Output Losses [dB]',**form_i,key='-iLoss-'), + sg.Input('2', **form_iv, key='-Losses-'), + sg.Text('TX Impairments, C/I [dB]', **form_i, key='-iSCIR-'), + sg.Input('25, 25', **form_iv, key='-Sat_CIR-')], + [sg.Text('Beam Diameter [\N{DEGREE SIGN}]', **form_i, key='-iSBeam-'), + sg.Input('3', **form_iv, key='-Sat_Beam-'), + sg.Text('Offset from Peak [dB] ', **form_i, key='-iGOff-'), + sg.Input('0', **form_iv, key='-Gain_Offset-'), sg.Text('', size=(7, 1)), + sg.Text('', **form_CRC, key='-CRC1-'), LEDIndicator('-OK1-')], + [sg.Text('Frequency [GHz]', **form_i, key='-iFreq-'), + sg.Input('12', **form_iv, key='-Freq-'), + sg.Text('Link Availability [%]', **form_i, key='-iAvail-'), + sg.Input('99.9', **form_iv, key='-Avail-')], + [sg.Text('Output Power', **form_o,key='-iOPow-'), + sg.Input('', size=(35, 1), key='-Feed_P-', justification='center')], + [sg.Text('Satellite Antenna Gain​', **form_o,key='-iSGain-'), + sg.Input('', size=(35, 1), key='-Sat_G-', justification='center')], + [sg.Text('Equivalent Isotropic Radiated Power​', **form_o,key='-iEIRP-'), + sg.Input('', size=(35, 1), key='-EIRP-', justification='center')], + [sg.Text('Path Length @ Elevation', **form_o, key='-iPathLength-'), + sg.Input('', size=(35, 1), key='-PathLength-', justification='center')], + [sg.Text('Path Dispersion Loss', **form_o, key='-iPLoss-'), + sg.Input('', size=(35, 1), key='-PLoss-', justification='center')], + [sg.Text('Atmospheric Attenuation', **form_o, key='-iAtmLoss-'), + sg.Input('', size=(35, 1), key='-AtmLoss-', justification='center')], + [sg.Text('Power Flux Density', **form_o,key='-iPFD-'), + sg.Input('', size=(35, 1), key='-PFD-', justification='center')], + ], key='-SatLink-') + + fr2=sg.Frame('Radio Front End ',[ + [sg.Text('Receive Antenna Size [m]​',**form_i,key='-iCPE-'), + sg.Input('0.6', **form_iv, key='-CPE_Ant-'), + sg.Text('Noise Temperature [K] ​', **form_i, key='-iCPE_T-'), + sg.Input('140', **form_iv, key='-CPE_T-'), sg.Text('',size=(7, 1)), + sg.Text('', **form_CRC, key='-CRC2-'),LEDIndicator('-OK2-')], + [sg.Text('Customer Antenna Effective Area and G/T', **form_o,key='-iCGain-'), + sg.Input('', size=(35, 1), key='-CPE_G-', justification='center')], + [sg.Text('RX Power at Antenna Output', **form_o,key='-iRXPow-'), + sg.Input('', size=(35, 1), key='-RX_P-', justification='center')], + [sg.Text('Noise Power Density Antenna Output', **form_o, key='-iN0-'), + sg.Input('', size=(35, 1), key='-N0-', justification='center')], + [sg.Text('Bit Rate at infinite Bandwidth', **form_o,key='-iBRinf-'), + sg.Input('', size=(35, 1), key='-BRinf-', justification='center')], + [sg.Text('Bit Rate at Spectral Efficiency=1', **form_o,key='-iBRUnit-'), + sg.Input('', size=(35, 1), key='-BRUnit-', justification='center')], + [sg.Text('Bit Rate at Spectral Efficiency=2', **form_o,key='-iBRdouble-'), + sg.Input('', size=(35, 1), key='-BRdouble-', justification='center')] + ], key='-iRadio-') + + fr3=sg.Frame('Baseband Unit',[ + [sg.Text('Occupied Bandwidth [MHz]',**form_i,key='-iBW-'), + sg.Input('36', **form_iv, key='-BW-'), + sg.Text('Nyquist Filter Rolloff [%]',**form_i,key='-iRO-'), + sg.Input('5', **form_iv, key='-RO-'), + sg.Text('Higher Layers Overhead [%]', **form_i, key='-iOH-'), + sg.Input('5', **form_iv, key='-OH-')], + [sg.Text('RX Impairments, C/I [dB]',**form_i,key='-iCIR-'), + sg.Input('20', **form_iv, key='-CIR-'), + sg.Text('Implementation Penalty [dB]​',**form_i,key='-iPenalty-'), + sg.Input('1.5', **form_iv, key='-Penalty-'), sg.Text('',size=(7, 1)), + sg.Text('', **form_CRC, key='-CRC3-'), LEDIndicator('-OK3-')], + [sg.Text('Signal to Noise Ratio in Available BW', **form_o,key='-iCNRbw-'), + sg.Input('', size=(35, 1), key='-CNRbw-', justification='center')], + [sg.Text('Signal to Noise Ratio in Nyquist BW', **form_o,key='-iCNRnyq-'), + sg.Input('', size=(35, 1), key='-CNRnyq-', justification='center')], + [sg.Text('Signal to Noise Ratio at Receiver Output', **form_o,key='-iCNRrcv-'), + sg.Input('', size=(35, 1), key='-CNRrcv-', justification='center')], + [sg.Text('Theoretical Bit Rate', **form_o,key='-iBRnyq-'), + sg.Input('', size=(35, 1), key='-BRnyq-', justification='center')], + [sg.Text('Practical Bit Rate', **form_o,key='-iBRrcv-'), + sg.Input('', size=(35, 1), key='-BRrcv-', justification='center')], + [sg.Text('Practical Higher Layers Bit Rate', **form_o,key='-iBRhigh-'), + sg.Input('', size=(35, 1), key='-BRhigh-', justification='center')], + [sg.Button('Evaluation', key='-Evaluation-',visible=False, bind_return_key = True), + sg.Button('BW Sensitivity', pad=((100,5),(2,2)), key='-BW_Graph-'), + sg.Button('Power Sensitivity',key='-Pow_Graph-'), + sg.Button('BR Factor Map', key='-Map-'), + sg.Button('Back to Theory', key='-Back-')], + ], key='-Baseband-') + + fr4 = sg.Frame('Background Information (click on items)', + [[sg.Multiline('Click on parameter\'s label to get information', size=(80, 15),key='-Dialog-')], + [sg.Button('Advanced'), sg.Button('Write Contribution', key='-Write_Ct-'), + sg.Button('Read Contributions',key='-Read_Ct-'), sg.Button('Help')]],element_justification='center') + + layout2 =[ + [sg.Column([[fr1],[fr2],[fr3]]), + sg.Column([[sg.Text('', size=(55, 1), justification='center', key='-FCRC-')], + [sg.Button('Wiki : Harry Nyquist​', size=(25,1), key='-W_Nyquist-'), + sg.Button('Wiki : Richard Hamming​', size=(25,1), key='-W_Hamming-')], + [sg.Button('Wiki : Andrew Viterbi​', size=(25,1), key='-W_Viterbi-'), + sg.Button('Wiki : Claude Berrou​', size=(25,1), key='-W_Berrou-')], + [sg.Image(filename='Satellite.png', key='-Satellite-',background_color='black', enable_events=True)], + [fr4]], element_justification='center')]] + + window2 = sg.Window('Shannon and Friends in the Real World', layout2, finalize=True) + + window2['-FCRC-'].Update('Program\'s CRC: ' + File_CRC) + + # Needed for the Web version (on a finalized window) + window2['-Freq-'].Update('12') + window2['-Losses-'].Update('2') + window2['-SatAlt-'].Update('35786') + window2['-SatLatLong-'].Update('0.0, 19.2') + window2['-Avail-'].Update('99.9') + window2['-GSLatLong-'].Update('49.7, 6.3') + window2['-HPA_P-'].Update('120') + window2['-Sat_CIR-'].Update('25, 25') + window2['-Sat_Beam-'].Update('3') + window2['-Gain_Offset-'].Update('0') + window2['-CPE_Ant-'].Update('0.6') + window2['-CPE_T-'].Update('120') + window2['-Penalty-'].Update('1.5') + window2['-BW-'].Update('36') + window2['-CIR-'].Update('25') + window2['-RO-'].Update('5') + window2['-OH-'].Update('5') + window2['-AtmLoss-'].Update(' ... LOADING ...') + + First_Pass = True + while True: + + event2, values = window2.read(timeout=Win_time_out,timeout_key = '__TIMEOUT__') + + if event2 != '__TIMEOUT__' : print(event2) + + P_err = 0 # Error ID + + try: + + if event2 == sg.WIN_CLOSED or event2 == '-Back-': # closes window + break + + elif event2 == '-Evaluation-'or event2 == '__TIMEOUT__' : + + if First_Pass : + window2['-Dialog-'].Update(Shd.Help2['-Satellite-']) + First_Pass = False + + P_err = 1 # Error ID + Freq = float(values['-Freq-']) # GHz + HPA_Power = float(values['-HPA_P-']) # Watts + Sat_Loss = float(values['-Losses-']) # dB + Sat_CIR_List = values['-Sat_CIR-'] # list if comma separated CIR contributions in dB + Sat_CIR = Combine_CNR(*[float(val) for val in Sat_CIR_List.split(',')]) + Sig_Power = HPA_Power * 10 ** (-Sat_Loss / 10) # Watts + Sat_Beam = float(values['-Sat_Beam-']) #dB + Gain_Offset = float(values['-Gain_Offset-']) #dB + Sat_Ant_eff = 0.65 # Factor + + window2['-Feed_P-'].Update(Power_Format(Sig_Power)) + + Lambda = 300e6 / Freq / 1e9 # meter + Sat_Gain_l = Sat_Ant_eff * ( pi * 70 / Sat_Beam ) ** 2 + Sat_Gain_l = Sat_Gain_l * 10**(-Gain_Offset/10) + Sat_Ant_d = 70 * Lambda / Sat_Beam + Sat_Gain_dB = 10 * log(Sat_Gain_l, 10) + + window2['-Sat_G-'].Update(Gain_Format(Sat_Gain_l)) + + EIRP_l = Sig_Power * Sat_Gain_l + EIRP_dB = 10 * log(EIRP_l, 10) + window2['-EIRP-'].Update(Power_Format(EIRP_l)) + + Avail = float(values['-Avail-']) + + R_earth=6378 + + [lat_GS, lon_GS] = [float(val) for val in values['-GSLatLong-'].split(',')] + [lat_sat, lon_sat] = [float(val) for val in values['-SatLatLong-'].split(',')] + + h_sat = float(values['-SatAlt-']) + + Path_Length = sqrt (h_sat**2 + 2 * R_earth * (R_earth + h_sat ) * + ( 1 - cos(np.radians(lat_sat-lat_GS)) * cos(np.radians(lon_sat-lon_GS)))) + + # elevation = itur.utils.elevation_angle(h_sat, lat_sat, lon_sat, lat_GS, lon_GS) # non signed + + Phi = acos(cos(np.radians(lat_sat-lat_GS)) * cos(np.radians(lon_sat-lon_GS))) + + if Phi > 0 : + elevation = np.degrees(atan((cos(Phi)-R_earth/(R_earth+h_sat))/sqrt(1-cos(Phi)**2))) + else : + elevation = 90 + + if elevation <= 0 : + Atm_Loss = 999 + else: + Atm_Loss = itur.atmospheric_attenuation_slant_path(lat_GS, lon_GS, Freq, + elevation, 100-Avail, 1).value + + window2['-AtmLoss-'].Update("{:.1f}".format(10 ** (Atm_Loss/10)) + ' [Linear] .. ' + + "{:.1f}".format(Atm_Loss) + " [dB]") + window2['-PathLength-'].Update("{:.1f}".format(Path_Length) + " [km] @ " + + "{:.1f}".format(elevation) + " [\N{DEGREE SIGN}]") + + Free_Space_Loss_l = (4 * pi * Path_Length * 1000 / Lambda) ** 2 + Free_Space_Loss_dB = 10 * log(Free_Space_Loss_l, 10) + + Path_Loss_l = 4 * pi * (Path_Length * 1000) ** 2 + Path_Loss_dB = 10 * log(Path_Loss_l, 10) + window2['-PLoss-'].Update(PLoss_Format(Path_Loss_l)) + + PFD_l = EIRP_l / Path_Loss_l * 10 ** (-Atm_Loss/ 10) + PFD_dB = 10 * log(PFD_l, 10) + window2['-PFD-'].Update(PFD_Format(PFD_l)) + + P_err = 2 # Error ID + CPE_Ant_d = float(values['-CPE_Ant-']) # meter + CPE_T_Clear= float(values['-CPE_T-']) # K + CPE_Ant_eff = 0.6 + CPE_T_Att = (CPE_T_Clear - 40) + 40 * 10 ** (-Atm_Loss/10) + 290 * (1 - 10 ** (-Atm_Loss/10)) + k_Boltz = 1.38e-23 # J/K + + P_err = 3 # Error ID + Penalties = float(values['-Penalty-']) # dB, overall implementation penalty + Bandwidth = float(values['-BW-']) # MHz + CNR_Imp_List = values['-CIR-'] # List of comma separated CNR impairments in dB + CNR_Imp = Combine_CNR(*[float(val) for val in CNR_Imp_List.split(',')]) + Rolloff = float(values['-RO-']) # percent + Overheads = float(values['-OH-']) # percent + + CPE_Ae = pi * CPE_Ant_d ** 2 / 4 * CPE_Ant_eff + CPE_Gain_l = (pi * CPE_Ant_d / Lambda) ** 2 * CPE_Ant_eff + CPE_Gain_dB = 10 * log(CPE_Gain_l, 10) + CPE_Beam = 70 * Lambda / CPE_Ant_d # diameter in degrees + CPE_G_T = 10 * log(CPE_Gain_l / CPE_T_Att, 10) + window2['-CPE_G-'].Update( + "{:.1f}".format(CPE_Ae)+" m\N{SUPERSCRIPT TWO} .. {:.1f}".format(CPE_G_T)+" dB/K") + + RX_Power_l = PFD_l * CPE_Ae + # Alternative : RX_Power_l=EIRP_l/Free_Space_Loss_l*CPE_Gain_l + RX_Power_dB = 10 * log(RX_Power_l,10) + N0 = k_Boltz * CPE_T_Att # W/Hz + C_N0_l = RX_Power_l / N0 # Hz + C_N0_dB = 10 * log(C_N0_l, 10) # dBHz + window2['-RX_P-'].Update('C : ' + Power_Format(RX_Power_l)) + window2['-N0-'].Update('N\N{SUBSCRIPT ZERO} : ' + PSD_Format(N0*1e6)) + + BW_Spe_1 = C_N0_l / 1e6 # C_N0 without Penalty in MHz + BW_Spe_1half = BW_Spe_1 / (2 ** 0.5 - 1) # MHz + BW_Spe_1quarter = BW_Spe_1 / (2 ** 0.25 - 1) + BW_Spe_double = BW_Spe_1 / (2 ** 2 - 1) + + BR_Spe_1 = BW_Spe_1 # Mbps + BR_Spe_1half = BW_Spe_1half / 2 + BR_Spe_1quarter = BW_Spe_1quarter / 4 + BR_Spe_double= BW_Spe_double * 2 + BR_infinity = BW_Spe_1 / log(2) + BR_Spe_1_Norm = BR_Spe_1 / BR_infinity + BR_Spe_1half_Norm = BR_Spe_1half / BR_infinity + BR_Spe_1quarter_Norm = BR_Spe_1quarter / BR_infinity + BR_Spe_double_Norm = BR_Spe_double / BR_infinity + window2['-BRinf-'].Update('1.443 C/N\N{SUBSCRIPT ZERO} : ' + + BR_Format(BR_infinity)+" .. {:.0%}".format(1)) + window2['-BRUnit-'].Update('C/N\N{SUBSCRIPT ZERO} : ' + + BR_Format(BR_Spe_1)+" .. {:.0%}".format(BR_Spe_1_Norm)) + window2['-BRdouble-'].Update('0.667 C/N\N{SUBSCRIPT ZERO} : ' + + BR_Format(BR_Spe_double)+" .. {:.0%}".format(BR_Spe_double_Norm)) + + CNR_Spe_1 = 0 # dB + CNR_Spe_1half = -10 * log(BW_Spe_1half / BW_Spe_1, 10) + CNR_Spe_1quarter = -10 * log(BW_Spe_1quarter / BW_Spe_1, 10) + CNR_Spe_double = -10 * log(BW_Spe_double/ BW_Spe_1, 10) + + CNR_BW = CNR_Spe_1 + 10 * log(BW_Spe_1 / Bandwidth, 10) # dB + BW_Nyq = Bandwidth / (1 + Rolloff / 100) # MHz + CNR_Nyq = CNR_Spe_1 + 10 * log(BW_Spe_1 / BW_Nyq, 10) # dBB + CNR_Rcv = Combine_CNR (CNR_Nyq, CNR_Imp, Sat_CIR) # + BR_BW = Shannon (Bandwidth, CNR_BW) # Mbps + BR_Nyq = Shannon (BW_Nyq, CNR_Nyq) # Mbps + BR_Rcv = Shannon (BW_Nyq, CNR_Rcv,Penalties) # Mbps + BR_Rcv_Higher = BR_Rcv / (1 + Overheads / 100) # Mbps + BR_BW_Norm = BR_BW / BR_infinity + BR_Nyq_Norm = BR_Nyq / BR_infinity + BR_Rcv_Norm = BR_Rcv / BR_infinity + BR_Rcv_H_Norm = BR_Rcv_Higher / BR_infinity + Spe_BW = BR_BW / Bandwidth + Spe_Nyq = BR_Nyq / Bandwidth # Efficiency in available bandwidth + Bits_per_Symbol = BR_Nyq / BW_Nyq # Efficiency in Nyquist bandwidth + Spe_Rcv = BR_Rcv / Bandwidth + Spe_Higher = BR_Rcv_Higher / Bandwidth + + window2['-CNRbw-'].Update("{:.1f}".format(CNR_BW)+" dB in "+"{:.1f}".format(Bandwidth)+" MHz") + window2['-CNRnyq-'].Update("{:.1f}".format(CNR_Nyq)+" dB in "+"{:.1f}".format(BW_Nyq)+" MHz") + window2['-CNRrcv-'].Update("{:.1f}".format(CNR_Rcv)+" dB") + window2['-BRnyq-'].Update(BR_Format(BR_Nyq) + " .. {:.0%}".format(BR_Nyq_Norm)+ + " .. {:.1f}".format(Spe_Nyq)+" bps/Hz .. {:.1f}".format(Bits_per_Symbol)+" b/S") + window2['-BRrcv-'].Update(BR_Format(BR_Rcv) + " .. {:.0%}".format(BR_Rcv_Norm)+ + " .. {:.1f}".format(Spe_Rcv)+" bps/Hz") + window2['-BRhigh-'].Update(BR_Format(BR_Rcv_Higher)+" .. {:.0%}".format(BR_Rcv_H_Norm)+ + " .. {:.1f}".format(Spe_Higher)+" bps/Hz") + + Params1 = str(Freq) + ',' + str(Path_Length) + ',' + str(Atm_Loss) + ',' + \ + str(HPA_Power) + ',' + str(Sat_Loss) + ',' + str(Sat_CIR) + ',' + \ + str(Sat_Beam) + ',' + str(Gain_Offset) + Params2 = str(CPE_Ant_d) + ',' + str(CPE_T_Clear) + Params3 = str(Bandwidth) + ',' + str(Rolloff) + ',' + str(Overheads) + ',' + \ + str(CNR_Imp) + ',' + str(Penalties) + + Params1_CRC=hex(binascii.crc32(Params1.encode('ascii')) & 0xFFFFFFFF)[2:].upper() + Params2_CRC = hex(binascii.crc32(Params2.encode('ascii')) & 0xFFFFFFFF)[2:].upper() + Params3_CRC = hex(binascii.crc32(Params3.encode('ascii')) & 0xFFFFFFFF)[2:].upper() + + elif event2 in ('-iFreq-','-iHPA-','-iSBeam-','-iLoss-', '-iGOff-','-iFade-', '-iSCIR-', + '-iOPow-','-iSGain-', '-iEIRP-', '-iPFD-', '-iCPE-','-iCGain-','-iRXPow-', + '-iBRinf-','-iBRhalf-', '-iBRUnit-','-iBRdouble-','-iBW-','-iRO-','-iCIR-', + '-iPenalty-', '-iOH-','-iNBW-', '-iCNRbw-','-iCNRnyq-','-iCNRrcv-','-iBRbw-', + '-iBRnyq-','-iBRrcv-','-iBRhigh-','-Satellite-','-iPLoss-','Advanced','Help', + '-CRC1-', '-CRC2-', '-CRC3-', '-iN0-','-iCPE_T-', '-iSatAlt-','-iSatLatLong-', + '-iGSLatLong-', '-iAvail-', '-iPathLength-','-iAtmLoss-'): + window2['-Dialog-'].Update(Shd.Help2[event2]) + + elif event2 == '-BW_Graph-' : + BW = np.zeros(20) + BR = np.zeros(20) + CNR = np.zeros(20) + CNR[0] = CNR_Nyq+10*log(8,10) + BW[0] = Bandwidth/8 + BR[0] = Shannon(BW[0]/(1+Rolloff/100), CNR[0], Penalties) / (1 + Overheads / 100) + for i in range(1, 20): + BW[i] = BW[i - 1] * 2 ** (1 / 3) + CNR[i] = CNR[i - 1] - 10 * log(BW[i] / BW[i - 1], 10) + CNR_Rcv_i = Combine_CNR(CNR[i], CNR_Imp, Sat_CIR) + BR[i] = Shannon(BW[i]/(1+Rolloff/100), CNR_Rcv_i, Penalties) / (1 + Overheads / 100) + + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + plt.plot(BW, BR, 'b') + Mark = ('D', 's', 'p', 'h', 'x') + for i in range(5): + ind = 3 * (i + 1) + BR_norm = BR[ind] / BR[9] + plt.plot(BW[ind], BR[ind], Mark[i] + 'b',label="{:.1f}".format(BW[ind]) + + " MHz" + " , {:.1f}".format(BR[ind]) + " Mbps" + " : {:.0%}".format(BR_norm)) + plt.title('Higher Layers Bit Rate at Constant HPA Output Power : ' + + "{:.1f}".format(HPA_Power) + " W") + plt.xlabel('Occupied Bandwidth [MHz]') + plt.ylabel('Bit Rate [Mbps]') + plt.grid(True) + plt.legend(loc='lower right') + + if Web_Version: + window_matfig(fig, title_fig='Bandwidth Sensitivity', + title_win='Shannon and Friends in the Real World') + else: + plt.show(block=False) + + elif event2 == '-Pow_Graph-' : + Power = np.zeros(20) + BR = np.zeros(20) + CNR = np.zeros(20) + Power[0] = HPA_Power / 8 + CNR[0] = CNR_Nyq-10*log(8,10) + CNR_Rcv_i = Combine_CNR(CNR[0], CNR_Imp, Sat_CIR) + BR[0] = Shannon(BW_Nyq, CNR_Rcv_i, Penalties) / (1 + Overheads / 100) + for i in range(1, 20): + Power[i] = Power[i-1] * 2 ** (1 / 3) + CNR[i] = CNR[i - 1] + 10 * log( 2 ** (1 / 3) , 10 ) + CNR_Rcv_i = Combine_CNR(CNR[i], CNR_Imp, Sat_CIR) + BR[i] = Shannon(BW_Nyq, CNR_Rcv_i, Penalties) / (1 + Overheads / 100) + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + plt.plot(Power, BR, 'b') + Mark = ('D', 's', 'p', 'h', 'x') + for i in range(5): + ind = 3 * (i + 1) + BR_norm=BR[ind]/BR[9] + plt.plot(Power[ind], BR[ind], Mark[i] + 'b', label="{:.1f}".format(Power[ind]) + " W" + + " , {:.1f}".format(BR[ind]) + " Mbps"+" : {:.0%}".format(BR_norm)) + plt.title('Higher Layers Bit Rate at Constant Occupied Bandwidth : ' + + "{:.1f}".format(Bandwidth) + " MHz") + plt.xlabel('HPA Output Power @ Operating Point [Watts]') + plt.ylabel('Bit Rate [Mbps]') + plt.grid(True) + plt.legend(loc='lower right') + + if Web_Version: + window_matfig(fig, title_fig='Power Sensitivity', + title_win='Shannon and Friends in the Real World') + else: + plt.show(block=False) + + elif event2 == '-Map-': + BR_mul = np.zeros((21, 21)) + BW_mul = np.zeros((21, 21)) + P_mul = np.zeros((21, 21)) + BR_00 = BR_Rcv_Higher + for i in range(21): + for j in range(21): + BW_mul[i, j] = (i + 1) / 4 + P_mul[i, j] = (j + 1) / 4 + CNR_ij = CNR_Nyq + 10 * log( P_mul[i, j] / BW_mul[i, j] , 10 ) + CNR_Rcv_ij = Combine_CNR (CNR_ij, CNR_Imp, Sat_CIR) + BW_ij = BW_Nyq * BW_mul[i, j] + BR_ij = Shannon( BW_ij / (1 + Rolloff / 100), CNR_Rcv_ij, Penalties) / (1 + Overheads / 100) + BR_mul[i, j] = BR_ij / BR_00 + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + Map = plt.contour(BW_mul, P_mul, BR_mul, 20) + plt.clabel(Map, inline=1, fontsize=8, fmt='%.2f') + plt.title('Bit Rate Multiplying Factor, \n Reference : Power = {:.1f}'.format(HPA_Power) + + ' W , BW = ' + '{:.1f}'.format(Bandwidth) + + ' MHz , BR = {:.1f}'.format(BR_Rcv_Higher) + ' Mbps') + plt.xlabel('Bandwidth Multiplying Factor') + plt.ylabel('Power Multiplying Factor') + plt.grid(True) + + if Web_Version: + window_matfig(fig, title_fig='Multiplying Factors Map', + title_win='Shannon and Friends in the Real World') + else: + plt.show(block=False) + + elif event2 == '-W_Nyquist-': + webbrowser.open('https://en.wikipedia.org/wiki/Harry_Nyquist') + elif event2 == '-W_Hamming-': + webbrowser.open('https://en.wikipedia.org/wiki/Richard_Hamming') + elif event2 == '-W_Viterbi-': + webbrowser.open('https://en.wikipedia.org/wiki/Andrew_Viterbi') + elif event2 == '-W_Berrou-': + webbrowser.open('https://en.wikipedia.org/wiki/Claude_Berrou') + + elif event2 == '-Write_Ct-': + Contribution_Write('Shannon_Real.db') + + elif event2 == '-Read_Ct-': + Contribution_Read('Shannon_Real.db') + + else: + print("Untrapped event : "+event2) + + + except : + window2['-Dialog-'].Update('Invalid input fields') + if P_err == 1 : + window2['-CRC1-'].Update('-') + SetLED(window2, '-OK1-', 'red') + if P_err == 2 : + window2['-CRC2-'].Update('-') + SetLED(window2, '-OK2-', 'red') + if P_err == 3 : + window2['-CRC3-'].Update('-') + SetLED(window2, '-OK3-', 'red') + Err_msg=True + else: + window2['-CRC1-'].Update(Params1_CRC) + window2['-CRC2-'].Update(Params2_CRC) + window2['-CRC3-'].Update(Params3_CRC) + SetLED(window2, '-OK1-', 'green') + SetLED(window2, '-OK2-', 'green') + SetLED(window2, '-OK3-', 'green') + if Err_msg : + window2['-Dialog-'].Update('Input Validated') + Err_msg = False + + window2.close() + + except : + window['-Dialog-'].Update('Invalid input fields') + window['-CRC-'].Update('-') + SetLED(window, '-OK-', 'red') + Err_msg = True + else: + window['-CRC-'].Update(Params_CRC) + SetLED(window, '-OK-', 'green') + if Err_msg: + window['-Dialog-'].Update('Input Validated') + Err_msg = False + +window.close() diff --git a/Shannon.py.save b/Shannon.py.save new file mode 100644 index 0000000..3c43740 --- /dev/null +++ b/Shannon.py.save @@ -0,0 +1,1024 @@ +''' ---------------------------------------------------------------------------------------------------------------- + +Shannon Equation for Dummies - JPC Feb 2021 + +Educational Application + +Exploration from Claude's Shannon initial theory to its practical application to satellite communications + +The application Runs either in local windows or in web pages + +The Web version via localhost is fully functional although not as convenient as the windowed version (only 1 plot open) + +The Web version in remote does work for a single user (all users connected can send command but see the same page) + +--------------------------------------------------------------------------------------------------------------------''' + +''' ------------------------------------------ Imports --------------------------------------------------- + +The GUI has been designed to be compatible with both PySimpleGUIWeb and PySimpleGUI + +The PySimpleGUi version takes full benefit of the matplotlib windowing whereas the Web version is constrained to use +a web compatible method with only one graph at a time + +''' +from math import * +import matplotlib.pyplot as plt +import numpy as np +import webbrowser +import Shannon_Dict as Shd +import sqlite3 +import os +import binascii +import itur +from astropy.units import imperial + +Web_Version=True # +Web_Remote=True # +imperial.enable() + +if Web_Version : + import PySimpleGUIWeb as sg + from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, FigureCanvasAgg + import io + def draw_matfig(fig, element): + canv = FigureCanvasAgg(fig) + buf = io.BytesIO() + canv.print_figure(buf, format='png') + if buf is None: + return None + buf.seek(0) + data = buf.read() + element.update(data=data) + def window_matfig(fig, title_fig, title_win): + matlayout = [[sg.T(title_fig, font='Any 20')], + [sg.Image(key='-IMAGE-')], + [sg.B('Exit')]] + winmat = sg.Window(title_win, matlayout, finalize=True) + draw_matfig(fig, winmat['-IMAGE-']) + while True: + event, values = winmat.read() + if event == 'Exit' or event == sg.WIN_CLOSED: + break + plt.close() + winmat.close() +else: + import PySimpleGUI as sg + +if Web_Version and Web_Remote : + web_cfg={"web_ip":'127.0.0.1',"web_port":8080,"web_start_browser":False} +else: + web_cfg={} + +''' ------------------------------------------ Core Functions --------------------------------------------------- + +These functions are mainly application of formulas given in the first panel and formatting functions + +''' + +def Combine_CNR(*CNR): + ''' Combination of Carrier to Noise Ratio ''' + NCR_l = 0 + for CNR_dB in CNR: + NCR_l += 10 ** (-CNR_dB / 10) # Summation of normalized noise variances + return -10 * log(NCR_l, 10) + +def Shannon(BW=36.0, CNR=10.0, Penalty=0.0): + ''' Shannon Limit, returns Bit Rate ''' + CNR_l = 10 ** ((CNR - Penalty) / 10) + return BW * log(1 + CNR_l, 2) + +def BR_Multiplier(BW_mul=1.0, P_mul=2.0, CNR=10.0): + ''' Returns BR multiplier ''' + CNR_l = 10 ** (CNR / 10) + return BW_mul * log(1 + CNR_l * P_mul / BW_mul, 2) / log(1 + CNR_l, 2) + +def Shannon_Points(BW=36.0, CNR=10.0): + ''' Returns CNR_l, BR_inf, C/N0 and BR(BW,CNR) ''' + CNR_l = 10 ** (CNR / 10) + C_N0_l = CNR_l * BW + BR_infinity = C_N0_l / log(2) + BR_constrained = Shannon(BW, CNR) + return CNR_l, BR_infinity, C_N0_l, BR_constrained + +def Shannon_Sp_Eff(Sp_Eff=0.5, BW=36.0, CNR=10.0): + ''' Returns values at required Spe : CNR, BW, BR ''' + C_N0_l = 10 ** (CNR / 10) * BW + BW_Spe_1 = C_N0_l + BW_Spe = C_N0_l / (2 ** Sp_Eff - 1) + BR_Spe = BW_Spe * Sp_Eff + CNR_Spe = 10 * log(BW_Spe_1 / BW_Spe, 10) + return CNR_Spe, BW_Spe, BR_Spe + +def BR_Format(BR=100): + return "{:.1f}".format(BR) + ' Mbps' + +def Power_Format(Pow=100): + Pow_dB=10*log(Pow,10) + if Pow > 1 and Pow < 1e4 : + return "{:.1f}".format(Pow) + ' W .. ' + "{:.1f}".format(Pow_dB) + ' dBW' + elif Pow <= 1 and Pow > 1e-3 : + return "{:.4f}".format(Pow) + ' W .. ' + "{:.1f}".format(Pow_dB) + ' dBW' + else : + return "{:.1e}".format(Pow) + ' W .. ' + "{:.1f}".format(Pow_dB) + ' dBW' + +def PFD_Format(Pow=1): # PSD in W per m2 + Pow_dB=10*log(Pow,10) + return "{:.1e}".format(Pow) + ' W/m\N{SUPERSCRIPT TWO} .. ' + "{:.1f}".format(Pow_dB) + ' dBW/m\N{SUPERSCRIPT TWO}' + +def PSD_Format(Pow=1): # PSD in W per MHz + Pow_dB=10*log(Pow,10) + return "{:.1e}".format(Pow) + ' W/MHz .. ' + "{:.1f}".format(Pow_dB) + ' dBW/MHz' + + +def Gain_Format(Gain=1000): + Gain_dB=10*log(Gain,10) + return "{:.1f}".format(Gain) + ' .. ' + "{:.1f}".format(Gain_dB) + ' dBi' + +def PLoss_Format(Loss=10): + Loss_dB=10*log(Loss,10) + return "{:.2}".format(Loss) + ' m\N{SUPERSCRIPT TWO} .. ' + "{:.1f}".format(Loss_dB) + ' dBm\N{SUPERSCRIPT TWO}' + + +''' ------------------------------------------ Database Functions --------------------------------------------------- + +Functions for management of users' contributions + +''' + +def Contribution_Write (DB_File) : + + Sh_DB=sqlite3.connect(DB_File) + Sh_DB_c=Sh_DB.cursor() + + Sh_DB_c.execute("CREATE TABLE IF NOT EXISTS contributions " + "(num INTEGER, name TEXT, title TEXT, keywords TEXT, text TEXT, date TEXT, password TEXT)") + Sh_DB.commit() + + Sh_DB_c.execute("SELECT MAX(num) FROM contributions") + ID_Contrib=Sh_DB_c.fetchall()[0][0] + + if ID_Contrib == None: + ID_Contrib = 0 + + print (ID_Contrib) + + layout3 = [ [sg.Text('Initials / name',size=(20,1), justification='center'), + sg.Input('',size=(45,1), key='-Name-')], + [sg.Text('Title ', size=(20, 1), justification='center'), + sg.Input('', size=(45, 1), key='-Title-')], + [sg.Text('Keywords ',size=(20,1), justification='center'), + sg.Input('', size=(45,1), key='-Keywords-')], + [sg.Text('Password ', size=(20, 1), justification='center'), + sg.Input('', size=(45, 1), key='-Password-')], + [sg.Frame('Write your text here', + [[sg.Multiline('', size=(80, 15), key='-Text-')], + [sg.Button('Validate'), + sg.Button('Exit'), + sg.Button('Help')]], + element_justification='center')] + ] + window3 = sg.Window('Write Contribution', layout3, finalize=True) + + while True: + event3, values = window3.read() + print(event3) + + if event3 == sg.WIN_CLOSED or event3 == 'Exit' : # if user closes window + break + + elif event3 == 'Validate' : + + ID_Contrib += 1 + + Sh_DB_c.execute("INSERT INTO contributions VALUES ( " + str (ID_Contrib) + ", '" + values['-Name-'] + + "', '" + values['-Title-'] + "', '" + values['-Keywords-'] + "', '" + values['-Text-'] + + "', " + "Date('now')" + ", '" + values['-Password-'] + "' )") + Sh_DB.commit() + + window3['-Text-'].Update('Thank you, your contribution has been successfully stored, ID #'+str(ID_Contrib)) + + elif event3 == 'Help': + window3['-Text-'].Update('Write your contribution here as a free text. Edit it in the text window until you' + ' are happy with the result and then Validate. \n\nContributions should be candid observations' + ' about the technical subject, references to relevant material (ideally as web pointers), open ' + ' discussion items about adjacent subjects, suggestion for improvement... \n\nYou can retrieve' + ' your contribution via a search from the Read Panel and delete them there with the relevant ' + 'password. Keeping the "no password" default will allow anyone to delete.') + + Sh_DB.close() + + window3.close() + + return ID_Contrib + +def Contribution_Read (DB_File) : + + layout4 = [ [sg.Text('Filter on Name',size=(20,1), justification='center'), + sg.Input('',size=(45,1), key='-Name-')], + [sg.Text('Filter on Title ', size=(20, 1), justification='center'), + sg.Input('', size=(45, 1), key='-Title-')], + [sg.Text('Filter on Keywords ',size=(20,1), justification='center'), + sg.Input('', size=(45,1), key='-Keywords-')], + [sg.Text('Filter on Content ', size=(20, 1), justification='center'), + sg.Input('', size=(45, 1), key='-Content-')], + [sg.Frame('Contribution', + [[sg.Multiline('', size=(80, 15), key='-Text-')], + [sg.Button('Load / Filter', key='-Filter-'), + sg.Button('Read Next', key='-Next-'), + sg.Button('Delete', key='-Del-'), + sg.Button('Exit'), + sg.Button('Help')]], + element_justification='center')] + ] + + if os.path.isfile(DB_File): + Sh_DB = sqlite3.connect(DB_File) + Sh_DB_c = Sh_DB.cursor() + else: + sg.popup('No Database Found', keep_on_top=True) + return 0 + + window4 = sg.Window('Read Contributions', layout4) + DB_ind = 0 + + while True: + + event4, values = window4.read() + + print(event4) + + if event4 == sg.WIN_CLOSED or event4 == 'Exit': # if user closes window + break + + elif event4 == '-Filter-': + + Name = "'%" + values['-Name-'] + "%'" + Title = "'%" + values['-Title-'] + "%'" + Keywords = "'%" + values['-Keywords-'] + "%'" + Content = "'%" + values['-Content-'] + "%'" + Sh_DB_c.execute("SELECT num, name, title, keywords, text, date, password FROM contributions WHERE " + + "name LIKE " + Name + + " AND title LIKE " + Title + + " AND keywords LIKE " + Keywords + + " AND text LIKE " + Content + + " ORDER BY num DESC " + " LIMIT 50 ") + DB_Extract = Sh_DB_c.fetchall() + + print(len(DB_Extract)) + + if len(DB_Extract) > 0 : # First Contribution read automatically + + window4['-Text-'].Update( DB_Extract[0][2] + ' , ' + DB_Extract[0][1] + + '\n\n' + DB_Extract[0][4] + '\n\n Keywords : ' + DB_Extract[0][3] + + '\n\n ID #' + str(DB_Extract[0][0]) + ' - ' + str(DB_Extract[0][5]) ) + DB_ind = 1 + + elif event4 == '-Next-' and DB_ind>0 : + + if DB_ind < len(DB_Extract): + window4['-Text-'].Update(DB_Extract[DB_ind][2] + ' , ' + DB_Extract[DB_ind][1] + + '\n\n' + DB_Extract[DB_ind][4] + '\n\n Keywords : ' + DB_Extract[DB_ind][3] + + '\n\n ID #' + str(DB_Extract[DB_ind][0]) + ' - ' + str(DB_Extract[DB_ind][5])) + DB_ind +=1 + + elif event4 == '-Del-': + Password = sg.popup_get_text('Enter Item\'s Password') + print (Password) + DB_ind -= 1 + if DB_Extract[DB_ind][6] == Password : + Item_num=DB_Extract[DB_ind][0] + Sh_DB_c.execute("DELETE FROM contributions WHERE num = " + str(Item_num)) + window4['-Text-'].Update('Item ID# '+ str(Item_num) + ' deleted') + else: + window4['-Text-'].Update('Incorrect password, item unaffected') + + elif event4 == 'Help': + window4['-Text-'].Update('To read contributions you have to load them first. If the search fields are empty' + ', all contributions will be loaded. You should only enter 1 item per search field.' + '\n\n You can delete items if you know the associated password. Items are not encrypted' + ' in the Database and can be removed by the admin.') + + Sh_DB.commit() + Sh_DB.close() + window4.close() + + return DB_ind + + +''' ------------------------------------------ GUI Functions--------------------------------------------------- + +LEDs from PySimpleGUI demos + +''' + + +def LEDIndicator(key=None, radius=30): + return sg.Graph(canvas_size=(radius, radius), + graph_bottom_left=(-radius, -radius), + graph_top_right=(radius, radius), + pad=(0, 0), key=key) + +def SetLED(window, key, color): + graph = window[key] + graph.erase() + graph.draw_circle((0, 0), 12, fill_color=color, line_color='black') + + +''' ------------------------------------------ Main Program --------------------------------------------------- + +The program has 2 main panels associated with events collection loops + +As the loops are build, when the second panel is open, events are only collected for this panel + +In the windowed version, matplotlib plots don't interfere with the event loops : as many as desired can be open + +''' + +# ---------------- First Panel : Shannon's Equation ------------------- + +form_i = {"size":(22,1),"justification":'left',"enable_events":True} # input label format +form_iv = {"size":(8,1),"justification":'center'} # imput value format +form_ov = {"size":(20,1),"justification":'center'} # output value format +form_o = {"size":(65,1),"justification":'right',"enable_events":True} # output label format, also using input elements +form_CRC = {"size":(8,1),"justification":'center',"enable_events":True} # CRC Format + +sg.set_options(auto_size_buttons=False, button_element_size=(14,1)) + +col1=sg.Column([[sg.Frame('Theoretical Exploration', + [[sg.Text(' Reference C/N [dB] ', **form_i,key='-iCNR-'),sg.Input('12', **form_iv, key='-CNR-')], + [sg.Text(' Reference BW [MHz] ', **form_i,key='-iBW-'),sg.Input('36', **form_iv, key='-BW-'), + sg.Text('',size=(34,1)),sg.Text('', **form_CRC, key='-CRC-'), LEDIndicator('-OK-')], + [sg.Text('Carrier Power to Noise Power Density Ratio : C/N\N{SUBSCRIPT ZERO}', **form_o,key='-iC_N0-'), + sg.Input(**form_ov,key='-C_N0-')], + [sg.Text('Theoretical BR at infinite BW : 1.44 C/N\N{SUBSCRIPT ZERO}', **form_o,key='-iBRinf-'), + sg.Input(**form_ov,key='-BRinf-')], + [sg.Text('Theoretical BR at Spectral Efficiency = 1 : C/N\N{SUBSCRIPT ZERO}', **form_o,key='-iBRunit-'), + sg.Input(**form_ov,key='-BRunit-')], + [sg.Text('Theoretical BR at Reference (BW,C/N)', **form_o,key='-iBRbw-'), + sg.Input(**form_ov, key='-BRbw-')], + [sg.Text('Carrier to Noise Ratio : C / N = C / (N\N{SUBSCRIPT ZERO}.B)', **form_o,key='-iCNRlin-'), + sg.Input(**form_ov, key='-CNRlin-')], + [sg.Text(' BW Increase Factor ',**form_i,key='-iBWmul-'), sg.Input('1', **form_iv, key='-BWmul-')], + [sg.Text(' Power Increase Factor ',**form_i,key='-iCmul-'), sg.Input('2', **form_iv, key='-Cmul-')], + [sg.Text('Bit Rate Increase Factor', **form_o,key='-iBRmul-'), sg.Input(**form_ov, key='-BRmul-')], + [sg.Button('Evaluation', visible=False, key='-Evaluation-', bind_return_key = True), + sg.Button('BW Sensitivity', pad=((60,5),(2,2)), key='-BW_Graph-'), + sg.Button('Power Sensitivity',key='-Pow_Graph-'), + sg.Button('BR Factor Map', key='-Map-'), + sg.Button('Go to Real World', key='-Real-')]])]]) + +col2=sg.Column([[sg.Frame('Background Information (click on items)', + [[sg.Multiline('Click on parameter\'s label to get information',size=(80,15),key='-Dialog-')], + [sg.Button('Advanced'), sg.Button('Write Contribution', key='-Write_Ct-'), + sg.Button('Read Contributions',key='-Read_Ct-'),sg.Button('Help')]], element_justification='center')]]) + +layout=[ + [sg.Button('Wiki : Claude_Shannon',size=(25,1), key='-Wiki-')], + [sg.Image(filename='Shannon.png',key='-iShannon-',enable_events=True, background_color='black')], + [col1,col2] + ] + +window = sg.Window('Shannon\'s Equation for Dummies', layout , finalize=True, element_justification='center', **web_cfg) + +# Needed for the Web version (on a finalized window) +window['-CNR-'].Update('12') +window['-BW-'].Update('36') +window['-BWmul-'].Update('1') +window['-Cmul-'].Update('2') + +File_CRC=hex(binascii.crc32(open('Shannon.py', 'rb').read()) & 0xFFFFFFFF)[2:].upper() +print("File's CRC : ", File_CRC) + +Win_time_out = 500 # Time out approach required for enter capture in the Web version +First_Pass = True +Err_msg = False + +while True: + + event, values = window.read(timeout=Win_time_out, timeout_key='__TIMEOUT__') + + if event != '__TIMEOUT__' : print (event) + + try: + + if event == sg.WIN_CLOSED: # if user closes window + break + + elif event == '-Evaluation-' or event == '__TIMEOUT__' : + + if First_Pass : + window['-Dialog-'].Update(Shd.Help['-iShannon-']) + First_Pass = False + + CNR_List=values['-CNR-'] # CNR can be a combination of comma separated CNRs + CNR_Nyq=Combine_CNR(*[float(val) for val in CNR_List.split(',')]) + + BW_Nyq=float(values['-BW-']) + + CNR_l, BRinf, C_N0_l, BRbw = Shannon_Points(BW_Nyq,CNR_Nyq) + + BRunit = C_N0_l # Mbps / MHz : Sp Eff =1 + + window['-C_N0-'].Update("{:.1f}".format(C_N0_l) + ' MHz') + window['-BRinf-'].Update(BR_Format(BRinf)) + window['-BRunit-'].Update(BR_Format(BRunit)) + window['-BRbw-'].Update(BR_Format(BRbw)) + window['-CNRlin-'].Update("{:.1f}".format(CNR_Nyq)+" [dB] .. "+"{:.1f}".format(CNR_l)+' [Linear]') + + BWmul=float(values['-BWmul-']) + Cmul=float(values['-Cmul-']) + + BRmul=BR_Multiplier(BWmul,Cmul,CNR_Nyq) + window['-BRmul-'].Update("{:.2f}".format(BRmul)) + + Params = str(CNR_Nyq)+','+str(BW_Nyq)+','+str(BWmul)+','+str(Cmul) + Params_CRC = (hex(binascii.crc32(Params.encode('ascii')) & 0xFFFFFFFF)[2:].upper()) # Params' CRC + + elif event == '-Wiki-': + webbrowser.open('https://en.wikipedia.org/wiki/Claude_Shannon') + + elif event in ('-iC_N0-','-iCNR-','-iBRinf-','-iBRunit-','-iBRbw-','-iCNRlin-','-iBRmul-','-iCmul-', + '-iBWmul-','-iBW-','-iShannon-', '-CRC-', 'Advanced','Help' ): + window['-Dialog-'].Update(Shd.Help[event]) + + elif event == '-BW_Graph-': + BW = np.zeros(20) + BR = np.zeros(20) + CNR = np.zeros(20) + CNR[0] = CNR_Nyq + 10 * log(8, 10) + BW[0] = BW_Nyq / 8 + BR[0] = Shannon(BW[0], CNR[0]) + for i in range(1, 20): + BW[i] = BW[i - 1] * 2 ** (1 / 3) + CNR[i] = CNR[i - 1] - 10 * log(BW[i] / BW[i - 1], 10) + BR[i] = Shannon(BW[i], CNR[i]) + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + plt.plot(BW, BR, 'b') + Mark = ('D', 's', 'p', 'h', 'x') + for i in range(5): + ind = 3 * (i + 1) + BR_norm = BR[ind] / BR[9] + plt.plot(BW[ind], BR[ind], Mark[i] + 'b', label="{:.1f}".format(BW[ind]) + " MHz" + + " , {:.1f}".format(BR[ind]) + " Mbps" + " : {:.0%}".format(BR_norm)) + plt.title('Theoretical Bit Rate at Constant Power\nC/N\N{SUBSCRIPT ZERO} = ' + + "{:.1f}".format(C_N0_l) + " MHz" ) + plt.xlabel('Bandwidth [MHz]') + plt.ylabel('Bit Rate [Mbps]') + plt.grid(True) + plt.legend(loc='lower right') + + if Web_Version: + window_matfig(fig, title_fig='Bandwidth Sensitivity', title_win='Shannon for Dummies') + else: + plt.show(block=False) + + elif event == '-Pow_Graph-': + P_mul = np.zeros(20) + BR = np.zeros(20) + CNR = np.zeros(20) + P_mul[0] = 1 / 8 + CNR[0] = CNR_Nyq - 10 * log(8, 10) + BR[0] = Shannon(BW_Nyq, CNR[0]) + for i in range(1, 20): + P_mul[i] = P_mul[i-1] * 2 ** ( 1 / 3 ) + CNR[i] = CNR[i - 1] + 10 * log( 2 ** ( 1 / 3 ), 10 ) + BR[i] = Shannon(BW_Nyq, CNR[i]) + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + plt.plot(P_mul, BR, 'b') + Mark = ('D', 's', 'p', 'h', 'x') + for i in range(5): + ind = 3 * (i + 1) + BR_norm = BR[ind] / BR[9] + plt.plot(P_mul[ind], BR[ind], Mark[i] + 'b', label='{:.2f}'.format(P_mul[ind]) + + 'x , {:.1f}'.format(BR[ind]) + ' Mbps' + ' : {:.0%}'.format(BR_norm)) + plt.title('Theoretical Bit Rate at Constant Bandwidth : ' + '{:.1f}'.format(BW_Nyq) + ' MHz \n' + 'Reference : C/N = {:.1f}'.format(CNR_l) + ' [Linear Format]') + plt.xlabel('Power Multiplying Factor') + plt.ylabel('Bit Rate [Mbps]') + plt.grid(True) + plt.legend(loc='lower right') + + if Web_Version: + window_matfig(fig, title_fig='Power Sensitivity', title_win='Shannon for Dummies') + else: + plt.show(block=False) + + elif event == '-Map-' : + BR_mul=np.zeros((21,21)) + BW_mul=np.zeros((21,21)) + P_mul=np.zeros((21,21)) + for i in range(21): + for j in range(21): + BW_mul[i, j] = (i + 1)/4 + P_mul[i, j] = (j + 1)/4 + BR_mul[i, j] = BR_Multiplier(BW_mul[i, j], P_mul[i, j], CNR_Nyq) + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + Map = plt.contour(BW_mul,P_mul,BR_mul, 20) + plt.clabel(Map, inline=1, fontsize=8,fmt='%.2f') + plt.title('Bit Rate Multiplying Factor, \n Reference : C/N = {:.1f}'.format(CNR_Nyq) + ' dB, BW = ' + + '{:.1f}'.format(BW_Nyq) + ' MHz , C/N\N{SUBSCRIPT ZERO} = ' + '{:.1f}'.format(C_N0_l) + + ' MHz, BR = {:.1f}'.format(BRbw) + ' Mbps', fontsize=10) + plt.xlabel('Bandwidth Multiplying Factor') + plt.ylabel('Power Multiplying Factor') + plt.grid(True) + + if Web_Version: + window_matfig(fig, title_fig='Multiplying Factors Map', title_win='Shannon for Dummies') + else: + plt.show(block=False) + + elif event == '-Write_Ct-' : + Contribution_Write('Shannon_Theory.db') + + elif event == '-Read_Ct-' : + Contribution_Read('Shannon_Theory.db') + + elif event == '-Real-': # ---------------- Second Panel : Real World ------------------- + + P_i1, P_i2, P_i3 = '', '', '' + + fr1 = sg.Frame('Satellite Link',[ + [sg.Text('Satellite Altitude [km] ​', **form_i, key='-iSatAlt-'), + sg.Input('35786', **form_iv, key='-SatAlt-'), + sg.Text('Satellite Lat, Long ​[\N{DEGREE SIGN}]', **form_i, key='-iSatLatLong-'), + sg.Input('0.0, 19.2', **form_iv, key='-SatLatLong-'), + sg.Text('Ground Station Lat, Long ​[\N{DEGREE SIGN}]', **form_i, key='-iGSLatLong-'), + sg.Input('49.7, 6.3', **form_iv, key='-GSLatLong-')], + [sg.Text('HPA Output Power [W]',**form_i,key='-iHPA-'), + sg.Input('120', **form_iv, key='-HPA_P-'), + sg.Text('Output Losses [dB]',**form_i,key='-iLoss-'), + sg.Input('2', **form_iv, key='-Losses-'), + sg.Text('TX Impairments, C/I [dB]', **form_i, key='-iSCIR-'), + sg.Input('25, 25', **form_iv, key='-Sat_CIR-')], + [sg.Text('Beam Diameter [\N{DEGREE SIGN}]', **form_i, key='-iSBeam-'), + sg.Input('3', **form_iv, key='-Sat_Beam-'), + sg.Text('Offset from Peak [dB] ', **form_i, key='-iGOff-'), + sg.Input('0', **form_iv, key='-Gain_Offset-'), sg.Text('', size=(7, 1)), + sg.Text('', **form_CRC, key='-CRC1-'), LEDIndicator('-OK1-')], + [sg.Text('Frequency [GHz]', **form_i, key='-iFreq-'), + sg.Input('12', **form_iv, key='-Freq-'), + sg.Text('Link Availability [%]', **form_i, key='-iAvail-'), + sg.Input('99.9', **form_iv, key='-Avail-')], + [sg.Text('Output Power', **form_o,key='-iOPow-'), + sg.Input('', size=(35, 1), key='-Feed_P-', justification='center')], + [sg.Text('Satellite Antenna Gain​', **form_o,key='-iSGain-'), + sg.Input('', size=(35, 1), key='-Sat_G-', justification='center')], + [sg.Text('Equivalent Isotropic Radiated Power​', **form_o,key='-iEIRP-'), + sg.Input('', size=(35, 1), key='-EIRP-', justification='center')], + [sg.Text('Path Length @ Elevation', **form_o, key='-iPathLength-'), + sg.Input('', size=(35, 1), key='-PathLength-', justification='center')], + [sg.Text('Path Dispersion Loss', **form_o, key='-iPLoss-'), + sg.Input('', size=(35, 1), key='-PLoss-', justification='center')], + [sg.Text('Atmospheric Attenuation', **form_o, key='-iAtmLoss-'), + sg.Input('', size=(35, 1), key='-AtmLoss-', justification='center')], + [sg.Text('Power Flux Density', **form_o,key='-iPFD-'), + sg.Input('', size=(35, 1), key='-PFD-', justification='center')], + ], key='-SatLink-') + + fr2=sg.Frame('Radio Front End ',[ + [sg.Text('Receive Antenna Size [m]​',**form_i,key='-iCPE-'), + sg.Input('0.6', **form_iv, key='-CPE_Ant-'), + sg.Text('Noise Temperature [K] ​', **form_i, key='-iCPE_T-'), + sg.Input('140', **form_iv, key='-CPE_T-'), sg.Text('',size=(7, 1)), + sg.Text('', **form_CRC, key='-CRC2-'),LEDIndicator('-OK2-')], + [sg.Text('Customer Antenna Effective Area and G/T', **form_o,key='-iCGain-'), + sg.Input('', size=(35, 1), key='-CPE_G-', justification='center')], + [sg.Text('RX Power at Antenna Output', **form_o,key='-iRXPow-'), + sg.Input('', size=(35, 1), key='-RX_P-', justification='center')], + [sg.Text('Noise Power Density Antenna Output', **form_o, key='-iN0-'), + sg.Input('', size=(35, 1), key='-N0-', justification='center')], + [sg.Text('Bit Rate at infinite Bandwidth', **form_o,key='-iBRinf-'), + sg.Input('', size=(35, 1), key='-BRinf-', justification='center')], + [sg.Text('Bit Rate at Spectral Efficiency=1', **form_o,key='-iBRUnit-'), + sg.Input('', size=(35, 1), key='-BRUnit-', justification='center')], + [sg.Text('Bit Rate at Spectral Efficiency=2', **form_o,key='-iBRdouble-'), + sg.Input('', size=(35, 1), key='-BRdouble-', justification='center')] + ], key='-iRadio-') + + fr3=sg.Frame('Baseband Unit',[ + [sg.Text('Occupied Bandwidth [MHz]',**form_i,key='-iBW-'), + sg.Input('36', **form_iv, key='-BW-'), + sg.Text('Nyquist Filter Rolloff [%]',**form_i,key='-iRO-'), + sg.Input('5', **form_iv, key='-RO-'), + sg.Text('Higher Layers Overhead [%]', **form_i, key='-iOH-'), + sg.Input('5', **form_iv, key='-OH-')], + [sg.Text('RX Impairments, C/I [dB]',**form_i,key='-iCIR-'), + sg.Input('20', **form_iv, key='-CIR-'), + sg.Text('Implementation Penalty [dB]​',**form_i,key='-iPenalty-'), + sg.Input('1.5', **form_iv, key='-Penalty-'), sg.Text('',size=(7, 1)), + sg.Text('', **form_CRC, key='-CRC3-'), LEDIndicator('-OK3-')], + [sg.Text('Signal to Noise Ratio in Available BW', **form_o,key='-iCNRbw-'), + sg.Input('', size=(35, 1), key='-CNRbw-', justification='center')], + [sg.Text('Signal to Noise Ratio in Nyquist BW', **form_o,key='-iCNRnyq-'), + sg.Input('', size=(35, 1), key='-CNRnyq-', justification='center')], + [sg.Text('Signal to Noise Ratio at Receiver Output', **form_o,key='-iCNRrcv-'), + sg.Input('', size=(35, 1), key='-CNRrcv-', justification='center')], + [sg.Text('Theoretical Bit Rate', **form_o,key='-iBRnyq-'), + sg.Input('', size=(35, 1), key='-BRnyq-', justification='center')], + [sg.Text('Practical Bit Rate', **form_o,key='-iBRrcv-'), + sg.Input('', size=(35, 1), key='-BRrcv-', justification='center')], + [sg.Text('Practical Higher Layers Bit Rate', **form_o,key='-iBRhigh-'), + sg.Input('', size=(35, 1), key='-BRhigh-', justification='center')], + [sg.Button('Evaluation', key='-Evaluation-',visible=False, bind_return_key = True), + sg.Button('BW Sensitivity', pad=((100,5),(2,2)), key='-BW_Graph-'), + sg.Button('Power Sensitivity',key='-Pow_Graph-'), + sg.Button('BR Factor Map', key='-Map-'), + sg.Button('Back to Theory', key='-Back-')], + ], key='-Baseband-') + + fr4 = sg.Frame('Background Information (click on items)', + [[sg.Multiline('Click on parameter\'s label to get information', size=(80, 15),key='-Dialog-')], + [sg.Button('Advanced'), sg.Button('Write Contribution', key='-Write_Ct-'), + sg.Button('Read Contributions',key='-Read_Ct-'), sg.Button('Help')]],element_justification='center') + + layout2 =[ + [sg.Column([[fr1],[fr2],[fr3]]), + sg.Column([[sg.Text('', size=(55, 1), justification='center', key='-FCRC-')], + [sg.Button('Wiki : Harry Nyquist​', size=(25,1), key='-W_Nyquist-'), + sg.Button('Wiki : Richard Hamming​', size=(25,1), key='-W_Hamming-')], + [sg.Button('Wiki : Andrew Viterbi​', size=(25,1), key='-W_Viterbi-'), + sg.Button('Wiki : Claude Berrou​', size=(25,1), key='-W_Berrou-')], + [sg.Image(filename='Satellite.png', key='-Satellite-',background_color='black', enable_events=True)], + [fr4]], element_justification='center')]] + + window2 = sg.Window('Shannon and Friends in the Real World', layout2, finalize=True) + + window2['-FCRC-'].Update('Program\'s CRC: ' + File_CRC) + + # Needed for the Web version (on a finalized window) + window2['-Freq-'].Update('12') + window2['-SatAlt-'].Update('35786') + window2['-SatLatLong-'].Update('0.0, 19.2') + window2['-Avail-'].Update('99.9') + window2['-GSLatLong-'].Update('49.7, 6.3') + window2['-HPA_P-'].Update('120') + window2['-Sat_CIR-'].Update('25, 25') + window2['-Sat_Beam-'].Update('3') + window2['-Gain_Offset-'].Update('0') + window2['-CPE_Ant-'].Update('0.6') + window2['-CPE_T-'].Update('120') + window2['-Penalty-'].Update('1.5') + window2['-BW-'].Update('36') + window2['-CIR-'].Update('25') + window2['-RO-'].Update('5') + window2['-OH-'].Update('5') + window2['-AtmLoss-'].Update(' ... LOADING ...') + + First_Pass = True + + while True: + + event2, values = window2.read(timeout=Win_time_out,timeout_key = '__TIMEOUT__') + + if event2 != '__TIMEOUT__' : print(event2) + + P_err = 0 # Error ID + + try: + + if event2 == sg.WIN_CLOSED or event2 == '-Back-': # closes window + break + + elif event2 == '-Evaluation-'or event2 == '__TIMEOUT__' : + + if First_Pass : + window2['-Dialog-'].Update(Shd.Help2['-Satellite-']) + First_Pass = False + + P_err = 1 # Error ID + Freq = float(values['-Freq-']) # GHz + HPA_Power = float(values['-HPA_P-']) # Watts + Sat_Loss = float(values['-Losses-']) # dB + Sat_CIR_List = values['-Sat_CIR-'] # list if comma separated CIR contributions in dB + Sat_CIR = Combine_CNR(*[float(val) for val in Sat_CIR_List.split(',')]) + Sig_Power = HPA_Power * 10 ** (-Sat_Loss / 10) # Watts + Sat_Beam = float(values['-Sat_Beam-']) #dB + Gain_Offset = float(values['-Gain_Offset-']) #dB + Sat_Ant_eff = 0.65 # Factor + + window2['-Feed_P-'].Update(Power_Format(Sig_Power)) + + Lambda = 300e6 / Freq / 1e9 # meter + Sat_Gain_l = Sat_Ant_eff * ( pi * 70 / Sat_Beam ) ** 2 + Sat_Gain_l = Sat_Gain_l * 10**(-Gain_Offset/10) + Sat_Ant_d = 70 * Lambda / Sat_Beam + Sat_Gain_dB = 10 * log(Sat_Gain_l, 10) + + window2['-Sat_G-'].Update(Gain_Format(Sat_Gain_l)) + + EIRP_l = Sig_Power * Sat_Gain_l + EIRP_dB = 10 * log(EIRP_l, 10) + window2['-EIRP-'].Update(Power_Format(EIRP_l)) + + Avail = float(values['-Avail-']) + + R_earth=6378 + + [lat_GS, lon_GS] = [float(val) for val in values['-GSLatLong-'].split(',')] + [lat_sat, lon_sat] = [float(val) for val in values['-SatLatLong-'].split(',')] + + h_sat = float(values['-SatAlt-']) + + Path_Length = sqrt (h_sat**2 + 2 * R_earth * (R_earth + h_sat ) * + ( 1 - cos(np.radians(lat_sat-lat_GS)) * cos(np.radians(lon_sat-lon_GS)))) + + # elevation = itur.utils.elevation_angle(h_sat, lat_sat, lon_sat, lat_GS, lon_GS) # non signed + + Phi = acos(cos(np.radians(lat_sat-lat_GS)) * cos(np.radians(lon_sat-lon_GS))) + + if Phi > 0 : + elevation = np.degrees(atan((cos(Phi)-R_earth/(R_earth+h_sat))/sqrt(1-cos(Phi)**2))) + else : + elevation = 90 + + if elevation <= 0 : + Atm_Loss = 999 + else: + Atm_Loss = itur.atmospheric_attenuation_slant_path(lat_GS, lon_GS, Freq, + elevation, 100-Avail, 1).value + + window2['-AtmLoss-'].Update("{:.1f}".format(10 ** (Atm_Loss/10)) + ' [Linear] .. ' + + "{:.1f}".format(Atm_Loss) + " [dB]") + window2['-PathLength-'].Update("{:.1f}".format(Path_Length) + " [km] @ " + + "{:.1f}".format(elevation) + " [\N{DEGREE SIGN}]") + + Free_Space_Loss_l = (4 * pi * Path_Length * 1000 / Lambda) ** 2 + Free_Space_Loss_dB = 10 * log(Free_Space_Loss_l, 10) + + Path_Loss_l = 4 * pi * (Path_Length * 1000) ** 2 + Path_Loss_dB = 10 * log(Path_Loss_l, 10) + window2['-PLoss-'].Update(PLoss_Format(Path_Loss_l)) + + PFD_l = EIRP_l / Path_Loss_l * 10 ** (-Atm_Loss/ 10) + PFD_dB = 10 * log(PFD_l, 10) + window2['-PFD-'].Update(PFD_Format(PFD_l)) + + P_err = 2 # Error ID + CPE_Ant_d = float(values['-CPE_Ant-']) # meter + CPE_T_Clear= float(values['-CPE_T-']) # K + CPE_Ant_eff = 0.6 + CPE_T_Att = (CPE_T_Clear - 40) + 40 * 10 ** (-Atm_Loss/10) + 290 * (1 - 10 ** (-Atm_Loss/10)) + k_Boltz = 1.38e-23 # J/K + + P_err = 3 # Error ID + Penalties = float(values['-Penalty-']) # dB, overall implementation penalty + Bandwidth = float(values['-BW-']) # MHz + CNR_Imp_List = values['-CIR-'] # List of comma separated CNR impairments in dB + CNR_Imp = Combine_CNR(*[float(val) for val in CNR_Imp_List.split(',')]) + Rolloff = float(values['-RO-']) # percent + Overheads = float(values['-OH-']) # percent + + CPE_Ae = pi * CPE_Ant_d ** 2 / 4 * CPE_Ant_eff + CPE_Gain_l = (pi * CPE_Ant_d / Lambda) ** 2 * CPE_Ant_eff + CPE_Gain_dB = 10 * log(CPE_Gain_l, 10) + CPE_Beam = 70 * Lambda / CPE_Ant_d # diameter in degrees + CPE_G_T = 10 * log(CPE_Gain_l / CPE_T_Att, 10) + window2['-CPE_G-'].Update( + "{:.1f}".format(CPE_Ae)+" m\N{SUPERSCRIPT TWO} .. {:.1f}".format(CPE_G_T)+" dB/K") + + RX_Power_l = PFD_l * CPE_Ae + # Alternative : RX_Power_l=EIRP_l/Free_Space_Loss_l*CPE_Gain_l + RX_Power_dB = 10 * log(RX_Power_l,10) + N0 = k_Boltz * CPE_T_Att # W/Hz + C_N0_l = RX_Power_l / N0 # Hz + C_N0_dB = 10 * log(C_N0_l, 10) # dBHz + window2['-RX_P-'].Update('C : ' + Power_Format(RX_Power_l)) + window2['-N0-'].Update('N\N{SUBSCRIPT ZERO} : ' + PSD_Format(N0*1e6)) + + BW_Spe_1 = C_N0_l / 1e6 # C_N0 without Penalty in MHz + BW_Spe_1half = BW_Spe_1 / (2 ** 0.5 - 1) # MHz + BW_Spe_1quarter = BW_Spe_1 / (2 ** 0.25 - 1) + BW_Spe_double = BW_Spe_1 / (2 ** 2 - 1) + + BR_Spe_1 = BW_Spe_1 # Mbps + BR_Spe_1half = BW_Spe_1half / 2 + BR_Spe_1quarter = BW_Spe_1quarter / 4 + BR_Spe_double= BW_Spe_double * 2 + BR_infinity = BW_Spe_1 / log(2) + BR_Spe_1_Norm = BR_Spe_1 / BR_infinity + BR_Spe_1half_Norm = BR_Spe_1half / BR_infinity + BR_Spe_1quarter_Norm = BR_Spe_1quarter / BR_infinity + BR_Spe_double_Norm = BR_Spe_double / BR_infinity + window2['-BRinf-'].Update('1.443 C/N\N{SUBSCRIPT ZERO} : ' + + BR_Format(BR_infinity)+" .. {:.0%}".format(1)) + window2['-BRUnit-'].Update('C/N\N{SUBSCRIPT ZERO} : ' + + BR_Format(BR_Spe_1)+" .. {:.0%}".format(BR_Spe_1_Norm)) + window2['-BRdouble-'].Update('0.667 C/N\N{SUBSCRIPT ZERO} : ' + + BR_Format(BR_Spe_double)+" .. {:.0%}".format(BR_Spe_double_Norm)) + + CNR_Spe_1 = 0 # dB + CNR_Spe_1half = -10 * log(BW_Spe_1half / BW_Spe_1, 10) + CNR_Spe_1quarter = -10 * log(BW_Spe_1quarter / BW_Spe_1, 10) + CNR_Spe_double = -10 * log(BW_Spe_double/ BW_Spe_1, 10) + + CNR_BW = CNR_Spe_1 + 10 * log(BW_Spe_1 / Bandwidth, 10) # dB + BW_Nyq = Bandwidth / (1 + Rolloff / 100) # MHz + CNR_Nyq = CNR_Spe_1 + 10 * log(BW_Spe_1 / BW_Nyq, 10) # dBB + CNR_Rcv = Combine_CNR (CNR_Nyq, CNR_Imp, Sat_CIR) # + BR_BW = Shannon (Bandwidth, CNR_BW) # Mbps + BR_Nyq = Shannon (BW_Nyq, CNR_Nyq) # Mbps + BR_Rcv = Shannon (BW_Nyq, CNR_Rcv,Penalties) # Mbps + BR_Rcv_Higher = BR_Rcv / (1 + Overheads / 100) # Mbps + BR_BW_Norm = BR_BW / BR_infinity + BR_Nyq_Norm = BR_Nyq / BR_infinity + BR_Rcv_Norm = BR_Rcv / BR_infinity + BR_Rcv_H_Norm = BR_Rcv_Higher / BR_infinity + Spe_BW = BR_BW / Bandwidth + Spe_Nyq = BR_Nyq / Bandwidth # Efficiency in available bandwidth + Bits_per_Symbol = BR_Nyq / BW_Nyq # Efficiency in Nyquist bandwidth + Spe_Rcv = BR_Rcv / Bandwidth + Spe_Higher = BR_Rcv_Higher / Bandwidth + + window2['-CNRbw-'].Update("{:.1f}".format(CNR_BW)+" dB in "+"{:.1f}".format(Bandwidth)+" MHz") + window2['-CNRnyq-'].Update("{:.1f}".format(CNR_Nyq)+" dB in "+"{:.1f}".format(BW_Nyq)+" MHz") + window2['-CNRrcv-'].Update("{:.1f}".format(CNR_Rcv)+" dB") + window2['-BRnyq-'].Update(BR_Format(BR_Nyq) + " .. {:.0%}".format(BR_Nyq_Norm)+ + " .. {:.1f}".format(Spe_Nyq)+" bps/Hz .. {:.1f}".format(Bits_per_Symbol)+" b/S") + window2['-BRrcv-'].Update(BR_Format(BR_Rcv) + " .. {:.0%}".format(BR_Rcv_Norm)+ + " .. {:.1f}".format(Spe_Rcv)+" bps/Hz") + window2['-BRhigh-'].Update(BR_Format(BR_Rcv_Higher)+" .. {:.0%}".format(BR_Rcv_H_Norm)+ + " .. {:.1f}".format(Spe_Higher)+" bps/Hz") + + Params1 = str(Freq) + ',' + str(Path_Length) + ',' + str(Atm_Loss) + ',' + \ + str(HPA_Power) + ',' + str(Sat_Loss) + ',' + str(Sat_CIR) + ',' + \ + str(Sat_Beam) + ',' + str(Gain_Offset) + Params2 = str(CPE_Ant_d) + ',' + str(CPE_T_Clear) + Params3 = str(Bandwidth) + ',' + str(Rolloff) + ',' + str(Overheads) + ',' + \ + str(CNR_Imp) + ',' + str(Penalties) + + Params1_CRC=hex(binascii.crc32(Params1.encode('ascii')) & 0xFFFFFFFF)[2:].upper() + Params2_CRC = hex(binascii.crc32(Params2.encode('ascii')) & 0xFFFFFFFF)[2:].upper() + Params3_CRC = hex(binascii.crc32(Params3.encode('ascii')) & 0xFFFFFFFF)[2:].upper() + + elif event2 in ('-iFreq-','-iHPA-','-iSBeam-','-iLoss-', '-iGOff-','-iFade-', '-iSCIR-', + '-iOPow-','-iSGain-', '-iEIRP-', '-iPFD-', '-iCPE-','-iCGain-','-iRXPow-', + '-iBRinf-','-iBRhalf-', '-iBRUnit-','-iBRdouble-','-iBW-','-iRO-','-iCIR-', + '-iPenalty-', '-iOH-','-iNBW-', '-iCNRbw-','-iCNRnyq-','-iCNRrcv-','-iBRbw-', + '-iBRnyq-','-iBRrcv-','-iBRhigh-','-Satellite-','-iPLoss-','Advanced','Help', + '-CRC1-', '-CRC2-', '-CRC3-', '-iN0-','-iCPE_T-', '-iSatAlt-','-iSatLatLong-', + '-iGSLatLong-', '-iAvail-', '-iPathLength-','-iAtmLoss-'): + window2['-Dialog-'].Update(Shd.Help2[event2]) + + elif event2 == '-BW_Graph-' : + BW = np.zeros(20) + BR = np.zeros(20) + CNR = np.zeros(20) + CNR[0] = CNR_Nyq+10*log(8,10) + BW[0] = Bandwidth/8 + BR[0] = Shannon(BW[0]/(1+Rolloff/100), CNR[0], Penalties) / (1 + Overheads / 100) + for i in range(1, 20): + BW[i] = BW[i - 1] * 2 ** (1 / 3) + CNR[i] = CNR[i - 1] - 10 * log(BW[i] / BW[i - 1], 10) + CNR_Rcv_i = Combine_CNR(CNR[i], CNR_Imp, Sat_CIR) + BR[i] = Shannon(BW[i]/(1+Rolloff/100), CNR_Rcv_i, Penalties) / (1 + Overheads / 100) + + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + plt.plot(BW, BR, 'b') + Mark = ('D', 's', 'p', 'h', 'x') + for i in range(5): + ind = 3 * (i + 1) + BR_norm = BR[ind] / BR[9] + plt.plot(BW[ind], BR[ind], Mark[i] + 'b',label="{:.1f}".format(BW[ind]) + + " MHz" + " , {:.1f}".format(BR[ind]) + " Mbps" + " : {:.0%}".format(BR_norm)) + plt.title('Higher Layers Bit Rate at Constant HPA Output Power : ' + + "{:.1f}".format(HPA_Power) + " W") + plt.xlabel('Occupied Bandwidth [MHz]') + plt.ylabel('Bit Rate [Mbps]') + plt.grid(True) + plt.legend(loc='lower right') + + if Web_Version: + window_matfig(fig, title_fig='Bandwidth Sensitivity', + title_win='Shannon and Friends in the Real World') + else: + plt.show(block=False) + + elif event2 == '-Pow_Graph-' : + Power = np.zeros(20) + BR = np.zeros(20) + CNR = np.zeros(20) + Power[0] = HPA_Power / 8 + CNR[0] = CNR_Nyq-10*log(8,10) + CNR_Rcv_i = Combine_CNR(CNR[0], CNR_Imp, Sat_CIR) + BR[0] = Shannon(BW_Nyq, CNR_Rcv_i, Penalties) / (1 + Overheads / 100) + for i in range(1, 20): + Power[i] = Power[i-1] * 2 ** (1 / 3) + CNR[i] = CNR[i - 1] + 10 * log( 2 ** (1 / 3) , 10 ) + CNR_Rcv_i = Combine_CNR(CNR[i], CNR_Imp, Sat_CIR) + BR[i] = Shannon(BW_Nyq, CNR_Rcv_i, Penalties) / (1 + Overheads / 100) + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + plt.plot(Power, BR, 'b') + Mark = ('D', 's', 'p', 'h', 'x') + for i in range(5): + ind = 3 * (i + 1) + BR_norm=BR[ind]/BR[9] + plt.plot(Power[ind], BR[ind], Mark[i] + 'b', label="{:.1f}".format(Power[ind]) + " W" + + " , {:.1f}".format(BR[ind]) + " Mbps"+" : {:.0%}".format(BR_norm)) + plt.title('Higher Layers Bit Rate at Constant Occupied Bandwidth : ' + + "{:.1f}".format(Bandwidth) + " MHz") + plt.xlabel('HPA Output Power @ Operating Point [Watts]') + plt.ylabel('Bit Rate [Mbps]') + plt.grid(True) + plt.legend(loc='lower right') + + if Web_Version: + window_matfig(fig, title_fig='Power Sensitivity', + title_win='Shannon and Friends in the Real World') + else: + plt.show(block=False) + + elif event2 == '-Map-': + BR_mul = np.zeros((21, 21)) + BW_mul = np.zeros((21, 21)) + P_mul = np.zeros((21, 21)) + BR_00 = BR_Rcv_Higher + for i in range(21): + for j in range(21): + BW_mul[i, j] = (i + 1) / 4 + P_mul[i, j] = (j + 1) / 4 + CNR_ij = CNR_Nyq + 10 * log( P_mul[i, j] / BW_mul[i, j] , 10 ) + CNR_Rcv_ij = Combine_CNR (CNR_ij, CNR_Imp, Sat_CIR) + BW_ij = BW_Nyq * BW_mul[i, j] + BR_ij = Shannon( BW_ij / (1 + Rolloff / 100), CNR_Rcv_ij, Penalties) / (1 + Overheads / 100) + BR_mul[i, j] = BR_ij / BR_00 + fig = plt.figure(figsize=(6, 4)) + ax = fig.add_subplot(111) + Map = plt.contour(BW_mul, P_mul, BR_mul, 20) + plt.clabel(Map, inline=1, fontsize=8, fmt='%.2f') + plt.title('Bit Rate Multiplying Factor, \n Reference : Power = {:.1f}'.format(HPA_Power) + + ' W , BW = ' + '{:.1f}'.format(Bandwidth) + + ' MHz , BR = {:.1f}'.format(BR_Rcv_Higher) + ' Mbps') + plt.xlabel('Bandwidth Multiplying Factor') + plt.ylabel('Power Multiplying Factor') + plt.grid(True) + + if Web_Version: + window_matfig(fig, title_fig='Multiplying Factors Map', + title_win='Shannon and Friends in the Real World') + else: + plt.show(block=False) + + elif event2 == '-W_Nyquist-': + webbrowser.open('https://en.wikipedia.org/wiki/Harry_Nyquist') + elif event2 == '-W_Hamming-': + webbrowser.open('https://en.wikipedia.org/wiki/Richard_Hamming') + elif event2 == '-W_Viterbi-': + webbrowser.open('https://en.wikipedia.org/wiki/Andrew_Viterbi') + elif event2 == '-W_Berrou-': + webbrowser.open('https://en.wikipedia.org/wiki/Claude_Berrou') + + elif event2 == '-Write_Ct-': + Contribution_Write('Shannon_Real.db') + + elif event2 == '-Read_Ct-': + Contribution_Read('Shannon_Real.db') + + else: + print("Untrapped event : "+event2) + + + except : + window2['-Dialog-'].Update('Invalid input fields') + if P_err == 1 : + window2['-CRC1-'].Update('-') + SetLED(window2, '-OK1-', 'red') + if P_err == 2 : + window2['-CRC2-'].Update('-') + SetLED(window2, '-OK2-', 'red') + if P_err == 3 : + window2['-CRC3-'].Update('-') + SetLED(window2, '-OK3-', 'red') + Err_msg=True + else: + window2['-CRC1-'].Update(Params1_CRC) + window2['-CRC2-'].Update(Params2_CRC) + window2['-CRC3-'].Update(Params3_CRC) + SetLED(window2, '-OK1-', 'green') + SetLED(window2, '-OK2-', 'green') + SetLED(window2, '-OK3-', 'green') + if Err_msg : + window2['-Dialog-'].Update('Input Validated') + Err_msg = False + + window2.close() + + except : + window['-Dialog-'].Update('Invalid input fields') + window['-CRC-'].Update('-') + SetLED(window, '-OK-', 'red') + Err_msg = True + else: + window['-CRC-'].Update(Params_CRC) + SetLED(window, '-OK-', 'green') + if Err_msg: + window['-Dialog-'].Update('Input Validated') + Err_msg = False + +window.close() diff --git a/Shannon_Dict.py b/Shannon_Dict.py new file mode 100644 index 0000000..d8f83d9 --- /dev/null +++ b/Shannon_Dict.py @@ -0,0 +1,330 @@ + +Help={'-iCNR-':'Reference C/N [dB]\n\nReference Carrier to Noise Ratio in decibels : 10 log (C/N), where C is the ' + 'Carrier\'s power and N is the Noise\'s Power, both are measured in the reference ' + 'Channel Bandwidth.\n\nThe Carrier\'s power if often named Signal\'s Power and the Carrier to Noise Ratio' + ' is often named Signal to Noise Ratio.', + '-iBW-':'Reference BW [MHz]\n\nReference Channel Bandwidth, this is a key parameter of the communication channel.' + '\n\nThis bandwidth is usually a degree of freedom of the system design, eventually constrained by technological' + ' constraints and various kind of frequency usage regulations.', + '-iC_N0-':'Carrier Power to Noise Power Density Ratio : C/N\N{SUBSCRIPT ZERO}\n\nCarrier\'s power (in Watts) ' + 'divided by the Noise Spectral Power Density (in Watts per MHz), the result\'s units are MHz.', + '-iBRinf-':'Theoretical BR at infinite BW : 1.44 C/N\N{SUBSCRIPT ZERO}\n\nBit Rate theoretically achievable when ' + 'the signal occupies an infinite Bandwidth, this value is a useful asympotical limit.', + '-iBRunit-':'Theoretical BR at Spectral Efficiency = 1 : C/N\N{SUBSCRIPT ZERO}\n\nBit Rate theoretically ' + 'achievable at a Spectral Efficiency = 1 : Bit Rate in Mbps = Bandwith in MHz.' + '\n\nThe corresponding value, deduced from the Shannon\'s formula is given by C/N\N{SUBSCRIPT ZERO}.', + '-iBRbw-':'Theoretical BR at Reference (BW,C/N)\n\nBit Rate theoretically achievable when the Bandwidth is ' + 'constrained to the given reference value.', + '-iCNRlin-':'C / N = C / (N\N{SUBSCRIPT ZERO}.B)\n\nReference Carrier to Noise Ratio (or Signal to Noise Ratio).' + ' The C/N Ratio or CNR is usually given in dBs or 10 log (C/N). ' + '\n\nAlthough the logarithm is convenient for many evaluations (multiplications become additions), ' + 'it\'s also good to consider the ratio itself (named here Linear ' + 'Format) to get some physical sense of the power ratio.' + '\n\nThe Carrier to Noise Ratio in linear format, is the value used in the Shannon\'s formula.', + '-iBRmul-':'Bit Rate Increase Factor\n\nBit Rate multiplying factor achieved when the Bandwidth and the Power ' + 'and multiplied by a given set of values.', + '-iCmul-':'Power Increase Factor\n\nArbitrary multiplying factor applied to the Carrier\'s Power, for ' + 'sensitivity analysis.', + '-iBWmul-':'BW Increase Factor\n\nArbitrary multiplying factor applied to the Carrier\'s Bandwidth, for ' + 'sensitivity analysis.', + '-CRC-':'Cyclic Redundancy Check of the input parameters, for changes identification purposes.\n\n' + 'https://en.wikipedia.org/wiki/Cyclic_redundancy_check', + 'Advanced': 'The model assumes that the communication channel is \"AWGN\", just Adding White Gaussian Noise to ' + 'the signal. This noise is supposed to be random and white which means that noise at a given time is ' + 'independent of noise at any other time, this implies that the noise has a flat and infinite spectrum.' + 'This noise is also supposed to be Gaussian which means that its probability density function follows a ' + 'Gaussian law, with a variance associated to the Noise\'s power.\n\n' + 'Although these assumptions seem very strong, they are quite accurately matching the cases of interest. ' + 'Many impairments are actually non linear and/or non additive, but just combining equivalent C/N of all ' + 'impairments as if they were fully AWGN is in most of the cases very accurate.' + 'The reason for that is that the sum of random variables of unknown laws always tend to Gaussian and that ' + 'in most systems, thermal noise is dominating, is actually white and gaussian and is whitening the rest.\n\n' + 'The tool accepts lists of comma separated CNRs which will be combined in that way. \n\n' + 'In satellite systems, the noise is mainly coming from the electronics of the radio front end, the ground ' + 'seen by the antenna, the stars and the atmospheric attenuator. In case of rain, the signal is punished twice ' + ': the attenuation makes it weaker and the rain attenuator generates noise added to the overall noise.\n\n' + 'Overall the Shannon Limit is a pretty convenient tool to predict the real performances of communication ' + 'systems and even more importantly to get a sense of the role of the key design parameters.', + '-iShannon-':'The Shannon Limit allows to evaluate the theoretical capacity achievable over a communication ' + 'channel.\n\nAs a true genius, Claude Shannon has funded the communication theory, the information theory ' + 'and more (click the Wikipedia button for more info).\n\nThis equation is fundamental for the evaluation of ' + 'communication systems. It is an apparently simple but extremely powerful tool to guide communication systems\'' + ' designs.\n\nThis equation tells us what is achievable, not how to achieve it. It took almost 50 years to ' + 'approach this limit with the invention of Turbo codes.\n\nIn the satellite domain, DVB-S2x, using LDPC codes ' + 'iteratively decoded (Turbo-Like), is only 1 dB away from this limit.', + 'Help': 'Recommendations for using the tool\n\nThe first purpose of the tool is educational, allowing people to ' + 'better understand the physics of communications and the role of key parameters.\n\n' + 'The user should try multiple values in all the fields one per one, explore the graphs and try to ' + 'understand the underlying physics.\n\n' + 'The units for the different figures are as explicit as possible to facilitate the exploration.\n\n' + 'All labels can be \"clicked\" to get information about associated item. All values (including this text)' + ' can be copy/pasted for further usage.' + } + +Help2={'-iFreq-':'Frequency [GHz]\n\nFrequency of the electromagnetic wave supporting the communication in GHz ' + '(billions of cycles per second).\n\nFor satellite downlink (satellite to terminal), frequency bands ' + 'and frequencies are typically : L : 1.5 GHz , S : 2.2 GHz , C : 4 GHz , Ku : 12 GHz, Ka : 19 GHz, ' + 'Q : 40 GHz', + '-iSatAlt-':'Satellite Altitude [km]\n\nThe position of the satellite is expressed in latitude, longitude, ' + 'altitude. The program doesnt simulate the orbit, any satellite coordinates can be used. ' + 'A GEO satellite has a latitude of zero degrees and an altitude of 35786 km. LEO satellites ' + 'have an altitude lower than 2000 km. MEO\'s altitudes are between LEO and GEO : the O3B ' + 'constellation\'s altitude is 8063 km', + '-iSatLatLong-':'Satellite Latitude and Longitude [\N{DEGREE SIGN}]\n\nThe position of the satellite is ' + 'expressed in latitude, longitude, altitude. The program doesnt simulate the orbit, any ' + 'satellite coordinates can be used. A GEO satellite has a latitude of zero degrees and an ' + 'altitude of 35786 km. LEO satellites have an altitude lower than 2000 km. MEO\'s altitudes are' + ' between LEO and GEO : the O3B constellation\'s altitude is 8063 km', + '-iGSLatLong-':'Ground Station Latitude and Longitude [\N{DEGREE SIGN}]\n\nThe position of the ground station ' + 'is expressed in latitude, longitude (the ground station is assumed to be at the surface of ' + 'the earth).\n\nThe position of the ground station is affecting the link availability due to' + ' the differences in weather statistics at different locations on earth (tropical regions have ' + 'very heavy rains attenuating dramatically signals at high frequencies). It is also impacting ' + 'the elevation angle at which the satellite is seen and thus the length of the path in the rain.' + '\n\nThe position of the ground station is also impacting the overall path length and thus the ' + 'path dispersion loss.\n\nUseful link to find coordinates of interest : ' + 'https://www.gps-coordinates.net', + '-iAvail-':'Desired Link Availability [%]\n\nThe link availability in percentage of the time is a key ' + 'performance indicator for satellite communications.\n\nIn this program the only cause of ' + 'unavailability modelled in a probabilistic way is the attenuation caused by the atmosphere. A high ' + 'desired link availability corresponds to a high signal attenuation : only rare and severe weather ' + 'events exceeding this attenuation can interrupt the link.\n\nFor example for an availability of' + '99.9%, the attenuation considered in the link sizing is only exceeded for 0.1% of the time.', + '-iPathLength-':'Path Length [km] @ Elevation [\N{DEGREE SIGN}]\n\nDistance in kilometers from the satellite ' + 'to the ground station and elevation angle at which the satellite is seen. The actual distance' + ' depends on the satellite\'s altitude and on the relative positions of the satellite and the ' + 'ground station.\n\nThe minimum path length is the satellite altitude, achieved when the ground' + ' station is under the satellite (elevation = 90\N{DEGREE SIGN}).\n\nA negative elevation ' + 'implies that the satellite is not visible (beyond the horizon).', + '-iAtmLoss-':'Overall Atmospheric Attenuation [dB]\n\nThe Atmosphere is affecting radio wave propagation ' + 'with a signal attenuation caused by rain precipitations and clouds, by scintillation and ' + 'multi path effects, by sand and dust storms and also by atmospheric gases. \n\n' + 'Simply speaking, the attenuation is increasing with the rain intensity and with the signal ' + 'frequency. C band is almost unaffected, Ku band is significantly affected, Ka band is severely ' + 'affected, Q band is dramatically affected\n\nThe overall attenuation depends on the actual ' + 'geographical location and on actual weather events. By nature, it is is thus statistical ' + '(considering the past) or probabilistic (considering the future).\n\nAll effects included, ' + 'here are typical attenuation figures exceeded for 0.1% of the time in Europe from the GEO orbit ' + ': Ku: 2.5 dB, Ka: 6.9 dB, 22 dB \n\n' + 'The program uses ITU-Rpy, python implementation of the ITU-R P Recommendations: ' + 'https://itu-rpy.readthedocs.io/en/latest/index.html', + '-iHPA-':'HPA Power at operating point [W]\n\nPower of the High Power Amplifier used as a last stage of ' + 'amplification in the satellite payload.' + 'The value in watts is the value at operating point and for the carrier of interest.\n\n' + 'Some satellites operate their HPAs at saturation in single carrier mode (typical DTH case).' + 'Other satellites operate in multicarrier mode and reduced power (3dB Output Back Off is typical ' + 'for satellites serving VSATs)', + '-iSBeam-':'Satellite Half Power Beam Diameter [\N{DEGREE SIGN}]\n\nBeam diameter expressed as an angle at ' + 'satellite level. The power radiated at the edge of this beam is half of the power radiated at ' + 'the peak of the beam (on-axis value).\n\n' + 'The beam evaluated is a basic one with simple illumination of a parabolic reflector\n\n' + 'Typical beam size : 0.4-1.4 degrees for GEO HTS satellites, 3..6 degrees for GEO DTH satellites.', + '-iGOff-': 'Gain Offset from Peak [dB]\n\nThis offset allows to simulate terminals which are not all at ' + 'the beam peak. A 3 dB value would simulate a worst case position in a 3dB beam, typical approach ' + 'used in DTH. In single feed per beam HTS, a 1 dB value would give a typical median performance.' + 'If you know the EIRP you have, the best is to iterate this value to get this EIRP ' + '(the process will allow you to get a feeling of the tradeoff power / footprint size / EIRP. ', + '-iLoss-':'Output Section Losses [dB]\n\nLoss of signal\'s power in the path connecting the HPA to the ' + 'antenna. This loss is associated with filters, waveguide sections, switches ...\n\n' + 'Typical value : 2.5 dB for large classical satellites, 1 dB for active antennas with HPAs close to ' + 'the feeds. If the power value is given at antenna level, the value should just be set to zero.', + '-iSCIR-':'Satellite C/I [dB]\n\nEffect of signal impairments associated with satellite implementation,' + 'expressed as a signal to impairment noise ratio to be combined with the intrinsic Signal to Noise ' + 'Ratio affecting the link. Typical impairments are : intermodulation in the HPA, filtering effects, ' + 'oscillator\'s phase noise ...\n\n' + 'The tool supports comma separated lists of C/I or C/N values expressed in dB. In addition to ' + 'satellites impairments, one can use this feature to also simulate infrastructure C/N, uplink C/N, ' + 'uplink interferences ...', + '-iOPow-':'Output Power [W]\n\nThe output power in watts at antenna output is associated with the useful ' + 'signal carrying user\'s information. It is also common to express this value in dBs (dBs transform ' + 'multiplications in additions, easier for human computation. Nevertheless, reasoning in watts tells ' + 'more about the physics.', + '-iSGain-':'Satellite Antenna Gain \n\nAn antenna concentrating the signal in the direction of the users is ' + 'almost always required to compensate for the path loss associated with the distance from the ' + 'satellite to the terminal.\n\nThe antenna gain is the ratio between the signal radiated ' + 'on the axis of the antenna (direction of maximum radiation) and the signal radiated by an ' + 'antenna radiating equally in all directions (for the same input power).\n\n' + 'Antenna gains are without units but can be expressed in dB for convenience : dBi = dB relative to' + ' isotropic antenna (antenna radiating equally in all directions)', + '-iEIRP-':'Equivalent Isotropic Radiated Power\n\nThe product Power x Gain expressed in Watts is a convenient ' + 'characterisation of the satellite radiation capability. It does correspond to the power which would ' + 'be required for an isotropic antenna radiating in the same way in the direction of the antenna ' + 'considered.\n\nThere is no "power creation" of course : for the directive antenna, the integral of ' + 'the radiated signal over a sphere centered on the antenna is at best equal to the input power ' + '(lossless antenna).\n\n' + 'As the value in watts is usually pretty big, a value in dBW is more convenient ' + 'for practical human computations.', + '-iPLoss-':'Path Dispersion Loss\n\nAssuming communication in free space (thus also in the vacuum), ' + 'this figure characterises the effect' + ' of the distance from the satellite to the terminal. It gives an attenuation equivalent to the ' + 'inverse ratio of the power reaching one square meter at the terminal side and the equivalent ' + 'isotropic radiated power at satellite level.\n\n' + 'This simply equals the surface in square meters of a sphere with a radius equal to the path length.' + 'This attenuation is pretty big and is thus more humanly manageable in dB m\N{SUPERSCRIPT TWO}.\n\n' + 'As the the vacuum is lossless, this "attenuation" is simply associated with the fact that only ' + 'a marginal fraction of the power radiated is captured in one square meter at destination, ' + 'the rest is going somewhere else.', + '-iPFD-':'Power Flux Density\n\nSignal power per square meter at the terminal side. ' + 'The actual power captured by the terminal is given by this value multiplied by the effective surface ' + 'of the terminal\'s antenna.\n\nNote that if the surface of antenna is not perpendicular to the ' + 'propagation direction of the radio wave, the effective surface presented to the wave is reduced ' + 'and less power is captured.', + '-iCPE-':'Customer Antenna Size [m]\n\nSize of the terminal antenna. A basic parabolic antenna with state of ' + 'the art efficiency is assumed.\n\n' + 'The main source of noise is in general the terminal\'s radio front end' + ' attached to the antenna. A state of the art Noise Temperature of 80K is assumed for this front end.', + '-iCPE_T-':'Noise Temperature [K]\n\nTotal Receiver\'s Clear Sky Noise Temperature. It includes all noise ' + 'temperature\'s contributors : receiver, sky, ground seen by the antenna... Antenna catalogs often ' + 'provide this value, the proposed default of 120K is a reasonable typical value. The computation ' + 'under rain fade conditions assumes 40K is affected by rain attenuation and the rest is not. ', + '-iCGain-':'Customer Antenna Effective Area and G/T\n\nThe effective area in square meters is expressing the ' + 'capability of the terminal to capture the Power Flux Density ' + '(the multiplication of both give the power captured). The effective area is typically 60% of the ' + 'physical surface of the antenna\'s aperture.' + 'This capability can also be equivalently expressed as a gain as it\'s the case for the satellite ' + 'antenna.\n\nThe figure of merit of a receive antenna is best expressed as the G/T ratio, ' + 'ratio between antenna gain and the total Noise temperature in Kelvins. The noise is mainly coming ' + 'from the electronics of the radio front end, the ground seen by the antenna, the stars and the ' + 'atmospheric attenuator.\n\nIn case of rain, the signal is punished twice : the ' + 'attenuation makes it weaker and the rain attenuator generates noise added to the overall noise.\n\n' + 'The noise power density N\N{SUBSCRIPT ZERO} is derived from the noise temperature with a very ' + 'simple formula : N\N{SUBSCRIPT ZERO}=kTB (k being the Boltzmann constant), ' + 'the G/T leads easily to the key overall link figure of merit C/N\N{SUBSCRIPT ZERO}.', + '-iRXPow-':'RX Power at Antenna Output\n\nPower at receiver\'s antenna output before amplification. ' + 'This power is extremely small and can only be exploited after strong amplification.\n\n' + 'As the main source of noise is in general coming from this amplification, the first amplification ' + 'stage has to be a Low Noise Amplifier.\n\nThis power is "C" in the Shannon\'s equation.', + '-iN0-' : 'Noise Power Density Antenna Output\n\nNoise Spectral Power Density of the radio front end under ' + 'actual link conditions (in Watts per MHz). ' + 'This PSD is N\N{SUBSCRIPT ZERO} in the Shannon\'s equation', + '-iBRinf-':'Bit Rate at infinite Bandwidth\n\nBit Rate theoretically achievable when the signal occupies an ' + 'infinite Bandwidth, this value is a useful asymptotic limit. The corresponding value, deduced ' + 'from the Shannon\'s formula is given by 1.443 C/N\N{SUBSCRIPT ZERO}\n\nThis bit rate is an ' + 'asymptotic value and is thus never achieved in practice.', + '-iBRhalf-':'Bit Rate at Spectral Efficiency=1/2\n\nBit Rate theoretically achievable at a Spectral Efficiency ' + '= 1/2. The corresponding value, deduced from the Shannon\'s formula is given by 1.207 ' + 'C/N\N{SUBSCRIPT ZERO}\n\nThis operating point is bandwidth intensive ( bandwidth = 2 x bit rate). ' + 'Practical systems allow this operating point ( DVB-S2\'s QPSK 1/4 )', + '-iBRUnit-':'Bit Rate at Spectral Efficiency=1\n\nBit Rate theoretically achievable at a Spectral Efficiency ' + '= 1. The corresponding value, deduced from the Shannon\'s formula is given by ' + 'C/N\N{SUBSCRIPT ZERO}.\n\nThis data point has remarkable attributes : bandwidth = bit rate and ' + 'C/N = 1 (equivalent to 0 dB), which means Noise Power = Signal Power.', + '-iBRdouble-':'Bit Rate at Spectral Efficiency=2\n\nBit Rate theoretically achievable at a Spectral Efficiency ' + '= 1. The corresponding value, deduced from the Shannon\'s formula is given by ' + '0.667 C/N\N{SUBSCRIPT ZERO}.\n\nThis operating point is relatively bandwidth efficient ' + '( bandwidth = 0.5 x bit rate) and is often considered as a typical setting.', + '-iBW-':'Available Bandwidth [MHz]\n\nBandwith occupied by the communication channel. This bandwidth is usually' + ' a degree of freedom of the system design, eventually constrained by technological constraints and ' + 'various kind of frequency usage regulations. Interestingly this parameter is also often mentally ' + 'constrained by past usages which were driven by technological constraints at that time.', + '-iRO-':'Nyquist Filter Rolloff [%]\n\n' + 'To pass a limited bandwidth channel symbol have to be mapped on pulses, "filtered" to limit the ' + 'Bandwidth occupied. Theoretically, filtering can be "brickwall", one symbol per second passing in ' + '1 Hertz. Practically, an excess of bandwidth is required, also called "Roll-Off of the filter.\n\n' + 'The filter used is designed to respect the symmetry condition expressed in the Nyquist Criterion ' + 'avoiding inter-symbol interferences. Such a filter is called a Nyquist Filter. ' + 'and the mimimum theoretical bandwidth (Roll-Off = zero) is called Nyquist Bandwidth.\n\n' + 'The Roll-Off or Excess of Bandwidth is usually expressed as a percentage of the Nyquist Bandwidth.', + '-iCIR-':'Receiver\'s C/I [dB]\n\nEffect of signal impairment associated with terminal implementation, ' + 'expressed as a signal to noise ratio to be combined with the intrinsic Signal to Noise Ratio affecting' + ' the link.\n\nImpairments are multiple : Phase Noise of the radio front end, Quantization Noise of the' + ' receiver\'s Analog to Digital Conversion, effect of imperfect synchronisation ...\n\n' + 'The tool supports comma separated lists of C/I or C/N values expressed in dB. In addition to the ' + 'overall receiver\'s impairments, one can use this feature to simulate more details : downlink ' + 'interferences, LNB\'s phase noise, impairment of signal distribution ...\n\nNote that signal ' + 'impairments associated with the satellite and the receiver are combined together with the link ' + 'noise to evaluate the practical bit rate.', + '-iPenalty-':'Implementation Penalty vs theory [dB]\n\nTurbo and Turbo-like Codes are known for getting ' + '"almost Shannon" performances. There are however still some implementation taxes ' + ': codes always have a residual bit error rate, making it very low requires some CNR margin.\n\n' + 'Other practical aspects also cost signal\'s energy like time and frequency synchronisation, ' + 'physical layer framing...\n\nDVB-S2x, using LDPC codes and modern modulation related features ' + 'is typically 1 dB away of the Shannon Limit in Quasi Error Free operation. Real systems also have' + ' to take margins, considering a reasonable value of 0.5 dB, a total penalty of 1.5 dB can be ' + 'considered as typical.\n\n' + 'Original Turbo codes designed with higher residual bit error rates can get much closer ' + 'to the Shannon Limit. ', + '-iOH-':'Higher Layers Overhead [%]\n\nThe practical usage of information bits is based on a breakdown ' + 'in multiple communications layers, all spending bits for the logistics of carrying user bits.' + 'For example, the process of encapsulation of IP datagrams on a DVB-S2x physical layer using' + ' the GSE standard costs a few percents of net bit rate, spent in framing structures, integrity ' + 'control bits ...\n\n' + 'In a modern efficient satellite forward communication system the overhead to IP costs typically 5%', + '-iNBW-':'Nyquist Bandwidth\n\nThe modulated carrier is passing bits in groups mapped on modulation symbols.' + 'Satellite modulation schemes typically map from 1 to 8 bits on each symbol passing though the channel.' + 'The Bit Rate is directly linked to the symbol rate, the number of symbols per second passing ' + 'the channel ( BR = SR . Number of Bits per Symbol ).\n\n' + 'To pass a bandwidth limited channel, symbols have to be mapped on pulses "filtered" to limit the ' + 'bandwidth. Theoretically, filtering can be "brickwall", one symbol per second passing in 1 Hertz.' + 'Practically, an excess of bandwidth is required, also called "Roll-Off of the filter. ' + 'The filter used is also designed to respect the symmetry condition expressed in the Nyquist Criterion ' + 'avoiding inter-symbol interferences. Such a filter is thus called Nyquist Filter ' + 'and the minimum theoretical bandwidth (Roll-Off = zero) is called Nyquist Bandwidth.', + '-iCNRbw-':'Signal to Noise Ratio in Available BW\n\n Ratio of the Signal Power and the Noise Power Captured ' + ' in the available bandwidth.', + '-iCNRnyq-':'Signal to Noise Ratio in Nyquist BW\n\nRatio of the Signal Power and the Noise Power Captured ' + ' in the Nyquist Bandwidth = Available Bandwidth / ( 1 + Roll-Off).', + '-iCNRrcv-':'Signal to Noise Ratio at Receiver Output\n\nRatio of the Signal Power and the total Noise Power' + ' captured along the complete communication chain (at receiver ouptut). This ratio is the relevant one' + ' for real-life performance evaluation. It is computed by combining the Signal to Noise in the Nyquist ' + 'Bandwidth, the Receiver\'s C/I and the Satellite\'s C/I. Note that these 2 items are themselves ' + 'resulting of many items which can be detailed as comma separated lists.', + '-iBRbw-':'Theoretical Bit Rate in Available BW\n\nBit Rate theoretically achieved with zero Roll-Off in ' + 'the available bandwidth. This bit rate is given by a direct application of the Shannon Limit. ' + 'The normalized bit rate expressed as a percentage of the bit rate at infinite bandwidth is also given ' + 'as well as the spectral efficiency of the available bandwidth.', + '-iBRnyq-':'Theoretical Bit Rate in Nyquist BW\n\nBit Rate theoretically achieved in ' + 'the Nyquist bandwidth (after having removed the Nyquist Roll-Off from the available Bandwidth).' + 'This bit rate is given by a direct application of the Shannon Limit.\n\nThe normalized bit rate ' + 'expressed as a percentage of the bit rate at infinite bandwidth is also given as well as the spectral ' + 'efficiency of the available bandwidth.\n\nThe efficiency in bit per symbol is also given and does ' + 'correspond to the classical spectral efficiency in the Nyquist bandwidth.', + '-iBRrcv-':'Practical Physcial Layer Bit Rate\n\n Practical Bit Rate achieved using real-world conditions. ' + 'This bit rate is evaluated by using the "all degradations included" signal to noise ratio' + 'in the Shannon\'s formula.' + 'This bit rate does correspond to the user bits of the Physical Layer Frames.', + '-iBRhigh-':'Practical Higher Layers Bit Rate\n\nPractical Bit Rate achieved using real-world modulation ' + 'and coding and modern encapsulation methods of higher layers strcutures.\n\nThis Bit Rate does ' + 'typically correspond to the user bits of the IP datagrams', + '-Satellite-':'The evaluation is decomposed in 3 sections:\n\n' + '1. The satellite link : satellite transmitter and path to the receiver\'s location with ' + 'associated key characteristics \n\n' + '2. The radio front end : antenna and amplification unit capturing as much signal as possible ' + 'and as little noise as possible\n\n' + '3. The base-band processing unit : unit extracting from a modulated carrier the useful ' + 'information bits.' + ' All key functions are usually performed via digital signal processing : Nyquist filtering, ' + 'synchronisation, demodulation, error correction, higher layer "decapsulation"...\n\n' + 'All fields are initially filled with meaningful values, you should start the exploration by ' + 'changing the straightforward parameters and keep the intimidating figures unchanged. ' + 'All parameters are "clickable" for getting associated background information.', + '-CRC1-': 'Cyclic Redundancy Check of the input parameters, for changes identification purposes.', + '-CRC2-': 'Cyclic Redundancy Check of the input parameters, for changes identification purposes.', + '-CRC3-': 'Cyclic Redundancy Check of the input parameters, for changes identification purposes.', + 'Advanced': 'The Shannon Limit is a very powerful tool to analyse communication systems\' design, trade offs.' + 'All capacity evaluations in this tool are based on direct application of this formula taking ' + 'into account real world impairments via signal to noise combinations. With this approach, ' + 'using the overall C/N evaluated for a practical communication link gives a good estimate of the ' + 'capacity achievable.\n\nApplying in addition the known average penalty of real modulation and ' + 'coding schemes makes it accurate enough for initial systems evaluations.\n\nThe analytic formulas ' + 'derived from the Shannon Limit for given spectral efficiencies are also of great help to drive ' + 'the thinking in practical trade-offs.\n\n' + 'Additional useful links for people interested in a theoretical immersion :' + 'https://en.wikipedia.org/wiki/Nyquist_ISI_criterion\n' + 'https://en.wikipedia.org/wiki/Error_correction_code#Forward_error_correction\n' + 'https://en.wikipedia.org/wiki/Viterbi_decoder\n' + 'https://en.wikipedia.org/wiki/Turbo_code\n' + 'https://en.wikipedia.org/wiki/DVB-S2\n' + 'https://en.wikipedia.org/wiki/OSI_model\n', + 'Help':'Recommendations for using the tool\n\nThe first purpose of the tool is educational, allowing people to ' + 'better understand the physics of communications and the role of key parameters\n\n' + 'All labels can be \"clicked\" to get information about associated item. All values ' + '(including this text) can be copy/pasted for further usage.\n\n' + 'The user should try multiple values in the fields one per one (starting from the least intimidating), ' + 'explore the graphs and try to understand the underlying physics.\n\n' + 'The units for the different figures are as explicit as possible to facilitate the exploration.\n\n' + 'Despite the simplicity of the approach, the tool can also be useful to do a quick analysis of a ' + 'communication link with a first order approach, avoiding the trap of the illusion of precision.\n\n' + + } + diff --git a/Shannon_Real.db b/Shannon_Real.db new file mode 100644 index 0000000..6bcfaee Binary files /dev/null and b/Shannon_Real.db differ diff --git a/Shannon_Theory.db b/Shannon_Theory.db new file mode 100644 index 0000000..9e29d3c Binary files /dev/null and b/Shannon_Theory.db differ diff --git a/command.txt b/command.txt new file mode 100644 index 0000000..6d4b06b --- /dev/null +++ b/command.txt @@ -0,0 +1,2 @@ +docker build -t my-python-app . +docker run -it --rm --name my-running-app --network=nginx-proxy --env VIRTUAL_HOST=shannon.antopoid.com --env LETSENCRYPT_HOST=shannon.antopoid.com --env LETSENCRYPT_EMAIL=poidevin.freeboxos.fr@gmail.com my-python-app diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c1fef2b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.1' +services: + python: + image: python.slim:latest + container_name: python + restart: always + environment: + - VIRTUAL_HOST=shannon.antopoid.com + - LETSENCRYPT_HOST=shannon.antopoid.com + - LETSENCRYPT_EMAIL=poidevin.freeboxos.fr@gmail.com + ports: + - 8888:8080 + +networks: + default: + external: + name: nginx-proxy + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7b2feaa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,25 @@ +HTMLParser +tk-tools==0.16.0 +Flask==1.1.2 +Jinja2==3.0.3 +MarkupSafe==2.0.1 +astropy==5.3 +certifi==2021.10.8 +cycler==0.11.0 +fonttools==4.28.5 +itur==0.3.3 +kiwisolver==1.4.4 +matplotlib==3.7.2 +numpy==1.25 +packaging==21.3 +Pillow==10.0.0 +pyerfa==2.0.0.3 +pyparsing==3.0.6 +pyproj==3.6.0 +remi==2021.3.2 +PySimpleGUI==4.60.5 +python-dateutil==2.8.2 +PyYAML==6.0 +scipy==1.11.1 +six==1.16.0 + diff --git a/slim.report.json b/slim.report.json new file mode 100644 index 0000000..00ef60b --- /dev/null +++ b/slim.report.json @@ -0,0 +1,505 @@ +{ + "version": "1.1", + "engine": "linux|Transformer|1.40.4|d310b07567dc90763f5f27f94c618f057295b55d|2023-08-26_01:39:22AM", + "containerized": false, + "host_distro": { + "name": "Ubuntu", + "version": "22.04", + "display_name": "Ubuntu 22.04.3 LTS" + }, + "type": "build", + "state": "done", + "target_reference": "python:latest", + "system": { + "type": "", + "release": "", + "distro": { + "name": "", + "version": "", + "display_name": "" + } + }, + "source_image": { + "identity": { + "id": "sha256:e13fe79153bbf089fb6bac4fc8710eae318c6aa124a1ba4aa609e1e136496543", + "tags": [ + "latest" + ], + "names": [ + "python:latest" + ] + }, + "size": 1020536111, + "size_human": "1.0 GB", + "create_time": "2023-07-16T18:27:16Z", + "docker_version": "20.10.21", + "architecture": "amd64", + "os": "linux", + "env_vars": [ + "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "LANG=C.UTF-8", + "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D", + "PYTHON_VERSION=3.11.4", + "PYTHON_PIP_VERSION=23.1.2", + "PYTHON_SETUPTOOLS_VERSION=65.5.1", + "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/0d8570dc44796f4369b652222cf176b3db6ac70e/public/get-pip.py", + "PYTHON_GET_PIP_SHA256=96461deced5c2a487ddc65207ec5a9cffeca0d34e7af7ea1afc470ff0d746207" + ], + "container_entry": { + "exe_path": "" + } + }, + "minified_image_size": 267206999, + "minified_image_size_human": "267 MB", + "minified_image": "python.slim", + "minified_image_has_data": true, + "minified_by": 3.8192716314290855, + "artifact_location": "/tmp/slim-state/.slim-state/images/e13fe79153bbf089fb6bac4fc8710eae318c6aa124a1ba4aa609e1e136496543/artifacts", + "container_report_name": "creport.json", + "seccomp_profile_name": "python-seccomp.json", + "apparmor_profile_name": "python-apparmor-profile", + "image_stack": [ + { + "is_top_image": true, + "id": "sha256:e13fe79153bbf089fb6bac4fc8710eae318c6aa124a1ba4aa609e1e136496543", + "full_name": "python:latest", + "repo_name": "python", + "version_tag": "latest", + "raw_tags": [ + "python:latest" + ], + "create_time": "2023-07-16T18:27:16Z", + "new_size": 1020536111, + "new_size_human": "1.0 GB", + "instructions": [ + { + "type": "ADD", + "time": "2023-07-04T01:19:58Z", + "is_nop": true, + "local_image_exists": false, + "layer_index": 0, + "size": 74759018, + "size_human": "75 MB", + "params": "file:bd80a4461150784e5f2f5a1faa720cc347ad3e30ee0969adbfad574c316f5aef in /", + "command_snippet": "ADD file:bd80a4461150784e5f2f5a1faa720cc347a...", + "command_all": "ADD file:bd80a4461150784e5f2f5a1faa720cc347ad3e30ee0969adbfad574c316f5aef /", + "target": "/", + "source_type": "file", + "inst_set_time_bucket": "2023-07-04T01:15:00Z", + "inst_set_time_index": 1, + "inst_set_time_reverse_index": 3 + }, + { + "type": "CMD", + "time": "2023-07-04T01:19:58Z", + "is_nop": true, + "is_exec_form": true, + "local_image_exists": false, + "layer_index": 0, + "size": 0, + "params": "[\"bash\"]\n", + "command_snippet": "CMD [\"bash\"]\n", + "command_all": "CMD [\"bash\"]\n", + "inst_set_time_bucket": "2023-07-04T01:15:00Z", + "inst_set_time_index": 1, + "inst_set_time_reverse_index": 3 + }, + { + "type": "ENV", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 0, + "params": "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "command_snippet": "ENV PATH=/usr/local/bin:/usr/local/sbin:/usr...", + "command_all": "ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "ENV", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 0, + "params": "LANG=C.UTF-8", + "command_snippet": "ENV LANG=C.UTF-8", + "command_all": "ENV LANG=C.UTF-8", + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "RUN", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 9214160, + "size_human": "9.2 MB", + "command_snippet": "RUN set -eux; \tapt-get update; \tapt-get inst...", + "command_all": "RUN set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates \t\tnetbase \t\ttzdata \t; \trm -rf /var/lib/apt/lists/*", + "system_commands": [ + "set -eux", + "apt-get update", + "apt-get install -y --no-install-recommends ca-certificates netbase tzdata", + "rm -rf /var/lib/apt/lists/*" + ], + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "ENV", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 0, + "params": "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D", + "command_snippet": "ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8...", + "command_all": "ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D", + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "ENV", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 0, + "params": "PYTHON_VERSION=3.11.4", + "command_snippet": "ENV PYTHON_VERSION=3.11.4", + "command_all": "ENV PYTHON_VERSION=3.11.4", + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "RUN", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 53218187, + "size_human": "53 MB", + "command_snippet": "RUN set -eux; \t\tsavedAptMark=\"$(apt-mark sho...", + "command_all": "RUN set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tdpkg-dev \t\tgcc \t\tgnupg \t\tlibbluetooth-dev \t\tlibbz2-dev \t\tlibc6-dev \t\tlibdb-dev \t\tlibexpat1-dev \t\tlibffi-dev \t\tlibgdbm-dev \t\tliblzma-dev \t\tlibncursesw5-dev \t\tlibreadline-dev \t\tlibsqlite3-dev \t\tlibssl-dev \t\tmake \t\ttk-dev \t\tuuid-dev \t\twget \t\txz-utils \t\tzlib1g-dev \t; \t\twget -O python.tar.xz \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz\"; \twget -O python.tar.xz.asc \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc\"; \tGNUPGHOME=\"$(mktemp -d)\"; export GNUPGHOME; \tgpg --batch --keyserver hkps://keys.openpgp.org --recv-keys \"$GPG_KEY\"; \tgpg --batch --verify python.tar.xz.asc python.tar.xz; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\" python.tar.xz.asc; \tmkdir -p /usr/src/python; \ttar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; \trm python.tar.xz; \t\tcd /usr/src/python; \tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \t./configure \t\t--build=\"$gnuArch\" \t\t--enable-loadable-sqlite-extensions \t\t--enable-optimizations \t\t--enable-option-checking=fatal \t\t--enable-shared \t\t--with-lto \t\t--with-system-expat \t\t--without-ensurepip \t; \tnproc=\"$(nproc)\"; \tEXTRA_CFLAGS=\"$(dpkg-buildflags --get CFLAGS)\"; \tLDFLAGS=\"$(dpkg-buildflags --get LDFLAGS)\"; \tLDFLAGS=\"${LDFLAGS:--Wl},--strip-all\"; \tmake -j \"$nproc\" \t\t\"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \t\t\"LDFLAGS=${LDFLAGS:-}\" \t\t\"PROFILE_TASK=${PROFILE_TASK:-}\" \t; \trm python; \tmake -j \"$nproc\" \t\t\"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \t\t\"LDFLAGS=${LDFLAGS:--Wl},-rpath='\\$\\$ORIGIN/../lib'\" \t\t\"PROFILE_TASK=${PROFILE_TASK:-}\" \t\tpython \t; \tmake install; \t\tcd /; \trm -rf /usr/src/python; \t\tfind /usr/local -depth \t\t\\( \t\t\t\\( -type d -a \\( -name test -o -name tests -o -name idle_test \\) \\) \t\t\t-o \\( -type f -a \\( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \\) \\) \t\t\\) -exec rm -rf '{}' + \t; \t\tldconfig; \t\tapt-mark auto '.*' > /dev/null; \tapt-mark manual $savedAptMark; \tfind /usr/local -type f -executable -not \\( -name '*tkinter*' \\) -exec ldd '{}' ';' \t\t| awk '/=>/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -r apt-mark manual \t; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\tpython3 --version", + "system_commands": [ + "set -eux", + "savedAptMark=\"$(apt-mark showmanual)\"", + "apt-get update", + "apt-get install -y --no-install-recommends dpkg-dev gcc gnupg libbluetooth-dev libbz2-dev libc6-dev libdb-dev libexpat1-dev libffi-dev libgdbm-dev liblzma-dev libncursesw5-dev libreadline-dev libsqlite3-dev libssl-dev make tk-dev uuid-dev wget xz-utils zlib1g-dev", + "wget -O python.tar.xz \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz\"", + "wget -O python.tar.xz.asc \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc\"", + "GNUPGHOME=\"$(mktemp -d)\"", + "export GNUPGHOME", + "gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys \"$GPG_KEY\"", + "gpg --batch --verify python.tar.xz.asc python.tar.xz", + "gpgconf --kill all", + "rm -rf \"$GNUPGHOME\" python.tar.xz.asc", + "mkdir -p /usr/src/python", + "tar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz", + "rm python.tar.xz", + "cd /usr/src/python", + "gnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"", + "./configure --build=\"$gnuArch\" --enable-loadable-sqlite-extensions --enable-optimizations --enable-option-checking=fatal --enable-shared --with-lto --with-system-expat --without-ensurepip", + "nproc=\"$(nproc)\"", + "EXTRA_CFLAGS=\"$(dpkg-buildflags --get CFLAGS)\"", + "LDFLAGS=\"$(dpkg-buildflags --get LDFLAGS)\"", + "LDFLAGS=\"${LDFLAGS:--Wl},--strip-all\"", + "make -j \"$nproc\" \"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \"LDFLAGS=${LDFLAGS:-}\" \"PROFILE_TASK=${PROFILE_TASK:-}\"", + "rm python", + "make -j \"$nproc\" \"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \"LDFLAGS=${LDFLAGS:--Wl},-rpath='$$ORIGIN/../lib'\" \"PROFILE_TASK=${PROFILE_TASK:-}\" python", + "make install", + "cd /", + "rm -rf /usr/src/python", + "find /usr/local -depth ( ( -type d -a ( -name test -o -name tests -o -name idle_test ) ) -o ( -type f -a ( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' ) ) ) -exec rm -rf '{}' +", + "ldconfig", + "apt-mark auto '.*' > /dev/null", + "apt-mark manual $savedAptMark", + "find /usr/local -type f -executable -not ( -name '*tkinter*' ) -exec ldd '{}' '", + "' | awk '/=>/ { so = $(NF-1)", + "if (index(so, \"/usr/local/\") == 1) { next }", + "gsub(\"^/(usr/)?\", \"\", so)", + "print so }' | sort -u | xargs -r dpkg-query --search | cut -d: -f1 | sort -u | xargs -r apt-mark manual", + "apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false", + "rm -rf /var/lib/apt/lists/*", + "python3 --version" + ], + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "RUN", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 32, + "size_human": "32 B", + "command_snippet": "RUN set -eux; \tfor src in idle3 pydoc3 pytho...", + "command_all": "RUN set -eux; \tfor src in idle3 pydoc3 python3 python3-config; do \t\tdst=\"$(echo \"$src\" | tr -d 3)\"; \t\t[ -s \"/usr/local/bin/$src\" ]; \t\t[ ! -e \"/usr/local/bin/$dst\" ]; \t\tln -svT \"$src\" \"/usr/local/bin/$dst\"; \tdone", + "system_commands": [ + "set -eux", + "for src in idle3 pydoc3 python3 python3-config", + "do dst=\"$(echo \"$src\" | tr -d 3)\"", + "[ -s \"/usr/local/bin/$src\" ]", + "[ ! -e \"/usr/local/bin/$dst\" ]", + "ln -svT \"$src\" \"/usr/local/bin/$dst\"", + "done" + ], + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "ENV", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 0, + "params": "PYTHON_PIP_VERSION=23.1.2", + "command_snippet": "ENV PYTHON_PIP_VERSION=23.1.2", + "command_all": "ENV PYTHON_PIP_VERSION=23.1.2", + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "ENV", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 0, + "params": "PYTHON_SETUPTOOLS_VERSION=65.5.1", + "command_snippet": "ENV PYTHON_SETUPTOOLS_VERSION=65.5.1", + "command_all": "ENV PYTHON_SETUPTOOLS_VERSION=65.5.1", + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "ENV", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 0, + "params": "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/0d8570dc44796f4369b652222cf176b3db6ac70e/public/get-pip.py", + "command_snippet": "ENV PYTHON_GET_PIP_URL=https://github.com/py...", + "command_all": "ENV PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/0d8570dc44796f4369b652222cf176b3db6ac70e/public/get-pip.py", + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "ENV", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 0, + "params": "PYTHON_GET_PIP_SHA256=96461deced5c2a487ddc65207ec5a9cffeca0d34e7af7ea1afc470ff0d746207", + "command_snippet": "ENV PYTHON_GET_PIP_SHA256=96461deced5c2a487d...", + "command_all": "ENV PYTHON_GET_PIP_SHA256=96461deced5c2a487ddc65207ec5a9cffeca0d34e7af7ea1afc470ff0d746207", + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "RUN", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "local_image_exists": false, + "layer_index": 0, + "size": 12240418, + "size_human": "12 MB", + "command_snippet": "RUN set -eux; \t\tsavedAptMark=\"$(apt-mark sho...", + "command_all": "RUN set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends wget; \t\twget -O get-pip.py \"$PYTHON_GET_PIP_URL\"; \techo \"$PYTHON_GET_PIP_SHA256 *get-pip.py\" | sha256sum -c -; \t\tapt-mark auto '.*' > /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark > /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\texport PYTHONDONTWRITEBYTECODE=1; \t\tpython get-pip.py \t\t--disable-pip-version-check \t\t--no-cache-dir \t\t--no-compile \t\t\"pip==$PYTHON_PIP_VERSION\" \t\t\"setuptools==$PYTHON_SETUPTOOLS_VERSION\" \t; \trm -f get-pip.py; \t\tpip --version", + "system_commands": [ + "set -eux", + "savedAptMark=\"$(apt-mark showmanual)\"", + "apt-get update", + "apt-get install -y --no-install-recommends wget", + "wget -O get-pip.py \"$PYTHON_GET_PIP_URL\"", + "echo \"$PYTHON_GET_PIP_SHA256 *get-pip.py\" | sha256sum -c -", + "apt-mark auto '.*' > /dev/null", + "[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark > /dev/null", + "apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false", + "rm -rf /var/lib/apt/lists/*", + "export PYTHONDONTWRITEBYTECODE=1", + "python get-pip.py --disable-pip-version-check --no-cache-dir --no-compile \"pip==$PYTHON_PIP_VERSION\" \"setuptools==$PYTHON_SETUPTOOLS_VERSION\"", + "rm -f get-pip.py", + "pip --version" + ], + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "CMD", + "time": "2023-06-13T17:45:16Z", + "is_nop": false, + "is_exec_form": true, + "local_image_exists": true, + "intermediate_image_id": "sha256:be2470db10f711ec941d24bc9a489dd457b6000b624ee251e19a445ad9f38839", + "layer_index": 0, + "size": 0, + "params": "[\"python3\"]\n", + "command_snippet": "CMD [\"python3\"]\n", + "command_all": "CMD [\"python3\"]\n", + "comment": "buildkit.dockerfile.v0", + "is_buildkit_instruction": true, + "inst_set_time_bucket": "2023-06-13T17:45:00Z", + "inst_set_time_index": 0, + "inst_set_time_reverse_index": 4 + }, + { + "type": "WORKDIR", + "time": "2023-07-06T15:08:21Z", + "is_nop": true, + "local_image_exists": true, + "intermediate_image_id": "sha256:ad187361307c8e838bb1f0a48b91e97d83d7aea811827b4d2bab393284f739e0", + "layer_index": 0, + "size": 0, + "params": "/usr/src/app", + "command_snippet": "WORKDIR /usr/src/app", + "command_all": "WORKDIR /usr/src/app", + "system_commands": [ + "mkdir -p /usr/src/app" + ], + "inst_set_time_bucket": "2023-07-06T15:00:00Z", + "inst_set_time_index": 2, + "inst_set_time_reverse_index": 2 + }, + { + "type": "COPY", + "time": "2023-07-12T20:36:09Z", + "is_nop": true, + "local_image_exists": true, + "intermediate_image_id": "sha256:a1b245cb980c1ac2c038a60bbe84fb8c65a1be6aedce32e15f29a53e4ea8e364", + "layer_index": 0, + "size": 398, + "size_human": "398 B", + "params": "file:1383fead6d13fe1a3d2822aaafeadc3c38b2cfeea627e71b14c63805820e09a2 in ./", + "command_snippet": "COPY file:1383fead6d13fe1a3d2822aaafeadc3c38...", + "command_all": "COPY file:1383fead6d13fe1a3d2822aaafeadc3c38b2cfeea627e71b14c63805820e09a2 ./", + "target": "./", + "source_type": "file", + "inst_set_time_bucket": "2023-07-12T20:30:00Z", + "inst_set_time_index": 3, + "inst_set_time_reverse_index": 1 + }, + { + "type": "RUN", + "time": "2023-07-16T18:25:45Z", + "is_nop": false, + "local_image_exists": true, + "intermediate_image_id": "sha256:0bffdc8daa291358931fff5ca204342be123ed040aa8352657c0a97dc0da7a1b", + "layer_index": 0, + "size": 36428885, + "size_human": "36 MB", + "command_snippet": "RUN apt-get update -y && \\\n\tapt-get install ...", + "command_all": "RUN apt-get update -y && \\\n\tapt-get install -y tk tcl", + "system_commands": [ + "apt-get update -y", + "apt-get install -y tk tcl" + ], + "inst_set_time_bucket": "2023-07-16T18:15:00Z", + "inst_set_time_index": 4, + "inst_set_time_reverse_index": 0 + }, + { + "type": "RUN", + "time": "2023-07-16T18:27:15Z", + "is_nop": false, + "local_image_exists": true, + "intermediate_image_id": "sha256:1087081f44d63fc4d50ed96db8ee05d9ec956c1dbbcd8b125511ced95b4c1d7e", + "layer_index": 0, + "size": 833949399, + "size_human": "834 MB", + "command_snippet": "RUN pip install --force-reinstall -r require...", + "command_all": "RUN pip install --force-reinstall -r requirements.txt", + "system_commands": [ + "pip install --force-reinstall -r requirements.txt" + ], + "inst_set_time_bucket": "2023-07-16T18:15:00Z", + "inst_set_time_index": 4, + "inst_set_time_reverse_index": 0 + }, + { + "type": "COPY", + "time": "2023-07-16T18:27:16Z", + "is_nop": true, + "local_image_exists": true, + "intermediate_image_id": "sha256:7ae91476369b98ce3f0ec518e591385fdde2a0631944b93cb5672850c27086d5", + "layer_index": 0, + "size": 725614, + "size_human": "726 kB", + "params": "dir:c64af32925ea67cdf709617fb045107117c1bc58e9add8805e4e31a29cdfbc91 in .", + "command_snippet": "COPY dir:c64af32925ea67cdf709617fb045107117c...", + "command_all": "COPY dir:c64af32925ea67cdf709617fb045107117c1bc58e9add8805e4e31a29cdfbc91 .", + "target": ".", + "source_type": "dir", + "inst_set_time_bucket": "2023-07-16T18:15:00Z", + "inst_set_time_index": 4, + "inst_set_time_reverse_index": 0 + }, + { + "type": "CMD", + "time": "2023-07-16T18:27:16Z", + "is_last_instruction": true, + "is_nop": true, + "is_exec_form": true, + "local_image_exists": true, + "layer_index": 0, + "size": 0, + "params": "[\"python\",\"./Shannon.py\"]\n", + "command_snippet": "CMD [\"python\",\"./Shannon.py\"]\n", + "command_all": "CMD [\"python\",\"./Shannon.py\"]\n", + "raw_tags": [ + "python:latest" + ], + "inst_set_time_bucket": "2023-07-16T18:15:00Z", + "inst_set_time_index": 4, + "inst_set_time_reverse_index": 0 + } + ] + } + ], + "image_created": true, + "image_build_engine": "internal" +}