diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6839098 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +__pycache__ +*.pyc +*.pyo +.git +.gitignore +*.save +*.db +.venv +venv +README.md diff --git a/.gitea/workflows/deploy-shannon.yml b/.gitea/workflows/deploy-shannon.yml new file mode 100644 index 0000000..4fa94ad --- /dev/null +++ b/.gitea/workflows/deploy-shannon.yml @@ -0,0 +1,136 @@ +name: Build & Deploy Shannon + +on: + push: + branches: + - main + - master + paths: + - 'app.py' + - 'core/**' + - 'views/**' + - 'requirements.txt' + - 'Dockerfile' + - 'docker-stack.yml' + - '.streamlit/**' + - '.gitea/workflows/deploy-shannon.yml' + workflow_dispatch: + +env: + IMAGE_NAME: shannon/streamlit + +jobs: + build-and-deploy: + name: ๐Ÿ—๏ธ Build & Deploy Shannon + runs-on: ubuntu-latest + steps: + - name: ๐Ÿ“ฅ Checkout code + uses: https://github.com/actions/checkout@v4 + + - name: ๐Ÿ” Setup SSH + env: + SSH_KEY: ${{ secrets.SWARM_SSH_KEY }} + SSH_HOST: ${{ secrets.SWARM_MANAGER_HOST }} + run: | + mkdir -p ~/.ssh + echo "$SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts + + - name: ๐Ÿ“ฆ Copy source to server + env: + SSH_USER: ${{ secrets.SWARM_USER }} + SSH_HOST: ${{ secrets.SWARM_MANAGER_HOST }} + run: | + tar -czf /tmp/shannon-source.tar.gz \ + --exclude='.git' \ + --exclude='.venv' \ + --exclude='__pycache__' \ + --exclude='*.db' \ + --exclude='*.pyc' \ + -C . . + scp -i ~/.ssh/deploy_key /tmp/shannon-source.tar.gz "$SSH_USER@$SSH_HOST":/tmp/ + echo "โœ… Source copied" + + - name: ๐Ÿณ Build Docker Image + env: + SSH_USER: ${{ secrets.SWARM_USER }} + SSH_HOST: ${{ secrets.SWARM_MANAGER_HOST }} + run: | + COMMIT_SHA=$(git rev-parse --short HEAD) + echo "๐Ÿ“Œ Commit: $COMMIT_SHA" + + ssh -i ~/.ssh/deploy_key ${SSH_USER}@${SSH_HOST} << ENDSSH + set -e + cd /tmp + rm -rf shannon-build + mkdir -p shannon-build + cd shannon-build + tar -xzf /tmp/shannon-source.tar.gz + + echo "๐Ÿณ Building image with tag: $COMMIT_SHA" + docker build -t shannon/streamlit:$COMMIT_SHA \ + -t shannon/streamlit:latest . + + echo "๐Ÿงน Cleaning old images..." + docker image prune -f + + echo "โœ… Image built: $COMMIT_SHA" + ENDSSH + + - name: ๐Ÿš€ Deploy to Swarm + env: + SSH_USER: ${{ secrets.SWARM_USER }} + SSH_HOST: ${{ secrets.SWARM_MANAGER_HOST }} + run: | + COMMIT_SHA=$(git rev-parse --short HEAD) + + scp -i ~/.ssh/deploy_key ./docker-stack.yml ${SSH_USER}@${SSH_HOST}:/tmp/shannon-docker-stack.yml + + ssh -i ~/.ssh/deploy_key ${SSH_USER}@${SSH_HOST} << ENDSSH + set -e + + echo "๐Ÿš€ Deploying stack..." + docker stack deploy -c /tmp/shannon-docker-stack.yml shannon + + echo "๐Ÿ”„ Forcing service update to use new image..." + docker service update --force --image shannon/streamlit:$COMMIT_SHA shannon_shannon + + echo "โณ Waiting for service to update..." + sleep 15 + + echo "๐Ÿ“Š Service status:" + docker service ls --filter name=shannon_shannon + docker service ps shannon_shannon --no-trunc + + echo "โœ… Deployment complete!" + ENDSSH + + - name: ๐Ÿฅ Health Check + env: + SSH_USER: ${{ secrets.SWARM_USER }} + SSH_HOST: ${{ secrets.SWARM_MANAGER_HOST }} + run: | + ssh -i ~/.ssh/deploy_key ${SSH_USER}@${SSH_HOST} << ENDSSH + echo "๐Ÿฅ Running health check..." + + for i in 1 2 3 4 5 6 7 8 9 10 11 12; do + HTTP_CODE=\$(curl -s -o /dev/null -w "%{http_code}" https://shannon.antopoid.com/_stcore/health) + if [ "\$HTTP_CODE" = "200" ]; then + echo "โœ… Shannon is accessible (HTTP 200)!" + exit 0 + fi + echo "โณ Waiting for Shannon... (\$i/12) - Status: \$HTTP_CODE" + sleep 5 + done + + echo "โŒ Health check failed!" + echo "๐Ÿ“‹ Service logs:" + docker service logs shannon_shannon --tail 50 + exit 1 + ENDSSH + + - name: ๐Ÿงน Cleanup + run: | + rm -f ~/.ssh/deploy_key + echo "โœ… Deployment finished!" diff --git a/.streamlit/config.toml b/.streamlit/config.toml new file mode 100644 index 0000000..521e45e --- /dev/null +++ b/.streamlit/config.toml @@ -0,0 +1,21 @@ +[server] +port = 8080 +address = "0.0.0.0" +headless = true +enableCORS = false +enableXsrfProtection = false +maxUploadSize = 5 +runOnSave = false + +[browser] +gatherUsageStats = false + +[theme] +primaryColor = "#4FC3F7" +backgroundColor = "#0E1117" +secondaryBackgroundColor = "#1a1a2e" +textColor = "#E2E8F0" +font = "sans serif" + +[logger] +level = "info" diff --git a/Dockerfile b/Dockerfile index 6a745fd..87bdac7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,36 @@ -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" ] +FROM python:3.11-slim + +WORKDIR /app + +# Install system dependencies (gcc for build, curl for healthcheck) +RUN apt-get update && \ + apt-get install -y --no-install-recommends gcc curl && \ + rm -rf /var/lib/apt/lists/* + +# Install Python dependencies +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application +COPY .streamlit/ .streamlit/ +COPY core/ core/ +COPY views/ views/ +COPY app.py . +COPY Shannon.png . +COPY Satellite.png . + +# Create data directory for SQLite databases +RUN mkdir -p /app/data + +# Expose Streamlit port +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ + CMD curl -f http://localhost:8080/_stcore/health || exit 1 + +# Run Streamlit +ENTRYPOINT ["streamlit", "run", "app.py", \ + "--server.port=8080", \ + "--server.address=0.0.0.0", \ + "--server.headless=true"] diff --git a/Dockerfile.save b/Dockerfile.save deleted file mode 100644 index 04353fc..0000000 --- a/Dockerfile.save +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index e89619b..0000000 --- a/PySimpleGUIWeb.py +++ /dev/null @@ -1,8199 +0,0 @@ -#usr/bin/python3 - -version = __version__ = "0.39.0.6 Unreleased\n , VSeparator added (spelling error), added default key for one_line_progress_meter, auto-add keys to tables & trees, Graph.draw_image now uses image_data property instead of calling set_image, added theme_add_new, changed Remi call menu_item.set_on_click_listener to menu_item.onclick.connect so it can run with latest Remi" - -port = 'PySimpleGUIWeb' - -import sys -import datetime -import textwrap -import pickle -import threading -from queue import Queue -import remi -import logging -import traceback -import os -import base64, binascii -import mimetypes -from random import randint -import time -import pkg_resources - - -# from typing import List, Any, Union, Tuple, Dict # For doing types in comments. perhaps not required - - -try: - from io import StringIO -except: - from cStringIO import StringIO - -###### ##### ##### # # ### # # -# # # # # # # # # ##### # ###### # # # # # # # # ###### ##### -# # # # # # ## ## # # # # # # # # # # # # # # -###### # ##### # # ## # # # # ##### # #### # # # # # # ##### ##### -# # # # # # ##### # # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # # # -# # ##### # # # # ###### ###### ##### ##### ### ## ## ###### ##### - -""" - Welcome to the "core" PySimpleGUIWeb code.... - - This special port of the PySimpleGUI SDK to the browser is made possible by the magic of Remi - - https://github.com/dddomodossola/remi - - To be clear, PySimpleGUI would not be able to run in a web browser without this important GUI Framework - It may not be as widely known at tkinter or Qt, but it should be. Just as those are the best of the desktop - GUI frameworks, Remi is THE framework for doing Web Page GUIs in Python. Nothing else like it exists. - - ::::::::: :::::::::: ::: ::: ::::::::::: - :+: :+: :+: :+:+: :+:+: :+: - +:+ +:+ +:+ +:+ +:+:+ +:+ +:+ - +#++:++#: +#++:++# +#+ +:+ +#+ +#+ - +#+ +#+ +#+ +#+ +#+ +#+ - #+# #+# #+# #+# #+# #+# - ### ### ########## ### ### ########### - -""" - -g_time_start = 0 -g_time_end = 0 -g_time_delta = 0 - - - -def TimerStart(): - global g_time_start - - g_time_start = time.time() - - -def TimerStop(): - global g_time_delta, g_time_end - - g_time_end = time.time() - g_time_delta = g_time_end - g_time_start - print(g_time_delta*1000) - -# Because looks matter... -DEFAULT_BASE64_ICON = b'iVBORw0KGgoAAAANSUhEUgAAACEAAAAgCAMAAACrZuH4AAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAMGmYMGqZMWqaMmubMmycM22dNGuZNm2bNm6bNG2dN26cNG6dNG6eNW+fN3CfOHCeOXGfNXCgNnGhN3KiOHOjOXSjOHSkOnWmOnamOnanPHSiPXakPnalO3eoPnimO3ioPHioPHmpPHmqPXqqPnurPnusPnytP3yuQHimQnurQn2sQH2uQX6uQH6vR32qRn+sSXujSHynTH2mTn+nSX6pQH6wTIGsTYKuTYSvQoCxQoCyRIK0R4S1RYS2Roa4SIe4SIe6SIi7Soq7SYm8SYq8Sou+TY2/UYStUYWvVIWtUYeyVoewUIi0VIizUI6+Vo+8WImxXJG5YI2xZI+xZ5CzZJC0ZpG1b5a3apW4aZm/cZi4dJ2/eJ69fJ+9XZfEZZnCZJzHaZ/Jdp/AeKTI/tM8/9Q7/9Q8/9Q9/9Q+/tQ//9VA/9ZA/9ZB/9ZC/9dD/9ZE/tdJ/9dK/9hF/9hG/9hH/9hI/9hJ/9hK/9lL/9pK/9pL/thO/9pM/9pN/9tO/9tP/9xP/tpR/9xQ/9xR/9xS/9xT/91U/91V/t1W/95W/95X/95Y/95Z/99a/99b/txf/txh/txk/t5l/t1q/t5v/+Bb/+Bc/+Bd/+Be/+Bf/+Bg/+Fh/+Fi/+Jh/+Ji/uJk/uJl/+Jm/+Rm/uJo/+Ro/+Rr/+Zr/+Vs/+Vu/+Zs/+Zu/uF0/uVw/+dw/+dz/+d2/uB5/uB6/uJ9/uR7/uR+/uV//+hx/+hy/+h0/+h2/+l4/+l7/+h8gKXDg6vLgazOhKzMiqrEj6/KhK/Qka/Hk7HJlLHJlLPMmLTLmbbOkLXSmLvXn77XoLrPpr/Tn8DaocLdpcHYrcjdssfZus/g/uOC/uOH/uaB/uWE/uaF/uWK/+qA/uqH/uqI/uuN/uyM/ueS/ueW/ueY/umQ/uqQ/uuS/uuW/uyU/uyX/uqa/uue/uye/uyf/u6f/uyq/u+r/u+t/vCm/vCp/vCu/vCy/vC2/vK2/vO8/vO/wtTjwtXlzdrl/vTA/vPQAAAAiNpY5gAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAAFw8AABcPASe7rwsAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAKUSURBVDhPhdB3WE1xHMdxt5JV0dANoUiyd8kqkey996xclUuTlEKidO3qVnTbhIyMW/bee5NskjJLmR/f3++cK/94vP76Ps/n/Zx7z6mE/6koJowcK154vvHOL/GsKCZXkUgkWlf4vWGWq5tsDz+JWIzSokAiqXGe7nWu3HxhEYof7fhOqp1GtptQuMruVhQdxZ05U5G47tYUHbQ4oah6Fg9Z4ubm7i57JhQjdHS0RSzUPoG17u6zZTKZh8c8XlytqW9YWUOH1LqFOZ6enl5ec+XybFb0rweM1tPTM6yuq6vLs0lYJJfLvb19fHwDWGF0jh5lYNAe4/QFemOwxtfXz8/fPyBgwVMqzAcCF4ybAZ2MRCexJGBhYGBQUHDw4u1UHDG1G2ZqB/Q1MTHmzAE+kpCwL1RghlTaBt/6SaXS2kx9YH1IaOjSZST8vfA9JtoDnSngGgL7wkg4WVkofA9mcF1Sx8zMzBK4v3wFiYiMVLxlEy9u21syFhYNmgN7IyJXEYViNZvEYoCVVWOmUVvgQVSUQqGIjolRFvOAFd8HWVs34VoA+6OjY2JjY5Vxm4BC1UuhGG5jY9OUaQXci1MqlfHx8YmqjyhOViW9ZsUN29akJRmPFwkJCZsTSXIpilJffXiTzorLXYgtcxRJKpUqKTklJQ0oSt9FP/EonxVdNY4jla1kK4q2ZB6mIr+AipvduzFUzMSOtLT09IyMzMxtJKug/F0u/6dTexAWDcXXLGEjapKjfsILOLKEuYiSnTQeYCt3UHhbwEHjGMrETfBJU5zq5dSTcXC8hLJccSWP2cgLXHPu7cQNAcpyxF1dyjehAKb0cSYUAOXCUw6V8OFPgevTXFymC+fPPLU677Nw/1X8A/AbfAKGulaqFlIAAAAASUVORK5CYII=' - - - -# ----====----====----==== Constants the user CAN safely change ====----====----====----# -DEFAULT_WINDOW_ICON = 'default_icon.ico' -DEFAULT_ELEMENT_SIZE = (250, 26) # In pixels -DEFAULT_BUTTON_ELEMENT_SIZE = (10, 1) # In CHARACTERS -DEFAULT_MARGINS = (10, 5) # Margins for each LEFT/RIGHT margin is first term -DEFAULT_ELEMENT_PADDING = (5, 3) # Padding between elements (row, col) in pixels -DEFAULT_AUTOSIZE_TEXT = True -DEFAULT_AUTOSIZE_BUTTONS = True -DEFAULT_FONT = ("Helvetica", 15) -DEFAULT_TEXT_JUSTIFICATION = 'left' -DEFAULT_BORDER_WIDTH = 1 -DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form -DEFAULT_DEBUG_WINDOW_SIZE = (80, 20) -DEFAULT_OUTPUT_ELEMENT_SIZE = (40, 10) -DEFAULT_WINDOW_LOCATION = (None, None) -MAX_SCROLLED_TEXT_BOX_HEIGHT = 50 -DEFAULT_TOOLTIP_TIME = 400 - -DEFAULT_PIXELS_TO_CHARS_SCALING = (10,26) # 1 character represents x by y pixels -DEFAULT_PIXEL_TO_CHARS_CUTOFF = 20 # number of chars that triggers using pixels instead of chars - -#################### COLOR STUFF #################### -BLUES = ("#082567", "#0A37A3", "#00345B") -PURPLES = ("#480656", "#4F2398", "#380474") -GREENS = ("#01826B", "#40A860", "#96D2AB", "#00A949", "#003532") -YELLOWS = ("#F3FB62", "#F0F595") -TANS = ("#FFF9D5", "#F4EFCF", "#DDD8BA") -NICE_BUTTON_COLORS = ((GREENS[3], TANS[0]), - ('#000000', '#FFFFFF'), - ('#FFFFFF', '#000000'), - (YELLOWS[0], PURPLES[1]), - (YELLOWS[0], GREENS[3]), - (YELLOWS[0], BLUES[2])) - -COLOR_SYSTEM_DEFAULT = '1234567890' # Colors should never be this long - -DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default -OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = ('white', BLUES[0]) # Colors should never be this long - -CURRENT_LOOK_AND_FEEL = 'DarkBlue3' - - -DEFAULT_ERROR_BUTTON_COLOR = ("#FFFFFF", "#FF0000") -DEFAULT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_ELEMENTS_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_SCROLLBAR_COLOR = None -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[0]) # (Text, Background) or (Color "on", Color) as a way to remember -# DEFAULT_BUTTON_COLOR = (GREENS[3], TANS[0]) # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], GREENS[4]) # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = ('white', 'black') # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[2]) # Foreground, Background (None, None) == System Default -# DEFAULT_PROGRESS_BAR_COLOR = (GREENS[2], GREENS[0]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[1], BLUES[1]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[0], BLUES[0]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (PURPLES[1],PURPLES[0]) # a nice purple progress bar - -# A transparent button is simply one that matches the background -TRANSPARENT_BUTTON = 'This constant has been depricated. You must set your button background = background it is on for it to be transparent appearing' -# -------------------------------------------------------------------------------- -# Progress Bar Relief Choices -RELIEF_RAISED = 'raised' -RELIEF_SUNKEN = 'sunken' -RELIEF_FLAT = 'flat' -RELIEF_RIDGE = 'ridge' -RELIEF_GROOVE = 'groove' -RELIEF_SOLID = 'solid' - -DEFAULT_PROGRESS_BAR_COLOR = (GREENS[0], '#D0D0D0') # a nice green progress bar -DEFAULT_PROGRESS_BAR_COLOR_OFFICIAL = (GREENS[0], '#D0D0D0') # a nice green progress bar -DEFAULT_PROGRESS_BAR_SIZE = (25, 20) # Size of Progress Bar (characters for length, pixels for width) -DEFAULT_PROGRESS_BAR_BORDER_WIDTH = 1 -DEFAULT_PROGRESS_BAR_RELIEF = RELIEF_GROOVE -PROGRESS_BAR_STYLES = ('default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative') -DEFAULT_PROGRESS_BAR_STYLE = 'default' -DEFAULT_METER_ORIENTATION = 'horizontal' -DEFAULT_SLIDER_ORIENTATION = 'vertical' -DEFAULT_SLIDER_BORDER_WIDTH = 1 -DEFAULT_SLIDER_RELIEF = 00000 -DEFAULT_FRAME_RELIEF = 00000 - - -DEFAULT_LISTBOX_SELECT_MODE = 'extended' -SELECT_MODE_MULTIPLE = 'multiple' -LISTBOX_SELECT_MODE_MULTIPLE = 'multiple' -SELECT_MODE_BROWSE = 'browse' -LISTBOX_SELECT_MODE_BROWSE = 'browse' -SELECT_MODE_EXTENDED = 'extended' -LISTBOX_SELECT_MODE_EXTENDED = 'extended' -SELECT_MODE_SINGLE = 'single' -LISTBOX_SELECT_MODE_SINGLE = 'single' -SELECT_MODE_CONTIGUOUS = 'contiguous' -LISTBOX_SELECT_MODE_CONTIGUOUS = 'contiguous' - -TABLE_SELECT_MODE_NONE = 00000 -TABLE_SELECT_MODE_BROWSE = 00000 -TABLE_SELECT_MODE_EXTENDED = 00000 -DEFAULT_TABLE_SECECT_MODE = TABLE_SELECT_MODE_EXTENDED - -TITLE_LOCATION_TOP = 00000 -TITLE_LOCATION_BOTTOM = 00000 -TITLE_LOCATION_LEFT = 00000 -TITLE_LOCATION_RIGHT = 00000 -TITLE_LOCATION_TOP_LEFT = 00000 -TITLE_LOCATION_TOP_RIGHT = 00000 -TITLE_LOCATION_BOTTOM_LEFT = 00000 -TITLE_LOCATION_BOTTOM_RIGHT = 00000 - -THEME_DEFAULT = 'default' -THEME_WINNATIVE = 'winnative' -THEME_CLAM = 'clam' -THEME_ALT = 'alt' -THEME_CLASSIC = 'classic' -THEME_VISTA = 'vista' -THEME_XPNATIVE = 'xpnative' - -# DEFAULT_METER_ORIENTATION = 'Vertical' -# ----====----====----==== Constants the user should NOT f-with ====----====----====----# -ThisRow = 555666777 # magic number - -# DEFAULT_WINDOW_ICON = '' -MESSAGE_BOX_LINE_WIDTH = 60 - -# "Special" Key Values.. reserved -# Key representing a Read timeout -EVENT_TIMEOUT = TIMEOUT_EVENT = TIMEOUT_KEY = '__TIMEOUT__' -# Window closed event (user closed with X or destroyed using OS) -WIN_CLOSED = WINDOW_CLOSED = None - -# Key indicating should not create any return values for element -WRITE_ONLY_KEY = '__WRITE ONLY__' - -# MENU Constants, can be changed by user if desired -MENU_DISABLED_CHARACTER = '!' -MENU_KEY_SEPARATOR = '::' - - - - -# a shameful global variable. This represents the top-level window information. Needed because opening a second window is different than opening the first. -class MyWindows(): - def __init__(self): - self._NumOpenWindows = 0 - self.user_defined_icon = None - self.hidden_master_root = None - - def Decrement(self): - self._NumOpenWindows -= 1 * (self._NumOpenWindows != 0) # decrement if not 0 - # print('---- DECREMENTING Num Open Windows = {} ---'.format(self._NumOpenWindows)) - - def Increment(self): - self._NumOpenWindows += 1 - # print('++++ INCREMENTING Num Open Windows = {} ++++'.format(self._NumOpenWindows)) - - -_my_windows = MyWindows() # terrible hack using globals... means need a class for collecing windows - - -# ====================================================================== # -# One-liner functions that are handy as f_ck # -# ====================================================================== # -def RGB(red, green, blue): return '#%02x%02x%02x' % (red, green, blue) - - -# ====================================================================== # -# Enums for types # -# ====================================================================== # -# ------------------------- Button types ------------------------- # -# todo Consider removing the Submit, Cancel types... they are just 'RETURN' type in reality -# uncomment this line and indent to go back to using Enums -# Was enum previously ButtonType(Enum): -BUTTON_TYPE_BROWSE_FOLDER = 1 -BUTTON_TYPE_BROWSE_FILE = 2 -BUTTON_TYPE_BROWSE_FILES = 21 -BUTTON_TYPE_SAVEAS_FILE = 3 -BUTTON_TYPE_CLOSES_WIN = 5 -BUTTON_TYPE_CLOSES_WIN_ONLY = 6 -BUTTON_TYPE_READ_FORM = 7 -BUTTON_TYPE_REALTIME = 9 -BUTTON_TYPE_CALENDAR_CHOOSER = 30 -BUTTON_TYPE_COLOR_CHOOSER = 40 - -BROWSE_FILES_DELIMITER = ';' # the delimeter to be used between each file in the returned string - -# ------------------------- Element types ------------------------- # -# These used to be enums ElementType(Enum): -ELEM_TYPE_TEXT = 'text' -ELEM_TYPE_INPUT_TEXT = 'input' -ELEM_TYPE_INPUT_COMBO = 'combo' -ELEM_TYPE_INPUT_OPTION_MENU = 'option menu' -ELEM_TYPE_INPUT_RADIO = 'radio' -ELEM_TYPE_INPUT_MULTILINE = 'multiline' -ELEM_TYPE_MULTILINE_OUTPUT = 'multioutput' -ELEM_TYPE_INPUT_CHECKBOX = 'checkbox' -ELEM_TYPE_INPUT_SPIN = 'spin' -ELEM_TYPE_BUTTON = 'button' -ELEM_TYPE_BUTTONMENU = 'buttonmenu' -ELEM_TYPE_IMAGE = 'image' -ELEM_TYPE_CANVAS = 'canvas' -ELEM_TYPE_FRAME = 'frame' -ELEM_TYPE_GRAPH = 'graph' -ELEM_TYPE_TAB = 'tab' -ELEM_TYPE_TAB_GROUP = 'tabgroup' -ELEM_TYPE_INPUT_SLIDER = 'slider' -ELEM_TYPE_INPUT_LISTBOX = 'listbox' -ELEM_TYPE_OUTPUT = 'output' -ELEM_TYPE_COLUMN = 'column' -ELEM_TYPE_MENUBAR = 'menubar' -ELEM_TYPE_PROGRESS_BAR = 'progressbar' -ELEM_TYPE_BLANK = 'blank' -ELEM_TYPE_TABLE = 'table' -ELEM_TYPE_TREE = 'tree' -ELEM_TYPE_ERROR = 'error' -ELEM_TYPE_SEPARATOR = 'separator' - -# ------------------------- Popup Buttons Types ------------------------- # -POPUP_BUTTONS_YES_NO = 1 -POPUP_BUTTONS_CANCELLED = 2 -POPUP_BUTTONS_ERROR = 3 -POPUP_BUTTONS_OK_CANCEL = 4 -POPUP_BUTTONS_OK = 0 -POPUP_BUTTONS_NO_BUTTONS = 5 - - -# ---------------------------------------------------------------------- # -# Cascading structure.... Objects get larger # -# Button # -# Element # -# Row # -# Form # -# ---------------------------------------------------------------------- # -# ------------------------------------------------------------------------- # -# Element CLASS # -# ------------------------------------------------------------------------- # -class Element(): - def __init__(self, elem_type, size=(None, None), auto_size_text=None, font=None, background_color=None, text_color=None, - key=None, pad=None, tooltip=None, visible=True, size_px=(None, None), metadata=None): - - if elem_type != ELEM_TYPE_GRAPH: - self.Size = convert_tkinter_size_to_Wx(size) - else: - self.Size = size - if size_px != (None, None): - self.Size = size_px - self.Type = elem_type - self.AutoSizeText = auto_size_text - # self.Pad = DEFAULT_ELEMENT_PADDING if pad is None else pad - self.Pad = pad - if font is not None and type(font) is not str: - self.Font = font - elif font is not None: - self.Font = font.split(' ') - else: - self.Font = font - - self.TKStringVar = None - self.TKIntVar = None - self.TKText = None - self.TKEntry = None - self.TKImage = None - - self.ParentForm = None # type: Window - self.ParentContainer = None # will be a Form, Column, or Frame element - self.TextInputDefault = None - self.Position = (0, 0) # Default position Row 0, Col 0 - self.BackgroundColor = background_color if background_color is not None else DEFAULT_ELEMENT_BACKGROUND_COLOR - self.TextColor = text_color if text_color is not None else DEFAULT_ELEMENT_TEXT_COLOR - self.Key = key # dictionary key for return values - self.Tooltip = tooltip - self.TooltipObject = None - self.Visible = visible - self.metadata = metadata # type: Any - - - # ------------------------- REMI CHANGED CALLBACK ----------------------- - # called when a widget has changed and the element has events enabled - def _ChangedCallback(self, widget, *args): - # type: (Element, remi.Widget, Any) -> None - # print(f'Callback {args}') - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - def Update(self, widget, background_color=None, text_color=None, font=None, visible=None, disabled=None, tooltip=None): - if font is not None: - font_info = font_parse_string(font) # family, point size, other - widget.style['font-family'] = font_info[0] - widget.style['font-size'] = '{}px'.format(font_info[1]) - - if background_color not in (None, COLOR_SYSTEM_DEFAULT): - widget.style['background-color'] = background_color - if text_color not in (None, COLOR_SYSTEM_DEFAULT): - widget.style['color'] = text_color - - if disabled: - widget.set_enabled(False) - elif disabled is False: - widget.set_enabled(True) - if visible is False: - widget.attributes['hidden'] = 'true' - elif visible is True: - del(widget.attributes['hidden']) - if tooltip is not None: - widget.attributes['title'] = tooltip - - - # if font: - # widget.SetFont(font_to_wx_font(font)) - # if text_color not in (None, COLOR_SYSTEM_DEFAULT): - # widget.SetForegroundColour(text_color) - # if background_color not in (None, COLOR_SYSTEM_DEFAULT): - # widget.SetBackgroundColour(background_color) - # if visible is True: - # widget.Show() - # self.ParentForm.VisibilityChanged() - # elif visible is False: - # widget.Hide() - # self.ParentForm.VisibilityChanged() - # if disabled: - # widget.Enable(False) - # elif disabled is False: - # widget.Enable(True) - # if tooltip is not None: - # widget.SetToolTip(tooltip) - if visible is False: - widget.attributes['hidden'] = 'true' - elif visible is True: - del(widget.attributes['hidden']) - - - def __call__(self, *args, **kwargs): - """ - Makes it possible to "call" an already existing element. When you do make the "call", it actually calls - the Update method for the element. - Example: If this text element was in your layout: - sg.Text('foo', key='T') - Then you can call the Update method for that element by writing: - window.FindElement('T')('new text value') - - :param args: - :param kwargs: - :return: - """ - return self.Update(*args, **kwargs) - - -# ---------------------------------------------------------------------- # -# Input Class # -# ---------------------------------------------------------------------- # -class InputText(Element): - def __init__(self, default_text='', size=(None, None), disabled=False, password_char='', - justification=None, background_color=None, text_color=None, font=None, tooltip=None, - change_submits=False, enable_events=False, - do_not_clear=True, key=None, focus=False, pad=None, visible=True, size_px=(None, None)): - ''' - Input a line of text Element - :param default_text: Default value to display - :param size: Size of field in characters - :param password_char: If non-blank, will display this character for every character typed - :param background_color: Color for Element. Text or RGB Hex - ''' - self.DefaultText = default_text - self.PasswordCharacter = password_char - bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - self.Justification = justification or 'left' - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.QT_QLineEdit = None - self.ValueWasChanged = False - self.Widget = None # type: remi.gui.TextInput - super().__init__(ELEM_TYPE_INPUT_TEXT, size=size, background_color=bg, text_color=fg, key=key, pad=pad, - font=font, tooltip=tooltip, visible=visible, size_px=size_px) - - def _InputTextCallback(self,widget, key, keycode, ctrl, shift, alt): - # print(f'text widget value = {widget.get_value()}') - # widget.set_value('') - # widget.set_value(value) - self.ParentForm.LastButtonClicked = key - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - widget.set_value(widget.get_value()+key) - return (key, keycode, ctrl, shift, alt) - - def Update(self, value=None, disabled=None, select=None, background_color=None, text_color=None, font=None, visible=None): - if value is not None: - self.Widget.set_value(str(value)) - if disabled is True: - self.Widget.set_enabled(False) - elif disabled is False: - self.Widget.set_enabled(True) - - def Get(self): - return self.Widget.get_value() - - - get = Get - update = Update - - class TextInput_raw_onkeyup(remi.gui.TextInput): - @remi.gui.decorate_set_on_listener("(self, emitter, key, keycode, ctrl, shift, alt)") - @remi.gui.decorate_event_js("""var params={};params['key']=event.key; - params['keycode']=(event.which||event.keyCode); - params['ctrl']=event.ctrlKey; - params['shift']=event.shiftKey; - params['alt']=event.altKey; - sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params); - event.stopPropagation();event.preventDefault();return false;""") - def onkeyup(self, key, keycode, ctrl, shift, alt): - return (key, keycode, ctrl, shift, alt) - - @remi.gui.decorate_set_on_listener("(self, emitter, key, keycode, ctrl, shift, alt)") - @remi.gui.decorate_event_js("""var params={};params['key']=event.key; - params['keycode']=(event.which||event.keyCode); - params['ctrl']=event.ctrlKey; - params['shift']=event.shiftKey; - params['alt']=event.altKey; - sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params); - event.stopPropagation();event.preventDefault();return false;""") - def onkeydown(self, key, keycode, ctrl, shift, alt): - return (key, keycode, ctrl, shift, alt) - - -# ------------------------- INPUT TEXT Element lazy functions ------------------------- # -In = InputText -Input = InputText -I = InputText - -# ---------------------------------------------------------------------- # -# Combo # -# ---------------------------------------------------------------------- # -class Combo(Element): - def __init__(self, values, default_value=None, size=(None, None), auto_size_text=None, background_color=None, - text_color=None, change_submits=False, enable_events=False, disabled=False, key=None, pad=None, tooltip=None, - readonly=False, visible_items=10, font=None, auto_complete=True, visible=True, size_px=(None,None)): - ''' - Input Combo Box Element (also called Dropdown box) - :param values: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex - ''' - self.Values = [str(v) for v in values] - self.DefaultValue = default_value - self.ChangeSubmits = change_submits or enable_events - # self.InitializeAsDisabled = disabled - self.Disabled = disabled - self.Readonly = readonly - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.VisibleItems = visible_items - self.AutoComplete = auto_complete - self.Widget = None # type: remi.gui.DropDown - super().__init__(ELEM_TYPE_INPUT_COMBO, size=size, auto_size_text=auto_size_text, background_color=bg, - text_color=fg, key=key, pad=pad, tooltip=tooltip, font=font or DEFAULT_FONT, visible=visible, size_px=size_px) - - - - def Update(self, value=None, values=None, set_to_index=None, disabled=None, readonly=None, background_color=None, text_color=None, font=None, visible=None): - if values is not None: - self.Widget.empty() - for i, item in enumerate(values): - self.Widget.append(value=item, key=str(i)) - if value: - self.Widget.select_by_value(value) - if set_to_index is not None: - try: # just in case a bad index is passed in - self.Widget.select_by_key(str(set_to_index)) - except: - pass - - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible, disabled=disabled) - - update = Update - - -# ------------------------- INPUT COMBO Element lazy functions ------------------------- # -InputCombo = Combo -DropDown = Combo -Drop = Combo - - -# ---------------------------------------------------------------------- # -# Option Menu # -# ---------------------------------------------------------------------- # -class OptionMenu(Element): - def __init__(self, values, default_value=None, size=(None, None), disabled=False, auto_size_text=None, - background_color=None, text_color=None, key=None, pad=None, tooltip=None): - ''' - InputOptionMenu - :param values: - :param default_value: - :param size: - :param disabled: - :param auto_size_text: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - ''' - self.Values = values - self.DefaultValue = default_value - self.TKOptionMenu = None - self.Disabled = disabled - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - super().__init__(ELEM_TYPE_INPUT_OPTION_MENU, size=size, auto_size_text=auto_size_text, background_color=bg, - text_color=fg, key=key, pad=pad, tooltip=tooltip) - - def Update(self, value=None, values=None, disabled=None): - if values is not None: - self.Values = values - if self.Values is not None: - for index, v in enumerate(self.Values): - if v == value: - try: - self.TKStringVar.set(value) - except: - pass - self.DefaultValue = value - break - if disabled == True: - self.TKOptionMenu['state'] = 'disabled' - elif disabled == False: - self.TKOptionMenu['state'] = 'normal' - - - -# ------------------------- OPTION MENU Element lazy functions ------------------------- # -InputOptionMenu = OptionMenu - - - -# ---------------------------------------------------------------------- # -# Listbox # -# ---------------------------------------------------------------------- # -class Listbox(Element): - def __init__(self, values, default_values=None, select_mode=None, change_submits=False, enable_events=False, bind_return_key=False, size=(None, None), disabled=False, auto_size_text=None, font=None, background_color=None, text_color=None, key=None, pad=None, tooltip=None, visible=True, size_px=(None,None)): - """ - - :param values: - :param default_values: - :param select_mode: - :param change_submits: - :param enable_events: - :param bind_return_key: - :param size: - :param disabled: - :param auto_size_text: - :param font: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - :param visible: - :param size_px: - """ - self.Values = values - self.DefaultValues = default_values - self.TKListbox = None - self.ChangeSubmits = change_submits or enable_events - self.BindReturnKey = bind_return_key - self.Disabled = disabled - if select_mode == LISTBOX_SELECT_MODE_BROWSE: - self.SelectMode = SELECT_MODE_BROWSE - elif select_mode == LISTBOX_SELECT_MODE_EXTENDED: - self.SelectMode = SELECT_MODE_EXTENDED - elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE: - self.SelectMode = SELECT_MODE_MULTIPLE - elif select_mode == LISTBOX_SELECT_MODE_SINGLE: - self.SelectMode = SELECT_MODE_SINGLE - elif select_mode == LISTBOX_SELECT_MODE_CONTIGUOUS: - self.SelectMode = SELECT_MODE_CONTIGUOUS - else: - self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Widget = None # type: remi.gui.ListView - tsize = size # convert tkinter size to pixels - if size[0] is not None and size[0] < 100: - tsize = size[0]*DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1]*DEFAULT_PIXELS_TO_CHARS_SCALING[1] - - super().__init__(ELEM_TYPE_INPUT_LISTBOX, size=tsize, auto_size_text=auto_size_text, font=font, - background_color=bg, text_color=fg, key=key, pad=pad, tooltip=tooltip, visible=visible, size_px=size_px) - - def Update(self, values=None, disabled=None, set_to_index=None,background_color=None, text_color=None, font=None, visible=None): - if values is not None: - self.Values = values - self.Widget.empty() - for item in values: - self.Widget.append(remi.gui.ListItem(item)) - # if disabled == True: - # self.QT_ListWidget.setDisabled(True) - # elif disabled == False: - # self.QT_ListWidget.setDisabled(False) - # if set_to_index is not None: - # self.QT_ListWidget.setCurrentRow(set_to_index) - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible, disabled=disabled) - - return - - # def SetValue(self, values): - # # for index, item in enumerate(self.Values): - # for index, value in enumerate(self.Values): - # item = self.QT_ListWidget.item(index) - # if value in values: - # self.QT_ListWidget.setItemSelected(item, True) - - - def GetListValues(self): - return self.Values - - get_list_values = GetListValues - update = Update - - -# ---------------------------------------------------------------------- # -# Radio # -# ---------------------------------------------------------------------- # -class Radio(Element): - def __init__(self, text, group_id, default=False, disabled=False, size=(None, None), auto_size_text=None, - background_color=None, text_color=None, font=None, key=None, pad=None, tooltip=None, - change_submits=False): - ''' - Radio Button Element - :param text: - :param group_id: - :param default: - :param disabled: - :param size: - :param auto_size_text: - :param background_color: - :param text_color: - :param font: - :param key: - :param pad: - :param tooltip: - :param change_submits: - ''' - self.InitialState = default - self.Text = text - self.TKRadio = None - self.GroupID = group_id - self.Value = None - self.Disabled = disabled - self.TextColor = text_color or DEFAULT_TEXT_COLOR - self.ChangeSubmits = change_submits - - print('*** WARNING - Radio Buttons are not yet available on PySimpleGUIWeb ***') - - super().__init__(ELEM_TYPE_INPUT_RADIO, size=size, auto_size_text=auto_size_text, font=font, - background_color=background_color, text_color=self.TextColor, key=key, pad=pad, - tooltip=tooltip) - - def Update(self, value=None, disabled=None): - print('*** NOT IMPLEMENTED ***') - location = EncodeRadioRowCol(self.Position[0], self.Position[1]) - if value is not None: - try: - self.TKIntVar.set(location) - except: - pass - self.InitialState = value - if disabled == True: - self.TKRadio['state'] = 'disabled' - elif disabled == False: - self.TKRadio['state'] = 'normal' - - update = Update - - -# ---------------------------------------------------------------------- # -# Checkbox # -# ---------------------------------------------------------------------- # -class Checkbox(Element): - def __init__(self, text, default=False, size=(None, None), auto_size_text=None, font=None, background_color=None, - text_color=None, change_submits=False, enable_events=False, disabled=False, key=None, pad=None, tooltip=None, visible=True, size_px=(None,None)): - ''' - Checkbox Element - :param text: - :param default: - :param size: - :param auto_size_text: - :param font: - :param background_color: - :param text_color: - :param change_submits: - :param disabled: - :param key: - :param pad: - :param tooltip: - ''' - self.Text = text - self.InitialState = default - self.Disabled = disabled - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.ChangeSubmits = change_submits or enable_events - self.Widget = None # type: remi.gui.CheckBox - - super().__init__(ELEM_TYPE_INPUT_CHECKBOX, size=size, auto_size_text=auto_size_text, font=font, - background_color=background_color, text_color=self.TextColor, key=key, pad=pad, - tooltip=tooltip, visible=visible, size_px=size_px) - - def _ChangedCallback(self, widget, value): - # type: (remi.Widget, Any) -> None - # print(f'text widget value = {widget.get_value()}') - self.ParentForm.LastButtonClicked = self.Key - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - - def Get(self): - return self.Widget.get_value() - - def Update(self, value=None, disabled=None): - if value is not None: - self.Widget.set_value(value) - if disabled == True: - self.Widget.set_enabled(False) - elif disabled == False: - self.Widget.set_enabled(True) - - get = Get - update = Update - - -# ------------------------- CHECKBOX Element lazy functions ------------------------- # -CB = Checkbox -CBox = Checkbox -Check = Checkbox - - -# ---------------------------------------------------------------------- # -# Spin # -# ---------------------------------------------------------------------- # - -class Spin(Element): - # Values = None - # TKSpinBox = None - def __init__(self, values, initial_value=None, disabled=False, change_submits=False, enable_events=False, size=(None, None), readonly=True, auto_size_text=None, font=None, background_color=None, text_color=None, key=None, pad=None, - tooltip=None, visible=True, size_px=(None,None)): - ''' - Spinner Element - :param values: - :param initial_value: - :param disabled: - :param change_submits: - :param size: - :param auto_size_text: - :param font: - :param background_color: - :param text_color: - :param key: - :param pad: - :param tooltip: - ''' - self.Values = values - self.DefaultValue = initial_value or values[0] - self.ChangeSubmits = change_submits or enable_events - self.Disabled = disabled - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.CurrentValue = self.DefaultValue - self.ReadOnly = readonly - self.Widget = None # type: remi.gui.SpinBox - super().__init__(ELEM_TYPE_INPUT_SPIN, size, auto_size_text, font=font, background_color=bg, text_color=fg, - key=key, pad=pad, tooltip=tooltip, visible=visible, size_px=size_px) - return - - - def Update(self, value=None, values=None, disabled=None, background_color=None, text_color=None, font=None, visible=None): - if value is not None: - self.Widget.set_value(value) - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font,visible=visible) - - def Get(self): - return self.Widget.get_value() - - get = Get - update = Update - - -# ---------------------------------------------------------------------- # -# Multiline # -# ---------------------------------------------------------------------- # -class Multiline(Element): - def __init__(self, default_text='', enter_submits=False, disabled=False, autoscroll=False, size=(None, None), - auto_size_text=None, background_color=None, text_color=None, change_submits=False, enable_events=False, do_not_clear=True, - key=None, write_only=False, focus=False, font=None, pad=None, tooltip=None, visible=True, size_px=(None,None)): - ''' - Multiline Element - :param default_text: - :param enter_submits: - :param disabled: - :param autoscroll: - :param size: - :param auto_size_text: - :param background_color: - :param text_color: - :param do_not_clear: - :param key: - :param focus: - :param pad: - :param tooltip: - :param font: - ''' - self.DefaultText = default_text - self.EnterSubmits = enter_submits - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Autoscroll = autoscroll - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.WriteOnly = write_only - if size[0] is not None and size[0] < 100: - size = size[0]*DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1]*DEFAULT_PIXELS_TO_CHARS_SCALING[1] - self.Widget = None # type: remi.gui.TextInput - - super().__init__(ELEM_TYPE_INPUT_MULTILINE, size=size, auto_size_text=auto_size_text, background_color=bg, - text_color=fg, key=key, pad=pad, tooltip=tooltip, font=font or DEFAULT_FONT, visible=visible, size_px=size_px) - return - - def _InputTextCallback(self, widget:remi.Widget, value, keycode): - # print(f'text widget value = {widget.get_value()}') - self.ParentForm.LastButtonClicked = chr(int(keycode)) - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None, autoscroll=None): - if value is not None and not append: - self.Widget.set_value(value) - elif value is not None and append: - text = self.Widget.get_value() + str(value) - self.Widget.set_value(text) - # if background_color is not None: - # self.WxTextCtrl.SetBackgroundColour(background_color) - # if text_color is not None: - # self.WxTextCtrl.SetForegroundColour(text_color) - # if font is not None: - # self.WxTextCtrl.SetFont(font) - # if disabled: - # self.WxTextCtrl.Enable(True) - # elif disabled is False: - # self.WxTextCtrl.Enable(False) - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - - def print(self, *args, end=None, sep=None, text_color=None, background_color=None): - """ - Print like Python normally prints except route the output to a multline element and also add colors if desired - - :param args: List[Any] The arguments to print - :param end: (str) The end char to use just like print uses - :param sep: (str) The separation character like print uses - :param text_color: The color of the text - :param background_color: The background color of the line - """ - _print_to_element(self, *args, end=end, sep=sep, text_color=text_color, background_color=background_color) - - - - update = Update - -ML = Multiline -MLine = Multiline - - -# ---------------------------------------------------------------------- # -# Multiline Output # -# ---------------------------------------------------------------------- # -class MultilineOutput(Element): - def __init__(self, default_text='', enter_submits=False, disabled=False, autoscroll=False, size=(None, None), auto_size_text=None, background_color=None, - text_color=None, change_submits=False, enable_events=False, do_not_clear=True, key=None, focus=False, font=None, pad=None, tooltip=None, - visible=True, size_px=(None, None)): - ''' - Multiline Element - :param default_text: - :param enter_submits: - :param disabled: - :param autoscroll: - :param size: - :param auto_size_text: - :param background_color: - :param text_color: - :param do_not_clear: - :param key: - :param focus: - :param pad: - :param tooltip: - :param font: - ''' - self.DefaultText = default_text - self.EnterSubmits = enter_submits - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Autoscroll = autoscroll - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - tsize = size # convert tkinter size to pixels - if size[0] is not None and size[0] < 100: - tsize = size[0] * DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1] * DEFAULT_PIXELS_TO_CHARS_SCALING[1] - self.Widget = None # type: remi.gui.TextInput - self.CurrentValue = '' - - super().__init__(ELEM_TYPE_MULTILINE_OUTPUT, size=tsize, auto_size_text=auto_size_text, background_color=bg, - text_color=fg, key=key, pad=pad, tooltip=tooltip, font=font or DEFAULT_FONT, visible=visible, size_px=size_px) - return - - def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None, autoscroll=None): - autoscroll = self.Autoscroll if autoscroll is None else autoscroll - if value is not None and not append: - self.Widget.set_value(str(value)) - self.CurrentValue = str(value) - elif value is not None and append: - self.CurrentValue = self.CurrentValue + str(value) - self.Widget.set_value(self.CurrentValue) - self.Widget._set_updated() - app = self.ParentForm.App - - if hasattr(app, "websockets"): - app.execute_javascript( - 'element=document.getElementById("%(id)s"); element.innerHTML=`%(content)s`; if(%(autoscroll)s){element.scrollTop=999999;} ' % { - "id": self.Widget.identifier, "content": self.Widget.get_value(), "autoscroll": 'true' if autoscroll else 'false'}) - - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - def print(self, *args, end=None, sep=None, text_color=None, background_color=None): - """ - Print like Python normally prints except route the output to a multline element and also add colors if desired - - :param args: List[Any] The arguments to print - :param end: (str) The end char to use just like print uses - :param sep: (str) The separation character like print uses - :param text_color: The color of the text - :param background_color: The background color of the line - """ - _print_to_element(self, *args, end=end, sep=sep, text_color=text_color, background_color=background_color) - - - - - - update = Update - - -# ---------------------------------------------------------------------- # -# Text # -# ---------------------------------------------------------------------- # -class Text(Element): - def __init__(self, text='', size=(None, None), auto_size_text=None, click_submits=None, enable_events=False, relief=None, border_width=None, font=None, text_color=None, background_color=None, justification=None, pad=None, margins=None, key=None, tooltip=None, visible=True, size_px=(None,None), metadata=None): - """ - Text - :param text: - :param size: - :param auto_size_text: - :param click_submits: - :param enable_events: - :param relief: - :param font: - :param text_color: - :param background_color: - :param justification: - :param pad: - :param margins: - :param key: - :param tooltip: - :param visible: - :param size_px: - """ - self.DisplayText = str(text) - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.Justification = justification - self.Relief = relief - self.ClickSubmits = click_submits or enable_events - self.Margins = margins - self.size_px = size_px - if background_color is None: - bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - else: - bg = background_color - pixelsize = size - if size[1] is not None and size[1] < 10: - pixelsize = size[0]*10, size[1]*20 - self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH - self.Disabled = False - self.Widget = None #type: remi.gui.Label - - super().__init__(ELEM_TYPE_TEXT, pixelsize, auto_size_text, background_color=bg, font=font if font else DEFAULT_FONT, - text_color=self.TextColor, pad=pad, key=key, tooltip=tooltip, size_px=size_px, visible=visible, metadata=metadata) - return - - def Update(self, value=None, background_color=None, text_color=None, font=None, visible=None): - if value is not None: - self.Widget.set_text(str(value)) - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - update = Update - -# ------------------------- Text Element lazy functions ------------------------- # -Txt = Text -T = Text - - - -# ---------------------------------------------------------------------- # -# Output # -# Routes stdout, stderr to a scrolled window # -# ---------------------------------------------------------------------- # -class Output(Element): - def __init__(self, size=(None, None), background_color=None, text_color=None, pad=None, font=None, tooltip=None, - key=None, visible=True, size_px=(None,None), disabled=False): - ''' - Output Element - :param size: - :param background_color: - :param text_color: - :param pad: - :param font: - :param tooltip: - :param key: - ''' - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - # fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - fg = text_color if text_color is not None else 'black' if DEFAULT_INPUT_TEXT_COLOR == COLOR_SYSTEM_DEFAULT else DEFAULT_INPUT_TEXT_COLOR - self.Disabled = disabled - self.Widget = None # type: remi.gui.TextInput - if size_px == (None, None) and size == (None, None): - size = DEFAULT_OUTPUT_ELEMENT_SIZE - if size[0] is not None and size[0] < 100: - size = size[0]*DEFAULT_PIXELS_TO_CHARS_SCALING[0], size[1]*DEFAULT_PIXELS_TO_CHARS_SCALING[1] - super().__init__(ELEM_TYPE_OUTPUT, size=size, size_px=size_px, visible=visible, background_color=bg, text_color=fg, pad=pad, font=font, tooltip=tooltip, key=key) - - - def Update(self, value=None, disabled=None, append=False, background_color=None, text_color=None, font=None, visible=None): - if value is not None and not append: - self.Widget.set_value(str(value)) - self.CurrentValue = str(value) - elif value is not None and append: - self.CurrentValue = self.CurrentValue + '\n' + str(value) - self.Widget.set_value(self.CurrentValue) - self.Widget._set_updated() - app = self.ParentForm.App - if hasattr(app, "websockets"): - app.execute_javascript('element=document.getElementById("%(id)s"); element.innerHTML=`%(content)s`; element.scrollTop=999999; ' % { - "id":self.Widget.identifier, "content":self.Widget.get_value()}) - - super().Update(self.Widget, background_color=background_color, text_color=text_color, font=font, visible=visible) - - update = Update - - -# ---------------------------------------------------------------------- # -# Button Class # -# ---------------------------------------------------------------------- # -class Button(Element): - def __init__(self, button_text='', button_type=BUTTON_TYPE_READ_FORM, target=(None, None), tooltip=None, - file_types=(("ALL Files", "*"),), initial_folder=None, disabled=False, change_submits=False, enable_events=False, - image_filename=None, image_data=None, image_size=(None, None), image_subsample=None, border_width=None, - size=(None, None), auto_size_button=None, button_color=None, font=None, bind_return_key=False, - focus=False, pad=None, key=None, visible=True, size_px=(None,None)): - ''' - Button Element - :param button_text: - :param button_type: - :param target: - :param tooltip: - :param file_types: - :param initial_folder: - :param disabled: - :param image_filename: - :param image_size: - :param image_subsample: - :param border_width: - :param size: - :param auto_size_button: - :param button_color: - :param default_value: - :param font: - :param bind_return_key: - :param focus: - :param pad: - :param key: - ''' - self.AutoSizeButton = auto_size_button - self.BType = button_type - self.FileTypes = file_types - self.TKButton = None - self.Target = target - self.ButtonText = str(button_text) - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.TextColor = self.ButtonColor[0] - self.BackgroundColor = self.ButtonColor[1] - self.ImageFilename = image_filename - self.ImageData = image_data - self.ImageSize = image_size - self.ImageSubsample = image_subsample - self.UserData = None - self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH - self.BindReturnKey = bind_return_key - self.Focus = focus - self.TKCal = None - self.CalendarCloseWhenChosen = None - self.DefaultDate_M_D_Y = (None, None, None) - self.InitialFolder = initial_folder - self.Disabled = disabled - self.ChangeSubmits = change_submits or enable_events - self.QT_QPushButton = None - self.ColorChosen = None - self.Relief = None - # self.temp_size = size if size != (NONE, NONE) else - self.Widget = None # type: remi.gui.Button - super().__init__(ELEM_TYPE_BUTTON, size=size, font=font, pad=pad, key=key, tooltip=tooltip, text_color=self.TextColor, background_color=self.BackgroundColor, visible=visible, size_px=size_px) - return - - - - # ------- Button Callback ------- # - def _ButtonCallBack(self, event): - - # print('Button callback') - - # print(f'Parent = {self.ParentForm} Position = {self.Position}') - # Buttons modify targets or return from the form - # If modifying target, get the element object at the target and modify its StrVar - target = self.Target - target_element = None - if target[0] == ThisRow: - target = [self.Position[0], target[1]] - if target[1] < 0: - target[1] = self.Position[1] + target[1] - strvar = None - should_submit_window = False - if target == (None, None): - strvar = self.TKStringVar - else: - if not isinstance(target, str): - if target[0] < 0: - target = [self.Position[0] + target[0], target[1]] - target_element = self.ParentContainer._GetElementAtLocation(target) - else: - target_element = self.ParentForm.FindElement(target) - try: - strvar = target_element.TKStringVar - except: - pass - try: - if target_element.ChangeSubmits: - should_submit_window = True - except: - pass - filetypes = (("ALL Files", "*"),) if self.FileTypes is None else self.FileTypes - if self.BType == BUTTON_TYPE_BROWSE_FOLDER: # Browse Folder - wx_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.DirDialog(self.ParentForm.MasterFrame, style=wx.FD_OPEN) - else: - dialog = wx.DirDialog(self.ParentForm.MasterFrame) - folder_name = '' - if dialog.ShowModal() == wx.ID_OK: - folder_name = dialog.GetPath() - if folder_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = folder_name - else: - target_element.Update(folder_name) - elif self.BType == BUTTON_TYPE_BROWSE_FILE: # Browse File - qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.FileDialog(self.ParentForm.MasterFrame,defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_OPEN) - else: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_OPEN) - file_name = '' - if dialog.ShowModal() == wx.ID_OK: - file_name = dialog.GetPath() - else: - file_name = '' - if file_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_name - else: - target_element.Update(file_name) - elif self.BType == BUTTON_TYPE_BROWSE_FILES: # Browse Files - qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.FileDialog(self.ParentForm.MasterFrame,defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_MULTIPLE) - else: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_MULTIPLE) - file_names = '' - if dialog.ShowModal() == wx.ID_OK: - file_names = dialog.GetPaths() - else: - file_names = '' - if file_names != '': - file_names = BROWSE_FILES_DELIMITER.join(file_names) - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_names - else: - target_element.Update(file_names) - elif self.BType == BUTTON_TYPE_SAVEAS_FILE: # Save As File - qt_types = convert_tkinter_filetypes_to_wx(self.FileTypes) - if self.InitialFolder: - dialog = wx.FileDialog(self.ParentForm.MasterFrame,defaultDir=self.InitialFolder, wildcard=qt_types, style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) - else: - dialog = wx.FileDialog(self.ParentForm.MasterFrame, wildcard=qt_types, style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) - file_name = '' - if dialog.ShowModal() == wx.ID_OK: - file_name = dialog.GetPath() - else: - file_name = '' - if file_name != '': - if target_element.Type == ELEM_TYPE_BUTTON: - target_element.FileOrFolderName = file_name - else: - target_element.Update(file_name) - elif self.BType == BUTTON_TYPE_COLOR_CHOOSER: # Color Chooser - qcolor = QColorDialog.getColor() - rgb_color = qcolor.getRgb() - color= '#' + ''.join('%02x'% i for i in rgb_color[:3]) - if self.Target == (None, None): - self.FileOrFolderName = color - else: - target_element.Update(color) - elif self.BType == BUTTON_TYPE_CLOSES_WIN: # Closes Window - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = False - if self.ParentForm.CurrentlyRunningMainloop: - self.ParentForm.App.ExitMainLoop() - self.ParentForm.IgnoreClose = True - self.ParentForm.MasterFrame.Close() - if self.ParentForm.NonBlocking: - Window._DecrementOpenCount() - self.ParentForm._Close() - elif self.BType == BUTTON_TYPE_READ_FORM: # Read Button - # first, get the results table built - # modify the Results table in the parent FlexForm object - # if self.Key is not None: - # self.ParentForm.LastButtonClicked = self.Key - # else: - # self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = True - element_callback_quit_mainloop(self) - elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop - element_callback_quit_mainloop(self) - self.ParentForm._Close() - Window._DecrementOpenCount() - elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window - should_submit_window = False - - if should_submit_window: - self.ParentForm.LastButtonClicked = target_element.Key - self.ParentForm.FormRemainedOpen = True - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - return - - - def Update(self, text=None, button_color=(None, None), disabled=None, image_data=None, image_filename=None, font=None, visible=None, image_subsample=None, image_size=(None,None)): - if text is not None: - self.Widget.set_text(str(text)) - fg, bg = button_color - if image_data: - self.Widget.empty() - simage = SuperImage(image_data) - if image_size is not (None, None): - simage.set_size(image_size[0], image_size[1]) - self.Widget.append(simage) - if image_filename: - self.Widget.empty() - simage = SuperImage(image_filename) - if image_size is not (None, None): - simage.set_size(image_size[0], image_size[1]) - self.Widget.append(simage) - - super().Update(self.Widget, background_color=bg, text_color=fg, disabled=disabled, font=font, visible=visible) - - - def GetText(self): - return self.Widget.get_text() - - get_text = GetText - update = Update - -# ------------------------- Button lazy functions ------------------------- # -B = Button -Btn = Button -Butt = Button - - -def convert_tkinter_filetypes_to_wx(filetypes): - wx_filetypes = '' - for item in filetypes: - filetype = item[0] + ' (' + item[1] + ')|'+ item[1] - wx_filetypes += filetype - return wx_filetypes - - - - - -# ---------------------------------------------------------------------- # -# ProgreessBar # -# ---------------------------------------------------------------------- # -class ProgressBar(Element): - def __init__(self, max_value, orientation=None, size=(None, None), auto_size_text=None, bar_color=(None, None), - style=None, border_width=None, relief=None, key=None, pad=None): - ''' - ProgressBar Element - :param max_value: - :param orientation: - :param size: - :param auto_size_text: - :param bar_color: - :param style: - :param border_width: - :param relief: - :param key: - :param pad: - ''' - self.MaxValue = max_value - self.TKProgressBar = None - self.Cancelled = False - self.NotRunning = True - self.Orientation = orientation if orientation else DEFAULT_METER_ORIENTATION - self.BarColor = bar_color - self.BarStyle = style if style else DEFAULT_PROGRESS_BAR_STYLE - self.BorderWidth = border_width if border_width else DEFAULT_PROGRESS_BAR_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF - self.BarExpired = False - super().__init__(ELEM_TYPE_PROGRESS_BAR, size=size, auto_size_text=auto_size_text, key=key, pad=pad) - - # returns False if update failed - def UpdateBar(self, current_count, max=None): - print('*** NOT IMPLEMENTED ***') - return - if self.ParentForm.TKrootDestroyed: - return False - self.TKProgressBar.Update(current_count, max=max) - try: - self.ParentForm.TKroot.update() - except: - _my_windows.Decrement() - return False - return True - - update_bar = UpdateBar - -# ---------------------------------------------------------------------- # -# Image # -# ---------------------------------------------------------------------- # -class Image(Element): - def __init__(self, filename=None, data=None, background_color=None, size=(None, None), pad=None, key=None, - tooltip=None, right_click_menu=None, visible=True, enable_events=False): - ''' - Image Element - :param filename: - :param data: - :param background_color: - :param size: - :param pad: - :param key: - :param tooltip: - ''' - self.Filename = filename if filename else None # note that Remi expects a / at the front of resource files - self.Data = data - self.tktext_label = None - self.BackgroundColor = background_color - self.Disabled = False - self.EnableEvents = enable_events - sz = (0,0) if size == (None, None) else size - self.Widget = None #type: SuperImage - # if data is None and filename is None: # it is OK to have no image specified when intially creating - # print('* Warning... no image specified in Image Element! *') - super().__init__(ELEM_TYPE_IMAGE, size=sz, background_color=background_color, pad=pad, key=key, - tooltip=tooltip, visible=visible) - return - - def Update(self, filename=None, data=None, size=(None,None), visible=None): - if data is not None: - self.Widget.load(data) - # decoded = base64.b64decode(data) - # with open(r'.\decoded.out', 'wb') as f: - # f.write(decoded) - # filename = r'.\decoded.out' - if filename is not None: - self.Widget.load(filename) - # self.Widget.set_image(filename=filename) - # if size != (None, None): - # self.Widget.style['height'] = '{}px'.format(size[1]) - # self.Widget.style['width'] = '{}px'.format(size[0]) - super().Update(self.Widget, visible=visible) - - update = Update - - - -# class SuperImageOld(remi.gui.Image): -# def __init__(self, file_path_name=None, **kwargs): -# image = file_path_name -# super(SuperImage, self).__init__(image, **kwargs) -# -# self.imagedata = None -# self.mimetype = None -# self.encoding = None -# if image is None: -# return -# self.load(image) -# -# def load(self, file_path_name): -# if type(file_path_name) is bytes or len(file_path_name) > 200: -# try: -# self.imagedata = base64.b64decode(file_path_name, validate=True) -# except binascii.Error: -# self.imagedata = file_path_name -# else: -# self.mimetype, self.encoding = mimetypes.guess_type(file_path_name) -# with open(file_path_name, 'rb') as f: -# self.imagedata = f.read() -# self.refresh() -# -# def refresh(self): -# i = int(time.time() * 1e6) -# self.attributes['src'] = "/%s/get_image_data?update_index=%d" % (id(self), i) -# -# def get_image_data(self, update_index): -# headers = {'Content-type': self.mimetype if self.mimetype else 'application/octet-stream'} -# return [self.imagedata, headers] - - -class SuperImage(remi.gui.Image): - def __init__(self, file_path_name=None, **kwargs): - """ - This new app_instance variable is causing lots of problems. I do not know the value of the App - when I create this image. - :param app_instance: - :param file_path_name: - :param kwargs: - """ - # self.app_instance = app_instance - image = file_path_name - super(SuperImage, self).__init__(image, **kwargs) - - self.imagedata = None - self.mimetype = None - self.encoding = None - if not image: return - self.load(image) - - def load(self, file_path_name): - if type(file_path_name) is bytes: - try: - #here a base64 image is received - self.imagedata = base64.b64decode(file_path_name, validate=True) - self.attributes['src'] = "/%s/get_image_data?update_index=%s" % (id(self), str(time.time())) - except binascii.Error: - #here an image data is received (opencv image) - self.imagedata = file_path_name - self.refresh() - self.refresh() - else: - #here a filename is received - self.attributes['src'] = remi.gui.load_resource(file_path_name) - """print(f'***** Loading file = {file_path_name}') - self.mimetype, self.encoding = mimetypes.guess_type(file_path_name) - with open(file_path_name, 'rb') as f: - self.imagedata = f.read()""" - self.refresh() - - def refresh(self): - i = int(time.time() * 1e6) - # self.app_instance.execute_javascript(""" - if Window.App is not None: - Window.App.execute_javascript(""" - var url = '/%(id)s/get_image_data?update_index=%(frame_index)s'; - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = 'blob' - xhr.onload = function(e){ - var urlCreator = window.URL || window.webkitURL; - var imageUrl = urlCreator.createObjectURL(this.response); - document.getElementById('%(id)s').src = imageUrl; - } - xhr.send(); - """ % {'id': id(self), 'frame_index':i}) - - def get_image_data(self, update_index): - headers = {'Content-type': self.mimetype if self.mimetype else 'application/octet-stream'} - return [self.imagedata, headers] - - -# ---------------------------------------------------------------------- # -# Graph # -# ---------------------------------------------------------------------- # -class Graph(Element): - def __init__(self, canvas_size, graph_bottom_left, graph_top_right, background_color=None, pad=None, - change_submits=False, drag_submits=False, size_px=(None,None), enable_events=False, key=None, visible=True, disabled=False, tooltip=None): - ''' - Graph Element - :param canvas_size: - :param graph_bottom_left: - :param graph_top_right: - :param background_color: - :param pad: - :param key: - :param tooltip: - ''' - self.CanvasSize = canvas_size - self.BottomLeft = graph_bottom_left - self.TopRight = graph_top_right - self.ChangeSubmits = change_submits or enable_events - self.DragSubmits = drag_submits - self.ClickPosition = (None, None) - self.MouseButtonDown = False - self.Disabled = disabled - self.Widget = None # type: remi.gui.Svg - self.SvgGroup = None # type: remi.gui.SvgSubcontainer - super().__init__(ELEM_TYPE_GRAPH, size=canvas_size, size_px=size_px, visible=visible, background_color=background_color, pad=pad, tooltip=tooltip, key=key) - return - - def _convert_xy_to_canvas_xy(self, x_in, y_in): - if None in (x_in, y_in): - return None, None - scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) - scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) - new_x = 0 + scale_x * (x_in - self.BottomLeft[0]) - new_y = self.CanvasSize[1] + scale_y * (y_in - self.BottomLeft[1]) - return new_x, new_y - - def _convert_canvas_xy_to_xy(self, x_in, y_in): - if None in (x_in, y_in): - return None, None - x_in, y_in = int(x_in), int(y_in) - scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) - scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) - new_x = x_in / scale_x + self.BottomLeft[0] - new_y = (y_in - self.CanvasSize[1]) / scale_y + self.BottomLeft[1] - return int(new_x), int(new_y) - - def DrawLine(self, point_from, point_to, color='black', width=1): - if point_from == (None, None) or color is None: - return - converted_point_from = self._convert_xy_to_canvas_xy(point_from[0], point_from[1]) - converted_point_to = self._convert_xy_to_canvas_xy(point_to[0], point_to[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - line = remi.gui.SvgLine(converted_point_from[0], converted_point_from[1], converted_point_to[0], converted_point_to[1]) - line.set_stroke(width, color) - self.SvgGroup.append([line,]) - return line - - def DrawPoint(self, point, size=2, color='black'): - if point == (None, None): - return - converted_point = self._convert_xy_to_canvas_xy(point[0], point[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - rpoint = remi.gui.SvgCircle(converted_point[0], converted_point[1], size) - rpoint.set_stroke(size, color) - rpoint.set_fill(color) - self.SvgGroup.append([rpoint,]) - return rpoint - - - def DrawCircle(self, center_location, radius, fill_color=None, line_color='black'): - if center_location == (None, None): - return - converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - rpoint = remi.gui.SvgCircle(converted_point[0], converted_point[1], radius=radius) - rpoint.set_fill(fill_color) - rpoint.set_stroke(color=line_color) - self.SvgGroup.append([rpoint,]) - return rpoint - - - def DrawOval(self, top_left, bottom_right, fill_color=None, line_color=None): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return - - - # def DrawArc(self, top_left, bottom_right, extent, start_angle, style=None, arc_color='black'): - # converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - # converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - # tkstyle = tk.PIESLICE if style is None else style - # if self._TKCanvas2 is None: - # print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - # print('Call Window.Finalize() prior to this operation') - # return None - # return - - def DrawRectangle(self, top_left, bottom_right, fill_color=None, line_color='black'): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - - rpoint = remi.gui.SvgRectangle(converted_top_left[0], converted_top_left[1], abs(converted_bottom_right[0]-converted_top_left[0]), abs(converted_top_left[1] - converted_bottom_right[1])) - rpoint.set_stroke(width=1, color=line_color) - if fill_color is not None: - rpoint.set_fill(fill_color) - else: - rpoint.set_fill('transparent') - self.SvgGroup.append([rpoint,]) - return rpoint - - - - def DrawText(self, text, location, color='black', font=None, angle=0): - text = str(text) - if location == (None, None): - return - converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - - rpoint = remi.gui.SvgText(converted_point[0], converted_point[1], text) - self.SvgGroup.append([rpoint,]) - # self.SvgGroup.redraw() - return rpoint - - - def DrawImage(self, data=None, image_source=None, location=(None, None), size=(100, 100)): - if location == (None, None): - return - if data is not None: - image_source = data.decode('utf-8') if type(data) is bytes else data - converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - - rpoint = remi.gui.SvgImage('', converted_point[0], converted_point[0], size[0], size[1]) - - if type(image_source) is bytes or len(image_source) > 200: - # rpoint.set_image("data:image/svg;base64,%s"%image_source) - rpoint.image_data = "data:image/svg;base64,%s"%image_source - else: - mimetype, encoding = mimetypes.guess_type(image_source) - with open(image_source, 'rb') as f: - data = f.read() - b64 = base64.b64encode(data) - b64_str = b64.decode("utf-8") - image_string = "data:image/svg;base64,%s"%b64_str - # rpoint.set_image(image_string) - rpoint.image_data = image_string - self.SvgGroup.append([rpoint,]) - rpoint.redraw() - self.SvgGroup.redraw() - return rpoint - - def Erase(self): - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - self.Widget.empty() - self.SvgGroup = remi.gui.SvgSubcontainer(0, 0, "100%", "100%") - self.Widget.append(self.SvgGroup) - - def Update(self, background_color): - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - if self.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): - self.Widget.style['background-color'] = self.BackgroundColor - - def Move(self, x_direction, y_direction): - # TODO - IT's still not working yet! I'm trying!! - - # self.MoveFigure(self.SvgGroup, x_direction,y_direction) - # return - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - if self.Widget is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - print(self.SvgGroup.attributes) - cur_x = float(self.SvgGroup.attributes['x']) - cur_y = float(self.SvgGroup.attributes['y']) - self.SvgGroup.set_position(cur_x - x_direction,cur_y - y_direction) - self.SvgGroup.redraw() - - - def Relocate(self, x, y): - shift_converted = self._convert_xy_to_canvas_xy(x, y) - if self.Widget is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - # figure.empty() - self.SvgGroup.set_position(shift_converted[0], shift_converted[1]) - self.SvgGroup.redraw() - - - def MoveFigure(self, figure, x_direction, y_direction): - figure = figure # type: remi.gui.SvgCircle - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - if figure is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - print(figure.attributes) - try: - cur_x = float(figure.attributes['x']) - cur_y = float(figure.attributes['y']) - figure.set_position(cur_x - x_direction,cur_y - y_direction) - except: - cur_x1 = float(figure.attributes['x1']) - cur_x2 = float(figure.attributes['x2']) - cur_y1 = float(figure.attributes['y1']) - cur_y2 = float(figure.attributes['y2']) - figure.set_coords(cur_x1-x_direction, cur_y1-y_direction, cur_x2-x_direction, cur_y2-x_direction) - figure.redraw() - - def RelocateFigure(self, figure, x, y): - figure = figure #type: remi.gui.SvgCircle - zero_converted = self._convert_xy_to_canvas_xy(0, 0) - shift_converted = self._convert_xy_to_canvas_xy(x, y) - shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1]) - if figure is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - # figure.empty() - figure.set_position(shift_converted[0], shift_converted[1]) - figure.redraw() - - - def DeleteFigure(self, figure): - figure = figure # type: remi.gui.SvgCircle - if figure is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - self.SvgGroup.remove_child(figure) - del figure - - def change_coordinates(self, graph_bottom_left, graph_top_right): - """ - Changes the corrdinate system to a new one. The same 2 points in space are used to define the coorinate - system - the bottom left and the top right values of your graph. - - :param graph_bottom_left: Tuple[int, int] (x,y) The bottoms left corner of your coordinate system - :param graph_top_right: Tuple[int, int] (x,y) The top right corner of your coordinate system - """ - self.BottomLeft = graph_bottom_left - self.TopRight = graph_top_right - - - def _MouseDownCallback(self, widget, x,y, *args): - # print(f'Mouse down {x,y}') - self.MouseButtonDown = True - - def _MouseUpCallback(self, widget, x,y, *args): - self.ClickPosition = self._convert_canvas_xy_to_xy(int(x), int(y)) - self.MouseButtonDown = False - if self.ChangeSubmits: - # self.ClickPosition = (None, None) - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - # def ClickCallback(self, emitter, x, y): - def ClickCallback(self, widget:remi.gui.Svg, *args): - return - self.ClickPosition = (None, None) - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - def _DragCallback(self, emitter, x, y): - if not self.MouseButtonDown: # only return drag events when mouse is down - return - # print(f'In Drag Callback') - self.ClickPosition = self._convert_canvas_xy_to_xy(x, y) - # print(f'Position {self.ClickPosition}') - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - - - click_callback = ClickCallback - delete_figure = DeleteFigure - draw_circle = DrawCircle - draw_image = DrawImage - draw_line = DrawLine - draw_oval = DrawOval - draw_point = DrawPoint - draw_rectangle = DrawRectangle - draw_text = DrawText - erase = Erase - move = Move - move_figure = MoveFigure - relocate = Relocate - relocate_figure = RelocateFigure - update = Update - - -# ---------------------------------------------------------------------- # -# Frame # -# ---------------------------------------------------------------------- # - -# First the REMI implementation of a frame - -class CLASSframe( remi.gui.VBox ): - def __init__(self, title, *args, **kwargs): - super( CLASSframe, self ).__init__(*args, **kwargs) - self.style.update({"overflow":"visible","border-width":"1px","border-style":"solid","border-color":"#7d7d7d"}) - self.frame_label = remi.gui.Label('frame label') - self.frame_label.style.update({"position":"relative","overflow":"auto","background-color":"#ffffff","border-width":"1px","border-style":"solid","top":"-7px","width":"0px","height":"0px","left":"10px"}) - self.append(self.frame_label,'frame_label') - self.set_title(title) - - def set_title(self, title): - self.frame_label.set_text(title) - - -class Frame(Element): - def __init__(self, title, layout, title_color=None, background_color=None, title_location=None, - relief=DEFAULT_FRAME_RELIEF, element_justification='left', size=(None, None), font=None, pad=None, border_width=None, key=None, - tooltip=None): - ''' - Frame Element - :param title: - :param layout: - :param title_color: - :param background_color: - :param title_location: - :param relief: - :param size: - :param font: - :param pad: - :param border_width: - :param key: - :param tooltip: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - # self.ParentForm = None - self.TKFrame = None - self.Title = title - self.Relief = relief - self.TitleLocation = title_location - self.BorderWidth = border_width - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.Justification = 'left' - self.ElementJustification = element_justification - self.Widget = None # type: CLASSframe - - - - self.Layout(layout) - - super().__init__(ELEM_TYPE_FRAME, background_color=background_color, text_color=title_color, size=size, - font=font, pad=pad, key=key, tooltip=tooltip) - return - - def AddRow(self, *args): - ''' Parms are a variable number of Elements ''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - add_row = AddRow - layout = Layout - - -# ---------------------------------------------------------------------- # -# Separator # -# Routes stdout, stderr to a scrolled window # -# ---------------------------------------------------------------------- # -class VerticalSeparator(Element): - def __init__(self, pad=None): - ''' - VerticalSeperator - A separator that spans only 1 row in a vertical fashion - :param pad: - ''' - self.Orientation = 'vertical' # for now only vertical works - - super().__init__(ELEM_TYPE_SEPARATOR, pad=pad) - - -VSeperator = VerticalSeparator -VSeparator = VerticalSeparator -VSep = VerticalSeparator - - -# ---------------------------------------------------------------------- # -# Tab # -# ---------------------------------------------------------------------- # -class Tab(Element): - def __init__(self, title, layout, title_color=None, background_color=None, font=None, pad=None, disabled=False, element_justification='left', border_width=None, key=None, tooltip=None): - ''' - Tab Element - :param title: - :param layout: - :param title_color: - :param background_color: - :param font: - :param pad: - :param disabled: - :param border_width: - :param key: - :param tooltip: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.TKFrame = None - self.Title = title - self.BorderWidth = border_width - self.Disabled = disabled - self.ParentNotebook = None - self.Justification = 'left' - self.ElementJustification = element_justification - self.TabID = None - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.Widget = None # type: remi.gui.HBox - self._Layout(layout) - - super().__init__(ELEM_TYPE_TAB, background_color=background_color, text_color=title_color, font=font, pad=pad, - key=key, tooltip=tooltip) - return - - def _AddRow(self, *args): - ''' Parms are a variable number of Elements ''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def _Layout(self, rows): - for row in rows: - self._AddRow(*row) - return self - - # def Update(self, disabled=None): # TODO Disable / enable of tabs is not complete - # print('*** Tab.Update is not implemented ***') - # return - # if disabled is None: - # return - # self.Disabled = disabled - # state = 'disabled' if disabled is True else 'normal' - # self.ParentNotebook.tab(self.TabID, state=state) - # return self - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - -# ---------------------------------------------------------------------- # -# TabGroup # -# ---------------------------------------------------------------------- # -class TabGroup(Element): - def __init__(self, layout, tab_location=None, title_color=None, selected_title_color=None, background_color=None, - font=None, change_submits=False, enable_events=False,pad=None, border_width=None, theme=None, key=None, tooltip=None, visible=True): - ''' - TabGroup Element - :param layout: - :param tab_location: - :param title_color: - :param selected_title_color: - :param background_color: - :param font: - :param change_submits: - :param pad: - :param border_width: - :param theme: - :param key: - :param tooltip: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.SelectedTitleColor = selected_title_color - self.Rows = [] - self.TKNotebook = None - self.Widget = None # type: remi.gui.TabBox - self.Justification = 'left' - self.TabCount = 0 - self.BorderWidth = border_width - self.Theme = theme - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.ChangeSubmits = enable_events or change_submits - self.TabLocation = tab_location - self.Visible = visible - self.Disabled = False - self._Layout(layout) - - super().__init__(ELEM_TYPE_TAB_GROUP, background_color=background_color, text_color=title_color, font=font, - pad=pad, key=key, tooltip=tooltip) - return - - def _AddRow(self, *args): - ''' Parms are a variable number of Elements ''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def _Layout(self, rows): - for row in rows: - self._AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def FindKeyFromTabName(self, tab_name): - for row in self.Rows: - for element in row: - if element.Title == tab_name: - return element.Key - return None - - find_key_from_tab_name = FindKeyFromTabName - - -# ---------------------------------------------------------------------- # -# Slider # -# ---------------------------------------------------------------------- # -class Slider(Element): - def __init__(self, range=(None, None), default_value=None, resolution=None, tick_interval=None, orientation=None, - border_width=None, relief=None, change_submits=False, enable_events=False, disabled=False, size=(None, None), font=None, - background_color=None, text_color=None, key=None, pad=None, tooltip=None, visible=True, size_px=(None,None)): - """ - - :param range: - :param default_value: - :param resolution: - :param tick_interval: - :param orientation: - :param border_width: - :param relief: - :param change_submits: - :param enable_events: - :param disabled: - :param visible: - :param size_px: - """ - self.TKScale = None - self.Range = (1, 10) if range == (None, None) else range - self.DefaultValue = self.Range[0] if default_value is None else default_value - self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION - self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF - self.Resolution = 1 if resolution is None else resolution - self.ChangeSubmits = change_submits or enable_events - self.Disabled = disabled - self.TickInterval = tick_interval - temp_size = size - if temp_size == (None, None): - temp_size = (200, 20) if self.Orientation.startswith('h') else (200, 20) - elif size[0] is not None and size[0] < 100: - temp_size = size[0]*10, size[1]*3 - self.Widget = None # type: remi.gui.Slider - - super().__init__(ELEM_TYPE_INPUT_SLIDER, size=temp_size, font=font, background_color=background_color, - text_color=text_color, key=key, pad=pad, tooltip=tooltip, visible=visible, size_px=size_px) - return - - def Update(self, value=None, range=(None, None), disabled=None, visible=None): - if value is not None: - self.Widget.set_value(value) - self.DefaultValue = value - if range != (None, None): - self.Widget.attributes['min'] = '{}'.format(range[0]) - self.Widget.attributes['max'] = '{}'.format(range[1]) - super().Update(self.Widget, disabled=disabled, visible=visible) - - def _SliderCallback(self, widget:remi.Widget, value): - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - - update = Update - -# -# ---------------------------------------------------------------------- # -# Column # -# ---------------------------------------------------------------------- # -class Column(Element): - def __init__(self, layout, background_color=None, size=(None, None), pad=None, scrollable=False, vertical_scroll_only=False, element_justification='left', key=None): - ''' - Column Element - :param layout: - :param background_color: - :param size: - :param pad: - :param scrollable: - :param key: - ''' - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.TKFrame = None - self.Scrollable = scrollable - self.VerticalScrollOnly = vertical_scroll_only - self.ElementJustification = element_justification - # self.ImageFilename = image_filename - # self.ImageData = image_data - # self.ImageSize = image_size - # self.ImageSubsample = image_subsample - # bg = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - - self.Layout(layout) - - super().__init__(ELEM_TYPE_COLUMN, background_color=background_color, size=size, pad=pad, key=key) - return - - def AddRow(self, *args): - ''' Parms are a variable number of Elements ''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num, col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - add_row = AddRow - layout = Layout - - -Col = Column - - -# ---------------------------------------------------------------------- # -# Menu # -# ---------------------------------------------------------------------- # -class Menu(Element): - def __init__(self, menu_definition, background_color=COLOR_SYSTEM_DEFAULT, text_color=None, size=(None, None), tearoff=False, pad=None, key=None, disabled=False, font=None): - ''' - Menu Element - :param menu_definition: - :param background_color: - :param size: - :param tearoff: - :param pad: - :param key: - ''' - back_color = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.MenuDefinition = menu_definition - self.TKMenu = None - self.Tearoff = tearoff - self.Widget = None # type: remi.gui.MenuBar - self.MenuItemChosen = None - self.Disabled = disabled - - super().__init__(ELEM_TYPE_MENUBAR, background_color=back_color, text_color=text_color, size=size, pad=pad, key=key, font=font) - return - - - - def _ChangedCallbackMenu(self, widget, *user_data): - widget = widget # type: remi.gui.MenuItem - chosen = user_data[0] - self.MenuItemChosen = chosen - self.ParentForm.LastButtonClicked = chosen - self.ParentForm.MessageQueue.put(chosen) - - -# ---------------------------------------------------------------------- # -# Table # -# ---------------------------------------------------------------------- # -class Table(Element): - def __init__(self, values, headings=None, visible_column_map=None, col_widths=None, def_col_width=10, - auto_size_columns=True, max_col_width=20, select_mode=None, display_row_numbers=False, row_header_text='Row', starting_row_num=0, num_rows=None, row_height=None, font=None, justification='right', text_color=None, background_color=None, alternating_row_color=None, row_colors=None, vertical_scroll_only=True, disabled=False, - size=(None, None), change_submits=False, enable_events=False, bind_return_key=False, pad=None, key=None, tooltip=None, right_click_menu=None, visible=True, size_px=(None, None)): - ''' - Table - :param values: - :param headings: - :param visible_column_map: - :param col_widths: - :param def_col_width: - :param auto_size_columns: - :param max_col_width: - :param select_mode: - :param display_row_numbers: - :param num_rows: - :param row_height: - :param font: - :param justification: - :param text_color: - :param background_color: - :param alternating_row_color: - :param size: - :param change_submits: - :param enable_events: - :param bind_return_key: - :param pad: - :param key: - :param tooltip: - :param right_click_menu: - :param visible: - ''' - self.Values = values - self.ColumnHeadings = headings - self.ColumnsToDisplay = visible_column_map - self.ColumnWidths = col_widths - self.MaxColumnWidth = max_col_width - self.DefaultColumnWidth = def_col_width - self.AutoSizeColumns = auto_size_columns - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.TextColor = text_color - self.Justification = justification - self.InitialState = None - self.SelectMode = select_mode - self.DisplayRowNumbers = display_row_numbers - self.NumRows = num_rows if num_rows is not None else size[1] - self.RowHeight = row_height - self.TKTreeview = None - self.AlternatingRowColor = alternating_row_color - self.VerticalScrollOnly = vertical_scroll_only - self.SelectedRows = [] - self.ChangeSubmits = change_submits or enable_events - self.BindReturnKey = bind_return_key - self.StartingRowNumber = starting_row_num # When displaying row numbers, where to start - self.RowHeaderText = row_header_text - self.RightClickMenu = right_click_menu - self.RowColors = row_colors - self.Disabled = disabled - self.SelectedItem = None - self.SelectedRow = None - self.Widget = None # type: remi.Table - - super().__init__(ELEM_TYPE_TABLE, text_color=text_color, background_color=background_color, font=font, - size=size, pad=pad, key=key, tooltip=tooltip, visible=visible, size_px=size_px) - return - - def Update(self, values=None): - print('*** Table Update not yet supported ***') - return - if values is not None: - children = self.TKTreeview.get_children() - for i in children: - self.TKTreeview.detach(i) - self.TKTreeview.delete(i) - children = self.TKTreeview.get_children() - # self.TKTreeview.delete(*self.TKTreeview.get_children()) - for i, value in enumerate(values): - if self.DisplayRowNumbers: - value = [i + self.StartingRowNumber] + value - id = self.TKTreeview.insert('', 'end', text=i, iid=i + 1, values=value, tag=i % 2) - if self.AlternatingRowColor is not None: - self.TKTreeview.tag_configure(1, background=self.AlternatingRowColor) - self.Values = values - self.SelectedRows = [] - - - def _on_table_row_click(self, table, row, item): - # self.SelectedRow = row # type: remi.gui.TableRow - self.SelectedItem = item.get_text() - index = -1 - # each widget (and specifically in this case the table) has a _render_children_list attribute that - # is an ordered list of the children keys - # first, we search for the row in the children dictionary - for key, value in table.children.items(): - if value == row: - # if the row is found, we get the index in the ordered list - index = table._render_children_list.index(key) - break - self.SelectedRow = index - if self.ChangeSubmits: - self.ParentForm.LastButtonClicked = self.Key if self.Key is not None else '' - self.ParentForm.MessageQueue.put(self.ParentForm.LastButtonClicked) - else: - self.ParentForm.LastButtonClicked = '' - - - - -# ---------------------------------------------------------------------- # -# Tree # -# ---------------------------------------------------------------------- # -class Tree(Element): - def __init__(self, data=None, headings=None, visible_column_map=None, col_widths=None, col0_width=10, - def_col_width=10, auto_size_columns=True, max_col_width=20, select_mode=None, show_expanded=False, - change_submits=False, font=None, - justification='right', text_color=None, background_color=None, num_rows=None, pad=None, key=None, - tooltip=None): - ''' - Tree Element - :param headings: - :param visible_column_map: - :param col_widths: - :param def_col_width: - :param auto_size_columns: - :param max_col_width: - :param select_mode: - :param font: - :param justification: - :param text_color: - :param background_color: - :param num_rows: - :param pad: - :param key: - :param tooltip: - ''' - self.TreeData = data - self.ColumnHeadings = headings - self.ColumnsToDisplay = visible_column_map - self.ColumnWidths = col_widths - self.MaxColumnWidth = max_col_width - self.DefaultColumnWidth = def_col_width - self.AutoSizeColumns = auto_size_columns - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.TextColor = text_color - self.Justification = justification - self.InitialState = None - self.SelectMode = select_mode - self.ShowExpanded = show_expanded - self.NumRows = num_rows - self.Col0Width = col0_width - self.TKTreeview = None - self.SelectedRows = [] - self.ChangeSubmits = change_submits - - print('*** Tree Element not yet supported ***') - - super().__init__(ELEM_TYPE_TREE, text_color=text_color, background_color=background_color, font=font, pad=pad, - key=key, tooltip=tooltip) - - - def add_treeview_data(self, node): - # print(f'Inserting {node.key} under parent {node.parent}') - if node.key != '': - self.TKTreeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, - open=self.ShowExpanded) - for node in node.children: - self.add_treeview_data(node) - - def Update(self, values=None, key=None, value=None, text=None): - print('*** Tree Element not yet supported ***') - if values is not None: - children = self.TKTreeview.get_children() - for i in children: - self.TKTreeview.detach(i) - self.TKTreeview.delete(i) - children = self.TKTreeview.get_children() - self.TreeData = values - self.add_treeview_data(self.TreeData.root_node) - self.SelectedRows = [] - if key is not None: - item = self.TKTreeview.item(key) - if value is not None: - self.TKTreeview.item(key, values=value) - if text is not None: - self.TKTreeview.item(key, text=text) - item = self.TKTreeview.item(key) - return self - - update = Update - - -class TreeData(object): - class Node(object): - def __init__(self, parent, key, text, values): - self.parent = parent - self.children = [] - self.key = key - self.text = text - self.values = values - - def _Add(self, node): - self.children.append(node) - - def __init__(self): - self.tree_dict = {} - self.root_node = self.Node("", "", 'root', []) - self.tree_dict[""] = self.root_node - - def _AddNode(self, key, node): - self.tree_dict[key] = node - - def Insert(self, parent, key, text, values): - node = self.Node(parent, key, text, values) - self.tree_dict[key] = node - parent_node = self.tree_dict[parent] - parent_node._Add(node) - - def __repr__(self): - return self._NodeStr(self.root_node, 1) - - def _NodeStr(self, node, level): - return '\n'.join( - [str(node.key) + ' : ' + str(node.text)] + - [' ' * 4 * level + self._NodeStr(child, level + 1) for child in node.children]) - - insert = Insert - -# ---------------------------------------------------------------------- # -# Error Element # -# ---------------------------------------------------------------------- # -class ErrorElement(Element): - def __init__(self, key=None): - ''' - Error Element - :param key: - ''' - self.Key = key - - super().__init__(ELEM_TYPE_ERROR, key=key) - return - - def Update(self, *args, **kwargs): - PopupError('Keyword error in Update', - 'You need to stop this madness and check your spelling', - 'Bad key = {}'.format(self.Key), - 'Your bad line of code may resemble this:', - 'window.FindElement("{}")'.format(self.Key)) - return self - - def Get(self): - return 'This is NOT a valid Element!\nSTOP trying to do things with it or I will have to crash at some point!' - - get = Get - update = Update - - -# ------------------------------------------------------------------------- # -# Window CLASS # -# ------------------------------------------------------------------------- # -class Window: - - _NumOpenWindows = 0 - user_defined_icon = None - hidden_master_root = None - QTApplication = None - active_popups = {} - highest_level_app = None - stdout_is_rerouted = False - stdout_string_io = None - stdout_location = None - port_number = 6900 - active_windows = [ ] # type: [Window] - App = None # type: remi.App - - def __init__(self, title, layout=None, default_element_size=DEFAULT_ELEMENT_SIZE, default_button_element_size=(None, None), - auto_size_text=None, auto_size_buttons=None, location=(None, None), size=(None, None), - element_padding=None, button_color=None, font=None, - progress_bar_color=(None, None), background_color=None, border_depth=None, auto_close=False, - auto_close_duration=None, icon=DEFAULT_BASE64_ICON, force_toplevel=False, - alpha_channel=1, return_keyboard_events=False, return_key_down_events=False, use_default_focus=True, text_justification=None, - no_titlebar=False, grab_anywhere=False, keep_on_top=False, resizable=True, disable_close=False,margins=(None, None), element_justification='left', - disable_minimize=False, background_image=None, finalize=False, - web_debug=False, web_ip='0.0.0.0', web_port=0, web_start_browser=True, web_update_interval=.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 index d77a194..bbcd716 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,111 @@ -# Shannon-for-Dummies +# Shannon's Equation for Dummies -Educational application +**Educational Web Application for Satellite Communications** -Exploration from Claude's Shannon initial theory to its practical application to satellite communications. +Exploration from Claude Shannon's 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. +## ๐Ÿš€ Stack -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). +- **Frontend**: [Streamlit](https://streamlit.io) with [Plotly](https://plotly.com) interactive charts +- **Backend**: Python 3.11+ with scientific libraries (numpy, scipy, itur, astropy) +- **Database**: SQLite for community contributions +- **Deployment**: Docker + docker-compose, designed for 24/7 multi-user operation -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 ... +## ๐Ÿ“ Project Structure -The value of the application is essentially in the background information accessed by clicking labels of all inputs / outputs. +``` +. +โ”œโ”€โ”€ app.py # Main Streamlit entry point +โ”œโ”€โ”€ core/ # Core business logic +โ”‚ โ”œโ”€โ”€ calculations.py # Shannon equations & satellite link budget +โ”‚ โ”œโ”€โ”€ database.py # SQLite contribution management +โ”‚ โ””โ”€โ”€ help_texts.py # Educational help content +โ”œโ”€โ”€ views/ # UI pages +โ”‚ โ”œโ”€โ”€ theory.py # Theoretical exploration (Shannon limit) +โ”‚ โ”œโ”€โ”€ real_world.py # Real-world link budget calculator +โ”‚ โ””โ”€โ”€ contributions.py # Community knowledge database +โ”œโ”€โ”€ .streamlit/config.toml # Streamlit configuration +โ”œโ”€โ”€ Dockerfile # Container image definition +โ”œโ”€โ”€ docker-compose.yml # Orchestration with nginx-proxy +โ””โ”€โ”€ requirements.txt # Python dependencies +``` -A Knowledge DB is coupled to the application for collaborative contributions on the subject opening technical discussions. At this stage the DB is local. +## ๐Ÿ› ๏ธ Local Development + +### Prerequisites +- Python 3.11+ +- pip + +### Setup + +```bash +# Create virtual environment +python3 -m venv .venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate + +# Install dependencies +pip install -r requirements.txt + +# Run the app +streamlit run app.py +``` + +The app will open at `http://localhost:8501` + +## ๐Ÿณ Docker Deployment + +### Build and run + +```bash +docker-compose up -d +``` + +The app will be available at `http://localhost:8080` + +### Environment + +- **Port**: 8080 (configurable in `.streamlit/config.toml`) +- **Health Check**: `/_stcore/health` +- **Data Persistence**: SQLite databases stored in `shannon_data` volume + +### Production Setup + +The `docker-compose.yml` is configured for nginx-proxy: +- Network: `nginx-proxy` +- Virtual host: `shannon.antopoid.com` +- HTTPS ready (with appropriate Let's Encrypt configuration) + +## ๐Ÿ“š Features + +### 1. Theoretical Exploration +- Shannon capacity calculation (C = BW ร— logโ‚‚(1 + C/N)) +- Bandwidth and power sensitivity analysis +- Bit rate factor maps +- Interactive Plotly graphs + +### 2. Real-World Link Budget +- Complete satellite link calculation +- ITU-R atmospheric attenuation models +- Receiver noise and baseband impairments +- Practical Shannon limits with penalties + +### 3. Community Contributions +- Collaborative knowledge database +- Search and filter capabilities +- Read/Write/Delete permissions + +## ๐ŸŽ“ Educational Value + +The application provides extensive background information accessible through help expanders on every input/output, covering: +- Shannon's theorem and its implications +- Satellite communication fundamentals +- Atmospheric propagation effects +- Modulation and coding schemes +- Real-world system design trade-offs + +## ๐Ÿ“ License & Author + +ยฉ 2021-2026 ยท Shannon Equation for Dummies +Created by JPC (February 2021) +Migrated to Streamlit (2026) diff --git a/Shannon.py b/Shannon.py deleted file mode 100644 index b46244b..0000000 --- a/Shannon.py +++ /dev/null @@ -1,1028 +0,0 @@ -''' ---------------------------------------------------------------------------------------------------------------- - -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 deleted file mode 100644 index 3c43740..0000000 --- a/Shannon.py.save +++ /dev/null @@ -1,1024 +0,0 @@ -''' ---------------------------------------------------------------------------------------------------------------- - -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 deleted file mode 100644 index d8f83d9..0000000 --- a/Shannon_Dict.py +++ /dev/null @@ -1,330 +0,0 @@ - -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/app.py b/app.py new file mode 100644 index 0000000..f87e16a --- /dev/null +++ b/app.py @@ -0,0 +1,159 @@ +""" +Shannon Equation for Dummies โ€” Streamlit Application + +Main entry point with sidebar navigation. +Designed for containerized deployment (Docker), multi-user, 24/7 operation. + +Run with: + streamlit run app.py --server.port 8080 --server.address 0.0.0.0 +""" + +import streamlit as st + + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Page Configuration (must be first Streamlit call) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +st.set_page_config( + page_title="Shannon's Equation for Dummies", + page_icon="๐Ÿ“ก", + layout="wide", + initial_sidebar_state="expanded", +) + + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Custom CSS for a modern, clean look +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +st.markdown(""" + +""", unsafe_allow_html=True) + + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Sidebar Navigation +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +with st.sidebar: + st.markdown("## ๐Ÿ“ก Shannon for Dummies") + st.caption("Educational Application โ€” AP Feb 2021") + st.divider() + + page = st.radio( + "Navigation", + options=["animation", "orbits", "sat_types", "theory", "real_world", "contributions"], + format_func=lambda x: { + "animation": "๐Ÿ“ก Satellite Link Animation", + "orbits": "๐ŸŒ GEO / MEO / LEO Orbits", + "sat_types": "๐Ÿ›ฐ๏ธ Satellite Missions & Types", + "theory": "๐Ÿงฎ Theoretical Exploration", + "real_world": "๐Ÿ›ฐ๏ธ Real World Link Budget", + "contributions": "๐Ÿ’ฌ Community Contributions", + }[x], + label_visibility="collapsed", + ) + + st.divider() + + st.markdown( + "**About**\n\n" + "Exploration from Claude Shannon's initial theory " + "to its practical application to satellite communications.\n\n" + "Built with [Streamlit](https://streamlit.io) ยท " + "[Plotly](https://plotly.com)" + ) + + st.caption("ยฉ 2021-2026 ยท Shannon Equation for Dummies") + + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Page Routing +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +if page == "animation": + from views.satellite_animation import render + render() +elif page == "orbits": + from views.orbits_animation import render + render() +elif page == "sat_types": + from views.satellite_types import render + render() +elif page == "theory": + from views.theory import render + render() +elif page == "real_world": + from views.real_world import render + render() +elif page == "contributions": + from views.contributions import render + render() diff --git a/command.txt b/command.txt deleted file mode 100644 index 6d4b06b..0000000 --- a/command.txt +++ /dev/null @@ -1,2 +0,0 @@ -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/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/__pycache__/__init__.cpython-313.pyc b/core/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..0cffbef Binary files /dev/null and b/core/__pycache__/__init__.cpython-313.pyc differ diff --git a/core/__pycache__/calculations.cpython-313.pyc b/core/__pycache__/calculations.cpython-313.pyc new file mode 100644 index 0000000..0e8aa4f Binary files /dev/null and b/core/__pycache__/calculations.cpython-313.pyc differ diff --git a/core/__pycache__/database.cpython-313.pyc b/core/__pycache__/database.cpython-313.pyc new file mode 100644 index 0000000..1247daf Binary files /dev/null and b/core/__pycache__/database.cpython-313.pyc differ diff --git a/core/__pycache__/help_texts.cpython-313.pyc b/core/__pycache__/help_texts.cpython-313.pyc new file mode 100644 index 0000000..12a3fd5 Binary files /dev/null and b/core/__pycache__/help_texts.cpython-313.pyc differ diff --git a/core/calculations.py b/core/calculations.py new file mode 100644 index 0000000..b64ea8c --- /dev/null +++ b/core/calculations.py @@ -0,0 +1,280 @@ +""" +Shannon Equation - Core Calculations Module + +All scientific computation functions extracted from the original Shannon.py. +These are pure functions with no UI dependency. +""" + +from math import log, pi, sqrt, cos, acos, atan +import numpy as np + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Fundamental Shannon Functions +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +def combine_cnr(*cnr_values: float) -> float: + """Combine multiple Carrier-to-Noise Ratios (in dB) into one equivalent C/N. + + Uses the summation of normalized noise variances: + 1/CNR_total = sum(1/CNR_i) + """ + ncr_linear = 0.0 + for cnr_db in cnr_values: + ncr_linear += 10 ** (-cnr_db / 10) + return -10 * log(ncr_linear, 10) + + +def shannon_capacity(bw: float = 36.0, cnr: float = 10.0, penalty: float = 0.0) -> float: + """Shannon channel capacity (bit rate in Mbps). + + Args: + bw: Bandwidth in MHz. + cnr: Carrier-to-Noise Ratio in dB. + penalty: Implementation penalty in dB. + """ + cnr_linear = 10 ** ((cnr - penalty) / 10) + return bw * log(1 + cnr_linear, 2) + + +def br_multiplier(bw_mul: float = 1.0, p_mul: float = 2.0, cnr: float = 10.0) -> float: + """Bit Rate multiplying factor when BW and Power are scaled.""" + cnr_linear = 10 ** (cnr / 10) + return bw_mul * log(1 + cnr_linear * p_mul / bw_mul, 2) / log(1 + cnr_linear, 2) + + +def shannon_points(bw: float = 36.0, cnr: float = 10.0): + """Compute key Shannon operating points. + + Returns: + (cnr_linear, br_infinity, c_n0, br_constrained) + """ + cnr_linear = 10 ** (cnr / 10) + c_n0 = cnr_linear * bw + br_infinity = c_n0 / log(2) + br_constrained = shannon_capacity(bw, cnr) + return cnr_linear, br_infinity, c_n0, br_constrained + + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Satellite Link Budget Calculations +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +def compute_satellite_link( + freq_ghz: float, + hpa_power_w: float, + sat_loss_db: float, + sat_cir_list: list[float], + sat_beam_deg: float, + gain_offset_db: float, + sat_alt_km: float, + sat_lat: float, + sat_lon: float, + gs_lat: float, + gs_lon: float, + availability_pct: float, +) -> dict: + """Compute all satellite link parameters. + + Returns a dict with all computed values. + """ + import itur + + R_EARTH = 6378 # km + SAT_ANT_EFF = 0.65 + + # Signal power after losses + sig_power = hpa_power_w * 10 ** (-sat_loss_db / 10) + + # Satellite antenna + wavelength = 300e6 / freq_ghz / 1e9 # meters + sat_gain_linear = SAT_ANT_EFF * (pi * 70 / sat_beam_deg) ** 2 + sat_gain_linear *= 10 ** (-gain_offset_db / 10) + + # EIRP + eirp_linear = sig_power * sat_gain_linear + + # Satellite C/I + sat_cir = combine_cnr(*sat_cir_list) + + # Path geometry + path_length = sqrt( + sat_alt_km ** 2 + + 2 * R_EARTH * (R_EARTH + sat_alt_km) + * (1 - cos(np.radians(sat_lat - gs_lat)) * cos(np.radians(sat_lon - gs_lon))) + ) + + phi = acos(cos(np.radians(sat_lat - gs_lat)) * cos(np.radians(sat_lon - gs_lon))) + if phi > 0: + elevation = float(np.degrees( + atan((cos(phi) - R_EARTH / (R_EARTH + sat_alt_km)) / sqrt(1 - cos(phi) ** 2)) + )) + else: + elevation = 90.0 + + # Atmospheric attenuation + if elevation <= 0: + atm_loss_db = 999.0 + else: + atm_loss_db = float( + itur.atmospheric_attenuation_slant_path( + gs_lat, gs_lon, freq_ghz, elevation, 100 - availability_pct, 1 + ).value + ) + + # Path dispersion + path_loss_linear = 4 * pi * (path_length * 1000) ** 2 + free_space_loss_linear = (4 * pi * path_length * 1000 / wavelength) ** 2 + + # PFD + pfd_linear = eirp_linear / path_loss_linear * 10 ** (-atm_loss_db / 10) + + return { + "sig_power": sig_power, + "wavelength": wavelength, + "sat_gain_linear": sat_gain_linear, + "eirp_linear": eirp_linear, + "sat_cir": sat_cir, + "path_length": path_length, + "elevation": elevation, + "atm_loss_db": atm_loss_db, + "path_loss_linear": path_loss_linear, + "free_space_loss_linear": free_space_loss_linear, + "pfd_linear": pfd_linear, + } + + +def compute_receiver( + pfd_linear: float, + atm_loss_db: float, + wavelength: float, + cpe_ant_d: float, + cpe_t_clear: float, +) -> dict: + """Compute receiver-side parameters.""" + CPE_ANT_EFF = 0.6 + K_BOLTZ = 1.38e-23 # J/K + + cpe_t_att = (cpe_t_clear - 40) + 40 * 10 ** (-atm_loss_db / 10) + 290 * (1 - 10 ** (-atm_loss_db / 10)) + + cpe_ae = pi * cpe_ant_d ** 2 / 4 * CPE_ANT_EFF + cpe_gain_linear = (pi * cpe_ant_d / wavelength) ** 2 * CPE_ANT_EFF + cpe_g_t = 10 * log(cpe_gain_linear / cpe_t_att, 10) + + rx_power = pfd_linear * cpe_ae + n0 = K_BOLTZ * cpe_t_att + c_n0_hz = rx_power / n0 + c_n0_mhz = c_n0_hz / 1e6 + + br_infinity = c_n0_mhz / log(2) + + # Spectral efficiency points + bw_spe_1 = c_n0_mhz + bw_spe_double = c_n0_mhz / (2 ** 2 - 1) + + br_spe_1 = bw_spe_1 + br_spe_double = bw_spe_double * 2 + + return { + "cpe_ae": cpe_ae, + "cpe_gain_linear": cpe_gain_linear, + "cpe_g_t": cpe_g_t, + "cpe_t_att": cpe_t_att, + "rx_power": rx_power, + "n0": n0, + "c_n0_hz": c_n0_hz, + "c_n0_mhz": c_n0_mhz, + "br_infinity": br_infinity, + "bw_spe_1": bw_spe_1, + "br_spe_1": br_spe_1, + "br_spe_double": br_spe_double, + } + + +def compute_baseband( + c_n0_mhz: float, + br_infinity: float, + bw_spe_1: float, + sat_cir: float, + bandwidth: float, + rolloff: float, + overheads: float, + cnr_imp_list: list[float], + penalties: float, +) -> dict: + """Compute baseband processing results.""" + cnr_imp = combine_cnr(*cnr_imp_list) + + cnr_spe_1 = 0.0 # dB + cnr_bw = cnr_spe_1 + 10 * log(bw_spe_1 / bandwidth, 10) + bw_nyq = bandwidth / (1 + rolloff / 100) + cnr_nyq = cnr_spe_1 + 10 * log(bw_spe_1 / bw_nyq, 10) + cnr_rcv = combine_cnr(cnr_nyq, cnr_imp, sat_cir) + + br_nyq = shannon_capacity(bw_nyq, cnr_nyq) + br_rcv = shannon_capacity(bw_nyq, cnr_rcv, penalties) + br_rcv_higher = br_rcv / (1 + overheads / 100) + + spe_nyq = br_nyq / bandwidth + bits_per_symbol = br_nyq / bw_nyq + spe_rcv = br_rcv / bandwidth + spe_higher = br_rcv_higher / bandwidth + + return { + "cnr_bw": cnr_bw, + "cnr_nyq": cnr_nyq, + "cnr_rcv": cnr_rcv, + "cnr_imp": cnr_imp, + "bw_nyq": bw_nyq, + "br_nyq": br_nyq, + "br_rcv": br_rcv, + "br_rcv_higher": br_rcv_higher, + "br_nyq_norm": br_nyq / br_infinity, + "br_rcv_norm": br_rcv / br_infinity, + "br_rcv_h_norm": br_rcv_higher / br_infinity, + "spe_nyq": spe_nyq, + "bits_per_symbol": bits_per_symbol, + "spe_rcv": spe_rcv, + "spe_higher": spe_higher, + "bandwidth": bandwidth, + "rolloff": rolloff, + "overheads": overheads, + "penalties": penalties, + } + + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Formatting Helpers +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +def fmt_br(br: float) -> str: + return f"{br:.1f} Mbps" + + +def fmt_power(p: float) -> str: + p_db = 10 * log(p, 10) + if 1 < p < 1e4: + return f"{p:.1f} W ยท {p_db:.1f} dBW" + elif 1e-3 < p <= 1: + return f"{p:.4f} W ยท {p_db:.1f} dBW" + else: + return f"{p:.1e} W ยท {p_db:.1f} dBW" + + +def fmt_pfd(p: float) -> str: + p_db = 10 * log(p, 10) + return f"{p:.1e} W/mยฒ ยท {p_db:.1f} dBW/mยฒ" + + +def fmt_psd(p: float) -> str: + p_db = 10 * log(p, 10) + return f"{p:.1e} W/MHz ยท {p_db:.1f} dBW/MHz" + + +def fmt_gain(g: float) -> str: + g_db = 10 * log(g, 10) + return f"{g:.1f} ยท {g_db:.1f} dBi" + + +def fmt_ploss(loss: float) -> str: + loss_db = 10 * log(loss, 10) + return f"{loss:.2e} mยฒ ยท {loss_db:.1f} dBmยฒ" diff --git a/core/database.py b/core/database.py new file mode 100644 index 0000000..a6c5e6c --- /dev/null +++ b/core/database.py @@ -0,0 +1,107 @@ +""" +Database module for managing user contributions. + +Uses parameterized queries (no SQL injection) and context managers. +""" + +import sqlite3 +import os +from datetime import datetime + + +DB_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data") + + +def _get_db_path(db_name: str) -> str: + os.makedirs(DB_DIR, exist_ok=True) + return os.path.join(DB_DIR, db_name) + + +def _init_db(db_path: str): + with sqlite3.connect(db_path) as conn: + conn.execute( + """CREATE TABLE IF NOT EXISTS contributions ( + num INTEGER PRIMARY KEY, + name TEXT NOT NULL, + title TEXT NOT NULL, + keywords TEXT, + text TEXT NOT NULL, + date TEXT NOT NULL, + password TEXT DEFAULT '' + )""" + ) + + +def write_contribution( + db_name: str, name: str, title: str, keywords: str, text: str, password: str = "" +) -> int: + """Write a new contribution. Returns the new contribution ID.""" + db_path = _get_db_path(db_name) + _init_db(db_path) + + with sqlite3.connect(db_path) as conn: + cursor = conn.cursor() + cursor.execute("SELECT COALESCE(MAX(num), 0) FROM contributions") + next_id = cursor.fetchone()[0] + 1 + + cursor.execute( + "INSERT INTO contributions (num, name, title, keywords, text, date, password) " + "VALUES (?, ?, ?, ?, ?, ?, ?)", + (next_id, name, title, keywords, text, datetime.now().strftime("%Y-%m-%d"), password), + ) + return next_id + + +def search_contributions( + db_name: str, + name_filter: str = "", + title_filter: str = "", + keywords_filter: str = "", + content_filter: str = "", + limit: int = 50, +) -> list[dict]: + """Search contributions with optional filters. Returns list of dicts.""" + db_path = _get_db_path(db_name) + if not os.path.isfile(db_path): + return [] + + _init_db(db_path) + + with sqlite3.connect(db_path) as conn: + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + cursor.execute( + """SELECT num, name, title, keywords, text, date, password + FROM contributions + WHERE name LIKE ? AND title LIKE ? AND keywords LIKE ? AND text LIKE ? + ORDER BY num DESC + LIMIT ?""", + ( + f"%{name_filter}%", + f"%{title_filter}%", + f"%{keywords_filter}%", + f"%{content_filter}%", + limit, + ), + ) + return [dict(row) for row in cursor.fetchall()] + + +def delete_contribution(db_name: str, num: int, password: str) -> bool: + """Delete a contribution if the password matches. Returns True on success.""" + db_path = _get_db_path(db_name) + if not os.path.isfile(db_path): + return False + + with sqlite3.connect(db_path) as conn: + cursor = conn.cursor() + cursor.execute( + "SELECT password FROM contributions WHERE num = ?", (num,) + ) + row = cursor.fetchone() + if row is None: + return False + if row[0] != password: + return False + cursor.execute("DELETE FROM contributions WHERE num = ?", (num,)) + return True diff --git a/core/help_texts.py b/core/help_texts.py new file mode 100644 index 0000000..cf630b2 --- /dev/null +++ b/core/help_texts.py @@ -0,0 +1,297 @@ +""" +Help texts for Shannon application. + +Cleaned-up version of Shannon_Dict.py, organized by section. +Unicode subscripts/superscripts replaced by readable equivalents for web display. +""" + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Panel 1: Theoretical Exploration +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +THEORY_HELP = { + "cnr": ( + "**Reference C/N [dB]**\n\n" + "Reference 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 measured in the reference " + "Channel Bandwidth.\n\n" + "The Carrier's power is often named Signal's Power and the Carrier to Noise Ratio " + "is often named Signal to Noise Ratio." + ), + "bw": ( + "**Reference BW [MHz]**\n\n" + "Reference Channel Bandwidth โ€” a key parameter of the communication channel.\n\n" + "This bandwidth is usually a degree of freedom of the system design, eventually " + "constrained by technological constraints and various kinds of frequency usage regulations." + ), + "c_n0": ( + "**Carrier Power to Noise Power Density Ratio: C/Nโ‚€**\n\n" + "Carrier's power (in Watts) divided by the Noise Spectral Power Density " + "(in Watts per MHz). The result's units are MHz." + ), + "br_inf": ( + "**Theoretical BR at infinite BW: 1.44ยทC/Nโ‚€**\n\n" + "Bit Rate theoretically achievable when the signal occupies an infinite Bandwidth. " + "This value is a useful asymptotic limit." + ), + "br_unit": ( + "**Theoretical BR at Spectral Efficiency = 1: C/Nโ‚€**\n\n" + "Bit Rate theoretically achievable at a Spectral Efficiency = 1: " + "Bit Rate in Mbps = Bandwidth in MHz.\n\n" + "The corresponding value, deduced from Shannon's formula, is given by C/Nโ‚€." + ), + "br_bw": ( + "**Theoretical BR at Reference (BW, C/N)**\n\n" + "Bit Rate theoretically achievable when the Bandwidth is constrained " + "to the given reference value." + ), + "cnr_lin": ( + "**C/N = C / (Nโ‚€ยทB)**\n\n" + "Reference Carrier to Noise Ratio (or Signal to Noise Ratio). " + "The C/N Ratio is usually given in dB: 10ยทlog(C/N).\n\n" + "Although the logarithm is convenient for many evaluations (multiplications become additions), " + "it's also good to consider the ratio itself (Linear Format) to get some physical sense " + "of the power ratio.\n\n" + "The Carrier to Noise Ratio in linear format is the value used in Shannon's formula." + ), + "br_mul": ( + "**Bit Rate Increase Factor**\n\n" + "Bit Rate multiplying factor achieved when the Bandwidth and the Power " + "are multiplied by a given set of values." + ), + "bw_mul": ( + "**BW Increase Factor**\n\n" + "Arbitrary multiplying factor applied to the Carrier's Bandwidth, for sensitivity analysis." + ), + "p_mul": ( + "**Power Increase Factor**\n\n" + "Arbitrary multiplying factor applied to the Carrier's Power, for sensitivity analysis." + ), + "shannon": ( + "**The Shannon Limit** allows to evaluate the theoretical capacity achievable over " + "a communication channel.\n\n" + "As a true genius, Claude Shannon founded communication theory, information theory " + "and more (click the Wikipedia button for more info).\n\n" + "This equation is fundamental for the evaluation of communication systems. " + "It is an apparently simple but extremely powerful tool to guide communication systems' designs.\n\n" + "This 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\n" + "In the satellite domain, DVB-S2x, using LDPC codes iteratively decoded (Turbo-Like), " + "is only 1 dB away from this limit." + ), + "advanced": ( + "**AWGN Channel Model**\n\n" + "The model assumes that the communication channel is **AWGN** (Additive White Gaussian Noise). " + "This noise is supposed to be random and white, meaning noise at a given time is independent " + "of noise at any other time โ€” implying a flat and infinite spectrum. " + "This noise is also supposed to be Gaussian.\n\n" + "Although these assumptions seem very strong, they accurately match the cases of interest. " + "Many impairments are actually non-linear and/or non-additive, but combining equivalent C/N " + "of all impairments as if they were fully AWGN is in most cases very accurate. " + "The reason is that the sum of random variables of unknown laws always tends to Gaussian, " + "and thermal noise is dominating, actually white and gaussian, and whitening the rest.\n\n" + "The tool accepts lists of comma-separated CNRs which will be combined in that way.\n\n" + "Overall, the Shannon Limit is a pretty convenient tool to predict the real performances " + "of communication systems." + ), + "help": ( + "**Recommendations for using the tool**\n\n" + "The first purpose of the tool is educational, allowing people to better understand " + "the physics of communications and the role of key parameters.\n\n" + "- Try multiple values in all the fields one by one\n" + "- Explore the graphs and try to understand the underlying physics\n" + "- The units are as explicit as possible to facilitate the exploration\n" + "- Click on the โ„น๏ธ icons to get information about each parameter" + ), +} + + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Panel 2: Real World (Satellite Link Budget) +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +REAL_WORLD_HELP = { + "freq": ( + "**Frequency [GHz]**\n\n" + "Frequency of the electromagnetic wave supporting the communication.\n\n" + "For satellite downlink, typical bands: L: 1.5 GHz, S: 2.2 GHz, C: 4 GHz, " + "Ku: 12 GHz, Ka: 19 GHz, Q: 40 GHz" + ), + "sat_alt": ( + "**Satellite Altitude [km]**\n\n" + "The position of the satellite is expressed in latitude, longitude, altitude. " + "A GEO satellite has a latitude of 0ยฐ and an altitude of 35786 km. " + "LEO satellites have altitudes lower than 2000 km. " + "MEO altitudes are between LEO and GEO (O3B constellation: 8063 km)." + ), + "sat_latlon": ( + "**Satellite Latitude and Longitude [ยฐ]**\n\n" + "The program doesn't simulate the orbit โ€” any satellite coordinates can be used. " + "A GEO satellite has a latitude of 0ยฐ." + ), + "gs_latlon": ( + "**Ground Station Lat, Lon [ยฐ]**\n\n" + "The position of the ground station affects link availability due to weather statistics " + "(tropical regions have very heavy rains attenuating signals at high frequencies). " + "It also impacts the elevation angle and overall path length.\n\n" + "๐Ÿ”— [Find coordinates](https://www.gps-coordinates.net)" + ), + "availability": ( + "**Link Availability [%]**\n\n" + "A high desired link availability corresponds to high signal attenuation: " + "only rare and severe weather events exceeding this attenuation can interrupt the link.\n\n" + "For example, at 99.9% availability, the attenuation considered is only exceeded 0.1% of the time." + ), + "path_length": ( + "**Path Length [km] @ Elevation [ยฐ]**\n\n" + "Distance from the satellite to the ground station and elevation angle. " + "Minimum path length = satellite altitude (elevation = 90ยฐ). " + "A negative elevation means the satellite is not visible." + ), + "atm_loss": ( + "**Atmospheric Attenuation [dB]**\n\n" + "The atmosphere affects radio wave propagation with attenuation from rain, clouds, " + "scintillation, multi-path, sand/dust storms, and atmospheric gases.\n\n" + "Typical attenuation exceeded 0.1% of the time in Europe from GEO:\n" + "- Ku: 2.5 dB\n- Ka: 6.9 dB\n- Q: 22 dB\n\n" + "Uses [ITU-Rpy](https://itu-rpy.readthedocs.io/en/latest/index.html)." + ), + "hpa": ( + "**HPA Output Power [W]**\n\n" + "Power of the High Power Amplifier at the satellite's last amplification stage. " + "Some satellites operate at saturation (DTH), others in multicarrier mode with " + "reduced power (3 dB Output Back Off is typical for VSAT)." + ), + "sat_beam": ( + "**Satellite Beam Diameter [ยฐ]**\n\n" + "Half-power beam width. Typical values: 0.4โ€“1.4ยฐ for GEO HTS, 3โ€“6ยฐ for GEO DTH." + ), + "gain_offset": ( + "**Gain Offset from Peak [dB]**\n\n" + "Simulates terminals not at beam peak. 3 dB = worst case in 3 dB beam (DTH). " + "1 dB = typical median performance for single feed per beam HTS." + ), + "losses": ( + "**Output Section Losses [dB]**\n\n" + "Signal loss between HPA and antenna (filters, waveguides, switches). " + "Typical: 2.5 dB for classical satellites, 1 dB for active antennas." + ), + "sat_cir": ( + "**Satellite C/I [dB]**\n\n" + "Signal impairments from satellite implementation: intermodulation, filtering, phase noise. " + "Supports comma-separated lists to include uplink C/N, interferences, etc." + ), + "output_power": "**Output Power [W]** โ€” Signal power at antenna output carrying user information.", + "sat_gain": ( + "**Satellite Antenna Gain**\n\n" + "Ratio between signal radiated on-axis vs. isotropic antenna. " + "Expressed in dBi (dB relative to isotropic antenna)." + ), + "eirp": ( + "**Equivalent Isotropic Radiated Power (EIRP)**\n\n" + "Product Power ร— Gain in Watts. Represents the power required for an isotropic antenna " + "to match the directive antenna's radiation in that direction." + ), + "path_loss": ( + "**Path Dispersion Loss**\n\n" + "Free-space propagation loss โ€” simply the surface of a sphere with radius = path length. " + "Not actual absorption, just geometric spreading." + ), + "pfd": ( + "**Power Flux Density**\n\n" + "Signal power per square meter at the terminal side. " + "Actual captured power = PFD ร— antenna effective area." + ), + "cpe_ant": ( + "**Customer Antenna Size [m]**\n\n" + "Parabolic antenna with state-of-the-art efficiency (~60%)." + ), + "cpe_temp": ( + "**Noise Temperature [K]**\n\n" + "Total receiver's clear-sky noise temperature. Includes all contributors: " + "receiver, sky, ground seen by antenna. Default of 120 K is a reasonable typical value." + ), + "cpe_gain": ( + "**Antenna Effective Area and G/T**\n\n" + "G/T is the figure of merit of a receive antenna: Gain / Noise Temperature. " + "In case of rain, the signal is punished twice: attenuation weakens it and " + "the rain attenuator generates additional noise." + ), + "rx_power": "**RX Power** โ€” Extremely small power before amplification. This is 'C' in Shannon's equation.", + "n0": "**Noise Power Density Nโ‚€** โ€” Noise Spectral Power Density of the radio front end.", + "br_inf": ( + "**Bit Rate at infinite BW** โ€” Asymptotic limit: 1.443ยทC/Nโ‚€. Never achieved in practice." + ), + "br_unit": "**Bit Rate at Spectral Efficiency=1** โ€” Bandwidth = Bit Rate and C/N = 0 dB.", + "br_double": ( + "**Bit Rate at Spectral Efficiency=2** โ€” Bandwidth-efficient operating point " + "(BW = 0.5 ร— BR), often considered typical." + ), + "bandwidth": ( + "**Occupied Bandwidth [MHz]**\n\n" + "Bandwidth occupied by the communication channel โ€” a key degree of freedom in system design." + ), + "rolloff": ( + "**Nyquist Filter Rolloff [%]**\n\n" + "Excess bandwidth required beyond the theoretical Nyquist minimum. " + "The Nyquist filter avoids inter-symbol interference." + ), + "cir": ( + "**Receiver C/I [dB]**\n\n" + "Signal impairments from terminal: phase noise, quantization, synchronization errors. " + "Supports comma-separated lists." + ), + "penalty": ( + "**Implementation Penalty [dB]**\n\n" + "DVB-S2x with LDPC codes is typically 1 dB from Shannon Limit. " + "With 0.5 dB margin, a total of 1.5 dB is typical." + ), + "overhead": ( + "**Higher Layers Overhead [%]**\n\n" + "Encapsulation cost (IP over DVB-S2x via GSE). Typically ~5% for modern systems." + ), + "cnr_bw": "**SNR in Available BW** โ€” Signal-to-Noise Ratio in the available bandwidth.", + "cnr_nyq": "**SNR in Nyquist BW** โ€” SNR in Nyquist BW = Available BW / (1 + Roll-Off).", + "cnr_rcv": ( + "**SNR at Receiver Output** โ€” Combining link noise, satellite C/I, and receiver C/I. " + "This is the relevant ratio for real-life performance." + ), + "br_nyq": ( + "**Theoretical Bit Rate** โ€” Direct application of Shannon Limit in Nyquist BW. " + "Efficiency in bits/symbol also shown." + ), + "br_rcv": ( + "**Practical Physical Layer Bit Rate** โ€” Using all-degradations-included SNR " + "in Shannon's formula." + ), + "br_high": ( + "**Practical Higher Layers Bit Rate** โ€” Corresponds to user bits of IP datagrams." + ), + "satellite": ( + "The evaluation is decomposed in 3 sections:\n\n" + "1. **Satellite Link** โ€” transmitter and path to receiver with key characteristics\n" + "2. **Radio Front End** โ€” antenna and amplification capturing signal with minimal noise\n" + "3. **Baseband Unit** โ€” digital signal processing: filtering, synchronization, " + "demodulation, error correction, decapsulation\n\n" + "All fields are initially filled with meaningful values. Start by changing the " + "straightforward parameters. All parameters have help tooltips." + ), + "advanced": ( + "**Advanced Analysis Notes**\n\n" + "All capacity evaluations use direct application of the Shannon formula with real-world " + "impairments via C/N combinations. Useful links:\n\n" + "- [Nyquist ISI Criterion](https://en.wikipedia.org/wiki/Nyquist_ISI_criterion)\n" + "- [Error Correction Codes](https://en.wikipedia.org/wiki/Error_correction_code)\n" + "- [Viterbi Decoder](https://en.wikipedia.org/wiki/Viterbi_decoder)\n" + "- [Turbo Codes](https://en.wikipedia.org/wiki/Turbo_code)\n" + "- [DVB-S2](https://en.wikipedia.org/wiki/DVB-S2)\n" + "- [OSI Model](https://en.wikipedia.org/wiki/OSI_model)" + ), + "help": ( + "**Recommendations**\n\n" + "- Try multiple values one by one, starting from the least intimidating\n" + "- Explore the graphs to understand the physics\n" + "- Units are as explicit as possible\n" + "- The tool can also be used for a quick first-order link analysis" + ), +} diff --git a/docker-compose.yml b/docker-compose.yml index c1fef2b..b1750ee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,36 @@ -version: '3.1' +version: '3.8' + 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 + shannon: + deploy: + labels: + - traefik.enable=true + - traefik.http.routers.shannon.rule=Host(`shannon.antopoid.com`) + - traefik.http.routers.shannon.entrypoints=websecure + - traefik.http.routers.shannon.tls.certresolver=myhttpchallenge + - traefik.http.services.shannon.loadbalancer.server.port=8080 + build: + context: . + dockerfile: Dockerfile + container_name: shannon-streamlit + restart: always + expose: + - 8080 + volumes: + # Persist SQLite databases across container restarts + - shannon_data:/app/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/_stcore/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + shannon_data: + driver: local networks: - default: - external: - name: nginx-proxy - + default: + external: + name: nginx-proxy diff --git a/docker-stack.yml b/docker-stack.yml new file mode 100644 index 0000000..7e40fd8 --- /dev/null +++ b/docker-stack.yml @@ -0,0 +1,43 @@ +# Docker Stack pour dรฉploiement en Swarm +# Compatible avec CI/CD Gitea Actions +version: '3.8' + +services: + shannon: + image: shannon/streamlit:${IMAGE_TAG:-latest} + expose: + - 8080 + environment: + - STREAMLIT_SERVER_HEADLESS=true + volumes: + - shannon_data:/app/data + networks: + - traefik_traefikfront + deploy: + labels: + - traefik.enable=true + - traefik.http.routers.shannon.rule=Host(`shannon.antopoid.com`) + - traefik.http.routers.shannon.entrypoints=websecure + - traefik.http.routers.shannon.tls.certresolver=myhttpchallenge + - traefik.http.services.shannon.loadbalancer.server.port=8080 + - traefik.http.middlewares.shannon-proxy-headers.headers.customrequestheaders.X-Forwarded-For= + - traefik.http.routers.shannon.middlewares=shannon-proxy-headers + - traefik.http.services.shannon.loadbalancer.passhostheader=true + placement: + constraints: + - "node.hostname==macmini" + replicas: 1 + restart_policy: + condition: on-failure + update_config: + parallelism: 1 + delay: 10s + order: start-first + +volumes: + shannon_data: + driver: local + +networks: + traefik_traefikfront: + external: true diff --git a/requirements.txt b/requirements.txt index 7b2feaa..f4dab0f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,25 +1,19 @@ -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 - +# Core scientific dependencies +numpy>=1.25 +scipy>=1.11 +matplotlib>=3.7 +plotly>=5.18 + +# Streamlit +streamlit>=1.30 + +# Satellite propagation model +itur>=0.3.3 + +# Astronomy utilities +astropy>=5.3 + +# Geospatial +pyproj>=3.6 + +# Database (built-in sqlite3, no extra dep needed) diff --git a/slim.report.json b/slim.report.json deleted file mode 100644 index 00ef60b..0000000 --- a/slim.report.json +++ /dev/null @@ -1,505 +0,0 @@ -{ - "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" -} diff --git a/views/__init__.py b/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/views/__pycache__/__init__.cpython-313.pyc b/views/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..9108dab Binary files /dev/null and b/views/__pycache__/__init__.cpython-313.pyc differ diff --git a/views/__pycache__/contributions.cpython-313.pyc b/views/__pycache__/contributions.cpython-313.pyc new file mode 100644 index 0000000..1cfc105 Binary files /dev/null and b/views/__pycache__/contributions.cpython-313.pyc differ diff --git a/views/__pycache__/orbits_animation.cpython-313.pyc b/views/__pycache__/orbits_animation.cpython-313.pyc new file mode 100644 index 0000000..c5f1459 Binary files /dev/null and b/views/__pycache__/orbits_animation.cpython-313.pyc differ diff --git a/views/__pycache__/real_world.cpython-313.pyc b/views/__pycache__/real_world.cpython-313.pyc new file mode 100644 index 0000000..16450ec Binary files /dev/null and b/views/__pycache__/real_world.cpython-313.pyc differ diff --git a/views/__pycache__/satellite_animation.cpython-313.pyc b/views/__pycache__/satellite_animation.cpython-313.pyc new file mode 100644 index 0000000..dbb746b Binary files /dev/null and b/views/__pycache__/satellite_animation.cpython-313.pyc differ diff --git a/views/__pycache__/satellite_types.cpython-313.pyc b/views/__pycache__/satellite_types.cpython-313.pyc new file mode 100644 index 0000000..b6c18bb Binary files /dev/null and b/views/__pycache__/satellite_types.cpython-313.pyc differ diff --git a/views/__pycache__/theory.cpython-313.pyc b/views/__pycache__/theory.cpython-313.pyc new file mode 100644 index 0000000..ffbbee3 Binary files /dev/null and b/views/__pycache__/theory.cpython-313.pyc differ diff --git a/views/contributions.py b/views/contributions.py new file mode 100644 index 0000000..e6a40ab --- /dev/null +++ b/views/contributions.py @@ -0,0 +1,130 @@ +""" +Page 3: Community Contributions + +Read, write, search and delete user contributions stored in SQLite. +""" + +import streamlit as st +from core.database import write_contribution, search_contributions, delete_contribution + + +def render(): + """Render the Contributions page.""" + + st.markdown("# ๐Ÿ’ฌ Community Contributions") + st.markdown( + "Share your observations about Shannon's theorem, satellite communications, " + "or suggest improvements. Contributions are stored locally and shared with all users." + ) + + tab_read, tab_write = st.tabs(["๐Ÿ“– Read Contributions", "โœ๏ธ Write Contribution"]) + + # โ”€โ”€ Read Contributions โ”€โ”€ + with tab_read: + st.markdown("### ๐Ÿ” Search Contributions") + + db_choice = st.radio( + "Database", + ["Theory", "Real World"], + horizontal=True, + key="db_read", + ) + db_name = "Shannon_Theory.db" if db_choice == "Theory" else "Shannon_Real.db" + + col_f1, col_f2 = st.columns(2) + with col_f1: + name_filter = st.text_input("Filter by Name", key="filter_name") + title_filter = st.text_input("Filter by Title", key="filter_title") + with col_f2: + kw_filter = st.text_input("Filter by Keywords", key="filter_kw") + content_filter = st.text_input("Filter by Content", key="filter_content") + + if st.button("๐Ÿ” Search", type="primary", key="btn_search"): + results = search_contributions( + db_name, + name_filter=name_filter, + title_filter=title_filter, + keywords_filter=kw_filter, + content_filter=content_filter, + ) + st.session_state["contrib_results"] = results + + results = st.session_state.get("contrib_results", []) + + if results: + st.success(f"Found {len(results)} contribution(s).") + for i, contrib in enumerate(results): + with st.expander( + f"#{contrib['num']} โ€” {contrib['title']} by {contrib['name']} ({contrib['date']})", + expanded=(i == 0), + ): + st.markdown(contrib["text"]) + if contrib["keywords"]: + st.caption(f"๐Ÿท๏ธ Keywords: {contrib['keywords']}") + + # Delete functionality + with st.popover("๐Ÿ—‘๏ธ Delete"): + st.warning("This action cannot be undone.") + del_password = st.text_input( + "Enter contribution password", + type="password", + key=f"del_pw_{contrib['num']}", + ) + if st.button("Confirm Delete", key=f"del_btn_{contrib['num']}"): + if delete_contribution(db_name, contrib["num"], del_password): + st.success(f"โœ… Contribution #{contrib['num']} deleted.") + # Refresh results + st.session_state["contrib_results"] = search_contributions( + db_name, + name_filter=name_filter, + title_filter=title_filter, + keywords_filter=kw_filter, + content_filter=content_filter, + ) + st.rerun() + else: + st.error("โŒ Incorrect password or contribution not found.") + elif "contrib_results" in st.session_state: + st.info("No contributions found matching your filters.") + + # โ”€โ”€ Write Contribution โ”€โ”€ + with tab_write: + st.markdown("### โœ๏ธ New Contribution") + + db_choice_w = st.radio( + "Database", + ["Theory", "Real World"], + horizontal=True, + key="db_write", + ) + db_name_w = "Shannon_Theory.db" if db_choice_w == "Theory" else "Shannon_Real.db" + + with st.form("contribution_form", clear_on_submit=True): + name = st.text_input("Your Name / Initials *", max_chars=100) + title = st.text_input("Title *", max_chars=200) + keywords = st.text_input("Keywords (comma-separated)", max_chars=200) + text = st.text_area("Your contribution *", height=200) + password = st.text_input( + "Password (optional โ€” leave empty to allow anyone to delete)", + type="password", + ) + + submitted = st.form_submit_button("๐Ÿ“ค Submit", type="primary") + + if submitted: + if not name or not title or not text: + st.error("โŒ Please fill in all required fields (marked with *).") + else: + new_id = write_contribution(db_name_w, name, title, keywords, text, password) + st.success(f"โœ… Thank you! Your contribution has been stored with ID #{new_id}.") + st.balloons() + + with st.expander("โ“ Help"): + st.markdown( + "Write your contribution as a free text. Contributions should be:\n\n" + "- Candid observations about the technical subject\n" + "- References to relevant material (ideally as web links)\n" + "- Open discussion items about adjacent subjects\n" + "- Suggestions for improvement\n\n" + "You can retrieve and delete your contribution from the Read tab using your password." + ) diff --git a/views/orbits_animation.py b/views/orbits_animation.py new file mode 100644 index 0000000..b29a0f9 --- /dev/null +++ b/views/orbits_animation.py @@ -0,0 +1,585 @@ +""" +GEO / MEO / LEO Orbits โ€” Interactive Animation +================================================ +Animated comparison of satellite orbit types with key trade-offs. +""" + +import streamlit as st +import streamlit.components.v1 as components + + +_ORBITS_HTML = """ + + + + + + + + + +
+

