This commit is contained in:
SpaceNerd0717
2025-10-21 20:09:37 -04:00
parent cc62ae1a5a
commit cd9c1d2fd0
11 changed files with 15134 additions and 8306 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -2,6 +2,9 @@
[('__future__',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\__future__.py',
'PYMODULE'),
('_aix_support',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_aix_support.py',
'PYMODULE'),
('_compat_pickle',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_compat_pickle.py',
'PYMODULE'),
@@ -890,6 +893,9 @@
('subprocess',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\subprocess.py',
'PYMODULE'),
('sysconfig',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\sysconfig.py',
'PYMODULE'),
('tarfile',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tarfile.py',
'PYMODULE'),
@@ -905,6 +911,12 @@
('tkinter',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tkinter\\__init__.py',
'PYMODULE'),
('tkinter.colorchooser',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tkinter\\colorchooser.py',
'PYMODULE'),
('tkinter.commondialog',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tkinter\\commondialog.py',
'PYMODULE'),
('tkinter.constants',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tkinter\\constants.py',
'PYMODULE'),
@@ -923,6 +935,72 @@
('typing_extensions',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\typing_extensions.py',
'PYMODULE'),
('tzdata',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Africa',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Africa\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.America',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\America\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.America.Argentina',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\America\\Argentina\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.America.Indiana',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\America\\Indiana\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.America.Kentucky',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\America\\Kentucky\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.America.North_Dakota',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\America\\North_Dakota\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Antarctica',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Antarctica\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Arctic',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Arctic\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Asia',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Asia\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Atlantic',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Atlantic\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Australia',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Australia\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Brazil',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Brazil\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Canada',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Canada\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Chile',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Chile\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Etc',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Etc\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Europe',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Europe\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Indian',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Indian\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Mexico',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Mexico\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.Pacific',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\Pacific\\__init__.py',
'PYMODULE'),
('tzdata.zoneinfo.US',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tzdata\\zoneinfo\\US\\__init__.py',
'PYMODULE'),
('urllib',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\__init__.py',
'PYMODULE'),
@@ -1081,4 +1159,16 @@
'PYMODULE'),
('zipimport',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipimport.py',
'PYMODULE'),
('zoneinfo',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zoneinfo\\__init__.py',
'PYMODULE'),
('zoneinfo._common',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zoneinfo\\_common.py',
'PYMODULE'),
('zoneinfo._tzpath',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zoneinfo\\_tzpath.py',
'PYMODULE'),
('zoneinfo._zoneinfo',
'c:\\Users\\forjn\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zoneinfo\\_zoneinfo.py',
'PYMODULE')])

Binary file not shown.

View File

