12 Commits

Author SHA1 Message Date
William Valentin b259837af4 feat: Add test script for mouse wheel scrolling functionality in entry and edit windows
Build and Push Docker Image / build-and-push (push) Has been cancelled
2025-07-29 17:44:14 -07:00
William Valentin aad02f0d36 feat: Improve canvas scrolling functionality with enhanced mouse wheel event handling 2025-07-29 17:42:38 -07:00
William Valentin 30750710b8 feat: Enhance edit window UI with improved layout and scrolling functionality 2025-07-29 17:28:52 -07:00
William Valentin fd1f9a43c6 feat: Add release notes generation and Docker image information to build workflow 2025-07-29 17:09:57 -07:00
William Valentin 21dd1fc9c8 refactor: Update import statements to include 'src' prefix for module paths
Build and Push Docker Image / build-and-push (push) Has been cancelled
2025-07-29 16:52:46 -07:00
William Valentin 5243352867 refactor: Remove coverage.xml file to streamline project structure 2025-07-29 16:41:40 -07:00
William Valentin 387981aa47 refactor: Remove __init__.py file and associated metadata 2025-07-29 16:41:29 -07:00
William Valentin 13b2c9c416 fix: Correct dotenv loading to use dynamic directory based on execution context 2025-07-29 16:38:21 -07:00
William Valentin 4c04bfb92e feat: Add debug logging to PyInstaller deployment process 2025-07-29 16:36:04 -07:00
William Valentin 2fe45e65eb chore: Bump version to 1.2.1 in project files 2025-07-29 14:52:41 -07:00
William Valentin 036b4d1215 feat: Update MedTrackerApp to correctly handle quetiapine and its dosage data 2025-07-29 14:51:29 -07:00
William Valentin ce986db27b feat: Update DataManager to support new quetiapine medication format and adjust VSCode task command
Build and Push Docker Image / build-and-push (push) Has been cancelled
2025-07-29 14:00:33 -07:00
36 changed files with 1060 additions and 885 deletions
+48
View File
@@ -14,6 +14,8 @@ jobs:
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch full history for release notes generation
- name: Install Docker
run: curl -fsSL https://get.docker.com | sh
@@ -55,3 +57,49 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=gitea-http.taildb3494.ts.net/will/thechart:buildcache
cache-to: type=registry,ref=gitea-http.taildb3494.ts.net/will/thechart:buildcache,mode=max
- name: Generate release notes
id: release_notes
if: startsWith(gitea.ref, 'refs/tags/')
run: |
# Get the current tag
CURRENT_TAG=${GITEA_REF#refs/tags/}
# Get the previous tag
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
# Generate release notes from commits
if [ -n "$PREVIOUS_TAG" ]; then
echo "## Changes from $PREVIOUS_TAG to $CURRENT_TAG" > release_notes.md
echo "" >> release_notes.md
git log --pretty=format:"- %s (%h)" $PREVIOUS_TAG..$CURRENT_TAG >> release_notes.md
else
echo "## Initial Release $CURRENT_TAG" > release_notes.md
echo "" >> release_notes.md
git log --pretty=format:"- %s (%h)" >> release_notes.md
fi
# Add Docker image information
echo "" >> release_notes.md
echo "## Docker Images" >> release_notes.md
echo "" >> release_notes.md
echo "This release includes multi-platform Docker images:" >> release_notes.md
echo "- \`gitea-http.taildb3494.ts.net/will/thechart:$CURRENT_TAG\`" >> release_notes.md
echo "- \`gitea-http.taildb3494.ts.net/will/thechart:latest\`" >> release_notes.md
# Output the release notes content for use in next step
echo "release_notes<<EOF" >> $GITEA_OUTPUT
cat release_notes.md >> $GITEA_OUTPUT
echo "EOF" >> $GITEA_OUTPUT
- name: Create Release
if: startsWith(gitea.ref, 'refs/tags/')
uses: actions/create-release@v1
env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
with:
tag_name: ${{ gitea.ref_name }}
release_name: Release ${{ gitea.ref_name }}
body: ${{ steps.release_notes.outputs.release_notes }}
draft: false
prerelease: false
+1 -1
View File
@@ -47,7 +47,7 @@ htmlcov/
.pylint.d/
# IDEs and editors
.vscode/
#.vscode/
!.vscode/tasks.json
!.vscode/launch.json
.idea/
+7 -1
View File
@@ -4,7 +4,13 @@
{
"label": "Run TheChart App",
"type": "shell",
"command": "cd /home/will/Code/thechart && python -m src.main",
"command": "/home/will/Code/thechart/.venv/bin/python",
"args": [
"src/main.py"
],
"options": {
"cwd": "/home/will/Code/thechart"
},
"group": "build",
"isBackground": false,
"problemMatcher": []
+1 -1
View File
@@ -88,7 +88,7 @@ build: ## Build the Docker image
docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE} --push .
deploy: ## Deploy the application as a standalone executable
@echo "Deploying the application..."
pyinstaller --name ${TARGET} --optimize 2 --onefile --windowed --hidden-import='PIL._tkinter_finder' --icon='${ICON}' --add-data="./.env:." --add-data='./chart-671.png:.' --add-data='./thechart_data.csv:.' src/main.py
pyinstaller --name ${TARGET} --optimize 2 --onefile --windowed --hidden-import='PIL._tkinter_finder' --icon='${ICON}' --add-data="./.env:." --add-data='./chart-671.png:.' --add-data='./thechart_data.csv:.' --log-level=DEBUG src/main.py
cp -f ./thechart_data.csv ${ROOT}/Documents/
cp -f ./dist/${TARGET} ${ROOT}/Applications/
cp -f ./deploy/${TARGET}.desktop ${ROOT}/.local/share/applications/
-756
View File
@@ -1,756 +0,0 @@
<?xml version="1.0" ?>
<coverage version="7.10.1" timestamp="1753820440721" lines-valid="702" lines-covered="511" line-rate="0.7279" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.10.1 -->
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources>
<source>/home/will/Code/thechart/src</source>
</sources>
<packages>
<package name="." line-rate="0.7279" branch-rate="0" complexity="0">
<classes>
<class name="__init__.py" filename="__init__.py" complexity="0" line-rate="0" branch-rate="0">
<methods/>
<lines>
<line number="3" hits="0"/>
<line number="4" hits="0"/>
<line number="5" hits="0"/>
</lines>
</class>
<class name="constants.py" filename="constants.py" complexity="0" line-rate="1" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="3" hits="1"/>
<line number="5" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="1"/>
<line number="9" hits="1"/>
</lines>
</class>
<class name="data_manager.py" filename="data_manager.py" complexity="0" line-rate="0.5673" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="5" hits="1"/>
<line number="8" hits="1"/>
<line number="11" hits="1"/>
<line number="12" hits="1"/>
<line number="13" hits="1"/>
<line number="14" hits="1"/>
<line number="16" hits="1"/>
<line number="18" hits="1"/>
<line number="19" hits="1"/>
<line number="20" hits="1"/>
<line number="21" hits="1"/>
<line number="42" hits="1"/>
<line number="44" hits="1"/>
<line number="45" hits="1"/>
<line number="46" hits="1"/>
<line number="48" hits="1"/>
<line number="49" hits="1"/>
<line number="70" hits="1"/>
<line number="71" hits="1"/>
<line number="72" hits="0"/>
<line number="73" hits="0"/>
<line number="74" hits="1"/>
<line number="75" hits="1"/>
<line number="76" hits="1"/>
<line number="78" hits="1"/>
<line number="80" hits="1"/>
<line number="82" hits="1"/>
<line number="83" hits="1"/>
<line number="85" hits="1"/>
<line number="86" hits="1"/>
<line number="87" hits="1"/>
<line number="89" hits="1"/>
<line number="90" hits="1"/>
<line number="91" hits="1"/>
<line number="92" hits="1"/>
<line number="93" hits="1"/>
<line number="94" hits="1"/>
<line number="95" hits="1"/>
<line number="97" hits="1"/>
<line number="99" hits="1"/>
<line number="100" hits="1"/>
<line number="101" hits="1"/>
<line number="104" hits="1"/>
<line number="105" hits="1"/>
<line number="108" hits="1"/>
<line number="112" hits="1"/>
<line number="114" hits="0"/>
<line number="135" hits="1"/>
<line number="150" hits="0"/>
<line number="151" hits="0"/>
<line number="152" hits="1"/>
<line number="153" hits="1"/>
<line number="154" hits="1"/>
<line number="156" hits="1"/>
<line number="158" hits="1"/>
<line number="159" hits="1"/>
<line number="161" hits="1"/>
<line number="163" hits="1"/>
<line number="164" hits="1"/>
<line number="165" hits="0"/>
<line number="166" hits="0"/>
<line number="167" hits="0"/>
<line number="169" hits="1"/>
<line number="171" hits="0"/>
<line number="173" hits="0"/>
<line number="174" hits="0"/>
<line number="175" hits="0"/>
<line number="176" hits="0"/>
<line number="179" hits="0"/>
<line number="181" hits="0"/>
<line number="197" hits="0"/>
<line number="200" hits="0"/>
<line number="201" hits="0"/>
<line number="202" hits="0"/>
<line number="204" hits="0"/>
<line number="205" hits="0"/>
<line number="207" hits="0"/>
<line number="210" hits="0"/>
<line number="212" hits="0"/>
<line number="213" hits="0"/>
<line number="214" hits="0"/>
<line number="215" hits="0"/>
<line number="216" hits="0"/>
<line number="218" hits="1"/>
<line number="222" hits="0"/>
<line number="223" hits="0"/>
<line number="224" hits="0"/>
<line number="225" hits="0"/>
<line number="227" hits="0"/>
<line number="228" hits="0"/>
<line number="230" hits="0"/>
<line number="231" hits="0"/>
<line number="233" hits="0"/>
<line number="234" hits="0"/>
<line number="235" hits="0"/>
<line number="236" hits="0"/>
<line number="237" hits="0"/>
<line number="239" hits="0"/>
<line number="240" hits="0"/>
<line number="241" hits="0"/>
<line number="242" hits="0"/>
</lines>
</class>
<class name="graph_manager.py" filename="graph_manager.py" complexity="0" line-rate="0.971" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="4" hits="1"/>
<line number="5" hits="1"/>
<line number="6" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="1"/>
<line number="11" hits="1"/>
<line number="14" hits="1"/>
<line number="15" hits="1"/>
<line number="18" hits="1"/>
<line number="19" hits="1"/>
<line number="22" hits="1"/>
<line number="30" hits="1"/>
<line number="31" hits="1"/>
<line number="34" hits="1"/>
<line number="37" hits="1"/>
<line number="38" hits="1"/>
<line number="41" hits="1"/>
<line number="42" hits="1"/>
<line number="45" hits="1"/>
<line number="46" hits="1"/>
<line number="47" hits="1"/>
<line number="48" hits="1"/>
<line number="51" hits="1"/>
<line number="54" hits="1"/>
<line number="56" hits="1"/>
<line number="58" hits="1"/>
<line number="62" hits="1"/>
<line number="69" hits="1"/>
<line number="70" hits="1"/>
<line number="76" hits="1"/>
<line number="78" hits="1"/>
<line number="80" hits="0"/>
<line number="81" hits="0"/>
<line number="83" hits="1"/>
<line number="85" hits="1"/>
<line number="86" hits="1"/>
<line number="88" hits="1"/>
<line number="90" hits="1"/>
<line number="91" hits="1"/>
<line number="93" hits="1"/>
<line number="94" hits="1"/>
<line number="95" hits="1"/>
<line number="96" hits="1"/>
<line number="99" hits="1"/>
<line number="102" hits="1"/>
<line number="103" hits="1"/>
<line number="106" hits="1"/>
<line number="107" hits="1"/>
<line number="108" hits="1"/>
<line number="109" hits="1"/>
<line number="110" hits="1"/>
<line number="111" hits="1"/>
<line number="112" hits="1"/>
<line number="113" hits="1"/>
<line number="114" hits="1"/>
<line number="117" hits="1"/>
<line number="120" hits="1"/>
<line number="121" hits="1"/>
<line number="122" hits="1"/>
<line number="123" hits="1"/>
<line number="124" hits="1"/>
<line number="125" hits="1"/>
<line number="128" hits="1"/>
<line number="130" hits="1"/>
<line number="139" hits="1"/>
<line number="147" hits="1"/>
<line number="149" hits="1"/>
</lines>
</class>
<class name="init.py" filename="init.py" complexity="0" line-rate="0.9524" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="6" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="1"/>
<line number="9" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="1"/>
<line number="13" hits="1"/>
<line number="19" hits="1"/>
<line number="21" hits="1"/>
<line number="23" hits="1"/>
<line number="24" hits="1"/>
<line number="25" hits="1"/>
<line number="26" hits="1"/>
<line number="27" hits="1"/>
<line number="28" hits="0"/>
<line number="29" hits="1"/>
<line number="30" hits="1"/>
<line number="31" hits="1"/>
</lines>
</class>
<class name="logger.py" filename="logger.py" complexity="0" line-rate="1" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="3" hits="1"/>
<line number="5" hits="1"/>
<line number="8" hits="1"/>
<line number="9" hits="1"/>
<line number="10" hits="1"/>
<line number="12" hits="1"/>
<line number="13" hits="1"/>
<line number="14" hits="1"/>
<line number="15" hits="1"/>
<line number="17" hits="1"/>
<line number="18" hits="1"/>
<line number="20" hits="1"/>
<line number="22" hits="1"/>
<line number="23" hits="1"/>
<line number="24" hits="1"/>
<line number="25" hits="1"/>
<line number="26" hits="1"/>
<line number="28" hits="1"/>
<line number="29" hits="1"/>
<line number="30" hits="1"/>
<line number="31" hits="1"/>
<line number="32" hits="1"/>
<line number="34" hits="1"/>
<line number="35" hits="1"/>
<line number="36" hits="1"/>
<line number="37" hits="1"/>
<line number="38" hits="1"/>
<line number="40" hits="1"/>
</lines>
</class>
<class name="main.py" filename="main.py" complexity="0" line-rate="0.7857" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="5" hits="1"/>
<line number="6" hits="1"/>
<line number="8" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="1"/>
<line number="12" hits="1"/>
<line number="13" hits="1"/>
<line number="14" hits="1"/>
<line number="17" hits="1"/>
<line number="18" hits="1"/>
<line number="19" hits="1"/>
<line number="20" hits="1"/>
<line number="21" hits="1"/>
<line number="22" hits="1"/>
<line number="25" hits="1"/>
<line number="26" hits="1"/>
<line number="28" hits="1"/>
<line number="29" hits="1"/>
<line number="30" hits="1"/>
<line number="31" hits="1"/>
<line number="32" hits="1"/>
<line number="34" hits="1"/>
<line number="39" hits="1"/>
<line number="40" hits="1"/>
<line number="41" hits="1"/>
<line number="42" hits="1"/>
<line number="45" hits="1"/>
<line number="46" hits="1"/>
<line number="49" hits="1"/>
<line number="50" hits="1"/>
<line number="51" hits="1"/>
<line number="52" hits="1"/>
<line number="55" hits="1"/>
<line number="57" hits="1"/>
<line number="59" hits="1"/>
<line number="62" hits="1"/>
<line number="63" hits="1"/>
<line number="66" hits="1"/>
<line number="67" hits="1"/>
<line number="70" hits="1"/>
<line number="71" hits="1"/>
<line number="72" hits="1"/>
<line number="73" hits="1"/>
<line number="76" hits="1"/>
<line number="77" hits="1"/>
<line number="80" hits="1"/>
<line number="81" hits="1"/>
<line number="82" hits="1"/>
<line number="83" hits="1"/>
<line number="84" hits="1"/>
<line number="85" hits="1"/>
<line number="88" hits="1"/>
<line number="102" hits="1"/>
<line number="103" hits="1"/>
<line number="104" hits="1"/>
<line number="107" hits="1"/>
<line number="109" hits="1"/>
<line number="111" hits="1"/>
<line number="112" hits="1"/>
<line number="113" hits="1"/>
<line number="114" hits="1"/>
<line number="115" hits="1"/>
<line number="116" hits="1"/>
<line number="118" hits="1"/>
<line number="120" hits="0"/>
<line number="123" hits="0"/>
<line number="124" hits="0"/>
<line number="125" hits="0"/>
<line number="127" hits="0"/>
<line number="147" hits="0"/>
<line number="150" hits="0"/>
<line number="156" hits="0"/>
<line number="158" hits="1"/>
<line number="176" hits="0"/>
<line number="195" hits="0"/>
<line number="196" hits="0"/>
<line number="197" hits="0"/>
<line number="200" hits="0"/>
<line number="201" hits="0"/>
<line number="204" hits="0"/>
<line number="205" hits="0"/>
<line number="206" hits="0"/>
<line number="213" hits="0"/>
<line number="215" hits="1"/>
<line number="216" hits="1"/>
<line number="219" hits="1"/>
<line number="220" hits="1"/>
<line number="222" hits="1"/>
<line number="225" hits="1"/>
<line number="226" hits="1"/>
<line number="227" hits="1"/>
<line number="228" hits="1"/>
<line number="229" hits="1"/>
<line number="230" hits="1"/>
<line number="232" hits="1"/>
<line number="233" hits="1"/>
<line number="234" hits="1"/>
<line number="237" hits="1"/>
<line number="238" hits="1"/>
<line number="241" hits="1"/>
<line number="243" hits="1"/>
<line number="244" hits="1"/>
<line number="247" hits="1"/>
<line number="248" hits="1"/>
<line number="249" hits="1"/>
<line number="251" hits="1"/>
<line number="269" hits="1"/>
<line number="272" hits="1"/>
<line number="273" hits="1"/>
<line number="274" hits="1"/>
<line number="276" hits="0"/>
<line number="277" hits="0"/>
<line number="280" hits="0"/>
<line number="281" hits="0"/>
<line number="284" hits="0"/>
<line number="285" hits="0"/>
<line number="286" hits="0"/>
<line number="293" hits="0"/>
<line number="295" hits="1"/>
<line number="297" hits="1"/>
<line number="298" hits="1"/>
<line number="304" hits="1"/>
<line number="305" hits="0"/>
<line number="307" hits="0"/>
<line number="308" hits="0"/>
<line number="309" hits="0"/>
<line number="312" hits="0"/>
<line number="314" hits="0"/>
<line number="316" hits="1"/>
<line number="318" hits="1"/>
<line number="319" hits="1"/>
<line number="320" hits="1"/>
<line number="321" hits="1"/>
<line number="322" hits="1"/>
<line number="323" hits="1"/>
<line number="324" hits="1"/>
<line number="326" hits="1"/>
<line number="328" hits="1"/>
<line number="331" hits="1"/>
<line number="332" hits="1"/>
<line number="335" hits="1"/>
<line number="338" hits="1"/>
<line number="340" hits="1"/>
<line number="355" hits="1"/>
<line number="356" hits="0"/>
<line number="359" hits="1"/>
<line number="361" hits="1"/>
<line number="362" hits="1"/>
<line number="363" hits="1"/>
<line number="366" hits="1"/>
</lines>
</class>
<class name="ui_manager.py" filename="ui_manager.py" complexity="0" line-rate="0.6614" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="5" hits="1"/>
<line number="6" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="1"/>
<line number="10" hits="1"/>
<line number="13" hits="1"/>
<line number="16" hits="1"/>
<line number="17" hits="1"/>
<line number="18" hits="1"/>
<line number="20" hits="1"/>
<line number="22" hits="1"/>
<line number="23" hits="1"/>
<line number="26" hits="1"/>
<line number="28" hits="1"/>
<line number="29" hits="1"/>
<line number="33" hits="1"/>
<line number="34" hits="1"/>
<line number="35" hits="1"/>
<line number="36" hits="1"/>
<line number="37" hits="1"/>
<line number="39" hits="1"/>
<line number="40" hits="1"/>
<line number="43" hits="1"/>
<line number="44" hits="1"/>
<line number="45" hits="0"/>
<line number="46" hits="0"/>
<line number="47" hits="1"/>
<line number="48" hits="1"/>
<line number="49" hits="1"/>
<line number="50" hits="1"/>
<line number="51" hits="1"/>
<line number="52" hits="1"/>
<line number="54" hits="1"/>
<line number="57" hits="1"/>
<line number="58" hits="1"/>
<line number="59" hits="1"/>
<line number="60" hits="1"/>
<line number="63" hits="1"/>
<line number="64" hits="1"/>
<line number="67" hits="1"/>
<line number="70" hits="1"/>
<line number="71" hits="1"/>
<line number="74" hits="1"/>
<line number="75" hits="0"/>
<line number="77" hits="1"/>
<line number="78" hits="0"/>
<line number="80" hits="1"/>
<line number="81" hits="1"/>
<line number="82" hits="1"/>
<line number="83" hits="1"/>
<line number="86" hits="1"/>
<line number="87" hits="1"/>
<line number="90" hits="1"/>
<line number="93" hits="1"/>
<line number="94" hits="0"/>
<line number="95" hits="0"/>
<line number="97" hits="1"/>
<line number="100" hits="1"/>
<line number="108" hits="1"/>
<line number="115" hits="1"/>
<line number="116" hits="1"/>
<line number="119" hits="1"/>
<line number="128" hits="1"/>
<line number="131" hits="1"/>
<line number="132" hits="1"/>
<line number="133" hits="1"/>
<line number="136" hits="1"/>
<line number="144" hits="1"/>
<line number="146" hits="1"/>
<line number="151" hits="1"/>
<line number="152" hits="1"/>
<line number="154" hits="1"/>
<line number="157" hits="1"/>
<line number="161" hits="1"/>
<line number="164" hits="1"/>
<line number="169" hits="1"/>
<line number="172" hits="1"/>
<line number="180" hits="1"/>
<line number="182" hits="1"/>
<line number="185" hits="1"/>
<line number="188" hits="1"/>
<line number="189" hits="1"/>
<line number="191" hits="1"/>
<line number="205" hits="1"/>
<line number="207" hits="1"/>
<line number="221" hits="1"/>
<line number="222" hits="1"/>
<line number="224" hits="1"/>
<line number="238" hits="1"/>
<line number="239" hits="1"/>
<line number="241" hits="1"/>
<line number="244" hits="1"/>
<line number="245" hits="1"/>
<line number="246" hits="1"/>
<line number="248" hits="1"/>
<line number="250" hits="1"/>
<line number="252" hits="1"/>
<line number="253" hits="1"/>
<line number="254" hits="1"/>
<line number="256" hits="1"/>
<line number="260" hits="1"/>
<line number="261" hits="1"/>
<line number="263" hits="1"/>
<line number="264" hits="1"/>
<line number="275" hits="1"/>
<line number="277" hits="1"/>
<line number="281" hits="1"/>
<line number="282" hits="1"/>
<line number="283" hits="1"/>
<line number="284" hits="1"/>
<line number="287" hits="1"/>
<line number="290" hits="1"/>
<line number="292" hits="1"/>
<line number="293" hits="1"/>
<line number="300" hits="1"/>
<line number="301" hits="0"/>
<line number="303" hits="0"/>
<line number="319" hits="0"/>
<line number="320" hits="0"/>
<line number="322" hits="0"/>
<line number="342" hits="0"/>
<line number="344" hits="0"/>
<line number="345" hits="0"/>
<line number="365" hits="1"/>
<line number="368" hits="1"/>
<line number="369" hits="1"/>
<line number="372" hits="1"/>
<line number="375" hits="1"/>
<line number="376" hits="1"/>
<line number="387" hits="1"/>
<line number="390" hits="1"/>
<line number="391" hits="1"/>
<line number="392" hits="1"/>
<line number="395" hits="1"/>
<line number="400" hits="1"/>
<line number="401" hits="1"/>
<line number="404" hits="1"/>
<line number="405" hits="1"/>
<line number="406" hits="1"/>
<line number="408" hits="1"/>
<line number="410" hits="1"/>
<line number="420" hits="1"/>
<line number="423" hits="1"/>
<line number="424" hits="1"/>
<line number="425" hits="0"/>
<line number="426" hits="0"/>
<line number="427" hits="0"/>
<line number="429" hits="1"/>
<line number="437" hits="1"/>
<line number="445" hits="1"/>
<line number="446" hits="1"/>
<line number="447" hits="1"/>
<line number="448" hits="1"/>
<line number="449" hits="1"/>
<line number="450" hits="1"/>
<line number="451" hits="0"/>
<line number="452" hits="0"/>
<line number="453" hits="0"/>
<line number="457" hits="1"/>
<line number="458" hits="0"/>
<line number="459" hits="0"/>
<line number="460" hits="0"/>
<line number="464" hits="1"/>
<line number="465" hits="1"/>
<line number="469" hits="1"/>
<line number="470" hits="1"/>
<line number="472" hits="1"/>
<line number="476" hits="1"/>
<line number="478" hits="1"/>
<line number="482" hits="1"/>
<line number="483" hits="1"/>
<line number="484" hits="1"/>
<line number="486" hits="1"/>
<line number="489" hits="1"/>
<line number="492" hits="1"/>
<line number="493" hits="1"/>
<line number="496" hits="1"/>
<line number="497" hits="1"/>
<line number="499" hits="1"/>
<line number="500" hits="1"/>
<line number="501" hits="1"/>
<line number="502" hits="1"/>
<line number="504" hits="1"/>
<line number="515" hits="1"/>
<line number="518" hits="1"/>
<line number="519" hits="1"/>
<line number="521" hits="1"/>
<line number="529" hits="1"/>
<line number="530" hits="1"/>
<line number="531" hits="1"/>
<line number="532" hits="1"/>
<line number="536" hits="1"/>
<line number="538" hits="1"/>
<line number="546" hits="1"/>
<line number="547" hits="1"/>
<line number="550" hits="1"/>
<line number="552" hits="0"/>
<line number="554" hits="0"/>
<line number="561" hits="0"/>
<line number="563" hits="0"/>
<line number="566" hits="0"/>
<line number="567" hits="0"/>
<line number="571" hits="0"/>
<line number="573" hits="0"/>
<line number="589" hits="1"/>
<line number="596" hits="1"/>
<line number="601" hits="1"/>
<line number="607" hits="1"/>
<line number="611" hits="1"/>
<line number="615" hits="1"/>
<line number="616" hits="1"/>
<line number="617" hits="1"/>
<line number="619" hits="1"/>
<line number="621" hits="1"/>
<line number="623" hits="1"/>
<line number="624" hits="1"/>
<line number="627" hits="1"/>
<line number="628" hits="1"/>
<line number="629" hits="1"/>
<line number="632" hits="1"/>
<line number="635" hits="1"/>
<line number="636" hits="1"/>
<line number="639" hits="1"/>
<line number="642" hits="1"/>
<line number="645" hits="1"/>
<line number="646" hits="0"/>
<line number="648" hits="1"/>
<line number="650" hits="1"/>
<line number="656" hits="1"/>
<line number="659" hits="1"/>
<line number="660" hits="0"/>
<line number="661" hits="0"/>
<line number="662" hits="0"/>
<line number="663" hits="0"/>
<line number="664" hits="0"/>
<line number="666" hits="0"/>
<line number="667" hits="0"/>
<line number="668" hits="0"/>
<line number="669" hits="0"/>
<line number="670" hits="0"/>
<line number="671" hits="0"/>
<line number="673" hits="0"/>
<line number="674" hits="0"/>
<line number="676" hits="0"/>
<line number="678" hits="1"/>
<line number="681" hits="1"/>
<line number="687" hits="1"/>
<line number="689" hits="1"/>
<line number="691" hits="1"/>
<line number="698" hits="0"/>
<line number="701" hits="0"/>
<line number="703" hits="0"/>
<line number="704" hits="0"/>
<line number="709" hits="0"/>
<line number="712" hits="0"/>
<line number="713" hits="0"/>
<line number="716" hits="0"/>
<line number="719" hits="0"/>
<line number="721" hits="0"/>
<line number="722" hits="0"/>
<line number="723" hits="0"/>
<line number="725" hits="0"/>
<line number="728" hits="0"/>
<line number="731" hits="0"/>
<line number="737" hits="1"/>
<line number="739" hits="0"/>
<line number="740" hits="0"/>
<line number="742" hits="0"/>
<line number="743" hits="0"/>
<line number="745" hits="0"/>
<line number="748" hits="0"/>
<line number="750" hits="0"/>
<line number="751" hits="0"/>
<line number="756" hits="0"/>
<line number="759" hits="0"/>
<line number="760" hits="0"/>
<line number="763" hits="0"/>
<line number="766" hits="0"/>
<line number="768" hits="0"/>
<line number="769" hits="0"/>
<line number="770" hits="0"/>
<line number="772" hits="0"/>
<line number="775" hits="0"/>
<line number="778" hits="0"/>
<line number="784" hits="1"/>
<line number="786" hits="0"/>
<line number="787" hits="0"/>
<line number="789" hits="0"/>
<line number="790" hits="0"/>
<line number="792" hits="0"/>
<line number="793" hits="0"/>
<line number="794" hits="0"/>
<line number="795" hits="0"/>
<line number="798" hits="0"/>
<line number="799" hits="0"/>
<line number="802" hits="0"/>
<line number="805" hits="0"/>
<line number="807" hits="0"/>
<line number="808" hits="0"/>
<line number="809" hits="0"/>
<line number="811" hits="0"/>
<line number="813" hits="0"/>
<line number="816" hits="0"/>
<line number="818" hits="0"/>
<line number="820" hits="0"/>
<line number="823" hits="0"/>
<line number="824" hits="0"/>
<line number="828" hits="0"/>
<line number="829" hits="0"/>
<line number="830" hits="0"/>
<line number="832" hits="0"/>
<line number="834" hits="0"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "thechart"
version = "1.0.1"
version = "1.2.1"
description = "Chart to monitor your medication intake over time."
readme = "README.md"
requires-python = ">=3.13"
+1 -1
View File
@@ -12,7 +12,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def test_automated_multiple_punches():
+1 -1
View File
@@ -10,7 +10,7 @@ import sys
# Add the src directory to the Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from data_manager import DataManager
from src.data_manager import DataManager
# Set up simple logging
logging.basicConfig(level=logging.DEBUG)
+1 -1
View File
@@ -10,7 +10,7 @@ import sys
# Add the src directory to the path so we can import our modules
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from data_manager import DataManager
from src.data_manager import DataManager
def test_delete_functionality():
+1 -1
View File
@@ -14,7 +14,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def demonstrate_multiple_doses():
+2 -2
View File
@@ -11,7 +11,7 @@ import sys
# Add the src directory to the path so we can import our modules
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from data_manager import DataManager
from src.data_manager import DataManager
def test_dose_editing_functionality():
@@ -100,7 +100,7 @@ def test_dose_editing_functionality():
try:
import tkinter as tk
from ui_manager import UIManager
from src.ui_manager import UIManager
# Create a temporary UI manager to test the parsing
root = tk.Tk()
+2 -2
View File
@@ -9,8 +9,8 @@ from datetime import datetime
sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
from data_manager import DataManager
from init import logger
from src.data_manager import DataManager
from src.init import logger
def test_dose_tracking():
+2 -2
View File
@@ -9,8 +9,8 @@ import sys
# Add src to path
sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
from data_manager import DataManager
from init import logger
from src.data_manager import DataManager
from src.init import logger
def test_edit_functionality():
+1 -1
View File
@@ -11,7 +11,7 @@ import sys
# Add the src directory to the path so we can import our modules
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from data_manager import DataManager
from src.data_manager import DataManager
def test_edit_window_functionality():
+1 -1
View File
@@ -12,7 +12,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def test_edit_window_punch_buttons():
+1 -1
View File
@@ -11,7 +11,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def final_verification_test():
+1 -1
View File
@@ -12,7 +12,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def test_parse_dose_text():
+1 -1
View File
@@ -12,7 +12,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def test_multiple_punch_and_save():
+1 -1
View File
@@ -12,7 +12,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def test_programmatic_punch():
+1 -1
View File
@@ -11,7 +11,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def test_punch_button_step_by_step():
+1 -1
View File
@@ -11,7 +11,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def test_punch_button_only():
+1 -1
View File
@@ -12,7 +12,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import logging
from ui_manager import UIManager
from src.ui_manager import UIManager
def test_save_functionality():
+2 -2
View File
@@ -14,8 +14,8 @@ sys.path.append(os.path.join(os.path.dirname(__file__), "src"))
def test_scrollable_input():
"""Test the scrollable input frame."""
from init import logger
from ui_manager import UIManager
from src.init import logger
from src.ui_manager import UIManager
# Create a test window
root = tk.Tk()
-5
View File
@@ -1,5 +0,0 @@
"""The Chart - Medication tracking application."""
__version__ = "1.1.0"
__author__ = "Will"
__description__ = "Chart to monitor your medication intake over time."
+5 -1
View File
@@ -1,8 +1,12 @@
import os
import sys
from dotenv import load_dotenv
load_dotenv(override=True)
extDataDir = os.getcwd()
if getattr(sys, "frozen", False):
extDataDir = sys._MEIPASS
load_dotenv(dotenv_path=os.path.join(extDataDir, ".env"))
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
LOG_PATH = os.getenv("LOG_PATH", "/tmp/logs/thechart")
+28 -3
View File
@@ -108,9 +108,32 @@ class DataManager:
return False
# Find the row to update using original_date as a unique identifier
# Handle both old format (10 columns) and new format (14 columns)
if len(values) == 14:
# New format with dose columns
# Handle both old format (10 columns) and new format (16 columns)
if len(values) == 16:
# New format with all dose columns including quetiapine
df.loc[
df["date"] == original_date,
[
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"bupropion_doses",
"hydroxyzine",
"hydroxyzine_doses",
"gabapentin",
"gabapentin_doses",
"propranolol",
"propranolol_doses",
"quetiapine",
"quetiapine_doses",
"note",
],
] = values
elif len(values) == 14:
# Format without quetiapine
df.loc[
df["date"] == original_date,
[
@@ -192,6 +215,8 @@ class DataManager:
"gabapentin_doses": "",
"propranolol": 0,
"propranolol_doses": "",
"quetiapine": 0,
"quetiapine_doses": "",
"note": "",
}
df = pd.concat([df, pd.DataFrame([new_entry])], ignore_index=True)
+2 -2
View File
@@ -138,8 +138,8 @@ class MedTrackerApp:
full_row["gabapentin_doses"],
full_row["propranolol"],
full_row["propranolol_doses"],
full_row.get("quetiapine", 0),
full_row.get("quetiapine_doses", ""),
full_row["quetiapine"],
full_row["quetiapine_doses"],
full_row["note"],
)
else:
+715 -45
View File
@@ -70,18 +70,6 @@ class UIManager:
input_frame = ttk.Frame(canvas)
input_frame.grid_columnconfigure(1, weight=1)
# Configure canvas scrolling
def configure_scroll_region(event=None):
canvas.configure(scrollregion=canvas.bbox("all"))
def on_mousewheel(event):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
input_frame.bind("<Configure>", configure_scroll_region)
canvas.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
canvas.bind("<Button-4>", lambda e: canvas.yview_scroll(-1, "units")) # Linux
canvas.bind("<Button-5>", lambda e: canvas.yview_scroll(1, "units")) # Linux
# Place canvas and scrollbar in the container
canvas.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
@@ -94,8 +82,53 @@ class UIManager:
canvas_width = canvas.winfo_width()
canvas.itemconfig(canvas_window, width=canvas_width)
# Configure canvas scrolling
def configure_scroll_region(event=None):
canvas.configure(scrollregion=canvas.bbox("all"))
def on_mousewheel(event):
# Check if canvas is scrollable before scrolling
if canvas.cget("scrollregion"):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def on_mousewheel_linux_up(event):
# Linux mouse wheel up
if canvas.cget("scrollregion"):
canvas.yview_scroll(-1, "units")
def on_mousewheel_linux_down(event):
# Linux mouse wheel down
if canvas.cget("scrollregion"):
canvas.yview_scroll(1, "units")
input_frame.bind("<Configure>", configure_scroll_region)
canvas.bind("<Configure>", configure_canvas_width)
# Bind mouse wheel events to canvas and main container
canvas.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
canvas.bind("<Button-4>", on_mousewheel_linux_up) # Linux
canvas.bind("<Button-5>", on_mousewheel_linux_down) # Linux
main_container.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
main_container.bind("<Button-4>", on_mousewheel_linux_up) # Linux
main_container.bind("<Button-5>", on_mousewheel_linux_down) # Linux
# Bind mouse wheel to input frame and its children for better scrolling
self._bind_mousewheel_to_widget_tree(input_frame, canvas)
# Set focus to canvas to ensure it receives scroll events
canvas.focus_set()
# Add mouse enter/leave events to manage focus for scrolling
def on_mouse_enter(event):
canvas.focus_set()
def on_mouse_leave(event):
# Don't change focus when leaving to avoid disrupting user interaction
pass
main_container.bind("<Enter>", on_mouse_enter)
canvas.bind("<Enter>", on_mouse_enter)
# Create variables for symptoms
symptom_vars: dict[str, tk.IntVar] = {
"depression": tk.IntVar(value=0),
@@ -168,6 +201,11 @@ class UIManager:
# Set default date to today
date_var.set(datetime.now().strftime("%m/%d/%Y"))
# Ensure mouse wheel binding is applied to all newly created widgets
main_container.update_idletasks()
canvas.configure(scrollregion=canvas.bbox("all"))
self._bind_mousewheel_to_widget_tree(input_frame, canvas)
# Return all UI elements and variables
return {
"frame": main_container,
@@ -277,14 +315,81 @@ class UIManager:
def create_edit_window(
self, values: tuple[str, ...], callbacks: dict[str, Callable]
) -> tk.Toplevel:
"""Create a new window for editing an entry."""
"""Create a new window for editing an entry with improved UI."""
edit_win: tk.Toplevel = tk.Toplevel(master=self.root)
edit_win.title("Edit Entry")
edit_win.transient(self.root) # Make window modal
edit_win.minsize(400, 300)
edit_win.minsize(600, 700)
edit_win.geometry("800x800")
# Configure grid columns to expand properly
edit_win.grid_columnconfigure(1, weight=1)
# Create scrollable container
canvas = tk.Canvas(edit_win, highlightthickness=0)
scrollbar = ttk.Scrollbar(edit_win, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
# Configure main container with padding inside the canvas
main_container = ttk.Frame(canvas, padding="20")
# Pack canvas and scrollbar
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Create window in canvas for the main container
canvas_window = canvas.create_window((0, 0), window=main_container, anchor="nw")
# Configure grid for main container
main_container.grid_columnconfigure(0, weight=1)
# Configure scrolling
def configure_scroll_region(event=None):
canvas.configure(scrollregion=canvas.bbox("all"))
def configure_canvas_width(event=None):
canvas_width = canvas.winfo_width()
canvas.itemconfig(canvas_window, width=canvas_width)
def on_mousewheel(event):
# Check if canvas is scrollable before scrolling
if canvas.cget("scrollregion"):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def on_mousewheel_linux_up(event):
# Linux mouse wheel up
if canvas.cget("scrollregion"):
canvas.yview_scroll(-1, "units")
def on_mousewheel_linux_down(event):
# Linux mouse wheel down
if canvas.cget("scrollregion"):
canvas.yview_scroll(1, "units")
main_container.bind("<Configure>", configure_scroll_region)
canvas.bind("<Configure>", configure_canvas_width)
# Bind mouse wheel events to canvas and edit window
canvas.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
canvas.bind("<Button-4>", on_mousewheel_linux_up) # Linux
canvas.bind("<Button-5>", on_mousewheel_linux_down) # Linux
edit_win.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
edit_win.bind("<Button-4>", on_mousewheel_linux_up) # Linux
edit_win.bind("<Button-5>", on_mousewheel_linux_down) # Linux
# Bind mouse wheel to main container and its children for better scrolling
self._bind_mousewheel_to_widget_tree(main_container, canvas)
# Set focus to canvas to ensure it receives scroll events
canvas.focus_set()
# Add mouse enter/leave events to manage focus for scrolling
def on_mouse_enter(event):
canvas.focus_set()
def on_mouse_leave(event):
# Don't change focus when leaving to avoid disrupting user interaction
pass
edit_win.bind("<Enter>", on_mouse_enter)
canvas.bind("<Enter>", on_mouse_enter)
# Unpack values - handle both old and new CSV formats
if len(values) == 10:
@@ -361,21 +466,20 @@ class UIManager:
note,
) = values_list[:16]
# Create variables and fields
vars_dict = self._create_edit_fields(edit_win, date, dep, anx, slp, app)
# Medicine checkboxes
current_row = 6 # After the 5 fields (date, dep, anx, slp, app)
med_vars = self._create_medicine_checkboxes(
edit_win, current_row, bup, hydro, gaba, prop, quet
)
vars_dict.update(med_vars)
# Dose information display (editable)
current_row += 1
dose_vars = self._add_dose_display_to_edit(
edit_win,
current_row,
# Create improved UI sections
vars_dict = self._create_improved_edit_ui(
main_container,
date,
dep,
anx,
slp,
app,
bup,
hydro,
gaba,
prop,
quet,
note,
{
"bupropion": bup_doses,
"hydroxyzine": hydro_doses,
@@ -384,29 +488,595 @@ class UIManager:
"quetiapine": quet_doses,
},
)
vars_dict.update(dose_vars)
# Note field
current_row += 2 # Account for dose display
vars_dict["note"] = tk.StringVar(value=str(note))
ttk.Label(edit_win, text="Note:").grid(
row=current_row, column=0, sticky="w", padx=5, pady=2
)
ttk.Entry(edit_win, textvariable=vars_dict["note"]).grid(
row=current_row, column=1, sticky="ew", padx=5, pady=2
)
# Add action buttons
self._add_improved_edit_buttons(main_container, vars_dict, callbacks, edit_win)
# Buttons
current_row += 1
self._add_edit_window_buttons(edit_win, current_row, vars_dict, callbacks)
# Update scroll region after adding all content
edit_win.update_idletasks()
canvas.configure(scrollregion=canvas.bbox("all"))
# Ensure mouse wheel binding is applied to all newly created widgets
self._bind_mousewheel_to_widget_tree(main_container, canvas)
# Make window modal
edit_win.update_idletasks()
edit_win.focus_set()
edit_win.grab_set()
return edit_win
def _create_improved_edit_ui(
self,
parent: ttk.Frame,
date: str,
dep: int,
anx: int,
slp: int,
app: int,
bup: int,
hydro: int,
gaba: int,
prop: int,
quet: int,
note: str,
dose_data: dict[str, str],
) -> dict[str, Any]:
"""Create improved UI layout for edit window with better organization."""
vars_dict = {}
row = 0
# Header with entry date
header_frame = ttk.Frame(parent)
header_frame.grid(row=row, column=0, sticky="ew", pady=(0, 20))
header_frame.grid_columnconfigure(1, weight=1)
ttk.Label(
header_frame, text="Editing Entry for:", font=("TkDefaultFont", 12, "bold")
).grid(row=0, column=0, sticky="w")
vars_dict["date"] = tk.StringVar(value=str(date))
date_entry = ttk.Entry(
header_frame,
textvariable=vars_dict["date"],
font=("TkDefaultFont", 12),
width=15,
)
date_entry.grid(row=0, column=1, sticky="w", padx=(10, 0))
row += 1
# Symptoms section
symptoms_frame = ttk.LabelFrame(
parent, text="Daily Symptoms (0-10 scale)", padding="15"
)
symptoms_frame.grid(row=row, column=0, sticky="ew", pady=(0, 15))
symptoms_frame.grid_columnconfigure(1, weight=1)
# Create symptom scales with better layout
symptoms = [
("Depression", "depression", dep),
("Anxiety", "anxiety", anx),
("Sleep Quality", "sleep", slp),
("Appetite", "appetite", app),
]
for i, (label, key, value) in enumerate(symptoms):
self._create_improved_symptom_scale(
symptoms_frame, i, label, key, value, vars_dict
)
row += 1
# Medications section
meds_frame = ttk.LabelFrame(parent, text="Medications Taken", padding="15")
meds_frame.grid(row=row, column=0, sticky="ew", pady=(0, 15))
meds_frame.grid_columnconfigure(0, weight=1)
# Create medicine checkboxes with better styling
med_vars = self._create_improved_medicine_section(
meds_frame, bup, hydro, gaba, prop, quet
)
vars_dict.update(med_vars)
row += 1
# Dose tracking section
dose_frame = ttk.LabelFrame(parent, text="Dose Tracking", padding="15")
dose_frame.grid(row=row, column=0, sticky="ew", pady=(0, 15))
dose_frame.grid_columnconfigure(0, weight=1)
dose_vars = self._create_improved_dose_tracking(dose_frame, dose_data)
vars_dict.update(dose_vars)
row += 1
# Notes section
notes_frame = ttk.LabelFrame(parent, text="Notes", padding="15")
notes_frame.grid(row=row, column=0, sticky="ew", pady=(0, 20))
notes_frame.grid_columnconfigure(0, weight=1)
vars_dict["note"] = tk.StringVar(value=str(note))
note_text = tk.Text(
notes_frame, height=4, wrap=tk.WORD, font=("TkDefaultFont", 10)
)
note_text.grid(row=0, column=0, sticky="ew")
note_text.insert(1.0, str(note))
vars_dict["note_text"] = note_text
# Add scrollbar for notes
note_scroll = ttk.Scrollbar(
notes_frame, orient="vertical", command=note_text.yview
)
note_scroll.grid(row=0, column=1, sticky="ns")
note_text.configure(yscrollcommand=note_scroll.set)
return vars_dict
def _create_improved_symptom_scale(
self,
parent: ttk.Frame,
row: int,
label: str,
key: str,
value: int,
vars_dict: dict[str, Any],
) -> None:
"""Create an improved symptom scale with better visual feedback."""
# Ensure value is properly converted
try:
value = int(float(value)) if value not in ["", None] else 0
except (ValueError, TypeError):
value = 0
vars_dict[key] = tk.IntVar(value=value)
# Label
ttk.Label(parent, text=f"{label}:", font=("TkDefaultFont", 10, "bold")).grid(
row=row, column=0, sticky="w", pady=8
)
# Scale container
scale_container = ttk.Frame(parent)
scale_container.grid(row=row, column=1, sticky="ew", padx=(20, 0), pady=8)
scale_container.grid_columnconfigure(0, weight=1)
# Scale with value labels
scale_frame = ttk.Frame(scale_container)
scale_frame.grid(row=0, column=0, sticky="ew")
scale_frame.grid_columnconfigure(1, weight=1)
# Current value display
value_label = ttk.Label(
scale_frame,
text=str(value),
font=("TkDefaultFont", 12, "bold"),
foreground="#2E86AB",
width=3,
)
value_label.grid(row=0, column=0, padx=(0, 10))
# Scale widget
scale = ttk.Scale(
scale_frame,
from_=0,
to=10,
variable=vars_dict[key],
orient=tk.HORIZONTAL,
length=300,
)
scale.grid(row=0, column=1, sticky="ew")
# Scale labels (0, 5, 10)
labels_frame = ttk.Frame(scale_container)
labels_frame.grid(row=1, column=0, sticky="ew", pady=(5, 0))
ttk.Label(labels_frame, text="0", font=("TkDefaultFont", 8)).grid(
row=0, column=0, sticky="w"
)
labels_frame.grid_columnconfigure(1, weight=1)
ttk.Label(labels_frame, text="5", font=("TkDefaultFont", 8)).grid(
row=0, column=1
)
ttk.Label(labels_frame, text="10", font=("TkDefaultFont", 8)).grid(
row=0, column=2, sticky="e"
)
# Update label when scale changes
def update_value_label(event=None):
current_val = vars_dict[key].get()
value_label.configure(text=str(current_val))
# Change color based on value
if current_val <= 3:
value_label.configure(foreground="#28A745") # Green for low/good
elif current_val <= 6:
value_label.configure(foreground="#FFC107") # Yellow for medium
else:
value_label.configure(foreground="#DC3545") # Red for high/bad
scale.bind("<Motion>", update_value_label)
scale.bind("<ButtonRelease-1>", update_value_label)
scale.bind("<KeyRelease>", update_value_label)
update_value_label() # Set initial color
def _create_improved_medicine_section(
self, parent: ttk.Frame, bup: int, hydro: int, gaba: int, prop: int, quet: int
) -> dict[str, tk.IntVar]:
"""Create improved medicine checkboxes with better layout."""
vars_dict = {}
# Create a grid layout for medicines
medicines = [
("bupropion", bup, "Bupropion", "150/300 mg", "#E8F4FD"),
("hydroxyzine", hydro, "Hydroxyzine", "25 mg", "#FFF2E8"),
("gabapentin", gaba, "Gabapentin", "100 mg", "#F0F8E8"),
("propranolol", prop, "Propranolol", "10 mg", "#FCE8F3"),
("quetiapine", quet, "Quetiapine", "25 mg", "#E8F0FF"),
]
# Create medicine cards in a 2-column layout
for i, (key, value, name, dose, _bg_color) in enumerate(medicines):
row = i // 2
col = i % 2
# Medicine card frame
med_card = ttk.Frame(parent, relief="solid", borderwidth=1)
med_card.grid(row=row, column=col, sticky="ew", padx=5, pady=5)
parent.grid_columnconfigure(col, weight=1)
vars_dict[key] = tk.IntVar(value=int(value))
# Checkbox with medicine name
check_frame = ttk.Frame(med_card)
check_frame.pack(fill="x", padx=10, pady=8)
checkbox = ttk.Checkbutton(
check_frame,
text=f"{name} ({dose})",
variable=vars_dict[key],
style="Medicine.TCheckbutton",
)
checkbox.pack(anchor="w")
return vars_dict
def _create_improved_dose_tracking(
self, parent: ttk.Frame, dose_data: dict[str, str]
) -> dict[str, Any]:
"""Create improved dose tracking interface."""
vars_dict = {}
# Create notebook for organized dose tracking
notebook = ttk.Notebook(parent)
notebook.pack(fill="both", expand=True)
medicines = [
("bupropion", "Bupropion"),
("hydroxyzine", "Hydroxyzine"),
("gabapentin", "Gabapentin"),
("propranolol", "Propranolol"),
("quetiapine", "Quetiapine"),
]
for med_key, med_name in medicines:
# Create tab for each medicine
tab_frame = ttk.Frame(notebook)
notebook.add(tab_frame, text=med_name)
# Configure tab layout
tab_frame.grid_columnconfigure(0, weight=1)
# Quick dose entry section
entry_frame = ttk.LabelFrame(tab_frame, text="Add New Dose", padding="10")
entry_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=5)
entry_frame.grid_columnconfigure(1, weight=1)
ttk.Label(entry_frame, text="Dose amount:").grid(
row=0, column=0, sticky="w"
)
dose_entry_var = tk.StringVar()
vars_dict[f"{med_key}_entry_var"] = dose_entry_var
dose_entry = ttk.Entry(entry_frame, textvariable=dose_entry_var, width=15)
dose_entry.grid(row=0, column=1, sticky="w", padx=(10, 10))
# Quick dose buttons
quick_frame = ttk.Frame(entry_frame)
quick_frame.grid(row=0, column=2, sticky="w")
# Common dose amounts (customize per medicine)
quick_doses = self._get_quick_doses(med_key)
for i, dose in enumerate(quick_doses):
ttk.Button(
quick_frame,
text=dose,
width=8,
command=lambda d=dose, var=dose_entry_var: var.set(d),
).grid(row=0, column=i, padx=2)
# Take dose button
def create_take_dose_command(med_name, entry_var, med_key):
def take_dose():
self._take_dose_improved(med_name, entry_var, med_key, vars_dict)
return take_dose
take_button = ttk.Button(
entry_frame,
text=f"Take {med_name}",
style="Accent.TButton",
command=create_take_dose_command(med_name, dose_entry_var, med_key),
)
take_button.grid(row=1, column=0, columnspan=3, pady=(10, 0), sticky="ew")
# Dose history section
history_frame = ttk.LabelFrame(
tab_frame, text="Today's Doses", padding="10"
)
history_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=5)
history_frame.grid_columnconfigure(0, weight=1)
# Dose history display with fixed height to prevent excessive expansion
dose_text = tk.Text(
history_frame,
height=4, # Reduced height to fit better in scrollable window
wrap=tk.WORD,
font=("Consolas", 10),
state="normal",
)
dose_text.grid(row=0, column=0, sticky="ew")
# Populate with existing doses
doses_str = dose_data.get(med_key, "")
self._populate_dose_history(dose_text, doses_str)
vars_dict[f"{med_key}_doses_text"] = dose_text
# Scrollbar for dose history
dose_scroll = ttk.Scrollbar(
history_frame, orient="vertical", command=dose_text.yview
)
dose_scroll.grid(row=0, column=1, sticky="ns")
dose_text.configure(yscrollcommand=dose_scroll.set)
return vars_dict
def _get_quick_doses(self, medicine_key: str) -> list[str]:
"""Get common dose amounts for quick selection."""
dose_map = {
"bupropion": ["150", "300"],
"hydroxyzine": ["25", "50"],
"gabapentin": ["100", "300", "600"],
"propranolol": ["10", "20", "40"],
"quetiapine": ["25", "50", "100"],
}
return dose_map.get(medicine_key, ["25", "50"])
def _populate_dose_history(self, text_widget: tk.Text, doses_str: str) -> None:
"""Populate dose history text widget with formatted dose data."""
text_widget.delete(1.0, tk.END)
if not doses_str or str(doses_str) == "nan":
text_widget.insert(1.0, "No doses recorded today")
text_widget.configure(state="disabled")
return
doses_str = str(doses_str)
formatted_doses = []
for dose_entry in doses_str.split("|"):
if ":" in dose_entry:
timestamp, dose = dose_entry.split(":", 1)
try:
dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
time_str = dt.strftime("%I:%M %p")
formatted_doses.append(f"{time_str} - {dose}")
except ValueError:
formatted_doses.append(f"{dose_entry}")
if formatted_doses:
text_widget.insert(1.0, "\n".join(formatted_doses))
else:
text_widget.insert(1.0, "No doses recorded today")
def _take_dose_improved(
self,
med_name: str,
entry_var: tk.StringVar,
med_key: str,
vars_dict: dict[str, Any],
) -> None:
"""Handle taking a dose with improved feedback."""
dose = entry_var.get().strip()
if not dose:
messagebox.showerror("Error", f"Please enter a dose amount for {med_name}")
return
# Get current time
now = datetime.now()
time_str = now.strftime("%I:%M %p")
# Update dose history
dose_text_widget = vars_dict.get(f"{med_key}_doses_text")
if dose_text_widget:
current_content = dose_text_widget.get(1.0, tk.END).strip()
new_dose_line = f"{time_str} - {dose}"
if current_content == "No doses recorded today" or not current_content:
dose_text_widget.delete(1.0, tk.END)
dose_text_widget.insert(1.0, new_dose_line)
else:
dose_text_widget.insert(tk.END, f"\n{new_dose_line}")
# Clear entry
entry_var.set("")
# Success feedback
messagebox.showinfo(
"Dose Recorded", f"{med_name} dose of {dose} recorded at {time_str}"
)
def _add_improved_edit_buttons(
self,
parent: ttk.Frame,
vars_dict: dict[str, Any],
callbacks: dict[str, Callable],
edit_win: tk.Toplevel,
) -> None:
"""Add improved action buttons to edit window."""
button_frame = ttk.Frame(parent)
button_frame.grid(row=999, column=0, sticky="ew", pady=(20, 0))
button_frame.grid_columnconfigure((0, 1, 2), weight=1)
# Save button
def save_with_improved_data():
# Get note text from Text widget
note_text_widget = vars_dict.get("note_text")
note_content = ""
if note_text_widget:
note_content = note_text_widget.get(1.0, tk.END).strip()
# Extract dose data
dose_data = {}
medicine_list = [
"bupropion",
"hydroxyzine",
"gabapentin",
"propranolol",
"quetiapine",
]
for medicine in medicine_list:
dose_text_key = f"{medicine}_doses_text"
if dose_text_key in vars_dict:
dose_text_widget = vars_dict[dose_text_key]
raw_text = dose_text_widget.get(1.0, tk.END).strip()
dose_data[medicine] = self._parse_improved_dose_text(
raw_text, vars_dict["date"].get()
)
else:
dose_data[medicine] = ""
callbacks["save"](
edit_win,
vars_dict["date"].get(),
vars_dict["depression"].get(),
vars_dict["anxiety"].get(),
vars_dict["sleep"].get(),
vars_dict["appetite"].get(),
vars_dict["bupropion"].get(),
vars_dict["hydroxyzine"].get(),
vars_dict["gabapentin"].get(),
vars_dict["propranolol"].get(),
vars_dict["quetiapine"].get(),
note_content,
dose_data,
)
save_btn = ttk.Button(
button_frame,
text="💾 Save Changes",
style="Accent.TButton",
command=save_with_improved_data,
)
save_btn.grid(row=0, column=0, sticky="ew", padx=(0, 5))
# Cancel button
cancel_btn = ttk.Button(
button_frame, text="❌ Cancel", command=edit_win.destroy
)
cancel_btn.grid(row=0, column=1, sticky="ew", padx=5)
# Delete button
delete_btn = ttk.Button(
button_frame,
text="🗑️ Delete Entry",
style="Danger.TButton",
command=lambda: callbacks["delete"](edit_win),
)
delete_btn.grid(row=0, column=2, sticky="ew", padx=(5, 0))
def _parse_improved_dose_text(self, text: str, date: str) -> str:
"""Parse improved dose text format back to CSV format."""
if not text or "No doses recorded" in text:
return ""
lines = text.strip().split("\n")
dose_entries = []
for line in lines:
line = line.strip()
if line.startswith("") and " - " in line:
try:
# Remove bullet point and split
content = line[1:].strip() # Remove •
time_part, dose_part = content.split(" - ", 1)
# Parse time (could be 12-hour format)
try:
time_obj = datetime.strptime(time_part.strip(), "%I:%M %p")
except ValueError:
# Try 24-hour format
time_obj = datetime.strptime(time_part.strip(), "%H:%M")
# Create full timestamp
today = datetime.strptime(date, "%m/%d/%Y")
full_timestamp = today.replace(
hour=time_obj.hour, minute=time_obj.minute, second=0
)
timestamp_str = full_timestamp.strftime("%Y-%m-%d %H:%M:%S")
dose_entries.append(f"{timestamp_str}:{dose_part.strip()}")
except (ValueError, IndexError):
continue
return "|".join(dose_entries)
def _bind_mousewheel_to_widget_tree(
self, widget: tk.Widget, canvas: tk.Canvas
) -> None:
"""Recursively bind mouse wheel events to all widgets in the tree."""
def on_mousewheel(event):
# Check if canvas is scrollable before scrolling
if canvas.cget("scrollregion"):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def on_mousewheel_linux_up(event):
if canvas.cget("scrollregion"):
canvas.yview_scroll(-1, "units")
def on_mousewheel_linux_down(event):
if canvas.cget("scrollregion"):
canvas.yview_scroll(1, "units")
# Bind to the widget itself
try:
widget.bind("<MouseWheel>", on_mousewheel)
widget.bind("<Button-4>", on_mousewheel_linux_up)
widget.bind("<Button-5>", on_mousewheel_linux_down)
except tk.TclError:
# Some widgets might not support binding
pass
# Recursively bind to all children
try:
for child in widget.winfo_children():
# Skip widgets that have their own scrolling behavior or are problematic
skip_types = (tk.Text, tk.Listbox, tk.Canvas, ttk.Notebook)
if not isinstance(child, skip_types):
self._bind_mousewheel_to_widget_tree(child, canvas)
elif isinstance(child, ttk.Notebook):
# For notebooks, bind to their tab frames
for tab_id in child.tabs():
tab_widget = child.nametowidget(tab_id)
self._bind_mousewheel_to_widget_tree(tab_widget, canvas)
except tk.TclError:
# Handle potential errors when accessing children
pass
def _create_edit_fields(
self,
parent: tk.Toplevel,
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Test script to demonstrate the improved edit window."""
import sys
import tkinter as tk
from pathlib import Path
# Add src directory to path
sys.path.insert(0, str(Path(__file__).parent / "src"))
from src.logger import logger
from src.ui_manager import UIManager
def test_edit_window():
"""Test the improved edit window."""
root = tk.Tk()
root.title("Edit Window Test")
root.geometry("400x300")
ui_manager = UIManager(root, logger)
# Sample data for testing (16 fields format)
test_values = (
"12/25/2024", # date
7, # depression
5, # anxiety
6, # sleep
4, # appetite
1, # bupropion
"09:00:00:150|18:00:00:150", # bupropion_doses
1, # hydroxyzine
"21:30:00:25", # hydroxyzine_doses
0, # gabapentin
"", # gabapentin_doses
1, # propranolol
"07:00:00:10|14:00:00:10", # propranolol_doses
0, # quetiapine
"", # quetiapine_doses
# Had a good day overall, feeling better with new medication routine
"Had a good day overall, feeling better with the new medication routine.",
)
# Mock callbacks
def save_callback(win, *args):
print("Save called with args:", args)
win.destroy()
def delete_callback(win):
print("Delete called")
win.destroy()
callbacks = {"save": save_callback, "delete": delete_callback}
# Create the improved edit window
edit_win = ui_manager.create_edit_window(test_values, callbacks)
# Center the edit window
edit_win.update_idletasks()
x = (edit_win.winfo_screenwidth() // 2) - (edit_win.winfo_width() // 2)
y = (edit_win.winfo_screenheight() // 2) - (edit_win.winfo_height() // 2)
edit_win.geometry(f"+{x}+{y}")
root.mainloop()
if __name__ == "__main__":
test_edit_window()
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""
Test script to verify mouse wheel scrolling works in both the new entry window
and edit window of TheChart application.
"""
import logging
import tkinter as tk
from src.ui_manager import UIManager
def test_scrolling():
"""Test both new entry and edit window scrolling."""
print("Testing mouse wheel scrolling functionality...")
# Create test root window
root = tk.Tk()
root.title("Scrolling Test")
root.geometry("800x600")
# Create logger
logger = logging.getLogger("test")
logger.setLevel(logging.DEBUG)
# Create UI manager
ui_manager = UIManager(root, logger)
# Create main frame
main_frame = tk.Frame(root)
main_frame.pack(fill="both", expand=True)
main_frame.grid_rowconfigure(0, weight=1)
main_frame.grid_rowconfigure(1, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
main_frame.grid_columnconfigure(1, weight=1)
# Test 1: Create input frame (new entry window)
print("✓ Creating new entry input frame with mouse wheel scrolling...")
ui_manager.create_input_frame(main_frame)
# Test 2: Create edit window
def test_edit_window():
print("✓ Creating edit window with mouse wheel scrolling...")
# Sample data for edit window
test_values = (
"01/15/2025", # date
"3", # depression
"5", # anxiety
"7", # sleep
"4", # appetite
"1", # bupropion
"09:00: 150", # bup_doses
"0", # hydroxyzine
"", # hydro_doses
"1", # gabapentin
"20:00: 100", # gaba_doses
"0", # propranolol
"", # prop_doses
"0", # quetiapine
"", # quet_doses
"Test note", # note
)
callbacks = {
"save": lambda *args: print("Save callback called"),
"delete": lambda *args: print("Delete callback called"),
}
edit_window = ui_manager.create_edit_window(test_values, callbacks)
return edit_window
# Add test button
test_button = tk.Button(
main_frame,
text="Test Edit Window Scrolling",
command=test_edit_window,
font=("TkDefaultFont", 12),
bg="#4CAF50",
fg="white",
padx=20,
pady=10,
)
test_button.grid(row=2, column=0, columnspan=2, pady=20)
# Add instructions
instructions = tk.Label(
main_frame,
text="Instructions:\n\n"
"1. Use mouse wheel anywhere in the 'New Entry' section to test scrolling\n"
"2. Click 'Test Edit Window Scrolling' button\n"
"3. Use mouse wheel anywhere in the edit window to test scrolling\n"
"4. Both windows should scroll smoothly with mouse wheel\n\n"
"✓ Mouse wheel scrolling has been enhanced for both windows!",
font=("TkDefaultFont", 10),
justify="left",
bg="#E8F5E8",
padx=20,
pady=15,
)
instructions.grid(row=3, column=0, columnspan=2, padx=20, pady=10, sticky="ew")
print("✓ Test setup complete!")
print("\nMouse wheel scrolling features implemented:")
print(" • Recursive binding to all child widgets")
print(" • Platform-specific event handling (Windows/Linux)")
print(" • Focus management for consistent scrolling")
print(" • Works anywhere within the scrollable areas")
print("\nTest the scrolling by moving your mouse wheel over any part of the")
print("'New Entry' section or the edit window when opened.")
root.mainloop()
if __name__ == "__main__":
test_scrolling()
+25 -25
View File
@@ -20,9 +20,9 @@ class TestConstants:
if 'constants' in sys.modules:
importlib.reload(sys.modules['constants'])
else:
import constants
import src.constants
assert constants.LOG_LEVEL == "INFO"
assert src.constants.LOG_LEVEL == "INFO"
def test_custom_log_level(self):
"""Test custom LOG_LEVEL from environment."""
@@ -31,9 +31,9 @@ class TestConstants:
if 'constants' in sys.modules:
importlib.reload(sys.modules['constants'])
else:
import constants
import src.constants
assert constants.LOG_LEVEL == "DEBUG"
assert src.constants.LOG_LEVEL == "DEBUG"
def test_default_log_path(self):
"""Test default LOG_PATH when not set in environment."""
@@ -42,9 +42,9 @@ class TestConstants:
if 'constants' in sys.modules:
importlib.reload(sys.modules['constants'])
else:
import constants
import src.constants
assert constants.LOG_PATH == "/tmp/logs/thechart"
assert src.constants.LOG_PATH == "/tmp/logs/thechart"
def test_custom_log_path(self):
"""Test custom LOG_PATH from environment."""
@@ -53,9 +53,9 @@ class TestConstants:
if 'constants' in sys.modules:
importlib.reload(sys.modules['constants'])
else:
import constants
import src.constants
assert constants.LOG_PATH == "/custom/log/path"
assert src.constants.LOG_PATH == "/custom/log/path"
def test_default_log_clear(self):
"""Test default LOG_CLEAR when not set in environment."""
@@ -64,9 +64,9 @@ class TestConstants:
if 'constants' in sys.modules:
importlib.reload(sys.modules['constants'])
else:
import constants
import src.constants
assert constants.LOG_CLEAR == "False"
assert src.constants.LOG_CLEAR == "False"
def test_custom_log_clear_true(self):
"""Test LOG_CLEAR when set to true in environment."""
@@ -75,9 +75,9 @@ class TestConstants:
if 'constants' in sys.modules:
importlib.reload(sys.modules['constants'])
else:
import constants
import src.constants
assert constants.LOG_CLEAR == "True"
assert src.constants.LOG_CLEAR == "True"
def test_custom_log_clear_false(self):
"""Test LOG_CLEAR when set to false in environment."""
@@ -86,9 +86,9 @@ class TestConstants:
if 'constants' in sys.modules:
importlib.reload(sys.modules['constants'])
else:
import constants
import src.constants
assert constants.LOG_CLEAR == "False"
assert src.constants.LOG_CLEAR == "False"
def test_log_level_case_insensitive(self):
"""Test that LOG_LEVEL is converted to uppercase."""
@@ -97,9 +97,9 @@ class TestConstants:
if 'constants' in sys.modules:
importlib.reload(sys.modules['constants'])
else:
import constants
import src.constants
assert constants.LOG_LEVEL == "WARNING"
assert src.constants.LOG_LEVEL == "WARNING"
def test_dotenv_override(self):
"""Test that dotenv override parameter is set to True."""
@@ -109,22 +109,22 @@ class TestConstants:
if 'constants' in sys.modules:
importlib.reload(sys.modules['constants'])
else:
import constants
import src.constants
mock_load_dotenv.assert_called_once_with(override=True)
def test_all_constants_are_strings(self):
"""Test that all constants are string type."""
import constants
import src.constants
assert isinstance(constants.LOG_LEVEL, str)
assert isinstance(constants.LOG_PATH, str)
assert isinstance(constants.LOG_CLEAR, str)
assert isinstance(src.constants.LOG_LEVEL, str)
assert isinstance(src.constants.LOG_PATH, str)
assert isinstance(src.constants.LOG_CLEAR, str)
def test_constants_not_empty(self):
"""Test that constants are not empty strings."""
import constants
import src.constants
assert constants.LOG_LEVEL != ""
assert constants.LOG_PATH != ""
assert constants.LOG_CLEAR != ""
assert src.constants.LOG_LEVEL != ""
assert src.constants.LOG_PATH != ""
assert src.constants.LOG_CLEAR != ""
+1 -1
View File
@@ -12,7 +12,7 @@ import matplotlib.pyplot as plt
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from graph_manager import GraphManager
from src.graph_manager import GraphManager
class TestGraphManager:
+19 -19
View File
@@ -24,7 +24,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
mock_mkdir.assert_called_once()
@@ -38,7 +38,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
mock_mkdir.assert_not_called()
@@ -53,7 +53,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
mock_print.assert_called()
@@ -70,7 +70,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
mock_init_logger.assert_called_once_with('init', testing_mode=False)
@@ -87,7 +87,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
mock_init_logger.assert_called_once_with('init', testing_mode=True)
@@ -98,7 +98,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
expected_files = (
f"{temp_log_dir}/thechart.log",
@@ -106,7 +106,7 @@ class TestInit:
f"{temp_log_dir}/thechart.error.log",
)
assert init.log_files == expected_files
assert src.init.log_files == expected_files
def test_testing_mode_detection(self, temp_log_dir):
"""Test that testing mode is detected correctly."""
@@ -117,14 +117,14 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
assert init.testing_mode is True
assert src.init.testing_mode is True
# Test with non-DEBUG level
with patch('init.LOG_LEVEL', 'INFO'):
importlib.reload(sys.modules['init'])
assert init.testing_mode is False
assert src.init.testing_mode is False
def test_log_clear_true(self, temp_log_dir):
"""Test log file clearing when LOG_CLEAR is True."""
@@ -147,7 +147,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
# Check that files were truncated
for log_file in log_files:
@@ -176,7 +176,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
# Check that files were not truncated
for log_file in log_files:
@@ -203,7 +203,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
def test_log_clear_permission_error(self, temp_log_dir):
"""Test handling of permission errors during log clearing."""
@@ -226,7 +226,7 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
def test_module_exports(self, temp_log_dir):
"""Test that module exports expected objects."""
@@ -235,12 +235,12 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
# Check that expected objects are available
assert hasattr(init, 'logger')
assert hasattr(init, 'log_files')
assert hasattr(init, 'testing_mode')
assert hasattr(src.init, 'logger')
assert hasattr(src.init, 'log_files')
assert hasattr(src.init, 'testing_mode')
def test_log_path_printing(self, temp_log_dir):
"""Test that LOG_PATH is printed when directory is created."""
@@ -253,6 +253,6 @@ class TestInit:
if 'init' in sys.modules:
importlib.reload(sys.modules['init'])
else:
import init
import src.init
mock_print.assert_called_with(temp_log_dir + '/new_dir')
+1 -1
View File
@@ -10,7 +10,7 @@ from unittest.mock import patch, Mock
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from logger import init_logger
from src.logger import init_logger
class TestLogger:
+1 -1
View File
@@ -10,7 +10,7 @@ import pandas as pd
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from main import MedTrackerApp
from src.main import MedTrackerApp
class TestMedTrackerApp:
Generated
+1 -1
View File
@@ -698,7 +698,7 @@ wheels = [
[[package]]
name = "thechart"
version = "1.0.1"
version = "1.2.1"
source = { virtual = "." }
dependencies = [
{ name = "colorlog" },