๐Ÿ›ฐ๏ธ Orbit Comparison

+
+

+ Hover over a satellite to see its details. +

+
+
+ +
+ + +
+ SlowFast +
+
+ +
+

๐Ÿ“ Orbit Types

+
+ + LEO + 160 โ€“ 2 000 km +
+
+ + MEO + 2 000 โ€“ 35 786 km +
+
+ + GEO + 35 786 km (fixed) +
+
+ + + + +""" + + +def render(): + """Render the GEO/MEO/LEO orbit comparison animation.""" + st.markdown("## ๐ŸŒ Satellite Orbits โ€” GEO vs MEO vs LEO") + st.markdown( + "Compare the three main satellite orbit types. " + "Hover over satellites or legend items to explore their characteristics." + ) + st.divider() + + components.html(_ORBITS_HTML, height=680, scrolling=False) + + # โ”€โ”€ Educational content below โ”€โ”€ + st.divider() + st.markdown("### ๐Ÿ“Š Orbit Comparison at a Glance") + + col1, col2, col3 = st.columns(3) + + with col1: + st.markdown(""" +#### ๐ŸŸข LEO โ€” Low Earth Orbit +**160 โ€“ 2 000 km** + +- โšก Latency: **4 โ€“ 20 ms** (RTT) +- ๐Ÿ“‰ FSPL: ~155 dB (Ku-band) +- ๐Ÿ”„ Period: ~90 โ€“ 120 min +- ๐Ÿ“ก Small footprint โ†’ **large constellations** needed (hundreds to thousands) +- ๐Ÿค Requires **handover** between satellites +- ๐Ÿ›ฐ๏ธ *Starlink (550 km), OneWeb (1 200 km), Iridium (780 km)* +""") + + with col2: + st.markdown(""" +#### ๐ŸŸก MEO โ€” Medium Earth Orbit +**2 000 โ€“ 35 786 km** + +- โšก Latency: **40 โ€“ 80 ms** (RTT) +- ๐Ÿ“‰ FSPL: ~186 dB (Ku-band) +- ๐Ÿ”„ Period: ~2 โ€“ 12 h +- ๐Ÿ“ก Medium footprint โ†’ **medium constellations** (20 โ€“ 50 sats) +- ๐ŸŒ Good balance between coverage and latency +- ๐Ÿ›ฐ๏ธ *GPS (20 200 km), Galileo, O3b/SES (8 000 km)* +""") + + with col3: + st.markdown(""" +#### ๐Ÿ”ด GEO โ€” Geostationary Orbit +**35 786 km (fixed)** + +- โšก Latency: **240 โ€“ 280 ms** (RTT) +- ๐Ÿ“‰ FSPL: ~205 dB (Ku-band) +- ๐Ÿ”„ Period: 23 h 56 min (= 1 sidereal day) +- ๐Ÿ“ก Huge footprint โ†’ **3 sats = global** coverage +- ๐Ÿ“Œ **Fixed position** in the sky โ€” no tracking needed +- ๐Ÿ›ฐ๏ธ *Intelsat, SES, Eutelsat, ViaSat* +""") + + with st.expander("๐Ÿ”ฌ Key Trade-offs in Detail"): + st.markdown(r""" +| Parameter | LEO | MEO | GEO | +|:---|:---:|:---:|:---:| +| **Altitude** | 160 โ€“ 2 000 km | 2 000 โ€“ 35 786 km | 35 786 km | +| **Round-trip latency** | 4 โ€“ 20 ms | 40 โ€“ 80 ms | 240 โ€“ 280 ms | +| **Free-Space Path Loss** | ~155 dB | ~186 dB | ~205 dB | +| **Orbital period** | 90 โ€“ 120 min | 2 โ€“ 12 h | 23h 56m | +| **Coverage per sat** | ~1 000 km | ~12 000 km | ~15 000 km | +| **Constellation size** | Hundreds โ€“ thousands | 20 โ€“ 50 | 3 | +| **Antenna tracking** | Required (fast) | Required (slow) | Fixed dish | +| **Doppler shift** | High | Moderate | Negligible | +| **Launch cost/sat** | Lower | Medium | Higher | +| **Orbital lifetime** | 5 โ€“ 7 years | 10 โ€“ 15 years | 15+ years | + +**FSPL formula:** $\text{FSPL (dB)} = 20 \log_{10}(d) + 20 \log_{10}(f) + 32.44$ + +Where $d$ is distance in km and $f$ is frequency in MHz. + +**Why it matters for Shannon:** +The higher the orbit, the greater the FSPL, the lower the received C/N. +Since $C = B \log_2(1 + C/N)$, a higher orbit means lower achievable bit rate +for the same bandwidth and transmit power. LEO compensates with lower FSPL +but requires more satellites and complex handover. +""") + + with st.expander("๐Ÿ›ฐ๏ธ Notable Constellations"): + st.markdown(""" +| Constellation | Orbit | Altitude | # Satellites | Use Case | +|:---|:---:|:---:|:---:|:---| +| **Starlink** | LEO | 550 km | ~6 000+ | Broadband Internet | +| **OneWeb** | LEO | 1 200 km | ~650 | Enterprise connectivity | +| **Iridium NEXT** | LEO | 780 km | 66 + spares | Global voice/data | +| **O3b mPOWER** | MEO | 8 000 km | 11+ | Managed connectivity | +| **GPS** | MEO | 20 200 km | 31 | Navigation | +| **Galileo** | MEO | 23 222 km | 30 | Navigation | +| **Intelsat** | GEO | 35 786 km | 50+ | Video & enterprise | +| **SES** | GEO + MEO | Mixed | 70+ | Video & data | +| **Eutelsat** | GEO | 35 786 km | 35+ | Video broadcasting | +""") diff --git a/views/real_world.py b/views/real_world.py new file mode 100644 index 0000000..52465d8 --- /dev/null +++ b/views/real_world.py @@ -0,0 +1,412 @@ +""" +Page 2: Shannon and Friends in the Real World + +Complete satellite link budget with interactive Plotly graphs. +""" + +import streamlit as st +import numpy as np +import plotly.graph_objects as go +from math import log + +from core.calculations import ( + combine_cnr, + shannon_capacity, + compute_satellite_link, + compute_receiver, + compute_baseband, + fmt_br, + fmt_power, + fmt_gain, + fmt_pfd, + fmt_psd, + fmt_ploss, +) +from core.help_texts import REAL_WORLD_HELP + + +def _make_bw_sensitivity_real( + cnr_nyq, bandwidth, rolloff, overheads, penalties, cnr_imp, sat_cir, hpa_power +) -> go.Figure: + n = 40 + bw = np.zeros(n) + br = np.zeros(n) + cnr = np.zeros(n) + cnr[0] = cnr_nyq + 10 * log(8, 10) + bw[0] = bandwidth / 8 + cnr_rcv_0 = combine_cnr(cnr[0], cnr_imp, sat_cir) + br[0] = shannon_capacity(bw[0] / (1 + rolloff / 100), cnr_rcv_0, penalties) / (1 + overheads / 100) + for i in range(1, n): + bw[i] = bw[i - 1] * 2 ** (1 / 6) + 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_capacity(bw[i] / (1 + rolloff / 100), cnr_rcv_i, penalties) / (1 + overheads / 100) + + fig = go.Figure() + fig.add_trace(go.Scatter( + x=bw, y=br, mode="lines", + name="Higher Layers BR", + line=dict(color="#4FC3F7", width=3), + )) + + # Reference point + cnr_rcv_ref = combine_cnr(cnr_nyq, cnr_imp, sat_cir) + br_ref = shannon_capacity(bandwidth / (1 + rolloff / 100), cnr_rcv_ref, penalties) / (1 + overheads / 100) + fig.add_trace(go.Scatter( + x=[bandwidth], y=[br_ref], mode="markers+text", + name=f"Ref: {bandwidth:.0f} MHz, {br_ref:.1f} Mbps", + marker=dict(size=12, color="#FF7043", symbol="diamond"), + text=[f"{br_ref:.1f} Mbps"], + textposition="top center", + )) + + fig.update_layout( + title=f"Higher Layers Bit Rate at Constant HPA Power: {hpa_power:.1f} W", + xaxis_title="Occupied Bandwidth [MHz]", + yaxis_title="Bit Rate [Mbps]", + template="plotly_dark", + height=500, + ) + return fig + + +def _make_power_sensitivity_real( + cnr_nyq, bw_nyq, hpa_power, overheads, penalties, cnr_imp, sat_cir, bandwidth +) -> go.Figure: + n = 40 + power = np.zeros(n) + br = np.zeros(n) + cnr = np.zeros(n) + 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_capacity(bw_nyq, cnr_rcv_i, penalties) / (1 + overheads / 100) + for i in range(1, n): + power[i] = power[i - 1] * 2 ** (1 / 6) + cnr[i] = cnr[i - 1] + 10 * log(2 ** (1 / 6), 10) + cnr_rcv_i = combine_cnr(cnr[i], cnr_imp, sat_cir) + br[i] = shannon_capacity(bw_nyq, cnr_rcv_i, penalties) / (1 + overheads / 100) + + fig = go.Figure() + fig.add_trace(go.Scatter( + x=power, y=br, mode="lines", + name="Higher Layers BR", + line=dict(color="#81C784", width=3), + )) + + cnr_rcv_ref = combine_cnr(cnr_nyq, cnr_imp, sat_cir) + br_ref = shannon_capacity(bw_nyq, cnr_rcv_ref, penalties) / (1 + overheads / 100) + fig.add_trace(go.Scatter( + x=[hpa_power], y=[br_ref], mode="markers+text", + name=f"Ref: {hpa_power:.0f} W, {br_ref:.1f} Mbps", + marker=dict(size=12, color="#FF7043", symbol="diamond"), + text=[f"{br_ref:.1f} Mbps"], + textposition="top center", + )) + + fig.update_layout( + title=f"Higher Layers Bit Rate at Constant BW: {bandwidth:.1f} MHz", + xaxis_title="HPA Output Power [W]", + yaxis_title="Bit Rate [Mbps]", + template="plotly_dark", + height=500, + ) + return fig + + +def _make_br_factor_map_real( + cnr_nyq, bw_nyq, rolloff, overheads, penalties, cnr_imp, sat_cir, + hpa_power, bandwidth, br_rcv_higher, +) -> go.Figure: + n = 41 + bw_mul = np.zeros((n, n)) + p_mul = np.zeros((n, n)) + br_mul = np.zeros((n, n)) + for i in range(n): + for j in range(n): + bw_mul[i, j] = (i + 1) / 8 + p_mul[i, j] = (j + 1) / 8 + 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_capacity(bw_ij / (1 + rolloff / 100), cnr_rcv_ij, penalties) / (1 + overheads / 100) + br_mul[i, j] = br_ij / br_rcv_higher if br_rcv_higher > 0 else 0 + + fig = go.Figure(data=go.Contour( + z=br_mul, + x=bw_mul[:, 0], + y=p_mul[0, :], + colorscale="Viridis", + contours=dict(showlabels=True, labelfont=dict(size=10, color="white")), + colorbar=dict(title="BR Factor"), + )) + fig.update_layout( + title=f"BR Multiplying Factor
Ref: {hpa_power:.0f} W, " + f"{bandwidth:.0f} MHz, {br_rcv_higher:.1f} Mbps", + xaxis_title="Bandwidth Factor", + yaxis_title="Power Factor", + template="plotly_dark", + height=550, + ) + return fig + + +def render(): + """Render the Real World satellite link budget page.""" + + # โ”€โ”€ Header โ”€โ”€ + col_img, col_title = st.columns([1, 3]) + with col_img: + st.image("Satellite.png", width=200) + with col_title: + st.markdown("# ๐Ÿ›ฐ๏ธ Shannon & Friends in the Real World") + st.markdown("From theory to satellite communication link budget.") + + wiki_cols = st.columns(4) + wiki_cols[0].link_button("๐Ÿ“– Harry Nyquist", "https://en.wikipedia.org/wiki/Harry_Nyquist") + wiki_cols[1].link_button("๐Ÿ“– Richard Hamming", "https://en.wikipedia.org/wiki/Richard_Hamming") + wiki_cols[2].link_button("๐Ÿ“– Andrew Viterbi", "https://en.wikipedia.org/wiki/Andrew_Viterbi") + wiki_cols[3].link_button("๐Ÿ“– Claude Berrou", "https://en.wikipedia.org/wiki/Claude_Berrou") + + st.divider() + + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + # SECTION 1: Satellite Link + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + st.markdown("## ๐Ÿ“ก Satellite Link") + + col1, col2, col3 = st.columns(3) + with col1: + sat_alt = st.number_input("Satellite Altitude [km]", value=35786.0, step=100.0, + help=REAL_WORLD_HELP["sat_alt"]) + sat_lat = st.number_input("Satellite Latitude [ยฐ]", value=0.0, step=0.1, + help=REAL_WORLD_HELP["sat_latlon"]) + sat_lon = st.number_input("Satellite Longitude [ยฐ]", value=19.2, step=0.1, + help=REAL_WORLD_HELP["sat_latlon"]) + with col2: + gs_lat = st.number_input("Ground Station Latitude [ยฐ]", value=49.7, step=0.1, + help=REAL_WORLD_HELP["gs_latlon"]) + gs_lon = st.number_input("Ground Station Longitude [ยฐ]", value=6.3, step=0.1, + help=REAL_WORLD_HELP["gs_latlon"]) + freq = st.number_input("Frequency [GHz]", value=12.0, min_value=0.1, step=0.5, + help=REAL_WORLD_HELP["freq"]) + with col3: + hpa_power = st.number_input("HPA Output Power [W]", value=120.0, min_value=0.1, step=10.0, + help=REAL_WORLD_HELP["hpa"]) + sat_loss = st.number_input("Output Losses [dB]", value=2.0, min_value=0.0, step=0.5, + help=REAL_WORLD_HELP["losses"]) + availability = st.number_input("Link Availability [%]", value=99.9, + min_value=90.0, max_value=99.999, step=0.1, + help=REAL_WORLD_HELP["availability"]) + + col4, col5 = st.columns(2) + with col4: + sat_cir_input = st.text_input("TX Impairments C/I [dB] (comma-sep)", value="25, 25", + help=REAL_WORLD_HELP["sat_cir"]) + sat_beam = st.number_input("Beam Diameter [ยฐ]", value=3.0, min_value=0.1, step=0.1, + help=REAL_WORLD_HELP["sat_beam"]) + with col5: + gain_offset = st.number_input("Offset from Peak [dB]", value=0.0, min_value=0.0, step=0.5, + help=REAL_WORLD_HELP["gain_offset"]) + + # Parse satellite C/I + try: + sat_cir_list = [float(v.strip()) for v in sat_cir_input.split(",")] + except ValueError: + st.error("โŒ Invalid C/I values.") + return + + # Compute satellite link + try: + with st.spinner("Computing atmospheric attenuation (ITU-R model)..."): + sat = compute_satellite_link( + freq, hpa_power, sat_loss, sat_cir_list, sat_beam, gain_offset, + sat_alt, sat_lat, sat_lon, gs_lat, gs_lon, availability, + ) + except Exception as e: + st.error(f"โŒ Satellite link computation error: {e}") + return + + # Display satellite link results + st.markdown("#### ๐Ÿ“Š Satellite Link Results") + r1, r2, r3 = st.columns(3) + r1.metric("Output Power", fmt_power(sat["sig_power"]), help=REAL_WORLD_HELP["output_power"]) + r2.metric("Antenna Gain", fmt_gain(sat["sat_gain_linear"]), help=REAL_WORLD_HELP["sat_gain"]) + r3.metric("EIRP", fmt_power(sat["eirp_linear"]), help=REAL_WORLD_HELP["eirp"]) + + r4, r5, r6 = st.columns(3) + r4.metric("Path Length", f"{sat['path_length']:.1f} km @ {sat['elevation']:.1f}ยฐ", + help=REAL_WORLD_HELP["path_length"]) + r5.metric("Atmospheric Loss", f"{sat['atm_loss_db']:.1f} dB", + help=REAL_WORLD_HELP["atm_loss"]) + r6.metric("Path Dispersion", fmt_ploss(sat["path_loss_linear"]), + help=REAL_WORLD_HELP["path_loss"]) + + st.metric("Power Flux Density", fmt_pfd(sat["pfd_linear"]), help=REAL_WORLD_HELP["pfd"]) + + if sat["elevation"] <= 0: + st.warning("โš ๏ธ Satellite is below the horizon (negative elevation). Results may not be meaningful.") + + st.divider() + + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + # SECTION 2: Radio Front End + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + st.markdown("## ๐Ÿ“ป Radio Front End") + + col_r1, col_r2 = st.columns(2) + with col_r1: + cpe_ant_d = st.number_input("Receive Antenna Size [m]", value=0.6, min_value=0.1, step=0.1, + help=REAL_WORLD_HELP["cpe_ant"]) + with col_r2: + cpe_t_clear = st.number_input("Noise Temperature [K]", value=120.0, min_value=10.0, step=10.0, + help=REAL_WORLD_HELP["cpe_temp"]) + + try: + rcv = compute_receiver( + sat["pfd_linear"], sat["atm_loss_db"], sat["wavelength"], + cpe_ant_d, cpe_t_clear, + ) + except Exception as e: + st.error(f"โŒ Receiver computation error: {e}") + return + + st.markdown("#### ๐Ÿ“Š Receiver Results") + rx1, rx2 = st.columns(2) + rx1.metric("Antenna Area ยท G/T", f"{rcv['cpe_ae']:.2f} mยฒ ยท {rcv['cpe_g_t']:.1f} dB/K", + help=REAL_WORLD_HELP["cpe_gain"]) + rx2.metric("RX Power (C)", fmt_power(rcv["rx_power"]), help=REAL_WORLD_HELP["rx_power"]) + + rx3, rx4 = st.columns(2) + rx3.metric("Nโ‚€", fmt_psd(rcv["n0"] * 1e6), help=REAL_WORLD_HELP["n0"]) + rx4.metric("BR at โˆž BW", fmt_br(rcv["br_infinity"]), help=REAL_WORLD_HELP["br_inf"]) + + rx5, rx6 = st.columns(2) + rx5.metric("BR at SpEff=1", fmt_br(rcv["br_spe_1"]), help=REAL_WORLD_HELP["br_unit"]) + rx6.metric("BR at SpEff=2", fmt_br(rcv["br_spe_double"]), help=REAL_WORLD_HELP["br_double"]) + + st.divider() + + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + # SECTION 3: Baseband Unit + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + st.markdown("## ๐Ÿ’ป Baseband Unit") + + col_b1, col_b2, col_b3 = st.columns(3) + with col_b1: + bandwidth = st.number_input("Occupied Bandwidth [MHz]", value=36.0, min_value=0.1, step=1.0, + help=REAL_WORLD_HELP["bandwidth"], key="bw_real") + rolloff = st.number_input("Nyquist Rolloff [%]", value=5.0, min_value=0.0, max_value=100.0, step=1.0, + help=REAL_WORLD_HELP["rolloff"]) + with col_b2: + cir_input = st.text_input("RX Impairments C/I [dB]", value="20", + help=REAL_WORLD_HELP["cir"]) + penalties = st.number_input("Implementation Penalty [dB]", value=1.5, min_value=0.0, step=0.1, + help=REAL_WORLD_HELP["penalty"]) + with col_b3: + overheads = st.number_input("Higher Layers Overhead [%]", value=5.0, min_value=0.0, step=1.0, + help=REAL_WORLD_HELP["overhead"]) + + try: + cnr_imp_list = [float(v.strip()) for v in cir_input.split(",")] + except ValueError: + st.error("โŒ Invalid C/I values.") + return + + try: + bb = compute_baseband( + rcv["c_n0_mhz"], rcv["br_infinity"], rcv["bw_spe_1"], + sat["sat_cir"], bandwidth, rolloff, overheads, cnr_imp_list, penalties, + ) + except Exception as e: + st.error(f"โŒ Baseband computation error: {e}") + return + + st.markdown("#### ๐Ÿ“Š Baseband Results") + + b1, b2, b3 = st.columns(3) + b1.metric("SNR in Available BW", f"{bb['cnr_bw']:.1f} dB in {bandwidth:.1f} MHz", + help=REAL_WORLD_HELP["cnr_bw"]) + b2.metric("SNR in Nyquist BW", f"{bb['cnr_nyq']:.1f} dB in {bb['bw_nyq']:.1f} MHz", + help=REAL_WORLD_HELP["cnr_nyq"]) + b3.metric("SNR at Receiver", f"{bb['cnr_rcv']:.1f} dB", + help=REAL_WORLD_HELP["cnr_rcv"]) + + st.divider() + + b4, b5 = st.columns(2) + with b4: + st.markdown("##### ๐ŸŽฏ Theoretical") + st.metric( + "Theoretical BR", + f"{fmt_br(bb['br_nyq'])} ยท {bb['br_nyq_norm']:.0%}", + help=REAL_WORLD_HELP["br_nyq"], + ) + st.caption(f"Spectral Eff: {bb['spe_nyq']:.2f} bps/Hz ยท {bb['bits_per_symbol']:.2f} b/Symbol") + with b5: + st.markdown("##### ๐Ÿญ Practical") + st.metric( + "Physical Layer BR", + f"{fmt_br(bb['br_rcv'])} ยท {bb['br_rcv_norm']:.0%}", + help=REAL_WORLD_HELP["br_rcv"], + ) + st.metric( + "Higher Layers BR", + f"{fmt_br(bb['br_rcv_higher'])} ยท {bb['br_rcv_h_norm']:.0%}", + delta=f"{bb['spe_higher']:.2f} bps/Hz", + help=REAL_WORLD_HELP["br_high"], + ) + + st.divider() + + # โ”€โ”€ Graphs โ”€โ”€ + st.markdown("### ๐Ÿ“ˆ Interactive Graphs") + + tab_bw, tab_pow, tab_map = st.tabs([ + "๐Ÿ“ถ BW Sensitivity", + "โšก Power Sensitivity", + "๐Ÿ—บ๏ธ BR Factor Map", + ]) + + cnr_imp = combine_cnr(*cnr_imp_list) + + with tab_bw: + st.plotly_chart( + _make_bw_sensitivity_real( + bb["cnr_nyq"], bandwidth, rolloff, overheads, penalties, + cnr_imp, sat["sat_cir"], hpa_power, + ), + width="stretch", + ) + + with tab_pow: + st.plotly_chart( + _make_power_sensitivity_real( + bb["cnr_nyq"], bb["bw_nyq"], hpa_power, overheads, penalties, + cnr_imp, sat["sat_cir"], bandwidth, + ), + width="stretch", + ) + + with tab_map: + st.plotly_chart( + _make_br_factor_map_real( + bb["cnr_nyq"], bb["bw_nyq"], rolloff, overheads, penalties, + cnr_imp, sat["sat_cir"], hpa_power, bandwidth, bb["br_rcv_higher"], + ), + width="stretch", + ) + + # โ”€โ”€ Help โ”€โ”€ + with st.expander("๐Ÿ“˜ Background Information"): + help_topic = st.selectbox( + "Choose a topic:", + options=["satellite", "advanced", "help"], + format_func=lambda x: { + "satellite": "๐Ÿ›ฐ๏ธ Link Budget Overview", + "advanced": "๐Ÿ”ง Advanced Notes", + "help": "โ“ How to use", + }[x], + key="help_real", + ) + st.markdown(REAL_WORLD_HELP[help_topic]) diff --git a/views/satellite_animation.py b/views/satellite_animation.py new file mode 100644 index 0000000..abd57d6 --- /dev/null +++ b/views/satellite_animation.py @@ -0,0 +1,762 @@ +""" +Interactive Satellite Link Animation +===================================== +HTML5 Canvas animation showing a satellite communicating with a ground station, +with toggleable impairment layers (atmosphere, rain, free-space loss, noiseโ€ฆ). +""" + +import streamlit as st +import streamlit.components.v1 as components + + +_ANIMATION_HTML = """ + + + + + + + + + + +
+