@@ -181,10 +181,12 @@ imports:
&#8226; <a href="#threading">threading</a>
&#8226; <a href="#time">time</a>
&#8226; <a href="#tkinter">tkinter</a>
&#8226; <a href="#tkinter.colorchooser">tkinter.colorchooser</a>
&#8226; <a href="#traceback">traceback</a>
&#8226; <a href="#types">types</a>
&#8226; <a href="#warnings">warnings</a>
&#8226; <a href="#weakref">weakref</a>
&#8226; <a href="#zoneinfo">zoneinfo</a>
</div>
@@ -396,6 +398,26 @@ imported by:
</div>
<div class="node">
<a name="_aix_support"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/_aix_support.py" type="text/plain"><tt>_aix_support</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#contextlib">contextlib</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#subprocess">subprocess</a>
&#8226; <a href="#sys">sys</a>
&#8226; <a href="#sysconfig">sysconfig</a>
</div>
<div class="import">
imported by:
<a href="#sysconfig">sysconfig</a>
</div>
</div>
<div class="node">
<a name="_ast"></a>
<tt>_ast</tt> <span class="moduletype"><i>(builtin module)</i></span> <div class="import">
@@ -746,6 +768,7 @@ imported by:
<a href="#importlib">importlib</a>
&#8226; <a href="#importlib._bootstrap_external">importlib._bootstrap_external</a>
&#8226; <a href="#importlib.util">importlib.util</a>
&#8226; <a href="#sysconfig">sysconfig</a>
&#8226; <a href="#zipimport">zipimport</a>
</div>
@@ -1315,6 +1338,16 @@ imported by:
</div>
<div class="node">
<a name="_zoneinfo"></a>
<tt>_zoneinfo</tt> <span class="moduletype"><tt>c:\Users\forjn\AppData\Local\Programs\Python\Python312\DLLs\_zoneinfo.pyd</tt></span> <div class="import">
imported by:
<a href="#zoneinfo">zoneinfo</a>
</div>
</div>
<div class="node">
<a name="abc"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/abc.py" type="text/plain"><tt>abc</tt></a>
@@ -2471,6 +2504,7 @@ imported by:
&#8226; <a href="#random">random</a>
&#8226; <a href="#statistics">statistics</a>
&#8226; <a href="#urllib.request">urllib.request</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
@@ -2573,6 +2607,7 @@ imported by:
&#8226; <a href="#http.cookiejar">http.cookiejar</a>
&#8226; <a href="#requests.cookies">requests.cookies</a>
&#8226; <a href="#ssl">ssl</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
@@ -4003,6 +4038,7 @@ imported by:
&#8226; <a href="#urllib.parse">urllib.parse</a>
&#8226; <a href="#urllib3._collections">urllib3._collections</a>
&#8226; <a href="#urllib3.response">urllib3.response</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
@@ -4167,7 +4203,8 @@ imports:
</div>
<div class="import">
imported by:
<a href="#_threading_local">_threading_local</a>
<a href="#_aix_support">_aix_support</a>
&#8226; <a href="#_threading_local">_threading_local</a>
&#8226; <a href="#ast">ast</a>
&#8226; <a href="#asyncio.staggered">asyncio.staggered</a>
&#8226; <a href="#getpass">getpass</a>
@@ -5717,6 +5754,7 @@ imported by:
&#8226; <a href="#requests.sessions">requests.sessions</a>
&#8226; <a href="#urllib3.connection">urllib3.connection</a>
&#8226; <a href="#xmlrpc.client">xmlrpc.client</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
@@ -8966,6 +9004,7 @@ imported by:
&#8226; <a href="#urllib.parse">urllib.parse</a>
&#8226; <a href="#urllib3.poolmanager">urllib3.poolmanager</a>
&#8226; <a href="#urllib3.util.wait">urllib3.util.wait</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
@@ -9445,6 +9484,8 @@ imported by:
&#8226; <a href="#inspect">inspect</a>
&#8226; <a href="#pkgutil">pkgutil</a>
&#8226; <a href="#requests.compat">requests.compat</a>
&#8226; <a href="#zoneinfo._common">zoneinfo._common</a>
&#8226; <a href="#zoneinfo._tzpath">zoneinfo._tzpath</a>
</div>
@@ -9767,6 +9808,8 @@ imported by:
&#8226; <a href="#importlib.resources.abc">importlib.resources.abc</a>
&#8226; <a href="#importlib.resources.readers">importlib.resources.readers</a>
&#8226; <a href="#urllib3.contrib.emscripten.fetch">urllib3.contrib.emscripten.fetch</a>
&#8226; <a href="#zoneinfo._common">zoneinfo._common</a>
&#8226; <a href="#zoneinfo._tzpath">zoneinfo._tzpath</a>
</div>
@@ -11386,7 +11429,8 @@ imports:
</div>
<div class="import">
imported by:
<a href="#argparse">argparse</a>
<a href="#_aix_support">_aix_support</a>
&#8226; <a href="#argparse">argparse</a>
&#8226; <a href="#asyncio.base_events">asyncio.base_events</a>
&#8226; <a href="#asyncio.coroutines">asyncio.coroutines</a>
&#8226; <a href="#asyncio.events">asyncio.events</a>
@@ -11460,6 +11504,7 @@ imported by:
&#8226; <a href="#socket">socket</a>
&#8226; <a href="#ssl">ssl</a>
&#8226; <a href="#subprocess">subprocess</a>
&#8226; <a href="#sysconfig">sysconfig</a>
&#8226; <a href="#tarfile">tarfile</a>
&#8226; <a href="#tempfile">tempfile</a>
&#8226; <a href="#threading">threading</a>
@@ -11472,6 +11517,7 @@ imported by:
&#8226; <a href="#xml.sax">xml.sax</a>
&#8226; <a href="#xml.sax.saxutils">xml.sax.saxutils</a>
&#8226; <a href="#zipfile">zipfile</a>
&#8226; <a href="#zoneinfo._tzpath">zoneinfo._tzpath</a>
</div>
@@ -11492,6 +11538,7 @@ imported by:
&#8226; <a href="#pkgutil">pkgutil</a>
&#8226; <a href="#py_compile">py_compile</a>
&#8226; <a href="#requests.adapters">requests.adapters</a>
&#8226; <a href="#sysconfig">sysconfig</a>
&#8226; <a href="#tracemalloc">tracemalloc</a>
</div>
@@ -11557,6 +11604,7 @@ imported by:
<a href="#logging">logging</a>
&#8226; <a href="#multiprocessing.reduction">multiprocessing.reduction</a>
&#8226; <a href="#tracemalloc">tracemalloc</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
@@ -11655,6 +11703,7 @@ imports:
<div class="import">
imported by:
<a href="#pickle">pickle</a>
&#8226; <a href="#sysconfig">sysconfig</a>
</div>
@@ -11893,6 +11942,7 @@ imported by:
&#8226; <a href="#sre_constants">sre_constants</a>
&#8226; <a href="#sre_parse">sre_parse</a>
&#8226; <a href="#string">string</a>
&#8226; <a href="#sysconfig">sysconfig</a>
&#8226; <a href="#tarfile">tarfile</a>
&#8226; <a href="#textwrap">textwrap</a>
&#8226; <a href="#tkinter">tkinter</a>
@@ -11908,6 +11958,7 @@ imported by:
&#8226; <a href="#warnings">warnings</a>
&#8226; <a href="#zipfile._path">zipfile._path</a>
&#8226; <a href="#zipfile._path.glob">zipfile._path.glob</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
@@ -12920,6 +12971,7 @@ imported by:
&#8226; <a href="#requests.utils">requests.utils</a>
&#8226; <a href="#tarfile">tarfile</a>
&#8226; <a href="#zipfile">zipfile</a>
&#8226; <a href="#zoneinfo._common">zoneinfo._common</a>
</div>
@@ -12954,7 +13006,8 @@ imports:
</div>
<div class="import">
imported by:
<a href="#asyncio.base_events">asyncio.base_events</a>
<a href="#_aix_support">_aix_support</a>
&#8226; <a href="#asyncio.base_events">asyncio.base_events</a>
&#8226; <a href="#asyncio.base_subprocess">asyncio.base_subprocess</a>
&#8226; <a href="#asyncio.events">asyncio.events</a>
&#8226; <a href="#asyncio.subprocess">asyncio.subprocess</a>
@@ -12972,7 +13025,8 @@ imported by:
<a name="sys"></a>
<tt>sys</tt> <span class="moduletype"><i>(builtin module)</i></span> <div class="import">
imported by:
<a href="#_collections_abc">_collections_abc</a>
<a href="#_aix_support">_aix_support</a>
&#8226; <a href="#_collections_abc">_collections_abc</a>
&#8226; <a href="#_compression">_compression</a>
&#8226; <a href="#_pydatetime">_pydatetime</a>
&#8226; <a href="#_pydecimal">_pydecimal</a>
@@ -13069,6 +13123,7 @@ imported by:
&#8226; <a href="#ssl">ssl</a>
&#8226; <a href="#statistics">statistics</a>
&#8226; <a href="#subprocess">subprocess</a>
&#8226; <a href="#sysconfig">sysconfig</a>
&#8226; <a href="#tarfile">tarfile</a>
&#8226; <a href="#tempfile">tempfile</a>
&#8226; <a href="#threading">threading</a>
@@ -13097,6 +13152,32 @@ imported by:
</div>
<div class="node">
<a name="sysconfig"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/sysconfig.py" type="text/plain"><tt>sysconfig</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#_aix_support">_aix_support</a>
&#8226; <a href="#_imp">_imp</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#os.path">os.path</a>
&#8226; <a href="#pprint">pprint</a>
&#8226; <a href="#re">re</a>
&#8226; <a href="#sys">sys</a>
&#8226; <a href="#threading">threading</a>
&#8226; <a href="#types">types</a>
&#8226; <a href="#warnings">warnings</a>
</div>
<div class="import">
imported by:
<a href="#_aix_support">_aix_support</a>
&#8226; <a href="#zoneinfo._tzpath">zoneinfo._tzpath</a>
</div>
</div>
<div class="node">
<a name="tarfile"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/tarfile.py" type="text/plain"><tt>tarfile</tt></a>
@@ -13246,6 +13327,7 @@ imported by:
&#8226; <a href="#requests.auth">requests.auth</a>
&#8226; <a href="#requests.cookies">requests.cookies</a>
&#8226; <a href="#subprocess">subprocess</a>
&#8226; <a href="#sysconfig">sysconfig</a>
&#8226; <a href="#urllib3._collections">urllib3._collections</a>
&#8226; <a href="#zipfile">zipfile</a>
@@ -13314,6 +13396,7 @@ imports:
&#8226; <a href="#os">os</a>
&#8226; <a href="#re">re</a>
&#8226; <a href="#sys">sys</a>
&#8226; <a href="#tkinter.colorchooser">tkinter.colorchooser</a>
&#8226; <a href="#tkinter.constants">tkinter.constants</a>
&#8226; <a href="#traceback">traceback</a>
&#8226; <a href="#types">types</a>
@@ -13322,12 +13405,48 @@ imports:
<div class="import">
imported by:
<a href="#main.py">main.py</a>
&#8226; <a href="#tkinter.colorchooser">tkinter.colorchooser</a>
&#8226; <a href="#tkinter.commondialog">tkinter.commondialog</a>
&#8226; <a href="#tkinter.constants">tkinter.constants</a>
</div>
</div>
<div class="node">
<a name="tkinter.colorchooser"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/tkinter/colorchooser.py" type="text/plain"><tt>tkinter.colorchooser</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#tkinter">tkinter</a>
&#8226; <a href="#tkinter.commondialog">tkinter.commondialog</a>
</div>
<div class="import">
imported by:
<a href="#main.py">main.py</a>
&#8226; <a href="#tkinter">tkinter</a>
</div>
</div>
<div class="node">
<a name="tkinter.commondialog"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/tkinter/commondialog.py" type="text/plain"><tt>tkinter.commondialog</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#tkinter">tkinter</a>
</div>
<div class="import">
imported by:
<a href="#tkinter.colorchooser">tkinter.colorchooser</a>
</div>
</div>
<div class="node">
<a name="tkinter.constants"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/tkinter/constants.py" type="text/plain"><tt>tkinter.constants</tt></a>
@@ -13494,6 +13613,7 @@ imported by:
&#8226; <a href="#pprint">pprint</a>
&#8226; <a href="#queue">queue</a>
&#8226; <a href="#subprocess">subprocess</a>
&#8226; <a href="#sysconfig">sysconfig</a>
&#8226; <a href="#tempfile">tempfile</a>
&#8226; <a href="#tkinter">tkinter</a>
&#8226; <a href="#typing">typing</a>
@@ -13646,6 +13766,401 @@ imported by:
</div>
<div class="node">
<a name="tzdata"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/__init__.py" type="text/plain"><tt>tzdata</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata">tzdata</a>
&#8226; <a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
&#8226; <a href="#tzdata.zoneinfo.Africa">tzdata.zoneinfo.Africa</a>
&#8226; <a href="#tzdata.zoneinfo.America">tzdata.zoneinfo.America</a>
&#8226; <a href="#tzdata.zoneinfo.America.Argentina">tzdata.zoneinfo.America.Argentina</a>
&#8226; <a href="#tzdata.zoneinfo.America.Indiana">tzdata.zoneinfo.America.Indiana</a>
&#8226; <a href="#tzdata.zoneinfo.America.Kentucky">tzdata.zoneinfo.America.Kentucky</a>
&#8226; <a href="#tzdata.zoneinfo.America.North_Dakota">tzdata.zoneinfo.America.North_Dakota</a>
&#8226; <a href="#tzdata.zoneinfo.Antarctica">tzdata.zoneinfo.Antarctica</a>
&#8226; <a href="#tzdata.zoneinfo.Arctic">tzdata.zoneinfo.Arctic</a>
&#8226; <a href="#tzdata.zoneinfo.Asia">tzdata.zoneinfo.Asia</a>
&#8226; <a href="#tzdata.zoneinfo.Atlantic">tzdata.zoneinfo.Atlantic</a>
&#8226; <a href="#tzdata.zoneinfo.Australia">tzdata.zoneinfo.Australia</a>
&#8226; <a href="#tzdata.zoneinfo.Brazil">tzdata.zoneinfo.Brazil</a>
&#8226; <a href="#tzdata.zoneinfo.Canada">tzdata.zoneinfo.Canada</a>
&#8226; <a href="#tzdata.zoneinfo.Chile">tzdata.zoneinfo.Chile</a>
&#8226; <a href="#tzdata.zoneinfo.Etc">tzdata.zoneinfo.Etc</a>
&#8226; <a href="#tzdata.zoneinfo.Europe">tzdata.zoneinfo.Europe</a>
&#8226; <a href="#tzdata.zoneinfo.Indian">tzdata.zoneinfo.Indian</a>
&#8226; <a href="#tzdata.zoneinfo.Mexico">tzdata.zoneinfo.Mexico</a>
&#8226; <a href="#tzdata.zoneinfo.Pacific">tzdata.zoneinfo.Pacific</a>
&#8226; <a href="#tzdata.zoneinfo.US">tzdata.zoneinfo.US</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
&#8226; <a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
&#8226; <a href="#zoneinfo">zoneinfo</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/__init__.py" type="text/plain"><tt>tzdata.zoneinfo</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata">tzdata</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
&#8226; <a href="#tzdata.zoneinfo.Africa">tzdata.zoneinfo.Africa</a>
&#8226; <a href="#tzdata.zoneinfo.America">tzdata.zoneinfo.America</a>
&#8226; <a href="#tzdata.zoneinfo.Antarctica">tzdata.zoneinfo.Antarctica</a>
&#8226; <a href="#tzdata.zoneinfo.Arctic">tzdata.zoneinfo.Arctic</a>
&#8226; <a href="#tzdata.zoneinfo.Asia">tzdata.zoneinfo.Asia</a>
&#8226; <a href="#tzdata.zoneinfo.Atlantic">tzdata.zoneinfo.Atlantic</a>
&#8226; <a href="#tzdata.zoneinfo.Australia">tzdata.zoneinfo.Australia</a>
&#8226; <a href="#tzdata.zoneinfo.Brazil">tzdata.zoneinfo.Brazil</a>
&#8226; <a href="#tzdata.zoneinfo.Canada">tzdata.zoneinfo.Canada</a>
&#8226; <a href="#tzdata.zoneinfo.Chile">tzdata.zoneinfo.Chile</a>
&#8226; <a href="#tzdata.zoneinfo.Etc">tzdata.zoneinfo.Etc</a>
&#8226; <a href="#tzdata.zoneinfo.Europe">tzdata.zoneinfo.Europe</a>
&#8226; <a href="#tzdata.zoneinfo.Indian">tzdata.zoneinfo.Indian</a>
&#8226; <a href="#tzdata.zoneinfo.Mexico">tzdata.zoneinfo.Mexico</a>
&#8226; <a href="#tzdata.zoneinfo.Pacific">tzdata.zoneinfo.Pacific</a>
&#8226; <a href="#tzdata.zoneinfo.US">tzdata.zoneinfo.US</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Africa"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Africa/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Africa</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.America"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/America/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.America</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
&#8226; <a href="#tzdata.zoneinfo.America.Argentina">tzdata.zoneinfo.America.Argentina</a>
&#8226; <a href="#tzdata.zoneinfo.America.Indiana">tzdata.zoneinfo.America.Indiana</a>
&#8226; <a href="#tzdata.zoneinfo.America.Kentucky">tzdata.zoneinfo.America.Kentucky</a>
&#8226; <a href="#tzdata.zoneinfo.America.North_Dakota">tzdata.zoneinfo.America.North_Dakota</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.America.Argentina"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/America/Argentina/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.America.Argentina</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo.America">tzdata.zoneinfo.America</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.America.Indiana"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/America/Indiana/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.America.Indiana</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo.America">tzdata.zoneinfo.America</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.America.Kentucky"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/America/Kentucky/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.America.Kentucky</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo.America">tzdata.zoneinfo.America</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.America.North_Dakota"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/America/North_Dakota/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.America.North_Dakota</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo.America">tzdata.zoneinfo.America</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Antarctica"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Antarctica/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Antarctica</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Arctic"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Arctic/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Arctic</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Asia"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Asia/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Asia</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Atlantic"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Atlantic/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Atlantic</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Australia"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Australia/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Australia</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Brazil"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Brazil/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Brazil</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Canada"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Canada/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Canada</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Chile"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Chile/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Chile</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Etc"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Etc/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Etc</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Europe"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Europe/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Europe</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Indian"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Indian/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Indian</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Mexico"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Mexico/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Mexico</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.Pacific"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/Pacific/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.Pacific</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="tzdata.zoneinfo.US"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/site-packages/tzdata/zoneinfo/US/__init__.py" type="text/plain"><tt>tzdata.zoneinfo.US</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#tzdata.zoneinfo">tzdata.zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#tzdata">tzdata</a>
</div>
</div>
<div class="node">
<a name="unicodedata"></a>
<tt>unicodedata</tt> <span class="moduletype"><tt>c:\Users\forjn\AppData\Local\Programs\Python\Python312\DLLs\unicodedata.pyd</tt></span> <div class="import">
@@ -14866,6 +15381,7 @@ imported by:
&#8226; <a href="#sre_parse">sre_parse</a>
&#8226; <a href="#ssl">ssl</a>
&#8226; <a href="#subprocess">subprocess</a>
&#8226; <a href="#sysconfig">sysconfig</a>
&#8226; <a href="#tarfile">tarfile</a>
&#8226; <a href="#tempfile">tempfile</a>
&#8226; <a href="#threading">threading</a>
@@ -14883,6 +15399,7 @@ imported by:
&#8226; <a href="#urllib3.response">urllib3.response</a>
&#8226; <a href="#urllib3.util.ssl_">urllib3.util.ssl_</a>
&#8226; <a href="#zipfile">zipfile</a>
&#8226; <a href="#zoneinfo._tzpath">zoneinfo._tzpath</a>
</div>
@@ -14924,6 +15441,7 @@ imported by:
&#8226; <a href="#tempfile">tempfile</a>
&#8226; <a href="#urllib3.connectionpool">urllib3.connectionpool</a>
&#8226; <a href="#xml.sax.expatreader">xml.sax.expatreader</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
@@ -15296,6 +15814,99 @@ imported by:
</div>
<div class="node">
<a name="zoneinfo"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/zoneinfo/__init__.py" type="text/plain"><tt>zoneinfo</tt></a>
<span class="moduletype">Package</span> <div class="import">
imports:
<a href="#_zoneinfo">_zoneinfo</a>
&#8226; <a href="#tzdata">tzdata</a>
&#8226; <a href="#zoneinfo">zoneinfo</a>
&#8226; <a href="#zoneinfo._common">zoneinfo._common</a>
&#8226; <a href="#zoneinfo._tzpath">zoneinfo._tzpath</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#main.py">main.py</a>
&#8226; <a href="#zoneinfo">zoneinfo</a>
&#8226; <a href="#zoneinfo._common">zoneinfo._common</a>
&#8226; <a href="#zoneinfo._tzpath">zoneinfo._tzpath</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
</div>
<div class="node">
<a name="zoneinfo._common"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/zoneinfo/_common.py" type="text/plain"><tt>zoneinfo._common</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#importlib">importlib</a>
&#8226; <a href="#importlib.resources">importlib.resources</a>
&#8226; <a href="#struct">struct</a>
&#8226; <a href="#zoneinfo">zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#zoneinfo">zoneinfo</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
</div>
<div class="node">
<a name="zoneinfo._tzpath"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/zoneinfo/_tzpath.py" type="text/plain"><tt>zoneinfo._tzpath</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#importlib">importlib</a>
&#8226; <a href="#importlib.resources">importlib.resources</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#sysconfig">sysconfig</a>
&#8226; <a href="#warnings">warnings</a>
&#8226; <a href="#zoneinfo">zoneinfo</a>
</div>
<div class="import">
imported by:
<a href="#zoneinfo">zoneinfo</a>
&#8226; <a href="#zoneinfo._zoneinfo">zoneinfo._zoneinfo</a>
</div>
</div>
<div class="node">
<a name="zoneinfo._zoneinfo"></a>
<a target="code" href="///C:/Users/forjn/AppData/Local/Programs/Python/Python312/Lib/zoneinfo/_zoneinfo.py" type="text/plain"><tt>zoneinfo._zoneinfo</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#bisect">bisect</a>
&#8226; <a href="#calendar">calendar</a>
&#8226; <a href="#collections">collections</a>
&#8226; <a href="#datetime">datetime</a>
&#8226; <a href="#functools">functools</a>
&#8226; <a href="#pickle">pickle</a>
&#8226; <a href="#re">re</a>
&#8226; <a href="#weakref">weakref</a>
&#8226; <a href="#zoneinfo">zoneinfo</a>
&#8226; <a href="#zoneinfo._common">zoneinfo._common</a>
&#8226; <a href="#zoneinfo._tzpath">zoneinfo._tzpath</a>
</div>
<div class="import">
imported by:
<a href="#zoneinfo">zoneinfo</a>
</div>
</div>
<div class="node">
<a name="zstandard"></a>
<a target="code" href="" type="text/plain"><tt>zstandard</tt></a>

BIN
dist/exe/0.5.0/main.exe vendored Normal file

Binary file not shown.

BIN
dist/installers/0.5.0/RLCInstaller.exe vendored Normal file

Binary file not shown.

726
main.py
View File

@@ -1,7 +1,9 @@
import tkinter as tk
from tkinter import colorchooser
import time
import threading
from datetime import datetime, timedelta
import re
import requests
import csv
import io
@@ -12,7 +14,6 @@ try:
except Exception:
ZoneInfo = None
# Get the user's Documents folder (cross-platform)
documents_folder = os.path.join(os.path.expanduser("~"), "Documents")
@@ -25,29 +26,52 @@ COUNTDOWN_HTML = os.path.join(app_folder, "countdown.html")
GONOGO_HTML = os.path.join(app_folder, "gonogo.html")
SHEET_LINK = ""
session = requests.Session()
appVersion = "0.4.0"
appVersion = "0.5.0"
SETTINGS_FILE = os.path.join(app_folder, "settings.json")
# Default settings
DEFAULT_SETTINGS = {
"mode": "spreadsheet", # or 'buttons'
"mode": "spreadsheet",
"sheet_link": SHEET_LINK,
# rows are 1-based as shown in spreadsheet; default 2,3,4 -> indices 1,2,3
"range_row": 2,
"weather_row": 3,
"vehicle_row": 4,
"column": 12 # 1-based column (default column 12 -> index 11)
"column": 12
}
# default timezone: 'local' uses system local tz, otherwise an IANA name or 'UTC'
DEFAULT_SETTINGS.setdefault('timezone', 'local')
# Appearance defaults
DEFAULT_SETTINGS.setdefault('bg_color', '#000000')
DEFAULT_SETTINGS.setdefault('text_color', '#FFFFFF')
DEFAULT_SETTINGS.setdefault('font_family', 'Consolas')
DEFAULT_SETTINGS.setdefault('mission_font_px', 24)
DEFAULT_SETTINGS.setdefault('timer_font_px', 80)
DEFAULT_SETTINGS.setdefault('gn_bg_color', '#111111')
DEFAULT_SETTINGS.setdefault('gn_border_color', '#FFFFFF')
DEFAULT_SETTINGS.setdefault('gn_go_color', '#00FF00')
DEFAULT_SETTINGS.setdefault('gn_nogo_color', '#FF0000')
DEFAULT_SETTINGS.setdefault('gn_font_px', 20)
DEFAULT_SETTINGS.setdefault('appearance_mode', 'dark')
# HTML-only appearance defaults (these should not affect the Python GUI)
DEFAULT_SETTINGS.setdefault('html_bg_color', DEFAULT_SETTINGS.get('bg_color', '#000000'))
DEFAULT_SETTINGS.setdefault('html_text_color', DEFAULT_SETTINGS.get('text_color', '#FFFFFF'))
DEFAULT_SETTINGS.setdefault('html_font_family', DEFAULT_SETTINGS.get('font_family', 'Consolas'))
DEFAULT_SETTINGS.setdefault('html_mission_font_px', DEFAULT_SETTINGS.get('mission_font_px', 24))
DEFAULT_SETTINGS.setdefault('html_timer_font_px', DEFAULT_SETTINGS.get('timer_font_px', 80))
DEFAULT_SETTINGS.setdefault('html_gn_bg_color', DEFAULT_SETTINGS.get('gn_bg_color', '#111111'))
DEFAULT_SETTINGS.setdefault('html_gn_border_color', DEFAULT_SETTINGS.get('gn_border_color', '#FFFFFF'))
DEFAULT_SETTINGS.setdefault('html_gn_go_color', DEFAULT_SETTINGS.get('gn_go_color', '#00FF00'))
DEFAULT_SETTINGS.setdefault('html_gn_nogo_color', DEFAULT_SETTINGS.get('gn_nogo_color', '#FF0000'))
DEFAULT_SETTINGS.setdefault('html_gn_font_px', DEFAULT_SETTINGS.get('gn_font_px', 20))
# A small list of common timezone choices.
TIMEZONE_CHOICES = [
'local', 'UTC', 'US/Eastern', 'US/Central', 'US/Mountain', 'US/Pacific',
'Europe/London', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'
]
def load_settings():
try:
if os.path.exists(SETTINGS_FILE):
@@ -59,7 +83,6 @@ def load_settings():
save_settings(DEFAULT_SETTINGS)
return DEFAULT_SETTINGS.copy()
def save_settings(s):
try:
with open(SETTINGS_FILE, 'w', encoding='utf-8') as fh:
@@ -114,14 +137,42 @@ def fetch_gonogo():
def get_status_color(status):
"""Return color name for a Go/No-Go status string."""
try:
return "green" if str(status).strip().upper() == "GO" else "red"
s = str(status or '').strip().upper()
# normalize to letters only so variants like 'NO GO', 'NO-GO', 'NOGO' match
norm = re.sub(r'[^A-Z]', '', s)
if norm == 'GO':
return 'green'
if norm == 'NOGO':
return 'red'
# fallback: treat unknown/empty as white
return 'white'
except Exception:
return "white"
def format_status_display(status):
try:
s = str(status or '').strip().upper()
norm = re.sub(r'[^A-Z]', '', s)
if norm == 'GO':
return 'GO'
if norm == 'NOGO':
return 'NO-GO'
return s
except Exception:
return str(status or '')
# -------------------------
# Write Countdown HTML
# -------------------------
def write_countdown_html(mission_name, timer_text):
s = load_settings()
# Prefer HTML-specific settings; fall back to GUI appearance settings for backwards compatibility
bg = s.get('html_bg_color', s.get('bg_color', '#000000'))
text = s.get('html_text_color', s.get('text_color', '#FFFFFF'))
font = s.get('html_font_family', s.get('font_family', 'Consolas, monospace'))
mission_px = int(s.get('html_mission_font_px', s.get('mission_font_px', 48)))
timer_px = int(s.get('html_timer_font_px', s.get('timer_font_px', 120)))
html = f"""<!DOCTYPE html>
<html>
<head>
@@ -129,16 +180,16 @@ def write_countdown_html(mission_name, timer_text):
<style>
body {{
margin: 0;
background-color: black;
background-color: {bg};
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
font-family: Consolas, monospace;
color: {text};
font-family: {font};
}}
#mission {{ font-size: 4vw; margin-bottom: 0; }}
#timer {{ font-size: 8vw; margin-bottom: 40px; }}
#mission {{ font-size: {mission_px}px; margin-bottom: 0; }}
#timer {{ font-size: {timer_px}px; margin-bottom: 40px; }}
</style>
<script>
setTimeout(() => location.reload(), 1000);
@@ -158,6 +209,24 @@ setTimeout(() => location.reload(), 1000);
def write_gonogo_html(gonogo_values=None):
if gonogo_values is None:
gonogo_values = ["N/A", "N/A", "N/A"]
s = load_settings()
# Prefer HTML-specific settings; fall back to GUI appearance settings for backwards compatibility
bg = s.get('html_bg_color', s.get('bg_color', '#000000'))
text = s.get('html_text_color', s.get('text_color', '#FFFFFF'))
font = s.get('html_font_family', s.get('font_family', 'Consolas, monospace'))
gn_bg = s.get('html_gn_bg_color', s.get('gn_bg_color', '#111111'))
gn_border = s.get('html_gn_border_color', s.get('gn_border_color', '#FFFFFF'))
gn_go = s.get('html_gn_go_color', s.get('gn_go_color', '#00FF00'))
gn_nogo = s.get('html_gn_nogo_color', s.get('gn_nogo_color', '#FF0000'))
gn_px = int(s.get('html_gn_font_px', s.get('gn_font_px', 28)))
# normalize and format display values so variants like 'NO GO' become 'NO-GO'
disp0 = format_status_display(gonogo_values[0])
disp1 = format_status_display(gonogo_values[1])
disp2 = format_status_display(gonogo_values[2])
n0 = re.sub(r'[^A-Z]', '', (str(gonogo_values[0] or '')).strip().upper())
n1 = re.sub(r'[^A-Z]', '', (str(gonogo_values[1] or '')).strip().upper())
n2 = re.sub(r'[^A-Z]', '', (str(gonogo_values[2] or '')).strip().upper())
html = f"""<!DOCTYPE html>
<html>
<head>
@@ -165,9 +234,9 @@ def write_gonogo_html(gonogo_values=None):
<style>
body {{
margin: 0;
background-color: black;
color: white;
font-family: Consolas, monospace;
background-color: {bg};
color: {text};
font-family: {font};
display: flex;
justify-content: center;
align-items: center;
@@ -178,24 +247,24 @@ body {{
gap: 40px;
}}
.status-box {{
border: 2px solid white;
border: 2px solid {gn_border};
padding: 20px 40px;
font-size: 2.5vw;
font-size: {gn_px}px;
text-align: center;
background-color: #111;
background-color: {gn_bg};
}}
.go {{ color: #0f0; }}
.nogo {{ color: #f00; }}
.go {{ color: {gn_go}; }}
.nogo {{ color: {gn_nogo}; }}
</style>
<script>
setTimeout(() => location.reload(), 5000);
</script>
</head>
<body>
<div id="gonogo">
<div class="status-box {'go' if gonogo_values[0].lower()=='go' else 'nogo'}">Range: {gonogo_values[0]}</div>
<div class="status-box {'go' if gonogo_values[2].lower()=='go' else 'nogo'}">Vehicle: {gonogo_values[2]}</div>
<div class="status-box {'go' if gonogo_values[1].lower()=='go' else 'nogo'}">Weather: {gonogo_values[1]}</div>
<div id="gonogo">
<div class="status-box {'go' if n0=='GO' else 'nogo'}">Range: {disp0}</div>
<div class="status-box {'go' if n2=='GO' else 'nogo'}">Vehicle: {disp2}</div>
<div class="status-box {'go' if n1=='GO' else 'nogo'}">Weather: {disp1}</div>
</div>
</body>
</html>"""
@@ -227,7 +296,7 @@ class CountdownApp:
self.last_gonogo_update = time.time()
# Title
self.titletext = tk.Label(root, text="RocketLaunchCountdown", font=("Consolas", 24), fg="white", bg="black")
self.titletext = tk.Label(root, text=f"RocketLaunchCountdown {appVersion}", font=("Consolas", 24), fg="white", bg="black")
self.titletext.pack(pady=(10, 0))
# Display
@@ -325,8 +394,8 @@ class CountdownApp:
self.reset_btn = tk.Button(frame_buttons, text="⟳ Reset", command=self.reset, font=("Arial", 14))
self.reset_btn.grid(row=0, column=3, padx=5)
# Settings button moved next to control buttons (match size/style)
settings_btn = tk.Button(frame_buttons, text="Settings", command=self.show_settings_window, font=("Arial", 14), width=10)
settings_btn.grid(row=0, column=4, padx=6)
self.settings_btn = tk.Button(frame_buttons, text="Settings", command=self.show_settings_window, font=("Arial", 14), width=10)
self.settings_btn.grid(row=0, column=4, padx=6)
# Note: gonogo mode switching remains in Settings; manual buttons appear when mode == 'buttons'
@@ -372,6 +441,11 @@ class CountdownApp:
self.update_inputs()
# set initial manual button visibility from settings
self.update_manual_visibility()
# Apply appearance settings at startup so the mission entry and other widgets reflect the saved mode
try:
self.apply_appearance_settings()
except Exception:
pass
self.update_clock()
# ----------------------------
@@ -379,37 +453,68 @@ class CountdownApp:
# ----------------------------
def show_settings_window(self):
settings = load_settings()
win = tk.Toplevel(self.root)
win.config(bg="black")
win.title("Settings")
win.geometry("560x250")
win.transient(self.root)
win.title("Settings")
win.geometry("560x275")
# apply current appearance mode so the settings window matches the main UI
s_local = load_settings()
mode_local = s_local.get('appearance_mode', 'dark')
if mode_local == 'dark':
win_bg = '#000000'; win_text = '#FFFFFF'; btn_bg = '#FFFFFF'; btn_fg = '#000000'
else:
win_bg = '#FFFFFF'; win_text = '#000000'; btn_bg = '#000000'; btn_fg = '#FFFFFF'
win.config(bg=win_bg)
# set per-window widget defaults so nested widgets inherit the chosen theme
try:
win.option_add('*Foreground', win_text)
win.option_add('*Background', win_bg)
# entry specific defaults
win.option_add('*Entry.Background', '#222' if mode_local == 'dark' else '#b4b4b4')
win.option_add('*Entry.Foreground', win_text if mode_local == 'dark' else '#000000')
except Exception:
pass
# keep track of this Toplevel so other dialogs can close it if needed
try:
self.settings_win = win
def _clear_settings_ref(evt=None):
try:
self.settings_win = None
except Exception:
pass
win.bind('<Destroy>', _clear_settings_ref)
except Exception:
pass
# Mode selection
frame_mode = tk.Frame(win)
frame_mode.config(bg="black")
frame_mode.config(bg=win_bg)
frame_mode.pack(fill='x', pady=8, padx=8)
tk.Label(frame_mode, text="Mode:", fg="white", bg="black").pack(side='left')
tk.Label(frame_mode, text="Mode:", fg=win_text, bg=win_bg).pack(side='left')
mode_var = tk.StringVar(value=settings.get('mode', 'spreadsheet'))
tk.Radiobutton(frame_mode, text='Spreadsheet', variable=mode_var, value='spreadsheet', fg="white", bg="black", selectcolor="black").pack(side='left', padx=8)
tk.Radiobutton(frame_mode, text='Buttons (manual)', variable=mode_var, value='buttons', fg="white", bg="black", selectcolor="black").pack(side='left', padx=8)
tk.Radiobutton(frame_mode, text='Spreadsheet', variable=mode_var, value='spreadsheet', fg=win_text, bg=win_bg, selectcolor=win_bg).pack(side='left', padx=8)
tk.Radiobutton(frame_mode, text='Buttons (manual)', variable=mode_var, value='buttons', fg=win_text, bg=win_bg, selectcolor=win_bg).pack(side='left', padx=8)
# Spreadsheet config
frame_sheet = tk.LabelFrame(win, text='Spreadsheet configuration', fg='white', bg='black')
frame_sheet.config(bg="black")
frame_sheet = tk.LabelFrame(win, text='Spreadsheet configuration', fg=win_text, bg=win_bg)
frame_sheet.config(bg=win_bg)
frame_sheet.pack(fill='x', padx=8, pady=6)
tk.Label(frame_sheet, text='Sheet link (CSV export):', fg='white', bg='black').pack(anchor='w')
sheet_entry = tk.Entry(frame_sheet, width=80, fg='white', bg='#222', insertbackground='white')
tk.Label(frame_sheet, text='Sheet link (CSV export):', fg=win_text, bg=win_bg).pack(anchor='w')
# entry background chosen to contrast with window background
sheet_entry_bg = '#222' if mode_local == 'dark' else '#b4b4b4'
sheet_entry_fg = win_text if mode_local == 'dark' else '#000000'
sheet_entry = tk.Entry(frame_sheet, width=80, fg=sheet_entry_fg, bg=sheet_entry_bg, insertbackground=sheet_entry_fg)
sheet_entry.pack(fill='x', padx=6, pady=4)
sheet_entry.insert(0, settings.get('sheet_link', SHEET_LINK))
# Accept cells in 'L3' format for each parameter
cell_frame = tk.Frame(frame_sheet)
cell_frame.config(bg="black")
cell_frame.config(bg=win_bg)
cell_frame.pack(fill='x', padx=6, pady=2)
tk.Label(cell_frame, text='Range cell (e.g. L3):', fg='white', bg='black').grid(row=0, column=0)
range_cell = tk.Entry(cell_frame, width=8, fg='white', bg='#222', insertbackground='white')
tk.Label(cell_frame, text='Range cell (e.g. L3):', fg=win_text, bg=win_bg).grid(row=0, column=0)
range_cell_bg = '#222' if mode_local == 'dark' else '#b4b4b4'
range_cell_fg = win_text if mode_local == 'dark' else '#000000'
range_cell = tk.Entry(cell_frame, width=8, fg=range_cell_fg, bg=range_cell_bg, insertbackground=range_cell_fg)
range_cell.grid(row=0, column=1, padx=4)
# show as L3 if present, otherwise build from numeric settings
try:
@@ -430,8 +535,8 @@ class CountdownApp:
except Exception:
range_cell.insert(0, f"L3")
tk.Label(cell_frame, text='Weather cell (e.g. L4):', fg='white', bg='black').grid(row=0, column=2)
weather_cell = tk.Entry(cell_frame, width=8, fg='white', bg='#222', insertbackground='white')
tk.Label(cell_frame, text='Weather cell (e.g. L4):', fg=win_text, bg=win_bg).grid(row=0, column=2)
weather_cell = tk.Entry(cell_frame, width=8, fg=range_cell_fg, bg=range_cell_bg, insertbackground=range_cell_fg)
weather_cell.grid(row=0, column=3, padx=4)
try:
if 'weather_cell' in settings:
@@ -449,8 +554,8 @@ class CountdownApp:
except Exception:
weather_cell.insert(0, f"L4")
tk.Label(cell_frame, text='Vehicle cell (e.g. L5):', fg='white', bg='black').grid(row=0, column=4)
vehicle_cell = tk.Entry(cell_frame, width=8, fg='white', bg='#222', insertbackground='white')
tk.Label(cell_frame, text='Vehicle cell (e.g. L5):', fg=win_text, bg=win_bg).grid(row=0, column=4)
vehicle_cell = tk.Entry(cell_frame, width=8, fg=range_cell_fg, bg=range_cell_bg, insertbackground=range_cell_fg)
vehicle_cell.grid(row=0, column=5, padx=4)
try:
if 'vehicle_cell' in settings:
@@ -469,18 +574,23 @@ class CountdownApp:
vehicle_cell.insert(0, f"L5")
# Manual buttons config
frame_buttons_cfg = tk.LabelFrame(win, text='Manual Go/No-Go (Buttons mode)', fg='white', bg='black')
frame_buttons_cfg.config(bg='black')
frame_buttons_cfg = tk.LabelFrame(win, text='Manual Go/No-Go (Buttons mode)', fg=win_text, bg=win_bg)
frame_buttons_cfg.config(bg=win_bg)
frame_buttons_cfg.pack(fill='x', padx=8, pady=6)
# Appearance settings are in a separate window
frame_appearance_btn = tk.Frame(win, bg=win_bg)
frame_appearance_btn.pack(fill='x', padx=8, pady=6)
tk.Button(frame_appearance_btn, text='Appearance...', command=lambda: self.show_appearance_window(), fg=btn_fg, bg=btn_bg, activebackground='#444').pack(side='left')
# Timezone selector
tz_frame = tk.Frame(frame_sheet, bg='black')
tz_frame = tk.Frame(frame_sheet, bg=win_bg)
tz_frame.pack(fill='x', padx=6, pady=4)
tk.Label(tz_frame, text='Timezone:', fg='white', bg='black').pack(side='left')
tk.Label(tz_frame, text='Timezone:', fg=win_text, bg=win_bg).pack(side='left')
tz_var = tk.StringVar(value=settings.get('timezone', DEFAULT_SETTINGS.get('timezone', 'local')))
# OptionMenu with a few choices, but user may edit the text to any IANA name
tz_menu = tk.OptionMenu(tz_frame, tz_var, *TIMEZONE_CHOICES)
tz_menu.config(fg='white', bg='#222', activebackground='#333')
tz_menu.config(fg=win_text, bg=range_cell_bg, activebackground='#333')
tz_menu.pack(side='left', padx=6)
def set_manual(val_type, val):
@@ -554,24 +664,45 @@ class CountdownApp:
'manual_range': getattr(fetch_gonogo, 'manual_range', None),
'manual_weather': getattr(fetch_gonogo, 'manual_weather', None),
'manual_vehicle': getattr(fetch_gonogo, 'manual_vehicle', None),
'timezone': tz_var.get()
'timezone': tz_var.get(),
# preserve appearance settings (edited in Appearance window)
'bg_color': settings.get('bg_color', '#000000'),
'text_color': settings.get('text_color', '#FFFFFF'),
'gn_bg_color': settings.get('gn_bg_color', '#111111'),
'gn_border_color': settings.get('gn_border_color', '#FFFFFF'),
'gn_go_color': settings.get('gn_go_color', '#00FF00'),
'gn_nogo_color': settings.get('gn_nogo_color', '#FF0000'),
'font_family': settings.get('font_family', 'Consolas'),
'mission_font_px': int(settings.get('mission_font_px', 48)),
'timer_font_px': int(settings.get('timer_font_px', 120)),
'gn_font_px': int(settings.get('gn_font_px', 28))
}
# preserve the appearance_mode so saving Settings doesn't accidentally remove it
try:
new_settings['appearance_mode'] = settings.get('appearance_mode', DEFAULT_SETTINGS.get('appearance_mode', 'dark'))
except Exception:
new_settings['appearance_mode'] = DEFAULT_SETTINGS.get('appearance_mode', 'dark')
save_settings(new_settings)
# update immediately
self.gonogo_values = fetch_gonogo()
write_gonogo_html(self.gonogo_values)
# update manual visibility in main UI
self.update_manual_visibility()
# appearance changes are applied only from the Appearance window
win.destroy()
def on_cancel():
win.destroy()
btn_frame = tk.Frame(win)
btn_frame = tk.Frame(win, bg='black')
btn_frame = tk.Frame(win, bg=win_bg)
btn_frame.pack(fill='x', pady=8)
tk.Button(btn_frame, text='Save', command=on_save, fg='white', bg='#333', activebackground='#444').pack(side='right', padx=8)
tk.Button(btn_frame, text='Cancel', command=on_cancel, fg='white', bg='#333', activebackground='#444').pack(side='right')
tk.Button(btn_frame, text='Save', command=on_save, fg=btn_fg, bg=btn_bg, activebackground='#444').pack(side='right', padx=8)
tk.Button(btn_frame, text='Cancel', command=on_cancel, fg=btn_fg, bg=btn_bg, activebackground='#444').pack(side='right')
# ensure the new toplevel gets recursively themed like the main window
try:
self._theme_recursive(win, win_bg, win_text, btn_bg, btn_fg)
except Exception:
pass
# ----------------------------
@@ -634,6 +765,436 @@ class CountdownApp:
else:
self.manual_frame.pack_forget()
def apply_appearance_settings(self):
"""Apply appearance-related settings to the running Tk UI."""
s = load_settings()
# If an appearance_mode preset is selected, override specific settings with the preset
mode = s.get('appearance_mode', None)
if mode == 'dark':
s.update({
'bg_color': '#000000', 'text_color': '#FFFFFF', 'gn_bg_color': '#111111',
'gn_border_color': '#FFFFFF', 'gn_go_color': '#00FF00', 'gn_nogo_color': '#FF0000',
'font_family': 'Consolas', 'mission_font_px': 44, 'timer_font_px': 80, 'gn_font_px': 24
})
elif mode == 'light':
s.update({
'bg_color': '#FFFFFF', 'text_color': '#000000', 'gn_bg_color': '#EEEEEE',
'gn_border_color': '#333333', 'gn_go_color': '#008800', 'gn_nogo_color': '#AA0000',
'font_family': 'Consolas', 'mission_font_px': 44, 'timer_font_px': 80, 'gn_font_px': 24
})
bg = s.get('bg_color', '#000000')
text = s.get('text_color', '#FFFFFF')
font_family = s.get('font_family', 'Consolas')
timer_px = int(s.get('timer_font_px', 100))
mission_px = int(s.get('mission_font_px', 48))
gn_px = int(s.get('gn_font_px', 24))
gn_bg = s.get('gn_bg_color', '#111111')
gn_border = s.get('gn_border_color', '#FFFFFF')
gn_go = s.get('gn_go_color', '#00FF00')
gn_nogo = s.get('gn_nogo_color', '#FF0000')
# apply to main window elements
try:
self.root.config(bg=bg)
self.titletext.config(fg=text, bg=bg, font=(font_family, 20))
# timer label
self.text.config(fg=text, bg=bg, font=(font_family, timer_px, 'bold'))
# GN labels: set bg and font, and color depending on GO/NOGO
def style_gn_label(lbl, value):
try:
lbl.config(bg=bg, font=(font_family, gn_px))
v = (value or '').strip().upper()
if v == 'GO':
lbl.config(fg=gn_go)
elif v in ('NOGO', 'NO-GO'):
lbl.config(fg=gn_nogo)
else:
lbl.config(fg=text)
except Exception:
pass
style_gn_label(self.range_label, getattr(self, 'range_status', None))
style_gn_label(self.weather_label, getattr(self, 'weather', None))
style_gn_label(self.vehicle_label, getattr(self, 'vehicle', None))
# Buttons: invert colors depending on mode
# dark mode -> buttons white bg, black text
# light mode -> buttons black bg, white text
if mode == 'dark':
btn_bg = '#FFFFFF'
btn_fg = '#000000'
active_bg = '#DDDDDD'
else:
btn_bg = '#000000'
btn_fg = '#FFFFFF'
active_bg = '#222222'
for btn in (self.start_btn, self.hold_btn, self.resume_btn, self.scrub_btn, self.reset_btn, self.settings_btn):
try:
# preserve scrub button's custom color (red) if set
try:
cur_fg = btn.cget('fg')
except Exception:
cur_fg = None
if btn is getattr(self, 'scrub_btn', None) and cur_fg:
# keep existing foreground (usually red)
btn.config(bg=btn_bg, activebackground=active_bg)
else:
btn.config(bg=btn_bg, fg=btn_fg, activebackground=active_bg)
except Exception:
pass
# Manual toggle buttons
for btn in (self.range_toggle_btn, self.weather_toggle_btn, self.vehicle_toggle_btn):
try:
btn.config(bg=btn_bg, fg=btn_fg)
except Exception:
pass
# manual frame and footer
try:
self.manual_frame.config(bg=bg)
# Footer should invert colors depending on mode:
# - dark mode -> white background, black text
# - light mode -> black background, white text
mode = s.get('appearance_mode', 'dark')
if mode == 'dark':
footer_bg = '#FFFFFF'
footer_fg = '#000000'
else:
footer_bg = '#000000'
footer_fg = '#FFFFFF'
try:
self.footer_label.config(bg=footer_bg, fg=footer_fg)
except Exception:
# fall back to generic theme
self.footer_label.config(bg=bg, fg=text)
except Exception:
pass
except Exception:
pass
# Recursively theme frames and common widgets so no frame is left with old colors
try:
self._theme_recursive(self.root, bg, text, btn_bg, btn_fg)
except Exception:
pass
def update_gn_labels(self, range_val, weather_val, vehicle_val):
"""Update GN label texts and apply theme-aware styling."""
s = load_settings()
gn_px = int(s.get('gn_font_px', 28))
font_family = s.get('font_family', 'Consolas')
bg = s.get('bg_color', '#000000')
text = s.get('text_color', '#FFFFFF')
gn_go = s.get('gn_go_color', '#00FF00')
gn_nogo = s.get('gn_nogo_color', '#FF0000')
# Range
try:
display_range = format_status_display(range_val)
self.range_label.config(text=f"RANGE: {display_range}", bg=bg, font=(font_family, gn_px))
rv = (range_val or '').strip().upper()
rnorm = re.sub(r'[^A-Z]', '', rv)
if rnorm == 'GO':
self.range_label.config(fg=gn_go)
elif rnorm == 'NOGO':
self.range_label.config(fg=gn_nogo)
else:
self.range_label.config(fg=text)
except Exception:
pass
# Weather
try:
display_weather = format_status_display(weather_val)
self.weather_label.config(text=f"WEATHER: {display_weather}", bg=bg, font=(font_family, gn_px))
wv = (weather_val or '').strip().upper()
wnorm = re.sub(r'[^A-Z]', '', wv)
if wnorm == 'GO':
self.weather_label.config(fg=gn_go)
elif wnorm == 'NOGO':
self.weather_label.config(fg=gn_nogo)
else:
self.weather_label.config(fg=text)
except Exception:
pass
# Vehicle
try:
display_vehicle = format_status_display(vehicle_val)
self.vehicle_label.config(text=f"VEHICLE: {display_vehicle}", bg=bg, font=(font_family, gn_px))
vv = (vehicle_val or '').strip().upper()
vnorm = re.sub(r'[^A-Z]', '', vv)
if vnorm == 'GO':
self.vehicle_label.config(fg=gn_go)
elif vnorm == 'NOGO':
self.vehicle_label.config(fg=gn_nogo)
else:
self.vehicle_label.config(fg=text)
except Exception:
pass
def _theme_recursive(self, widget, bg, text, btn_bg, btn_fg):
# load settings so we can theme GN label backgrounds if configured
s = load_settings()
for child in widget.winfo_children():
# Frame and LabelFrame
try:
if isinstance(child, (tk.Frame, tk.LabelFrame)):
try:
child.config(bg=bg)
except Exception:
pass
# Labels: set bg, but don't override GN label fg
if isinstance(child, tk.Label):
try:
# preserve GN label fg colors and don't override the footer label (it has a special inverted style)
if child in (getattr(self, 'range_label', None), getattr(self, 'weather_label', None), getattr(self, 'vehicle_label', None)):
# GN labels keep fg but should have themed bg
child.config(bg=s.get('gn_bg_color', bg))
elif child is getattr(self, 'footer_label', None):
# footer_label was already styled by apply_appearance_settings; don't override it here
pass
else:
child.config(bg=bg, fg=text)
except Exception:
pass
# Entries: avoid overriding entries that were explicitly styled (like mission_entry)
if isinstance(child, tk.Entry):
try:
# Set entry bg/fg depending on appearance mode
mode_local = s.get('appearance_mode', 'dark')
if mode_local == 'dark':
child.config(bg='#222222', fg=text, insertbackground=text)
else:
# light mode entries should contrast with the white background
child.config(bg='#b4b4b4', fg='#000000', insertbackground='#000000')
except Exception:
pass
# OptionMenu/Menubutton
if isinstance(child, tk.Menubutton):
try:
child.config(bg=btn_bg, fg=btn_fg, activebackground='#555')
except Exception:
pass
# Radiobutton / Checkbutton
if isinstance(child, (tk.Radiobutton, tk.Checkbutton)):
try:
# selectcolor is the indicator background; set it to match the overall bg for neatness
child.config(bg=bg, fg=text, selectcolor=bg, activebackground=bg)
except Exception:
pass
# Buttons: ensure themed background and correct fg
if isinstance(child, tk.Button):
try:
# don't override scrub button's fg if it has a special color
if child is getattr(self, 'scrub_btn', None):
child.config(bg=btn_bg, activebackground='#555')
else:
child.config(bg=btn_bg, fg=btn_fg, activebackground='#555')
except Exception:
pass
except Exception:
pass
# Recurse
try:
if hasattr(child, 'winfo_children'):
self._theme_recursive(child, bg, text, btn_bg, btn_fg)
except Exception:
pass
def show_appearance_window(self):
# Re-implemented appearance window with proper theming and layout
settings = load_settings()
win = tk.Toplevel(self.root)
win.transient(self.root)
win.title('Appearance')
win.geometry('520x450')
# derive colors from appearance_mode so the dialog matches the main UI
mode_local = settings.get('appearance_mode', 'dark')
if mode_local == 'dark':
win_bg = '#000000'; win_text = '#FFFFFF'; btn_bg = '#FFFFFF'; btn_fg = '#000000'; entry_bg = '#222'; entry_fg = '#FFFFFF'
else:
win_bg = '#FFFFFF'; win_text = '#000000'; btn_bg = '#000000'; btn_fg = '#FFFFFF'; entry_bg = '#b4b4b4'; entry_fg = '#000000'
win.config(bg=win_bg)
tk.Label(win, text='Choose UI mode:', fg=win_text, bg=win_bg).pack(anchor='w', padx=12, pady=(10,0))
mode_var = tk.StringVar(value=settings.get('appearance_mode', 'dark'))
modes = ['dark', 'light']
mode_menu = tk.OptionMenu(win, mode_var, *modes)
mode_menu.config(fg=win_text, bg=entry_bg, activebackground='#333')
mode_menu.pack(anchor='w', padx=12, pady=6)
def on_save_mode():
choice = mode_var.get()
presets = {
'dark': {
'bg_color': '#000000', 'text_color': '#FFFFFF', 'gn_bg_color': '#111111',
'gn_border_color': '#FFFFFF', 'gn_go_color': '#00FF00', 'gn_nogo_color': '#FF0000',
'font_family': 'Consolas', 'mission_font_px': 24, 'timer_font_px': 80, 'gn_font_px': 20
},
'light': {
'bg_color': '#FFFFFF', 'text_color': '#000000', 'gn_bg_color': '#EEEEEE',
'gn_border_color': '#333333', 'gn_go_color': '#008800', 'gn_nogo_color': '#AA0000',
'font_family': 'Consolas', 'mission_font_px': 24, 'timer_font_px': 80, 'gn_font_px': 20
}
}
p = presets.get(choice, {})
s = load_settings()
s['appearance_mode'] = choice
s.update(p)
save_settings(s)
try:
self.apply_appearance_settings()
write_countdown_html(self.mission_name, self.text.cget('text'))
write_gonogo_html(self.gonogo_values)
except Exception:
pass
# close appearance window
win.destroy()
# also close the settings window if it is open
try:
if getattr(self, 'settings_win', None):
try:
self.settings_win.destroy()
except Exception:
pass
except Exception:
pass
def choose_color(entry_widget):
try:
col = colorchooser.askcolor()
if col and col[1]:
entry_widget.delete(0, tk.END)
entry_widget.insert(0, col[1])
except Exception:
pass
s = load_settings()
html_frame = tk.LabelFrame(win, text='HTML appearance (streaming)', fg=win_text, bg=win_bg)
html_frame.config(bg=win_bg)
html_frame.pack(fill='x', padx=8, pady=6)
# layout HTML appearance fields in a grid
tk.Label(html_frame, text='Background:', fg=win_text, bg=win_bg).grid(row=0, column=0, sticky='w', padx=6, pady=4)
bg_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
bg_entry.grid(row=0, column=1, padx=6, pady=4)
bg_entry.insert(0, s.get('html_bg_color', s.get('bg_color', '#000000')))
tk.Button(html_frame, text='Choose', command=lambda: choose_color(bg_entry), fg=btn_fg, bg=btn_bg).grid(row=0, column=2, padx=6)
tk.Label(html_frame, text='Text:', fg=win_text, bg=win_bg).grid(row=1, column=0, sticky='w', padx=6, pady=4)
text_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
text_entry.grid(row=1, column=1, padx=6, pady=4)
text_entry.insert(0, s.get('html_text_color', s.get('text_color', '#FFFFFF')))
tk.Button(html_frame, text='Choose', command=lambda: choose_color(text_entry), fg=btn_fg, bg=btn_bg).grid(row=1, column=2, padx=6)
tk.Label(html_frame, text='GN GO:', fg=win_text, bg=win_bg).grid(row=2, column=0, sticky='w', padx=6, pady=4)
gn_go_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
gn_go_entry.grid(row=2, column=1, padx=6, pady=4)
gn_go_entry.insert(0, s.get('html_gn_go_color', s.get('gn_go_color', '#00FF00')))
tk.Button(html_frame, text='Choose', command=lambda: choose_color(gn_go_entry), fg=btn_fg, bg=btn_bg).grid(row=2, column=2, padx=6)
tk.Label(html_frame, text='GN NO-GO:', fg=win_text, bg=win_bg).grid(row=3, column=0, sticky='w', padx=6, pady=4)
gn_nogo_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
gn_nogo_entry.grid(row=3, column=1, padx=6, pady=4)
gn_nogo_entry.insert(0, s.get('html_gn_nogo_color', s.get('gn_nogo_color', '#FF0000')))
tk.Button(html_frame, text='Choose', command=lambda: choose_color(gn_nogo_entry), fg=btn_fg, bg=btn_bg).grid(row=3, column=2, padx=6)
tk.Label(html_frame, text='GN box bg:', fg=win_text, bg=win_bg).grid(row=4, column=0, sticky='w', padx=6, pady=4)
gn_box_bg_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
gn_box_bg_entry.grid(row=4, column=1, padx=6, pady=4)
gn_box_bg_entry.insert(0, s.get('html_gn_bg_color', s.get('gn_bg_color', '#111111')))
tk.Button(html_frame, text='Choose', command=lambda: choose_color(gn_box_bg_entry), fg=btn_fg, bg=btn_bg).grid(row=4, column=2, padx=6)
tk.Label(html_frame, text='GN border:', fg=win_text, bg=win_bg).grid(row=5, column=0, sticky='w', padx=6, pady=4)
gn_border_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
gn_border_entry.grid(row=5, column=1, padx=6, pady=4)
gn_border_entry.insert(0, s.get('html_gn_border_color', s.get('gn_border_color', '#FFFFFF')))
tk.Button(html_frame, text='Choose', command=lambda: choose_color(gn_border_entry), fg=btn_fg, bg=btn_bg).grid(row=5, column=2, padx=6)
tk.Label(html_frame, text='Font family:', fg=win_text, bg=win_bg).grid(row=6, column=0, sticky='w', padx=6, pady=4)
font_entry = tk.Entry(html_frame, width=20, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
font_entry.grid(row=6, column=1, padx=6, pady=4, columnspan=2, sticky='w')
font_entry.insert(0, s.get('html_font_family', s.get('font_family', 'Consolas')))
tk.Label(html_frame, text='Mission px:', fg=win_text, bg=win_bg).grid(row=7, column=0, sticky='w', padx=6, pady=4)
mission_px_entry = tk.Entry(html_frame, width=6, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
mission_px_entry.grid(row=7, column=1, padx=6, pady=4, sticky='w')
mission_px_entry.insert(0, str(s.get('html_mission_font_px', s.get('mission_font_px', 24))))
tk.Label(html_frame, text='Timer px:', fg=win_text, bg=win_bg).grid(row=8, column=0, sticky='w', padx=6, pady=4)
timer_px_entry = tk.Entry(html_frame, width=6, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
timer_px_entry.grid(row=8, column=1, padx=6, pady=4, sticky='w')
timer_px_entry.insert(0, str(s.get('html_timer_font_px', s.get('timer_font_px', 80))))
def save_html_prefs():
try:
s_local = load_settings()
s_local['html_bg_color'] = bg_entry.get().strip() or s_local.get('html_bg_color')
s_local['html_text_color'] = text_entry.get().strip() or s_local.get('html_text_color')
s_local['html_gn_go_color'] = gn_go_entry.get().strip() or s_local.get('html_gn_go_color')
s_local['html_gn_nogo_color'] = gn_nogo_entry.get().strip() or s_local.get('html_gn_nogo_color')
s_local['html_gn_bg_color'] = gn_box_bg_entry.get().strip() or s_local.get('html_gn_bg_color')
s_local['html_gn_border_color'] = gn_border_entry.get().strip() or s_local.get('html_gn_border_color')
s_local['html_font_family'] = font_entry.get().strip() or s_local.get('html_font_family')
try:
s_local['html_mission_font_px'] = int(mission_px_entry.get())
except Exception:
pass
try:
s_local['html_timer_font_px'] = int(timer_px_entry.get())
except Exception:
pass
save_settings(s_local)
write_countdown_html(self.mission_name, self.text.cget('text'))
write_gonogo_html(self.gonogo_values)
except Exception:
pass
def reset_html_defaults():
try:
s_local = load_settings()
s_local['html_bg_color'] = DEFAULT_SETTINGS.get('html_bg_color')
s_local['html_text_color'] = DEFAULT_SETTINGS.get('html_text_color')
s_local['html_font_family'] = DEFAULT_SETTINGS.get('html_font_family')
s_local['html_mission_font_px'] = DEFAULT_SETTINGS.get('html_mission_font_px')
s_local['html_timer_font_px'] = DEFAULT_SETTINGS.get('html_timer_font_px')
s_local['html_gn_bg_color'] = DEFAULT_SETTINGS.get('html_gn_bg_color')
s_local['html_gn_border_color'] = DEFAULT_SETTINGS.get('html_gn_border_color')
s_local['html_gn_go_color'] = DEFAULT_SETTINGS.get('html_gn_go_color')
s_local['html_gn_nogo_color'] = DEFAULT_SETTINGS.get('html_gn_nogo_color')
s_local['html_gn_font_px'] = DEFAULT_SETTINGS.get('html_gn_font_px')
save_settings(s_local)
# update UI fields
bg_entry.delete(0, tk.END); bg_entry.insert(0, s_local['html_bg_color'])
text_entry.delete(0, tk.END); text_entry.insert(0, s_local['html_text_color'])
gn_go_entry.delete(0, tk.END); gn_go_entry.insert(0, s_local['html_gn_go_color'])
gn_nogo_entry.delete(0, tk.END); gn_nogo_entry.insert(0, s_local['html_gn_nogo_color'])
gn_box_bg_entry.delete(0, tk.END); gn_box_bg_entry.insert(0, s_local['html_gn_bg_color'])
gn_border_entry.delete(0, tk.END); gn_border_entry.insert(0, s_local['html_gn_border_color'])
font_entry.delete(0, tk.END); font_entry.insert(0, s_local['html_font_family'])
mission_px_entry.delete(0, tk.END); mission_px_entry.insert(0, str(s_local['html_mission_font_px']))
timer_px_entry.delete(0, tk.END); timer_px_entry.insert(0, str(s_local['html_timer_font_px']))
write_countdown_html(self.mission_name, self.text.cget('text'))
write_gonogo_html(self.gonogo_values)
except Exception:
pass
html_btns = tk.Frame(html_frame, bg=win_bg)
html_btns.grid(row=9, column=0, columnspan=3, pady=6)
tk.Button(html_btns, text='Save (HTML only)', command=save_html_prefs, fg=btn_fg, bg=btn_bg).pack(side='right', padx=6)
tk.Button(html_btns, text='Reset HTML defaults', command=reset_html_defaults, fg=btn_fg, bg=btn_bg).pack(side='right')
btn_frame = tk.Frame(win, bg=win_bg)
btn_frame.pack(fill='x', pady=8, padx=8)
tk.Button(btn_frame, text='Save', command=on_save_mode, fg=btn_fg, bg=btn_bg).pack(side='right', padx=6)
tk.Button(btn_frame, text='Cancel', command=win.destroy, fg=btn_fg, bg=btn_bg).pack(side='right')
try:
self._theme_recursive(win, win_bg, win_text, btn_bg, btn_fg)
except Exception:
pass
def _toggle_manual(self, which):
# get current values (Range, Weather, Vehicle)
cur = fetch_gonogo()
@@ -645,7 +1206,7 @@ class CountdownApp:
except Exception:
cur_val = 'N/A'
# toggle: if GO -> NOGO, else -> GO
new_val = 'NOGO' if cur_val == 'GO' else 'GO'
new_val = 'NO-GO' if cur_val == 'GO' else 'GO'
self.set_manual(which, new_val)
# ----------------------------
@@ -781,9 +1342,14 @@ class CountdownApp:
if now_time - self.last_gonogo_update > 0.1:
# fetch_gonogo returns [Range, Weather, Vehicle]
self.range_status, self.weather, self.vehicle = fetch_gonogo()
self.range_label.config(text=f"RANGE: {self.range_status}", fg=get_status_color(self.range_status))
self.weather_label.config(text=f"WEATHER: {self.weather}", fg=get_status_color(self.weather))
self.vehicle_label.config(text=f"VEHICLE: {self.vehicle}", fg=get_status_color(self.vehicle))
# update texts and styles using theme
try:
self.update_gn_labels(self.range_status, self.weather, self.vehicle)
except Exception:
# fallback to simple config
self.range_label.config(text=f"RANGE: {self.range_status}")
self.weather_label.config(text=f"WEATHER: {self.weather}")
self.vehicle_label.config(text=f"VEHICLE: {self.vehicle}")
self.gonogo_values = [self.range_status, self.weather, self.vehicle]
write_gonogo_html(self.gonogo_values)
self.last_gonogo_update = now_time
@@ -816,12 +1382,22 @@ if __name__ == "__main__":
footer_frame = tk.Frame(splash, bg="black")
footer_frame.pack(side="bottom", pady=0, fill="x")
# Footer uses inverted colors: white bg/black text in dark mode, black bg/white text in light mode
s = load_settings()
splash_mode = s.get('appearance_mode', 'dark')
if splash_mode == 'dark':
splash_footer_bg = '#FFFFFF'
splash_footer_fg = '#000000'
else:
splash_footer_bg = '#000000'
splash_footer_fg = '#FFFFFF'
footer_label = tk.Label(
footer_frame,
text="Made by HamsterSpaceNerd3000", # or whatever you want
text="Made by HamsterSpaceNerd3000",
font=("Consolas", 12),
fg="black",
bg="white"
fg=splash_footer_fg,
bg=splash_footer_bg
)
footer_label.pack(fill="x")
@@ -847,9 +1423,24 @@ if __name__ == "__main__":
if init_state['error']:
info.config(text=f"Initialization error: {init_state['error']}")
else:
info.config(text="Ready. You may open browser sources now, then click Continue.")
cont_btn.config(state="normal")
splash.after(5000, on_continue)
# show a visible countdown before auto-start; allow Continue to skip
AUTO_START_SECONDS = 5
remaining = AUTO_START_SECONDS
cont_btn.config(state='normal')
def tick():
nonlocal remaining
if remaining <= 0:
on_continue()
return
info.config(text=f"Ready — auto-starting in {remaining}...")
cont_btn.config(text=f"Continue ({remaining})")
remaining -= 1
splash.after(1000, tick)
# clicking Continue will immediately proceed
cont_btn.config(command=on_continue)
tick()
return
splash.after(200, check_init)
@@ -860,7 +1451,6 @@ if __name__ == "__main__":
app = CountdownApp(root)
root.mainloop()
cont_btn.config(command=on_continue)
# begin polling
splash.after(100, check_init)
splash.mainloop()

View File

@@ -1,9 +1,11 @@
RocketLaunchCountdown is a python and HTML system to operate a launch countdown and go/nogo indicator. The go/nogo indicator is operated either by a spreadsheet, using a sharelink inserted in the settings windows. Or there are buttons in the app that can manage it as well. Which mode is used is dictated by a simple radio button in the settings window.
Latest release: [RocketLaunchCountdown 0.4.0 THE CLOCKS UPDATE](https://github.com/HamsterSpaceNerd3000/RocketLaunchCountdown/releases/tag/Prealpha_040)
Latest release: [RocketLaunchCountdown 0.5.0](https://github.com/HamsterSpaceNerd3000/RocketLaunchCountdown/releases/tag/Prealpha_050)
Features Added:
Full rework of the clock system (countdown method based on clock time, not how long until).
Minor background fixes
UI Customization is here! Light mode has been added and can be turned on in the settings window. Also in the settings window is the ability to adjust the colors of the HTML files for the clock and go/no-go.
More background optimizations and simplifications.
Added game version to title on main screen.
Added more Nitrogen
INSTALL INSTRUCTIONS