โšก Impairments

+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+ + +
+

๐Ÿ“ก Satellite Link Overview

+ + Toggle each impairment to see how it affects the signal. + Cyan = Downlink (sat โ†’ ground). + Green = Uplink (ground โ†’ sat). + The signal strength bar shows the cumulative effect. + +
+ + + + +""" + + +def render(): + """Render the satellite link animation page.""" + st.markdown("## ๐Ÿ›ฐ๏ธ Satellite Link โ€” Interactive Animation") + st.markdown( + "Visualise how a signal travels from a **GEO satellite** to a " + "**ground station** and discover the impairments that degrade it. " + "Toggle each effect on/off to see the impact on signal strength." + ) + st.divider() + + components.html(_ANIMATION_HTML, height=680, scrolling=False) + + # โ”€โ”€ Pedagogical summary below the animation โ”€โ”€ + st.divider() + st.markdown("### ๐Ÿ“š Understanding the Link Budget") + + col1, col2 = st.columns(2) + + with col1: + st.markdown(""" +**Uplink & Downlink path:** + +The signal travels ~36 000 km from a geostationary satellite to Earth. +Along the way it encounters multiple sources of degradation: + +1. **Free-Space Path Loss** โ€” the dominant factor, purely geometric (1/rยฒ) +2. **Atmospheric gases** โ€” Oโ‚‚ and Hโ‚‚O absorption (ITU-R P.676) +3. **Rain** โ€” scattering & absorption, worst at Ka-band (ITU-R P.618) +4. **Ionosphere** โ€” Faraday rotation, scintillation (ITU-R P.531) +""") + + with col2: + st.markdown(""" +**At the receiver:** + +Even after the signal arrives, further degradation occurs: + +5. **Thermal noise** โ€” every component adds noise: $N = k \\cdot T_{sys} \\cdot B$ +6. **Pointing loss** โ€” antenna misalignment reduces gain +7. **Implementation losses** โ€” ADC quantisation, filter roll-off, etc. + +The **Shannon limit** $C = B \\log_2(1 + C/N)$ tells us the maximum +bit rate achievable given the remaining signal-to-noise ratio. +""") + + with st.expander("๐Ÿ”— Key ITU-R Recommendations"): + st.markdown(""" +| Recommendation | Topic | +|:---:|:---| +| **P.618** | Rain attenuation & propagation effects for satellite links | +| **P.676** | Gaseous attenuation on terrestrial and slant paths | +| **P.531** | Ionospheric effects on radiowave propagation | +| **P.837** | Rainfall rate statistics for prediction models | +| **P.839** | Rain height model for prediction methods | +| **S.1428** | Reference satellite link for system design | +""") diff --git a/views/satellite_types.py b/views/satellite_types.py new file mode 100644 index 0000000..7cb3558 --- /dev/null +++ b/views/satellite_types.py @@ -0,0 +1,917 @@ +""" +Satellite Types โ€” Mission Control Dashboard +============================================= +Each satellite category has its own unique animated scene: + - Navigation โ†’ GPS triangulation on a world map + - Communication โ†’ Data-flow network between ground stations + - Earth Observation โ†’ Top-down terrain scan with image strips + - Weather โ†’ Atmospheric cloud/rain/temperature visualisation + - Science โ†’ Deep-space telescope view (nebula zoom) + - Defense โ†’ Tactical radar sweep with blips +""" + +import streamlit as st +import streamlit.components.v1 as components + + +_SATTYPES_HTML = r""" + + + + + + + + + +
+

๐Ÿ›ฐ๏ธ Satellite Missions

Select a mission type above
to explore its animation.

+
Mission Control โ€” choose a category
+ + + + +""" + + +def render(): + """Render the satellite missions / types dashboard page.""" + st.markdown("## ๐Ÿ›ฐ๏ธ Satellite Missions โ€” Types & Applications") + st.markdown( + "Explore **six categories** of satellite missions through unique animated scenes. " + "Click a tab to switch between navigation, communication, observation, weather, " + "science and defense โ€” each with its own visual story." + ) + st.divider() + + components.html(_SATTYPES_HTML, height=720, scrolling=False) + + # โ”€โ”€ Educational content below โ”€โ”€ + st.divider() + st.markdown("### ๐Ÿ“‹ Mission Comparison") + + st.markdown(""" +| Category | Orbit | Altitude | Key Band | Examples | +|:---|:---:|:---:|:---:|:---| +| ๐Ÿงญ **Navigation** | MEO | 20 200 km | L-band | GPS, Galileo, GLONASS, BeiDou | +| ๐Ÿ“ก **Communication** | GEO / LEO | 550 โ€“ 36 000 km | C / Ku / Ka | Starlink, Intelsat, SES | +| ๐Ÿ“ธ **Earth Observation** | LEO (SSO) | 500 โ€“ 800 km | X-band | Sentinel, Landsat, Planet | +| ๐ŸŒฆ๏ธ **Weather** | GEO / LEO | 800 โ€“ 36 000 km | L / Ka | Meteosat, GOES, Himawari | +| ๐Ÿ”ญ **Science** | Various | Variable | S / X / Ka | JWST, Hubble, Gaia | +| ๐Ÿ›ก๏ธ **Defense** | LEO / GEO / HEO | Variable | UHF / EHF | SBIRS, WGS, Syracuse | +""") + + col1, col2 = st.columns(2) + + with col1: + with st.expander("๐Ÿงญ Navigation Satellites"): + st.markdown(""" +**How GNSS works:** + +Each satellite broadcasts its precise position and exact time +(from onboard atomic clocks). A receiver picks up +signals from $\\geq 4$ satellites and solves the position equations: + +$$d_i = c \\cdot (t_{\\text{rx}} - t_{\\text{tx},i})$$ + +Where $d_i$ is the pseudorange to satellite $i$, $c$ is the speed of +light, and $t_{\\text{rx}}$ is the receiver clock time. + +With 4+ satellites the receiver solves for $(x, y, z, \\Delta t)$ โ€” +3D position plus its own clock error. + +**Key systems:** +- **GPS** (USA) โ€” 31 sats, L1 / L2 / L5 +- **Galileo** (EU) โ€” 30 sats, E1 / E5a / E5b / E6 +- **GLONASS** (Russia) โ€” 24 sats +- **BeiDou** (China) โ€” 35+ sats +""") + + with st.expander("๐Ÿ“ก Communication Satellites"): + st.markdown(""" +**Evolution:** + +1. **1960s** โ€” Early Bird: 240 voice channels +2. **1980s** โ€” Large GEO: thousands of transponders +3. **2000s** โ€” HTS: spot beams, frequency reuse +4. **2020s** โ€” Mega-constellations (Starlink 6 000+ LEO) + +**Shannon connection:** + +$$C = B \\cdot \\log_2\\!\\left(1 + \\frac{C}{N}\\right)$$ + +A GEO satellite at 36 000 km suffers ~50 dB more path loss than +LEO at 550 km, but compensates with larger antennas, higher power +and advanced modulation (DVB-S2X, 256-APSK). +""") + + with st.expander("๐ŸŒฆ๏ธ Weather Satellites"): + st.markdown(""" +**Two approaches:** + +1. **GEO** (Meteosat, GOES, Himawari) โ€” continuous monitoring, + full disk every 10โ€“15 min, 16+ spectral bands. +2. **LEO polar** (MetOp, NOAA) โ€” higher resolution but 2 passes/day, + microwave sounders penetrate clouds. + +**Data volume:** GOES-16 generates ~3.6 TB/day of imagery. +""") + + with col2: + with st.expander("๐Ÿ“ธ Earth Observation Satellites"): + st.markdown(""" +**Imaging technologies:** + +| Type | Resolution | Use | +|:---|:---:|:---| +| **Optical** | 0.3 โ€“ 1 m | Urban mapping, defence | +| **Multispectral** | 5 โ€“ 30 m | Agriculture (NDVI) | +| **Hyperspectral** | 30 m, 200+ bands | Mineral detection | +| **SAR** | 1 โ€“ 10 m | All-weather imaging | +| **InSAR** | mm displacement | Ground subsidence | + +**Sun-Synchronous Orbit (SSO):** ~98ยฐ inclination ensures +consistent solar illumination for temporal comparison. +""") + + with st.expander("๐Ÿ”ญ Science & Exploration"): + st.markdown(""" +| Mission | Location | Purpose | +|:---|:---:|:---| +| **JWST** | L2 (1.5 M km) | IR astronomy | +| **Hubble** | LEO (540 km) | Optical / UV | +| **Gaia** | L2 | 3D map of 1.8 B stars | +| **Chandra** | HEO | X-ray astronomy | + +**Deep space challenge:** Voyager 1 at 24 billion km transmits +at ~160 **bits**/s with 23 W through NASA's 70 m DSN antennas. +""") + + with st.expander("๐Ÿ›ก๏ธ Defense & Intelligence"): + st.markdown(""" +**Categories:** + +- **MILCOM** โ€” WGS, AEHF, Syracuse (secure links, EHF) +- **SIGINT** โ€” intercept electromagnetic emissions +- **IMINT** โ€” high-res optical/radar reconnaissance +- **Early Warning** โ€” SBIRS IR missile detection from GEO +- **ELINT** โ€” radar system characterisation + +**Resilience:** frequency hopping, spread spectrum, +radiation hardening, satellite crosslinks. +""") diff --git a/views/theory.py b/views/theory.py new file mode 100644 index 0000000..431d711 --- /dev/null +++ b/views/theory.py @@ -0,0 +1,269 @@ +""" +Page 1: Shannon's Equation โ€” Theoretical Exploration + +Interactive exploration of the Shannon capacity formula with Plotly graphs. +""" + +import streamlit as st +import numpy as np +import plotly.graph_objects as go +from math import log + +from core.calculations import ( + combine_cnr, + shannon_capacity, + shannon_points, + br_multiplier, + fmt_br, +) +from core.help_texts import THEORY_HELP + + +def _make_bw_sensitivity_plot(cnr_nyq: float, bw_nyq: float, c_n0: float) -> go.Figure: + """Bandwidth sensitivity at constant power.""" + n = 40 + bw = np.zeros(n) + br = np.zeros(n) + cnr = np.zeros(n) + cnr[0] = cnr_nyq + 10 * log(8, 10) + bw[0] = bw_nyq / 8 + br[0] = shannon_capacity(bw[0], cnr[0]) + for i in range(1, n): + bw[i] = bw[i - 1] * 2 ** (1 / 6) + cnr[i] = cnr[i - 1] - 10 * log(bw[i] / bw[i - 1], 10) + br[i] = shannon_capacity(bw[i], cnr[i]) + + fig = go.Figure() + fig.add_trace(go.Scatter( + x=bw, y=br, mode="lines", + name="Shannon Capacity", + line=dict(color="#4FC3F7", width=3), + )) + + # Mark reference point + ref_br = shannon_capacity(bw_nyq, cnr_nyq) + fig.add_trace(go.Scatter( + x=[bw_nyq], y=[ref_br], mode="markers+text", + name=f"Reference: {bw_nyq:.1f} MHz, {ref_br:.1f} Mbps", + marker=dict(size=12, color="#FF7043", symbol="diamond"), + text=[f"{ref_br:.1f} Mbps"], + textposition="top center", + )) + + fig.update_layout( + title=f"Theoretical Bit Rate at Constant Power
C/Nโ‚€ = {c_n0:.1f} MHz", + xaxis_title="Bandwidth [MHz]", + yaxis_title="Bit Rate [Mbps]", + template="plotly_dark", + height=500, + showlegend=True, + legend=dict(yanchor="bottom", y=0.02, xanchor="right", x=0.98), + ) + return fig + + +def _make_power_sensitivity_plot(cnr_nyq: float, bw_nyq: float, cnr_linear: float) -> go.Figure: + """Power sensitivity at constant bandwidth.""" + n = 40 + p_mul = np.zeros(n) + br = np.zeros(n) + cnr = np.zeros(n) + p_mul[0] = 1 / 8 + cnr[0] = cnr_nyq - 10 * log(8, 10) + br[0] = shannon_capacity(bw_nyq, cnr[0]) + for i in range(1, n): + p_mul[i] = p_mul[i - 1] * 2 ** (1 / 6) + cnr[i] = cnr[i - 1] + 10 * log(2 ** (1 / 6), 10) + br[i] = shannon_capacity(bw_nyq, cnr[i]) + + fig = go.Figure() + fig.add_trace(go.Scatter( + x=p_mul, y=br, mode="lines", + name="Shannon Capacity", + line=dict(color="#81C784", width=3), + )) + + # Reference point (multiplier = 1) + ref_br = shannon_capacity(bw_nyq, cnr_nyq) + fig.add_trace(go.Scatter( + x=[1.0], y=[ref_br], mode="markers+text", + name=f"Reference: 1x, {ref_br:.1f} Mbps", + marker=dict(size=12, color="#FF7043", symbol="diamond"), + text=[f"{ref_br:.1f} Mbps"], + textposition="top center", + )) + + fig.update_layout( + title=f"Theoretical Bit Rate at Constant Bandwidth: {bw_nyq:.1f} MHz
" + f"Reference: C/N = {cnr_linear:.1f} [Linear]", + xaxis_title="Power Multiplying Factor", + yaxis_title="Bit Rate [Mbps]", + template="plotly_dark", + height=500, + showlegend=True, + legend=dict(yanchor="bottom", y=0.02, xanchor="right", x=0.98), + ) + return fig + + +def _make_br_factor_map(cnr_nyq: float, bw_nyq: float, c_n0: float, br_bw: float) -> go.Figure: + """Contour map of BR multiplying factors.""" + n = 41 + bw_mul = np.zeros((n, n)) + p_mul = np.zeros((n, n)) + br_mul = np.zeros((n, n)) + for i in range(n): + for j in range(n): + bw_mul[i, j] = (i + 1) / 8 + p_mul[i, j] = (j + 1) / 8 + br_mul[i, j] = br_multiplier(bw_mul[i, j], p_mul[i, j], cnr_nyq) + + fig = go.Figure(data=go.Contour( + z=br_mul, + x=bw_mul[:, 0], + y=p_mul[0, :], + colorscale="Viridis", + contours=dict(showlabels=True, labelfont=dict(size=10, color="white")), + colorbar=dict(title="BR Factor"), + )) + fig.update_layout( + title=f"Bit Rate Multiplying Factor
Ref: C/N = {cnr_nyq:.1f} dB, " + f"BW = {bw_nyq:.1f} MHz, C/Nโ‚€ = {c_n0:.1f} MHz, " + f"BR = {br_bw:.1f} Mbps", + xaxis_title="Bandwidth Multiplying Factor", + yaxis_title="Power Multiplying Factor", + template="plotly_dark", + height=550, + ) + return fig + + +def render(): + """Render the Theoretical Exploration page.""" + + # โ”€โ”€ Header โ”€โ”€ + col_img, col_title = st.columns([1, 3]) + with col_img: + st.image("Shannon.png", width=200) + with col_title: + st.markdown("# ๐Ÿ“ก Shannon's Equation for Dummies") + st.markdown( + "Exploration of Claude Shannon's channel capacity theorem โ€” " + "the fundamental limit of digital communications." + ) + st.link_button("๐Ÿ“– Wiki: Claude Shannon", "https://en.wikipedia.org/wiki/Claude_Shannon") + + st.divider() + + # โ”€โ”€ Input Parameters โ”€โ”€ + st.markdown("### โš™๏ธ Input Parameters") + + col_in1, col_in2 = st.columns(2) + with col_in1: + cnr_input = st.text_input( + "Reference C/N [dB]", + value="12", + help=THEORY_HELP["cnr"], + ) + with col_in2: + bw_input = st.number_input( + "Reference BW [MHz]", + value=36.0, min_value=0.1, step=1.0, + help=THEORY_HELP["bw"], + ) + + # Parse CNR (supports comma-separated combinations) + try: + cnr_values = [float(v.strip()) for v in cnr_input.split(",")] + cnr_nyq = combine_cnr(*cnr_values) + except (ValueError, ZeroDivisionError): + st.error("โŒ Invalid C/N values. Use comma-separated numbers (e.g., '12' or '12, 15').") + return + + # โ”€โ”€ Computed Results โ”€โ”€ + cnr_linear, br_inf, c_n0, br_bw = shannon_points(bw_input, cnr_nyq) + br_unit = c_n0 # Spectral efficiency = 1 + + st.markdown("### ๐Ÿ“Š Results") + st.info(THEORY_HELP["c_n0"], icon="โ„น๏ธ") if st.checkbox("Show C/Nโ‚€ explanation", value=False) else None + + m1, m2, m3, m4 = st.columns(4) + m1.metric("C/Nโ‚€", f"{c_n0:.1f} MHz", help=THEORY_HELP["c_n0"]) + m2.metric("BR at โˆž BW", fmt_br(br_inf), help=THEORY_HELP["br_inf"]) + m3.metric("BR at SpEff=1", fmt_br(br_unit), help=THEORY_HELP["br_unit"]) + m4.metric("BR at Ref BW", fmt_br(br_bw), help=THEORY_HELP["br_bw"]) + + st.metric( + "C/N Ratio", + f"{cnr_nyq:.1f} dB ยท {cnr_linear:.1f} linear", + help=THEORY_HELP["cnr_lin"], + ) + + st.divider() + + # โ”€โ”€ Sensitivity Analysis โ”€โ”€ + st.markdown("### ๐Ÿ”ฌ Sensitivity Analysis") + + col_s1, col_s2, col_s3 = st.columns(3) + with col_s1: + bw_mul_val = st.number_input( + "BW Increase Factor", + value=1.0, min_value=0.01, step=0.25, + help=THEORY_HELP["bw_mul"], + ) + with col_s2: + p_mul_val = st.number_input( + "Power Increase Factor", + value=2.0, min_value=0.01, step=0.25, + help=THEORY_HELP["p_mul"], + ) + with col_s3: + br_mul_val = br_multiplier(bw_mul_val, p_mul_val, cnr_nyq) + st.metric( + "Bit Rate Factor", + f"{br_mul_val:.3f}", + delta=f"{(br_mul_val - 1) * 100:+.1f}%", + help=THEORY_HELP["br_mul"], + ) + + st.divider() + + # โ”€โ”€ Graphs โ”€โ”€ + st.markdown("### ๐Ÿ“ˆ Interactive Graphs") + + tab_bw, tab_pow, tab_map = st.tabs([ + "๐Ÿ“ถ Bandwidth Sensitivity", + "โšก Power Sensitivity", + "๐Ÿ—บ๏ธ BR Factor Map", + ]) + + with tab_bw: + st.plotly_chart( + _make_bw_sensitivity_plot(cnr_nyq, bw_input, c_n0), + width="stretch", + ) + + with tab_pow: + st.plotly_chart( + _make_power_sensitivity_plot(cnr_nyq, bw_input, cnr_linear), + width="stretch", + ) + + with tab_map: + st.plotly_chart( + _make_br_factor_map(cnr_nyq, bw_input, c_n0, br_bw), + width="stretch", + ) + + # โ”€โ”€ Help Section โ”€โ”€ + with st.expander("๐Ÿ“˜ Background Information"): + help_topic = st.selectbox( + "Choose a topic:", + options=["shannon", "advanced", "help"], + format_func=lambda x: { + "shannon": "๐Ÿง  Shannon's Equation", + "advanced": "๐Ÿ”ง Advanced (AWGN Model)", + "help": "โ“ How to use this tool", + }[x], + ) + st.markdown(THEORY_HELP[help_topic])