Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df9738ab17 | |||
| c3c88c63d2 | |||
| 86606d56b6 | |||
| 9790f2730a | |||
| fdcc210fc4 |
@@ -1,5 +1,5 @@
|
|||||||
TARGET=thechart
|
TARGET=thechart
|
||||||
VERSION=1.8.5
|
VERSION=1.9.5
|
||||||
ROOT=/home/will
|
ROOT=/home/will
|
||||||
ICON=chart-671.png
|
ICON=chart-671.png
|
||||||
SHELL=fish
|
SHELL=fish
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# TheChart
|
# TheChart
|
||||||
Advanced medication tracking application for monitoring treatment progress and symptom evolution.
|
Modern medication tracking application with advanced UI/UX for monitoring treatment progress and symptom evolution.
|
||||||
|
|
||||||
## Quick Start
|
## 🚀 Quick Start
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
make install
|
make install
|
||||||
@@ -14,11 +14,22 @@ make test
|
|||||||
```
|
```
|
||||||
|
|
||||||
## 📚 Documentation
|
## 📚 Documentation
|
||||||
- **[Features Guide](docs/FEATURES.md)** - Complete feature documentation
|
- **[Features Guide](docs/FEATURES.md)** - Complete feature documentation with UI/UX improvements
|
||||||
- **[Export System](docs/EXPORT_SYSTEM.md)** - Data export functionality and formats
|
- **[Keyboard Shortcuts](docs/KEYBOARD_SHORTCUTS.md)** - Comprehensive keyboard shortcuts for efficiency
|
||||||
|
- **[Export System](docs/EXPORT_SYSTEM.md)** - Data export functionality (JSON, XML, PDF)
|
||||||
- **[Development Guide](docs/DEVELOPMENT.md)** - Testing, development, and architecture
|
- **[Development Guide](docs/DEVELOPMENT.md)** - Testing, development, and architecture
|
||||||
- **[Changelog](docs/CHANGELOG.md)** - Version history and feature evolution
|
- **[Changelog](docs/CHANGELOG.md)** - Version history and recent UI improvements
|
||||||
- **[Quick Reference](#quick-reference)** - Common commands and shortcuts
|
- **[Documentation Index](docs/README.md)** - Complete documentation navigation guide
|
||||||
|
|
||||||
|
> 💡 **Quick Start**: New users should start with this README, then explore the [Features Guide](docs/FEATURES.md) for detailed functionality. The [Documentation Index](docs/README.md) provides comprehensive navigation.
|
||||||
|
|
||||||
|
## ✨ Recent Major Updates (v1.9.5)
|
||||||
|
- **🎨 Modern UI/UX**: Professional themes with ttkthemes integration
|
||||||
|
- **⌨️ Keyboard Shortcuts**: Comprehensive shortcut system for all operations
|
||||||
|
- **💡 Smart Tooltips**: Context-sensitive help throughout the application
|
||||||
|
- **🎭 8 Professional Themes**: Arc, Equilux, Adapta, Yaru, Ubuntu, Plastik, Breeze, Elegance
|
||||||
|
- **⚙️ Settings System**: Advanced configuration with theme persistence
|
||||||
|
- **📊 Enhanced Tables**: Improved selection highlighting and alternating row colors
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
@@ -483,6 +494,30 @@ thechart_data.csv # User data (created on first run)
|
|||||||
- **`pyproject.toml`**: Project configuration and dependencies
|
- **`pyproject.toml`**: Project configuration and dependencies
|
||||||
- **`uv.lock`**: Dependency lock file
|
- **`uv.lock`**: Dependency lock file
|
||||||
|
|
||||||
|
### Keyboard Shortcuts
|
||||||
|
```bash
|
||||||
|
# File Operations
|
||||||
|
Ctrl+S # Save/Add new entry
|
||||||
|
Ctrl+Q # Quit application
|
||||||
|
Ctrl+E # Export data
|
||||||
|
|
||||||
|
# Data Management
|
||||||
|
Ctrl+N # Clear entries
|
||||||
|
Ctrl+R / F5 # Refresh data
|
||||||
|
|
||||||
|
# Window Management
|
||||||
|
Ctrl+M # Manage medicines
|
||||||
|
Ctrl+P # Manage pathologies
|
||||||
|
|
||||||
|
# Table Operations
|
||||||
|
Delete # Delete selected entry
|
||||||
|
Escape # Clear selection
|
||||||
|
Double-click # Edit entry
|
||||||
|
|
||||||
|
# Help
|
||||||
|
F1 # Show keyboard shortcuts help
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Why uv?
|
## Why uv?
|
||||||
|
|||||||
@@ -5,6 +5,75 @@ All notable changes to TheChart project are documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.9.5] - 2025-08-05
|
||||||
|
|
||||||
|
### 🎨 Major UI/UX Overhaul
|
||||||
|
- **Added**: Professional theme system with ttkthemes integration
|
||||||
|
- **Added**: 8 curated themes (Arc, Equilux, Adapta, Yaru, Ubuntu, Plastik, Breeze, Elegance)
|
||||||
|
- **Added**: Dynamic theme switching without restart
|
||||||
|
- **Added**: Theme persistence between sessions
|
||||||
|
- **Added**: Comprehensive settings window with tabbed interface
|
||||||
|
- **Added**: Smart tooltip system with context-sensitive help
|
||||||
|
- **Improved**: Table selection highlighting and alternating row colors
|
||||||
|
- **Improved**: Modern styling for all UI components (buttons, frames, forms)
|
||||||
|
- **Improved**: Professional card-style layouts and enhanced spacing
|
||||||
|
|
||||||
|
### ⚙️ Settings and Configuration System
|
||||||
|
- **Added**: Advanced settings window (accessible via F2)
|
||||||
|
- **Added**: Theme selection with live preview
|
||||||
|
- **Added**: UI preferences and customization options
|
||||||
|
- **Added**: About dialog with detailed application information
|
||||||
|
- **Added**: Settings persistence across application restarts
|
||||||
|
|
||||||
|
### 💡 Enhanced User Experience
|
||||||
|
- **Added**: Intelligent tooltips for all interactive elements
|
||||||
|
- **Added**: Specialized help for pathology scales and medicine options
|
||||||
|
- **Added**: Non-intrusive tooltip timing (500-800ms delay)
|
||||||
|
- **Added**: Quick theme switching via menu bar
|
||||||
|
- **Improved**: Visual hierarchy with better typography and spacing
|
||||||
|
- **Improved**: Professional color schemes across all themes
|
||||||
|
|
||||||
|
### 🏗️ Technical Architecture Improvements
|
||||||
|
- **Added**: Modular theme manager with dependency injection
|
||||||
|
- **Added**: Tooltip management system
|
||||||
|
- **Added**: Enhanced UI manager with theme integration
|
||||||
|
- **Improved**: Code organization with separate concerns
|
||||||
|
- **Improved**: Error handling with graceful theme fallbacks
|
||||||
|
|
||||||
|
## [1.7.0] - 2025-08-05
|
||||||
|
|
||||||
|
### ⌨️ Keyboard Shortcuts System
|
||||||
|
- **Added**: Comprehensive keyboard shortcuts for improved productivity
|
||||||
|
- **Added**: File operations shortcuts (Ctrl+S, Ctrl+Q, Ctrl+E)
|
||||||
|
- **Added**: Data management shortcuts (Ctrl+N, Ctrl+R, F5)
|
||||||
|
- **Added**: Window management shortcuts (Ctrl+M, Ctrl+P)
|
||||||
|
- **Added**: Table operation shortcuts (Delete, Escape)
|
||||||
|
- **Added**: Help system shortcut (F1)
|
||||||
|
- **Added**: Menu integration showing shortcuts next to menu items
|
||||||
|
- **Added**: Button labels updated to show primary shortcuts
|
||||||
|
- **Added**: In-app help dialog accessible via F1
|
||||||
|
- **Added**: Status bar feedback for all keyboard operations
|
||||||
|
- **Improved**: Button text shows shortcuts (e.g., "Add Entry (Ctrl+S)")
|
||||||
|
- **Improved**: Case-insensitive shortcuts (Ctrl+S and Ctrl+Shift+S both work)
|
||||||
|
|
||||||
|
#### Keyboard Shortcuts Added:
|
||||||
|
- **Ctrl+S**: Save/Add new entry
|
||||||
|
- **Ctrl+Q**: Quit application (with confirmation)
|
||||||
|
- **Ctrl+E**: Export data
|
||||||
|
- **Ctrl+N**: Clear entries
|
||||||
|
- **Ctrl+R / F5**: Refresh data
|
||||||
|
- **Ctrl+M**: Manage medicines
|
||||||
|
- **Ctrl+P**: Manage pathologies
|
||||||
|
- **Delete**: Delete selected entry (with confirmation)
|
||||||
|
- **Escape**: Clear selection
|
||||||
|
- **F1**: Show keyboard shortcuts help
|
||||||
|
|
||||||
|
### 📚 Documentation Updates
|
||||||
|
- **Updated**: FEATURES.md with keyboard shortcuts section
|
||||||
|
- **Added**: KEYBOARD_SHORTCUTS.md with comprehensive shortcut reference
|
||||||
|
- **Updated**: In-app help system with shortcut information
|
||||||
|
- **Updated**: About dialog with keyboard shortcut mention
|
||||||
|
|
||||||
## [1.6.1] - 2025-07-31
|
## [1.6.1] - 2025-07-31
|
||||||
|
|
||||||
### 📚 Documentation Overhaul
|
### 📚 Documentation Overhaul
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
# Documentation Consolidation Summary
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This document summarizes the documentation consolidation and updates performed to improve the TheChart project documentation structure.
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### 1. Documentation Structure Consolidation
|
||||||
|
- **Removed**: `docs/UI_IMPROVEMENTS.md` (redundant file)
|
||||||
|
- **Consolidated**: UI/UX improvements documentation into `docs/FEATURES.md`
|
||||||
|
- **Enhanced**: Main `README.md` with recent updates section
|
||||||
|
- **Updated**: `docs/README.md` (documentation index) with comprehensive navigation
|
||||||
|
|
||||||
|
### 2. Content Integration
|
||||||
|
|
||||||
|
#### FEATURES.md Enhancements
|
||||||
|
- **Added**: Modern UI/UX System section (new in v1.9.5)
|
||||||
|
- **Added**: Professional Theme Engine documentation
|
||||||
|
- **Added**: Comprehensive Keyboard Shortcuts section
|
||||||
|
- **Added**: Settings and Theme Management documentation
|
||||||
|
- **Added**: Smart Tooltip System documentation
|
||||||
|
- **Added**: Enhanced Technical Architecture section
|
||||||
|
- **Added**: UI/UX Technical Implementation section
|
||||||
|
|
||||||
|
#### CHANGELOG.md Updates
|
||||||
|
- **Added**: Version 1.9.5 with comprehensive UI/UX overhaul documentation
|
||||||
|
- **Added**: Settings and Configuration System section
|
||||||
|
- **Added**: Enhanced User Experience section
|
||||||
|
- **Added**: Technical Architecture Improvements section
|
||||||
|
|
||||||
|
#### README.md Improvements
|
||||||
|
- **Updated**: Title and description to emphasize modern UI/UX
|
||||||
|
- **Added**: Recent Major Updates section highlighting v1.9.5 improvements
|
||||||
|
- **Added**: Quick start guidance for new users
|
||||||
|
- **Updated**: Documentation links with better descriptions
|
||||||
|
- **Added**: Documentation navigation guide reference
|
||||||
|
|
||||||
|
### 3. Cross-Reference Updates
|
||||||
|
- **Updated**: All internal links to reflect consolidated structure
|
||||||
|
- **Enhanced**: Documentation index with comprehensive navigation
|
||||||
|
- **Added**: Task-based navigation in docs/README.md
|
||||||
|
- **Improved**: User type-based documentation guidance
|
||||||
|
|
||||||
|
## Current Documentation Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/
|
||||||
|
├── README.md # Documentation index and navigation guide
|
||||||
|
├── FEATURES.md # Complete feature documentation (includes UI/UX)
|
||||||
|
├── KEYBOARD_SHORTCUTS.md # Comprehensive shortcut reference
|
||||||
|
├── MENU_THEMING.md # Menu theming system documentation
|
||||||
|
├── TESTING.md # Comprehensive testing guide (NEW)
|
||||||
|
├── EXPORT_SYSTEM.md # Data export functionality
|
||||||
|
├── DEVELOPMENT.md # Development guidelines
|
||||||
|
├── CHANGELOG.md # Version history and changes
|
||||||
|
└── DOCUMENTATION_SUMMARY.md # This summary file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Documentation Consolidation (NEW)
|
||||||
|
- **Added**: `docs/TESTING.md` - Comprehensive testing guide
|
||||||
|
- **Updated**: `scripts/README.md` - Reorganized test script documentation
|
||||||
|
- **Added**: `tests/test_theme_manager.py` - Unit tests for menu theming
|
||||||
|
- **Updated**: `scripts/test_menu_theming.py` - Converted to interactive demo
|
||||||
|
- **Organized**: Clear separation of unit tests, integration tests, and demos
|
||||||
|
├── EXPORT_SYSTEM.md # Data export functionality
|
||||||
|
├── DEVELOPMENT.md # Development setup and testing
|
||||||
|
├── CHANGELOG.md # Version history and improvements
|
||||||
|
└── DOCUMENTATION_SUMMARY.md # This summary (new)
|
||||||
|
|
||||||
|
README.md # Main project README with quick start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Highlights
|
||||||
|
|
||||||
|
### For End Users
|
||||||
|
1. **Modern UI/UX**: Complete documentation of the new theme system
|
||||||
|
2. **Keyboard Efficiency**: Comprehensive shortcut system documentation
|
||||||
|
3. **Feature Guidance**: Consolidated feature documentation with examples
|
||||||
|
4. **Quick Navigation**: Task-based and user-type-based navigation
|
||||||
|
|
||||||
|
### For Developers
|
||||||
|
1. **Technical Architecture**: Enhanced architecture documentation
|
||||||
|
2. **UI/UX Implementation**: Technical details of theme system
|
||||||
|
3. **Code Organization**: Clear separation of concerns documentation
|
||||||
|
4. **Development Workflow**: Comprehensive development guide
|
||||||
|
|
||||||
|
## Quality Improvements
|
||||||
|
|
||||||
|
### Content Quality
|
||||||
|
- **Comprehensive Coverage**: All major features and improvements documented
|
||||||
|
- **Clear Structure**: Hierarchical organization with clear headings
|
||||||
|
- **Practical Examples**: Code snippets and usage examples maintained
|
||||||
|
- **Cross-References**: Better linking between related sections
|
||||||
|
|
||||||
|
### User Experience
|
||||||
|
- **Progressive Disclosure**: Information organized by user expertise level
|
||||||
|
- **Task-Oriented**: Documentation organized around user tasks
|
||||||
|
- **Quick Access**: Multiple entry points and navigation paths
|
||||||
|
- **Searchable**: Clear headings and consistent formatting
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
- **Reduced Redundancy**: Eliminated duplicate information
|
||||||
|
- **Single Source of Truth**: Consolidated information reduces maintenance burden
|
||||||
|
- **Version Alignment**: Documentation synchronized with current codebase
|
||||||
|
- **Future-Proof**: Structure supports easy updates and additions
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Recommended Maintenance
|
||||||
|
1. **Keep Features Updated**: Update FEATURES.md as new UI/UX improvements are added
|
||||||
|
2. **Maintain Changelog**: Continue detailed changelog entries for version tracking
|
||||||
|
3. **Review Navigation**: Periodically review docs/README.md navigation for completeness
|
||||||
|
4. **User Feedback**: Collect user feedback on documentation effectiveness
|
||||||
|
|
||||||
|
### Future Enhancements
|
||||||
|
1. **Screenshots**: Consider adding screenshots of the new UI themes
|
||||||
|
2. **Video Guides**: Potential for video demonstrations of key features
|
||||||
|
3. **API Documentation**: If public APIs develop, consider separate API docs
|
||||||
|
4. **Internationalization**: Structure supports future translation efforts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Documentation consolidation completed**: All major UI/UX improvements are now properly documented and easily discoverable through the improved navigation structure.
|
||||||
+145
-16
@@ -1,7 +1,50 @@
|
|||||||
# TheChart - Features Documentation
|
# TheChart - Features Documentation
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
TheChart is a comprehensive medication tracking application that allows users to monitor medication intake, symptom tracking, and visualize treatment progress over time.
|
TheChart is a comprehensive medication tracking application with a modern, professional UI that allows users to monitor medication intake, track symptoms, and visualize treatment progress over time.
|
||||||
|
|
||||||
|
## 🎨 Modern UI/UX System (New in v1.9.5)
|
||||||
|
|
||||||
|
### Professional Theme Engine
|
||||||
|
TheChart features a sophisticated theme system powered by ttkthemes, offering 8 carefully curated professional themes.
|
||||||
|
|
||||||
|
#### Available Themes:
|
||||||
|
- **Arc**: Modern flat design with subtle shadows
|
||||||
|
- **Equilux**: Dark theme with excellent contrast
|
||||||
|
- **Adapta**: Clean, minimalist design
|
||||||
|
- **Yaru**: Ubuntu-inspired modern interface
|
||||||
|
- **Ubuntu**: Official Ubuntu styling
|
||||||
|
- **Plastik**: Classic professional appearance
|
||||||
|
- **Breeze**: KDE-inspired clean design
|
||||||
|
- **Elegance**: Sophisticated dark theme
|
||||||
|
|
||||||
|
#### UI Enhancements:
|
||||||
|
- **Modern Styling**: Card-style frames, enhanced buttons, professional form controls
|
||||||
|
- **Smart Tooltips**: Context-sensitive help for all interactive elements
|
||||||
|
- **Improved Tables**: Better selection highlighting and alternating row colors
|
||||||
|
- **Settings System**: Comprehensive preferences with theme persistence
|
||||||
|
- **Responsive Design**: Automatic layout adjustments and scaling
|
||||||
|
- **Menu Theming**: Complete menu integration with theme colors and hover effects
|
||||||
|
|
||||||
|
### ⌨️ Comprehensive Keyboard Shortcuts
|
||||||
|
Professional keyboard shortcut system for efficient navigation and operation.
|
||||||
|
|
||||||
|
#### File Operations:
|
||||||
|
- **Ctrl+S**: Save/Add new entry
|
||||||
|
- **Ctrl+Q**: Quit application (with confirmation)
|
||||||
|
- **Ctrl+E**: Export data
|
||||||
|
|
||||||
|
#### Data Management:
|
||||||
|
- **Ctrl+N**: Clear entries
|
||||||
|
- **Ctrl+R / F5**: Refresh data
|
||||||
|
- **Delete**: Delete selected entry
|
||||||
|
- **Escape**: Clear selection
|
||||||
|
|
||||||
|
#### Window Management:
|
||||||
|
- **Ctrl+M**: Manage medicines
|
||||||
|
- **Ctrl+P**: Manage pathologies
|
||||||
|
- **F1**: Show keyboard shortcuts help
|
||||||
|
- **F2**: Open settings window
|
||||||
|
|
||||||
## Core Features
|
## Core Features
|
||||||
|
|
||||||
@@ -37,6 +80,36 @@ Each medicine includes:
|
|||||||
2. **Manual Configuration**: Edit `medicines.json` directly
|
2. **Manual Configuration**: Edit `medicines.json` directly
|
||||||
3. **Programmatically**: Use the MedicineManager API
|
3. **Programmatically**: Use the MedicineManager API
|
||||||
|
|
||||||
|
### ⚙️ Settings and Theme Management
|
||||||
|
Advanced configuration system allowing users to customize their experience.
|
||||||
|
|
||||||
|
#### Settings Window (F2):
|
||||||
|
- **Theme Selection**: Choose from 8 professional themes with live preview
|
||||||
|
- **UI Preferences**: Font scaling, window behavior options
|
||||||
|
- **About Information**: Detailed application and version information
|
||||||
|
- **Tabbed Interface**: Organized settings categories for easy navigation
|
||||||
|
|
||||||
|
#### Theme Features:
|
||||||
|
- **Real-time Switching**: No restart required for theme changes
|
||||||
|
- **Persistence**: Selected theme remembered between sessions
|
||||||
|
- **Quick Access**: Theme menu for instant switching
|
||||||
|
- **Fallback Handling**: Graceful handling if themes fail to load
|
||||||
|
|
||||||
|
### 💡 Smart Tooltip System
|
||||||
|
Context-sensitive help system providing guidance throughout the application.
|
||||||
|
|
||||||
|
#### Tooltip Types:
|
||||||
|
- **Pathology Scales**: Usage guidance for symptom tracking
|
||||||
|
- **Medicine Checkboxes**: Medication information and dosage details
|
||||||
|
- **Action Buttons**: Functionality description with keyboard shortcuts
|
||||||
|
- **Form Controls**: Input guidance and format requirements
|
||||||
|
|
||||||
|
#### Features:
|
||||||
|
- **Delayed Display**: Non-intrusive timing (500-800ms delay)
|
||||||
|
- **Theme-aware Styling**: Tooltips match selected theme
|
||||||
|
- **Smart Positioning**: Automatic placement to avoid screen edges
|
||||||
|
- **Rich Content**: Multi-line descriptions with formatting
|
||||||
|
|
||||||
### 💊 Advanced Dose Tracking
|
### 💊 Advanced Dose Tracking
|
||||||
Comprehensive dose tracking system that records exact timestamps and dosages throughout the day.
|
Comprehensive dose tracking system that records exact timestamps and dosages throughout the day.
|
||||||
|
|
||||||
@@ -159,26 +232,82 @@ Professional testing infrastructure with high code coverage.
|
|||||||
- **Real-time Updates**: Immediate feedback and data updates
|
- **Real-time Updates**: Immediate feedback and data updates
|
||||||
- **Error Handling**: Comprehensive error messages and recovery options
|
- **Error Handling**: Comprehensive error messages and recovery options
|
||||||
|
|
||||||
|
### ⌨️ Keyboard Shortcuts
|
||||||
|
Comprehensive keyboard shortcuts for efficient navigation and data entry.
|
||||||
|
|
||||||
|
#### File Operations:
|
||||||
|
- **Ctrl+S**: Save/Add new entry - Quickly save current entry data
|
||||||
|
- **Ctrl+Q**: Quit application - Exit with confirmation dialog
|
||||||
|
- **Ctrl+E**: Export data - Open export dialog window
|
||||||
|
|
||||||
|
#### Data Management:
|
||||||
|
- **Ctrl+N**: Clear entries - Clear all input fields for new entry
|
||||||
|
- **Ctrl+R / F5**: Refresh data - Reload data from CSV and update displays
|
||||||
|
|
||||||
|
#### Window Management:
|
||||||
|
- **Ctrl+M**: Manage medicines - Open medicine management window
|
||||||
|
- **Ctrl+P**: Manage pathologies - Open pathology management window
|
||||||
|
|
||||||
|
#### Table Operations:
|
||||||
|
- **Delete**: Delete selected entry - Remove selected table entry with confirmation
|
||||||
|
- **Escape**: Clear selection - Clear current table selection
|
||||||
|
- **Double-click**: Edit entry - Open edit dialog for selected entry
|
||||||
|
|
||||||
|
#### Help System:
|
||||||
|
- **F1**: Show keyboard shortcuts - Display help dialog with all shortcuts
|
||||||
|
|
||||||
|
#### Integration Features:
|
||||||
|
- **Menu Display**: All shortcuts shown in menu bar next to items
|
||||||
|
- **Button Labels**: Primary buttons show their keyboard shortcuts
|
||||||
|
- **Case Insensitive**: Both Ctrl+S and Ctrl+Shift+S work
|
||||||
|
- **Focus Management**: Shortcuts work when main window has focus
|
||||||
|
- **Status Feedback**: All operations provide status bar feedback
|
||||||
|
|
||||||
## Technical Architecture
|
## Technical Architecture
|
||||||
|
|
||||||
### 🏗️ Modular Design
|
### � Modern UI Architecture
|
||||||
- **MedicineManager**: Core medicine CRUD operations
|
- **ThemeManager**: Centralized theme management with dynamic switching
|
||||||
- **PathologyManager**: Symptom and pathology management
|
- **TooltipManager**: Smart tooltip system with context-sensitive help
|
||||||
- **GraphManager**: All graph-related operations and visualizations
|
- **UIManager**: Enhanced UI component creation with theme integration
|
||||||
- **UIManager**: User interface creation and management
|
- **SettingsWindow**: Advanced configuration interface with persistence
|
||||||
- **DataManager**: CSV operations and data persistence
|
|
||||||
|
|
||||||
### 🔧 Configuration Management
|
### 🏗️ Core Application Design
|
||||||
- **JSON-based Configuration**: `medicines.json` and `pathologies.json`
|
- **MedicineManager**: Core medicine CRUD operations with JSON persistence
|
||||||
- **Dynamic Loading**: Runtime configuration updates
|
- **PathologyManager**: Symptom and pathology management system
|
||||||
- **Validation**: Input validation and error handling
|
- **GraphManager**: Professional graph rendering with matplotlib integration
|
||||||
- **Backward Compatibility**: Seamless updates and migrations
|
- **DataManager**: Robust CSV operations and data persistence with validation
|
||||||
|
|
||||||
### 📈 Data Processing
|
### 🔧 Configuration and Data Management
|
||||||
|
- **JSON-based Configuration**: `medicines.json` and `pathologies.json` for easy management
|
||||||
|
- **Dynamic Loading**: Runtime configuration updates without restarts
|
||||||
|
- **Data Validation**: Comprehensive input validation and error handling
|
||||||
|
- **Backward Compatibility**: Seamless updates and migrations across versions
|
||||||
|
|
||||||
|
### 📈 Advanced Data Processing
|
||||||
- **Pandas Integration**: Efficient data manipulation and analysis
|
- **Pandas Integration**: Efficient data manipulation and analysis
|
||||||
- **Matplotlib Visualization**: Professional graph rendering
|
- **Real-time Calculations**: Dynamic dose totals, averages, and statistics
|
||||||
- **Robust Parsing**: Handles various data formats and edge cases
|
- **Robust Parsing**: Handles various data formats and edge cases gracefully
|
||||||
- **Real-time Calculations**: Dynamic dose totals and averages
|
- **Performance Optimization**: Efficient batch operations and caching
|
||||||
|
|
||||||
|
## UI/UX Technical Implementation
|
||||||
|
|
||||||
|
### 🎭 Theme System Architecture
|
||||||
|
- **Multiple Theme Support**: 8 curated professional themes
|
||||||
|
- **Dynamic Style Application**: Real-time theme switching without restart
|
||||||
|
- **Color Extraction**: Automatic color scheme detection and application
|
||||||
|
- **Fallback Mechanisms**: Graceful handling when themes fail to load
|
||||||
|
|
||||||
|
### 💡 Enhanced User Experience
|
||||||
|
- **Smart Tooltips**: Context-sensitive help with delayed, non-intrusive display
|
||||||
|
- **Modern Styling**: Card-style frames, enhanced buttons, professional form controls
|
||||||
|
- **Improved Tables**: Better selection highlighting and alternating row colors
|
||||||
|
- **Responsive Design**: Automatic layout adjustments and proper scaling
|
||||||
|
|
||||||
|
### ⚙️ Settings and Persistence
|
||||||
|
- **Configuration Management**: Theme and preference persistence across sessions
|
||||||
|
- **Tabbed Settings Interface**: Organized categories for easy navigation
|
||||||
|
- **Live Preview**: Real-time theme preview in settings
|
||||||
|
- **Error Recovery**: Robust handling of corrupted settings with defaults
|
||||||
|
|
||||||
## Deployment and Distribution
|
## Deployment and Distribution
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Keyboard Shortcuts
|
||||||
|
|
||||||
|
TheChart application supports comprehensive keyboard shortcuts for improved productivity and efficient navigation.
|
||||||
|
|
||||||
|
## File Operations
|
||||||
|
- **Ctrl+S**: Save/Add new entry - Saves the current entry data to the database
|
||||||
|
- **Ctrl+Q**: Quit application - Exits the application (with confirmation dialog)
|
||||||
|
- **Ctrl+E**: Export data - Opens the export dialog window
|
||||||
|
|
||||||
|
## Data Management
|
||||||
|
- **Ctrl+N**: Clear entries - Clears all input fields to start a new entry
|
||||||
|
- **Ctrl+R** or **F5**: Refresh data - Reloads data from the CSV file and updates the display
|
||||||
|
|
||||||
|
## Window Management
|
||||||
|
- **Ctrl+M**: Manage medicines - Opens the medicine management window
|
||||||
|
- **Ctrl+P**: Manage pathologies - Opens the pathology management window
|
||||||
|
|
||||||
|
## Table Operations
|
||||||
|
- **Delete**: Delete selected entry - Deletes the currently selected entry in the table (with confirmation)
|
||||||
|
- **Escape**: Clear selection - Clears the current selection in the table
|
||||||
|
- **Double-click**: Edit entry - Opens the edit dialog for the selected entry
|
||||||
|
|
||||||
|
## Help
|
||||||
|
- **F1**: Show keyboard shortcuts help - Displays a dialog with all available keyboard shortcuts
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Menu Integration
|
||||||
|
All keyboard shortcuts are displayed in the menu bar next to their corresponding menu items for easy reference.
|
||||||
|
|
||||||
|
### Button Labels
|
||||||
|
Primary action buttons show their keyboard shortcuts in the button text (e.g., "Add Entry (Ctrl+S)").
|
||||||
|
|
||||||
|
### Case Sensitivity
|
||||||
|
- Shortcuts are case-insensitive
|
||||||
|
- Both `Ctrl+S` and `Ctrl+Shift+S` work
|
||||||
|
- Uppercase and lowercase variants are supported
|
||||||
|
|
||||||
|
### Focus Requirements
|
||||||
|
- Keyboard shortcuts work when the main window has focus
|
||||||
|
- Focus is automatically set to the main window on startup
|
||||||
|
- Shortcuts work across all tabs and interface elements
|
||||||
|
|
||||||
|
### Feedback System
|
||||||
|
- All operations provide feedback through the status bar
|
||||||
|
- Success and error messages are displayed
|
||||||
|
- Confirmation dialogs are shown for destructive operations (quit, delete)
|
||||||
|
|
||||||
|
## Usage Tips
|
||||||
|
|
||||||
|
### Quick Workflow
|
||||||
|
1. **Ctrl+N** - Clear fields for new entry
|
||||||
|
2. Enter data in the form
|
||||||
|
3. **Ctrl+S** - Save the entry
|
||||||
|
4. **F5** - Refresh to see updated data
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
- Use **Ctrl+M** and **Ctrl+P** to quickly access management windows
|
||||||
|
- Use **Delete** to remove unwanted entries from the table
|
||||||
|
- Use **Escape** to clear selections when needed
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
- Press **F1** anytime to see the keyboard shortcuts help dialog
|
||||||
|
- All shortcuts are also visible in the menu bar
|
||||||
|
- Button tooltips show additional keyboard shortcut information
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
- Keyboard shortcuts provide full application functionality without mouse use
|
||||||
|
- All critical operations have keyboard equivalents
|
||||||
|
- Shortcuts follow standard application conventions (Ctrl+S for save, Ctrl+Q for quit)
|
||||||
|
- Help system is easily accessible via F1
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# Menu Theming Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
TheChart application now supports full menu theming that integrates seamlessly with the application's theme system. All menus (File, Tools, Theme, Help) will automatically adopt colors that match the selected application theme.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Automatic Theme Integration
|
||||||
|
- Menus automatically inherit colors from the current application theme
|
||||||
|
- Background colors are slightly adjusted to provide subtle visual distinction
|
||||||
|
- Hover effects use the theme's accent colors for consistency
|
||||||
|
|
||||||
|
### Supported Menu Elements
|
||||||
|
- Main menu bar
|
||||||
|
- All dropdown menus (File, Tools, Theme, Help)
|
||||||
|
- Menu items and separators
|
||||||
|
- Hover/active states
|
||||||
|
- Disabled menu items
|
||||||
|
|
||||||
|
### Theme Colors Applied
|
||||||
|
|
||||||
|
For each theme, the following color properties are applied to menus:
|
||||||
|
|
||||||
|
- **Background**: Slightly darker/lighter than the main theme background
|
||||||
|
- **Foreground**: Uses the theme's text color
|
||||||
|
- **Active Background**: Uses the theme's selection/accent color
|
||||||
|
- **Active Foreground**: Uses the theme's selection text color
|
||||||
|
- **Disabled Foreground**: Grayed out color for disabled items
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### ThemeManager Methods
|
||||||
|
|
||||||
|
#### `get_menu_colors() -> dict[str, str]`
|
||||||
|
Returns a dictionary of colors specifically optimized for menu theming:
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"bg": "#edeeef", # Menu background
|
||||||
|
"fg": "#5c616c", # Menu text
|
||||||
|
"active_bg": "#0078d4", # Hover background
|
||||||
|
"active_fg": "#ffffff", # Hover text
|
||||||
|
"disabled_fg": "#888888" # Disabled text
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `configure_menu(menu: tk.Menu) -> None`
|
||||||
|
Applies theme colors to a specific menu widget:
|
||||||
|
```python
|
||||||
|
theme_manager.configure_menu(menubar)
|
||||||
|
theme_manager.configure_menu(file_menu)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Updates
|
||||||
|
|
||||||
|
When themes are changed using the Theme menu:
|
||||||
|
1. The new theme is applied to all UI components
|
||||||
|
2. The menu setup is refreshed (`_setup_menu()` is called)
|
||||||
|
3. All menus are automatically re-themed with the new colors
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Create menu
|
||||||
|
menubar = tk.Menu(root)
|
||||||
|
file_menu = tk.Menu(menubar, tearoff=0)
|
||||||
|
|
||||||
|
# Apply theming
|
||||||
|
theme_manager.configure_menu(menubar)
|
||||||
|
theme_manager.configure_menu(file_menu)
|
||||||
|
|
||||||
|
# Menus will now match the current theme
|
||||||
|
```
|
||||||
|
|
||||||
|
## Color Calculation
|
||||||
|
|
||||||
|
The menu background color is automatically calculated based on the main theme:
|
||||||
|
|
||||||
|
- **Light themes**: Menu background is made slightly darker than the main background
|
||||||
|
- **Dark themes**: Menu background is made slightly lighter than the main background
|
||||||
|
|
||||||
|
This provides subtle visual distinction while maintaining theme consistency.
|
||||||
|
|
||||||
|
## Supported Themes
|
||||||
|
|
||||||
|
Menu theming works with all available themes:
|
||||||
|
- arc
|
||||||
|
- equilux
|
||||||
|
- adapta
|
||||||
|
- yaru
|
||||||
|
- ubuntu
|
||||||
|
- plastik
|
||||||
|
- breeze
|
||||||
|
- elegance
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
A test script is available to verify menu theming functionality:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/will/Code/thechart
|
||||||
|
.venv/bin/python scripts/test_menu_theming.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This script creates a test window with menus that can be used to verify theming across different themes.
|
||||||
+52
-22
@@ -1,16 +1,27 @@
|
|||||||
# TheChart Documentation
|
# TheChart Documentation
|
||||||
|
|
||||||
Welcome to TheChart documentation! This guide will help you navigate the available documentation.
|
Welcome to TheChart documentation! This guide will help you navigate the available documentation for the modern medication tracking application.
|
||||||
|
|
||||||
## 📖 Documentation Index
|
## 📖 Documentation Index
|
||||||
|
|
||||||
### For Users
|
### For Users
|
||||||
- **[README.md](../README.md)** - Quick start guide and installation
|
- **[README.md](../README.md)** - Quick start guide and installation
|
||||||
- **[Features Guide](FEATURES.md)** - Complete feature documentation
|
- **[Features Guide](FEATURES.md)** - Complete feature documentation including new UI/UX improvements
|
||||||
|
- Modern Theme System (8 Professional Themes)
|
||||||
|
- Advanced Keyboard Shortcuts
|
||||||
|
- Smart Tooltip System
|
||||||
- Modular Medicine System
|
- Modular Medicine System
|
||||||
- Advanced Dose Tracking
|
- Advanced Dose Tracking
|
||||||
- Graph Visualizations
|
- Graph Visualizations
|
||||||
- Data Management
|
- Data Management
|
||||||
|
- **[Keyboard Shortcuts](KEYBOARD_SHORTCUTS.md)** - Comprehensive shortcut reference
|
||||||
|
- File operations shortcuts (Ctrl+S, Ctrl+Q, Ctrl+E)
|
||||||
|
- Data management shortcuts (Ctrl+N, Ctrl+R, F5)
|
||||||
|
- Navigation shortcuts (Ctrl+M, Ctrl+P, F1, F2)
|
||||||
|
- **[Export System](EXPORT_SYSTEM.md)** - Data export functionality and formats
|
||||||
|
- JSON, XML, and PDF export options
|
||||||
|
- Graph visualization inclusion
|
||||||
|
- Export manager architecture
|
||||||
|
|
||||||
### For Developers
|
### For Developers
|
||||||
- **[Development Guide](DEVELOPMENT.md)** - Development setup and testing
|
- **[Development Guide](DEVELOPMENT.md)** - Development setup and testing
|
||||||
@@ -21,54 +32,73 @@ Welcome to TheChart documentation! This guide will help you navigate the availab
|
|||||||
|
|
||||||
### Project History
|
### Project History
|
||||||
- **[Changelog](CHANGELOG.md)** - Version history and feature evolution
|
- **[Changelog](CHANGELOG.md)** - Version history and feature evolution
|
||||||
- Recent updates and improvements
|
- Recent UI/UX overhaul (v1.9.5)
|
||||||
- Migration notes
|
- Keyboard shortcuts system (v1.7.0)
|
||||||
- Future roadmap
|
- Medicine and dose tracking improvements
|
||||||
|
- Migration notes and future roadmap
|
||||||
|
|
||||||
## 🚀 Quick Navigation
|
## 🚀 Quick Navigation
|
||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
1. **Installation**: See [README.md - Installation](../README.md#installation)
|
1. **Installation**: See [README.md - Installation](../README.md#installation)
|
||||||
2. **First Run**: See [README.md - Running the Application](../README.md#running-the-application)
|
2. **First Run**: See [README.md - Running the Application](../README.md#running-the-application)
|
||||||
3. **Key Features**: See [FEATURES.md](FEATURES.md)
|
3. **UI/UX Features**: See [FEATURES.md - Modern UI/UX System](FEATURES.md#-modern-uiux-system-new-in-v195)
|
||||||
|
|
||||||
|
### Using the Application
|
||||||
|
1. **Theme Selection**: See [FEATURES.md - Settings and Theme Management](FEATURES.md#️-settings-and-theme-management)
|
||||||
|
2. **Keyboard Shortcuts**: See [KEYBOARD_SHORTCUTS.md](KEYBOARD_SHORTCUTS.md)
|
||||||
|
3. **Medicine Management**: See [FEATURES.md - Modular Medicine System](FEATURES.md#-modular-medicine-system)
|
||||||
|
4. **Dose Tracking**: See [FEATURES.md - Advanced Dose Tracking](FEATURES.md#-advanced-dose-tracking)
|
||||||
|
5. **Data Export**: See [EXPORT_SYSTEM.md](EXPORT_SYSTEM.md)
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
1. **Setup**: See [DEVELOPMENT.md - Development Environment Setup](DEVELOPMENT.md#development-environment-setup)
|
1. **Setup**: See [DEVELOPMENT.md - Development Environment Setup](DEVELOPMENT.md#development-environment-setup)
|
||||||
2. **Testing**: See [DEVELOPMENT.md - Testing Framework](DEVELOPMENT.md#testing-framework)
|
2. **Testing**: See [TESTING.md](TESTING.md) - Comprehensive testing guide
|
||||||
3. **Contributing**: See [DEVELOPMENT.md - Development Workflow](DEVELOPMENT.md#development-workflow)
|
3. **Architecture**: See [FEATURES.md - Technical Architecture](FEATURES.md#technical-architecture)
|
||||||
|
4. **Contributing**: See [DEVELOPMENT.md - Development Workflow](DEVELOPMENT.md#development-workflow)
|
||||||
|
|
||||||
### Advanced Usage
|
## 📋 What's New in Documentation
|
||||||
1. **Medicine Management**: See [FEATURES.md - Modular Medicine System](FEATURES.md#-modular-medicine-system)
|
|
||||||
2. **Dose Tracking**: See [FEATURES.md - Advanced Dose Tracking](FEATURES.md#-advanced-dose-tracking)
|
|
||||||
3. **Visualizations**: See [FEATURES.md - Enhanced Graph Visualization](FEATURES.md#-enhanced-graph-visualization)
|
|
||||||
|
|
||||||
## 📋 Documentation Standards
|
### Recent Updates (v1.9.5)
|
||||||
|
- **Consolidated Structure**: Merged UI improvements into main features documentation
|
||||||
|
- **Enhanced Features Guide**: Added comprehensive UI/UX documentation
|
||||||
|
- **Updated Changelog**: Detailed UI/UX overhaul documentation
|
||||||
|
- **Improved Navigation**: Better cross-referencing between documents
|
||||||
|
|
||||||
All documentation follows these principles:
|
### Documentation Highlights
|
||||||
- **Clear Structure**: Hierarchical organization with clear headings
|
- **Professional UI/UX**: Complete documentation of the new theme system
|
||||||
- **Practical Examples**: Code snippets and usage examples
|
- **Keyboard Efficiency**: Comprehensive shortcut system documentation
|
||||||
- **Up-to-date**: Synchronized with current codebase
|
- **Developer-Friendly**: Enhanced development and testing documentation
|
||||||
- **Comprehensive**: Covers all major features and workflows
|
- **User-Focused**: Clear separation of user vs developer documentation
|
||||||
- **Cross-referenced**: Links between related sections
|
|
||||||
|
|
||||||
## 🔍 Finding Information
|
## 🔍 Finding Information
|
||||||
|
|
||||||
### By Topic
|
### By Topic
|
||||||
- **Installation & Setup** → [README.md](../README.md)
|
- **Installation & Setup** → [README.md](../README.md)
|
||||||
|
- **UI/UX and Themes** → [FEATURES.md - Modern UI/UX System](FEATURES.md#-modern-uiux-system-new-in-v195)
|
||||||
- **Feature Usage** → [FEATURES.md](FEATURES.md)
|
- **Feature Usage** → [FEATURES.md](FEATURES.md)
|
||||||
|
- **Keyboard Shortcuts** → [KEYBOARD_SHORTCUTS.md](KEYBOARD_SHORTCUTS.md)
|
||||||
|
- **Menu Theming** → [MENU_THEMING.md](MENU_THEMING.md)
|
||||||
|
- **Testing** → [TESTING.md](TESTING.md)
|
||||||
|
- **Data Export** → [EXPORT_SYSTEM.md](EXPORT_SYSTEM.md)
|
||||||
- **Development** → [DEVELOPMENT.md](DEVELOPMENT.md)
|
- **Development** → [DEVELOPMENT.md](DEVELOPMENT.md)
|
||||||
- **Version History** → [CHANGELOG.md](CHANGELOG.md)
|
- **Version History** → [CHANGELOG.md](CHANGELOG.md)
|
||||||
|
|
||||||
### By User Type
|
### By User Type
|
||||||
- **End Users** → Start with [README.md](../README.md), then [FEATURES.md](FEATURES.md)
|
- **End Users** → Start with [README.md](../README.md), then [FEATURES.md](FEATURES.md)
|
||||||
- **Developers** → [DEVELOPMENT.md](DEVELOPMENT.md) and [CHANGELOG.md](CHANGELOG.md)
|
- **Power Users** → [KEYBOARD_SHORTCUTS.md](KEYBOARD_SHORTCUTS.md) and [EXPORT_SYSTEM.md](EXPORT_SYSTEM.md)
|
||||||
- **Contributors** → All documentation, especially [DEVELOPMENT.md](DEVELOPMENT.md)
|
- **Developers** → [DEVELOPMENT.md](DEVELOPMENT.md), [TESTING.md](TESTING.md), and [FEATURES.md - Technical Architecture](FEATURES.md#technical-architecture)
|
||||||
|
- **Contributors** → All documentation, especially [DEVELOPMENT.md](DEVELOPMENT.md) and [TESTING.md](TESTING.md)
|
||||||
|
|
||||||
### By Task
|
### By Task
|
||||||
- **Install TheChart** → [README.md - Installation](../README.md#installation)
|
- **Install TheChart** → [README.md - Installation](../README.md#installation)
|
||||||
|
- **Change Theme** → [FEATURES.md - Settings and Theme Management](FEATURES.md#️-settings-and-theme-management)
|
||||||
|
- **Learn Shortcuts** → [KEYBOARD_SHORTCUTS.md](KEYBOARD_SHORTCUTS.md)
|
||||||
- **Add New Medicine** → [FEATURES.md - Modular Medicine System](FEATURES.md#-modular-medicine-system)
|
- **Add New Medicine** → [FEATURES.md - Modular Medicine System](FEATURES.md#-modular-medicine-system)
|
||||||
- **Track Doses** → [FEATURES.md - Advanced Dose Tracking](FEATURES.md#-advanced-dose-tracking)
|
- **Track Doses** → [FEATURES.md - Advanced Dose Tracking](FEATURES.md#-advanced-dose-tracking)
|
||||||
- **Run Tests** → [DEVELOPMENT.md - Testing Framework](DEVELOPMENT.md#testing-framework)
|
- **Export Data** → [EXPORT_SYSTEM.md](EXPORT_SYSTEM.md)
|
||||||
|
- **Run Tests** → [TESTING.md](TESTING.md) - Comprehensive testing guide
|
||||||
|
- **Debug Issues** → [TESTING.md - Troubleshooting](TESTING.md#troubleshooting)
|
||||||
- **Deploy Application** → [README.md - Deployment](../README.md#deployment)
|
- **Deploy Application** → [README.md - Deployment](../README.md#deployment)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+296
@@ -0,0 +1,296 @@
|
|||||||
|
# Testing Guide
|
||||||
|
|
||||||
|
This document provides a comprehensive guide to testing in TheChart application.
|
||||||
|
|
||||||
|
## Test Organization
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
thechart/
|
||||||
|
├── tests/ # Unit tests (pytest)
|
||||||
|
│ ├── test_theme_manager.py
|
||||||
|
│ ├── test_data_manager.py
|
||||||
|
│ ├── test_ui_manager.py
|
||||||
|
│ ├── test_graph_manager.py
|
||||||
|
│ └── ...
|
||||||
|
├── scripts/ # Integration tests & demos
|
||||||
|
│ ├── integration_test.py
|
||||||
|
│ ├── test_menu_theming.py
|
||||||
|
│ ├── test_note_saving.py
|
||||||
|
│ └── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Categories
|
||||||
|
|
||||||
|
### 1. Unit Tests (`/tests/`)
|
||||||
|
|
||||||
|
**Purpose**: Test individual components in isolation
|
||||||
|
**Framework**: pytest
|
||||||
|
**Location**: `/tests/` directory
|
||||||
|
|
||||||
|
#### Running Unit Tests
|
||||||
|
```bash
|
||||||
|
cd /home/will/Code/thechart
|
||||||
|
source .venv/bin/activate.fish
|
||||||
|
python -m pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Available Unit Tests
|
||||||
|
- `test_theme_manager.py` - Theme system and menu theming
|
||||||
|
- `test_data_manager.py` - Data persistence and CSV operations
|
||||||
|
- `test_ui_manager.py` - UI component functionality
|
||||||
|
- `test_graph_manager.py` - Graph generation and display
|
||||||
|
- `test_constants.py` - Application constants
|
||||||
|
- `test_logger.py` - Logging system
|
||||||
|
- `test_main.py` - Main application logic
|
||||||
|
|
||||||
|
#### Writing Unit Tests
|
||||||
|
```python
|
||||||
|
# Example unit test structure
|
||||||
|
import unittest
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Add src to path
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||||
|
|
||||||
|
from your_module import YourClass
|
||||||
|
|
||||||
|
class TestYourClass(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up test fixtures."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Clean up after tests."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_functionality(self):
|
||||||
|
"""Test specific functionality."""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Integration Tests (`/scripts/`)
|
||||||
|
|
||||||
|
**Purpose**: Test complete workflows and system interactions
|
||||||
|
**Framework**: Custom test scripts
|
||||||
|
**Location**: `/scripts/` directory
|
||||||
|
|
||||||
|
#### Available Integration Tests
|
||||||
|
|
||||||
|
##### `integration_test.py`
|
||||||
|
Comprehensive export system test:
|
||||||
|
- Tests JSON, XML, PDF export formats
|
||||||
|
- Validates data integrity
|
||||||
|
- Tests file creation and cleanup
|
||||||
|
- No GUI dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python scripts/integration_test.py
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `test_note_saving.py`
|
||||||
|
Note persistence functionality:
|
||||||
|
- Tests note saving to CSV
|
||||||
|
- Validates special character handling
|
||||||
|
- Tests note retrieval
|
||||||
|
|
||||||
|
##### `test_update_entry.py`
|
||||||
|
Entry modification functionality:
|
||||||
|
- Tests data update operations
|
||||||
|
- Validates date handling
|
||||||
|
- Tests duplicate prevention
|
||||||
|
|
||||||
|
##### `test_keyboard_shortcuts.py`
|
||||||
|
Keyboard shortcut system:
|
||||||
|
- Tests key binding functionality
|
||||||
|
- Validates shortcut responses
|
||||||
|
- Tests keyboard event handling
|
||||||
|
|
||||||
|
### 3. Interactive Demonstrations (`/scripts/`)
|
||||||
|
|
||||||
|
**Purpose**: Visual and interactive testing of UI features
|
||||||
|
**Framework**: tkinter-based demos
|
||||||
|
|
||||||
|
##### `test_menu_theming.py`
|
||||||
|
Interactive menu theming demonstration:
|
||||||
|
- Live theme switching
|
||||||
|
- Visual color display
|
||||||
|
- Real-time menu updates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/python scripts/test_menu_theming.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
### Complete Test Suite
|
||||||
|
```bash
|
||||||
|
cd /home/will/Code/thechart
|
||||||
|
source .venv/bin/activate.fish
|
||||||
|
|
||||||
|
# Run unit tests
|
||||||
|
python -m pytest tests/ -v
|
||||||
|
|
||||||
|
# Run integration tests
|
||||||
|
python scripts/integration_test.py
|
||||||
|
|
||||||
|
# Run specific feature tests
|
||||||
|
python scripts/test_note_saving.py
|
||||||
|
python scripts/test_update_entry.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Individual Test Categories
|
||||||
|
```bash
|
||||||
|
# Unit tests only
|
||||||
|
python -m pytest tests/
|
||||||
|
|
||||||
|
# Specific unit test file
|
||||||
|
python -m pytest tests/test_theme_manager.py -v
|
||||||
|
|
||||||
|
# Integration test
|
||||||
|
python scripts/integration_test.py
|
||||||
|
|
||||||
|
# Interactive demo
|
||||||
|
python scripts/test_menu_theming.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Runner Script
|
||||||
|
```bash
|
||||||
|
# Use the main test runner
|
||||||
|
python scripts/run_tests.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Environment Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
1. **Virtual Environment**: Ensure `.venv` is activated
|
||||||
|
2. **Dependencies**: All requirements installed via `uv`
|
||||||
|
3. **Test Data**: Main `thechart_data.csv` file present
|
||||||
|
|
||||||
|
### Environment Activation
|
||||||
|
```bash
|
||||||
|
# Fish shell
|
||||||
|
source .venv/bin/activate.fish
|
||||||
|
|
||||||
|
# Bash/Zsh
|
||||||
|
source .venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing New Tests
|
||||||
|
|
||||||
|
### Unit Test Guidelines
|
||||||
|
1. Place in `/tests/` directory
|
||||||
|
2. Use pytest framework
|
||||||
|
3. Follow naming convention: `test_<module_name>.py`
|
||||||
|
4. Include setup/teardown for fixtures
|
||||||
|
5. Test edge cases and error conditions
|
||||||
|
|
||||||
|
### Integration Test Guidelines
|
||||||
|
1. Place in `/scripts/` directory
|
||||||
|
2. Test complete workflows
|
||||||
|
3. Include cleanup procedures
|
||||||
|
4. Document expected behavior
|
||||||
|
5. Handle GUI dependencies appropriately
|
||||||
|
|
||||||
|
### Interactive Demo Guidelines
|
||||||
|
1. Place in `/scripts/` directory
|
||||||
|
2. Include clear instructions
|
||||||
|
3. Provide visual feedback
|
||||||
|
4. Allow easy theme/feature switching
|
||||||
|
5. Include exit mechanisms
|
||||||
|
|
||||||
|
## Test Data Management
|
||||||
|
|
||||||
|
### Test File Creation
|
||||||
|
- Use `tempfile` module for temporary files
|
||||||
|
- Clean up created files in teardown
|
||||||
|
- Don't commit test data to repository
|
||||||
|
|
||||||
|
### CSV Test Data
|
||||||
|
- Most tests use main `thechart_data.csv`
|
||||||
|
- Some tests create temporary CSV files
|
||||||
|
- Integration tests may create export directories
|
||||||
|
|
||||||
|
## Continuous Integration
|
||||||
|
|
||||||
|
### Local Testing Workflow
|
||||||
|
```bash
|
||||||
|
# 1. Run linting
|
||||||
|
python -m flake8 src/ tests/ scripts/
|
||||||
|
|
||||||
|
# 2. Run unit tests
|
||||||
|
python -m pytest tests/ -v
|
||||||
|
|
||||||
|
# 3. Run integration tests
|
||||||
|
python scripts/integration_test.py
|
||||||
|
|
||||||
|
# 4. Run specific feature tests as needed
|
||||||
|
python scripts/test_note_saving.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pre-commit Checklist
|
||||||
|
- [ ] All unit tests pass
|
||||||
|
- [ ] Integration tests pass
|
||||||
|
- [ ] New functionality has tests
|
||||||
|
- [ ] Documentation updated
|
||||||
|
- [ ] Code follows style guidelines
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### Import Errors
|
||||||
|
```python
|
||||||
|
# Ensure src is in path
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GUI Test Issues
|
||||||
|
- Use `root.withdraw()` to hide test windows
|
||||||
|
- Ensure proper cleanup with `root.destroy()`
|
||||||
|
- Consider mocking GUI components for unit tests
|
||||||
|
|
||||||
|
#### File Permission Issues
|
||||||
|
- Ensure test has write permissions
|
||||||
|
- Use temporary directories for test files
|
||||||
|
- Clean up files in teardown methods
|
||||||
|
|
||||||
|
### Debug Mode
|
||||||
|
```bash
|
||||||
|
# Run with debug logging
|
||||||
|
python -c "import logging; logging.basicConfig(level=logging.DEBUG)" scripts/test_script.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Coverage
|
||||||
|
|
||||||
|
### Current Coverage Areas
|
||||||
|
- ✅ Theme management and menu theming
|
||||||
|
- ✅ Data persistence and CSV operations
|
||||||
|
- ✅ Export functionality (JSON, XML, PDF)
|
||||||
|
- ✅ UI component initialization
|
||||||
|
- ✅ Graph generation
|
||||||
|
- ✅ Note saving and retrieval
|
||||||
|
- ✅ Entry update operations
|
||||||
|
- ✅ Keyboard shortcuts
|
||||||
|
|
||||||
|
### Areas for Expansion
|
||||||
|
- Medicine and pathology management
|
||||||
|
- Settings persistence
|
||||||
|
- Error handling edge cases
|
||||||
|
- Performance testing
|
||||||
|
- UI interaction testing
|
||||||
|
|
||||||
|
## Contributing Tests
|
||||||
|
|
||||||
|
When contributing new tests:
|
||||||
|
|
||||||
|
1. **Choose the right category**: Unit vs Integration vs Demo
|
||||||
|
2. **Follow naming conventions**: Clear, descriptive names
|
||||||
|
3. **Include documentation**: Docstrings and comments
|
||||||
|
4. **Test edge cases**: Not just happy path
|
||||||
|
5. **Clean up resources**: Temporary files, windows, etc.
|
||||||
|
6. **Update documentation**: Add to this guide and scripts/README.md
|
||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "thechart"
|
name = "thechart"
|
||||||
version = "1.8.5"
|
version = "1.9.5"
|
||||||
description = "Chart to monitor your medication intake over time."
|
description = "Chart to monitor your medication intake over time."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
@@ -12,6 +12,7 @@ dependencies = [
|
|||||||
"pandas>=2.3.1",
|
"pandas>=2.3.1",
|
||||||
"reportlab>=4.4.3",
|
"reportlab>=4.4.3",
|
||||||
"tk>=0.1.0",
|
"tk>=0.1.0",
|
||||||
|
"ttkthemes>=3.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ matplotlib
|
|||||||
pandas
|
pandas
|
||||||
dotenv
|
dotenv
|
||||||
colorlog
|
colorlog
|
||||||
|
ttkthemes
|
||||||
|
|||||||
+5
-1
@@ -24,7 +24,9 @@ packaging==25.0
|
|||||||
pandas==2.3.1
|
pandas==2.3.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
pillow==11.3.0
|
pillow==11.3.0
|
||||||
# via matplotlib
|
# via
|
||||||
|
# matplotlib
|
||||||
|
# ttkthemes
|
||||||
pyparsing==3.2.3
|
pyparsing==3.2.3
|
||||||
# via matplotlib
|
# via matplotlib
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
@@ -39,5 +41,7 @@ six==1.17.0
|
|||||||
# via python-dateutil
|
# via python-dateutil
|
||||||
tk==0.1.0
|
tk==0.1.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
ttkthemes==3.2.2
|
||||||
|
# via -r requirements.in
|
||||||
tzdata==2025.2
|
tzdata==2025.2
|
||||||
# via pandas
|
# via pandas
|
||||||
|
|||||||
+53
-4
@@ -1,6 +1,6 @@
|
|||||||
# TheChart Scripts Directory
|
# TheChart Scripts Directory
|
||||||
|
|
||||||
This directory contains testing and utility scripts for TheChart application.
|
This directory contains interactive demonstrations and utility scripts for TheChart application.
|
||||||
|
|
||||||
## Scripts Overview
|
## Scripts Overview
|
||||||
|
|
||||||
@@ -36,15 +36,62 @@ Tests entry update functionality.
|
|||||||
- Validates data modification operations
|
- Validates data modification operations
|
||||||
- Tests date validation and duplicate handling
|
- Tests date validation and duplicate handling
|
||||||
|
|
||||||
## Usage
|
#### `test_keyboard_shortcuts.py`
|
||||||
|
Tests keyboard shortcut functionality.
|
||||||
|
- Validates keyboard event handling
|
||||||
|
- Tests shortcut combinations and responses
|
||||||
|
|
||||||
All scripts should be run from the project root directory:
|
### Interactive Demonstrations
|
||||||
|
|
||||||
|
#### `test_menu_theming.py`
|
||||||
|
Interactive demonstration of menu theming functionality.
|
||||||
|
- Live theme switching demonstration
|
||||||
|
- Visual display of theme colors
|
||||||
|
- Real-time menu color updates
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/will/Code/thechart
|
cd /home/will/Code/thechart
|
||||||
.venv/bin/python scripts/<script_name>.py
|
.venv/bin/python scripts/test_menu_theming.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
All scripts should be run from the project root directory using the virtual environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/will/Code/thechart
|
||||||
|
source .venv/bin/activate.fish # For fish shell
|
||||||
|
# OR
|
||||||
|
source .venv/bin/activate # For bash/zsh
|
||||||
|
|
||||||
|
python scripts/<script_name>.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Organization
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
Located in `/tests/` directory:
|
||||||
|
- `test_theme_manager.py` - Theme manager functionality tests
|
||||||
|
- `test_data_manager.py` - Data management tests
|
||||||
|
- `test_ui_manager.py` - UI component tests
|
||||||
|
- `test_graph_manager.py` - Graph functionality tests
|
||||||
|
- And more...
|
||||||
|
|
||||||
|
Run unit tests with:
|
||||||
|
```bash
|
||||||
|
cd /home/will/Code/thechart
|
||||||
|
.venv/bin/python -m pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
Located in `/scripts/` directory:
|
||||||
|
- `integration_test.py` - Export system integration test
|
||||||
|
- Feature-specific test scripts
|
||||||
|
|
||||||
|
### Interactive Demos
|
||||||
|
Located in `/scripts/` directory:
|
||||||
|
- `test_menu_theming.py` - Menu theming demonstration
|
||||||
|
|
||||||
## Test Data
|
## Test Data
|
||||||
|
|
||||||
- Integration tests create temporary export files in `integration_test_exports/` (auto-cleaned)
|
- Integration tests create temporary export files in `integration_test_exports/` (auto-cleaned)
|
||||||
@@ -59,3 +106,5 @@ When adding new scripts:
|
|||||||
3. Add proper docstrings and error handling
|
3. Add proper docstrings and error handling
|
||||||
4. Update this README with script documentation
|
4. Update this README with script documentation
|
||||||
5. Follow the project's linting and formatting standards
|
5. Follow the project's linting and formatting standards
|
||||||
|
6. For unit tests, place them in `/tests/` directory
|
||||||
|
7. For integration tests or demos, place them in `/scripts/` directory
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script for keyboard shortcuts functionality.
|
||||||
|
This script tests that the keyboard shortcuts are properly bound.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
# Add the src directory to the path so we can import the main module
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
from main import MedTrackerApp
|
||||||
|
|
||||||
|
|
||||||
|
def test_keyboard_shortcuts():
|
||||||
|
"""Test that keyboard shortcuts are properly bound."""
|
||||||
|
print("Testing keyboard shortcuts...")
|
||||||
|
|
||||||
|
# Create a test window
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw() # Hide the window for testing
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create the app instance
|
||||||
|
app = MedTrackerApp(root)
|
||||||
|
|
||||||
|
# Test that the shortcuts are bound
|
||||||
|
expected_shortcuts = [
|
||||||
|
"<Control-s>",
|
||||||
|
"<Control-S>",
|
||||||
|
"<Control-q>",
|
||||||
|
"<Control-Q>",
|
||||||
|
"<Control-e>",
|
||||||
|
"<Control-E>",
|
||||||
|
"<Control-n>",
|
||||||
|
"<Control-N>",
|
||||||
|
"<Control-r>",
|
||||||
|
"<Control-R>",
|
||||||
|
"<F5>",
|
||||||
|
"<Control-m>",
|
||||||
|
"<Control-M>",
|
||||||
|
"<Control-p>",
|
||||||
|
"<Control-P>",
|
||||||
|
"<Delete>",
|
||||||
|
"<Escape>",
|
||||||
|
"<F1>",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Check if shortcuts are bound
|
||||||
|
bound_shortcuts = []
|
||||||
|
for shortcut in expected_shortcuts:
|
||||||
|
if root.bind(shortcut):
|
||||||
|
bound_shortcuts.append(shortcut)
|
||||||
|
|
||||||
|
print(f"Successfully bound {len(bound_shortcuts)} keyboard shortcuts:")
|
||||||
|
for shortcut in bound_shortcuts:
|
||||||
|
print(f" ✓ {shortcut}")
|
||||||
|
|
||||||
|
# Test that methods exist
|
||||||
|
methods_to_test = [
|
||||||
|
"add_new_entry",
|
||||||
|
"handle_window_closing",
|
||||||
|
"_open_export_window",
|
||||||
|
"_clear_entries",
|
||||||
|
"refresh_data_display",
|
||||||
|
"_open_medicine_manager",
|
||||||
|
"_open_pathology_manager",
|
||||||
|
"_delete_selected_entry",
|
||||||
|
"_clear_selection",
|
||||||
|
"_show_keyboard_shortcuts",
|
||||||
|
]
|
||||||
|
|
||||||
|
for method_name in methods_to_test:
|
||||||
|
if hasattr(app, method_name):
|
||||||
|
print(f" ✓ Method {method_name} exists")
|
||||||
|
else:
|
||||||
|
print(f" ✗ Method {method_name} missing")
|
||||||
|
|
||||||
|
print("\n✅ Keyboard shortcuts test completed successfully!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error during testing: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
root.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = test_keyboard_shortcuts()
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Interactive demonstration of menu theming functionality."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
# Add the src directory to the path so we can import the modules
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../src"))
|
||||||
|
|
||||||
|
from theme_manager import ThemeManager
|
||||||
|
|
||||||
|
|
||||||
|
def demo_menu_theming():
|
||||||
|
"""Interactive demonstration of menu theming with different themes."""
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
print("Menu Theming Interactive Demo")
|
||||||
|
print("=============================")
|
||||||
|
|
||||||
|
# Create root window
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("Menu Theming Demo - TheChart")
|
||||||
|
root.geometry("500x400")
|
||||||
|
|
||||||
|
# Initialize theme manager
|
||||||
|
theme_manager = ThemeManager(root, logger)
|
||||||
|
|
||||||
|
# Create demo menubar using the new convenience method
|
||||||
|
menubar = theme_manager.create_themed_menu(root)
|
||||||
|
root.config(menu=menubar)
|
||||||
|
|
||||||
|
# Create submenus
|
||||||
|
file_menu = theme_manager.create_themed_menu(menubar, tearoff=0)
|
||||||
|
theme_menu = theme_manager.create_themed_menu(menubar, tearoff=0)
|
||||||
|
help_menu = theme_manager.create_themed_menu(menubar, tearoff=0)
|
||||||
|
|
||||||
|
menubar.add_cascade(label="File", menu=file_menu)
|
||||||
|
menubar.add_cascade(label="Theme", menu=theme_menu)
|
||||||
|
menubar.add_cascade(label="Help", menu=help_menu)
|
||||||
|
|
||||||
|
# Populate file menu
|
||||||
|
file_menu.add_command(label="Demo Item 1")
|
||||||
|
file_menu.add_command(label="Demo Item 2")
|
||||||
|
file_menu.add_separator()
|
||||||
|
file_menu.add_command(label="Exit Demo", command=root.quit)
|
||||||
|
|
||||||
|
# Populate help menu
|
||||||
|
help_menu.add_command(
|
||||||
|
label="About Demo",
|
||||||
|
command=lambda: tk.messagebox.showinfo(
|
||||||
|
"About", "Interactive menu theming demonstration for TheChart"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Theme information display
|
||||||
|
theme_info_frame = tk.Frame(root, relief="ridge", bd=2)
|
||||||
|
theme_info_frame.pack(fill="x", padx=20, pady=10)
|
||||||
|
|
||||||
|
current_theme_label = tk.Label(
|
||||||
|
theme_info_frame,
|
||||||
|
text=f"Current Theme: {theme_manager.get_current_theme().title()}",
|
||||||
|
font=("Arial", 12, "bold"),
|
||||||
|
)
|
||||||
|
current_theme_label.pack(pady=5)
|
||||||
|
|
||||||
|
colors_display = tk.Text(theme_info_frame, height=6, wrap="word")
|
||||||
|
colors_display.pack(fill="x", padx=10, pady=5)
|
||||||
|
|
||||||
|
def update_theme_display():
|
||||||
|
"""Update the theme information display."""
|
||||||
|
current_theme_label.config(
|
||||||
|
text=f"Current Theme: {theme_manager.get_current_theme().title()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
menu_colors = theme_manager.get_menu_colors()
|
||||||
|
colors_text = "Current Menu Colors:\n"
|
||||||
|
for key, value in menu_colors.items():
|
||||||
|
colors_text += f" {key}: {value}\n"
|
||||||
|
|
||||||
|
colors_display.delete(1.0, tk.END)
|
||||||
|
colors_display.insert(1.0, colors_text)
|
||||||
|
|
||||||
|
# Function to apply theme and update displays
|
||||||
|
def apply_theme_and_update(theme_name):
|
||||||
|
"""Apply theme and update all displays."""
|
||||||
|
print(f"Switching to theme: {theme_name}")
|
||||||
|
if theme_manager.apply_theme(theme_name):
|
||||||
|
# Re-theme all menus
|
||||||
|
theme_manager.configure_menu(menubar)
|
||||||
|
theme_manager.configure_menu(file_menu)
|
||||||
|
theme_manager.configure_menu(theme_menu)
|
||||||
|
theme_manager.configure_menu(help_menu)
|
||||||
|
|
||||||
|
# Update display
|
||||||
|
update_theme_display()
|
||||||
|
print(f" ✓ Successfully applied {theme_name} theme")
|
||||||
|
else:
|
||||||
|
print(f" ✗ Failed to apply {theme_name} theme")
|
||||||
|
|
||||||
|
# Create theme selection menu
|
||||||
|
available_themes = theme_manager.get_available_themes()
|
||||||
|
current_theme = theme_manager.get_current_theme()
|
||||||
|
|
||||||
|
for theme in available_themes:
|
||||||
|
theme_menu.add_radiobutton(
|
||||||
|
label=theme.title(),
|
||||||
|
command=lambda t=theme: apply_theme_and_update(t),
|
||||||
|
value=theme == current_theme,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Instructions
|
||||||
|
instructions_frame = tk.Frame(root)
|
||||||
|
instructions_frame.pack(fill="both", expand=True, padx=20, pady=10)
|
||||||
|
|
||||||
|
tk.Label(
|
||||||
|
instructions_frame,
|
||||||
|
text="Menu Theming Demonstration",
|
||||||
|
font=("Arial", 16, "bold"),
|
||||||
|
).pack(pady=10)
|
||||||
|
|
||||||
|
instructions_text = """
|
||||||
|
Instructions:
|
||||||
|
1. Use the Theme menu to switch between different themes
|
||||||
|
2. Observe how menu colors change to match each theme
|
||||||
|
3. Try the File and Help menus to see the color effects
|
||||||
|
4. Menu backgrounds, text, and hover effects all update automatically
|
||||||
|
|
||||||
|
Available themes: """ + ", ".join([t.title() for t in available_themes])
|
||||||
|
|
||||||
|
tk.Label(
|
||||||
|
instructions_frame,
|
||||||
|
text=instructions_text,
|
||||||
|
justify="left",
|
||||||
|
wraplength=450,
|
||||||
|
).pack(pady=10)
|
||||||
|
|
||||||
|
# Initialize display
|
||||||
|
update_theme_display()
|
||||||
|
|
||||||
|
print(f"Demo window opened with {len(available_themes)} available themes.")
|
||||||
|
print("Try the Theme menu to see different color schemes!")
|
||||||
|
|
||||||
|
# Show the window
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
demo_menu_theming()
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Quick verification script for consolidated testing structure."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def run_command(cmd, description):
|
||||||
|
"""Run a command and return the result."""
|
||||||
|
print(f"\n🔍 {description}")
|
||||||
|
print(f"Command: {cmd}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
shell=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
cwd="/home/will/Code/thechart",
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
print("✅ SUCCESS")
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout[:500]) # First 500 chars
|
||||||
|
else:
|
||||||
|
print("❌ FAILED")
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr[:500])
|
||||||
|
return result.returncode == 0
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ ERROR: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def verify_test_structure():
|
||||||
|
"""Verify the consolidated test structure."""
|
||||||
|
print("🧪 TheChart Testing Structure Verification")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Check if we're in the right directory
|
||||||
|
if not os.path.exists("src/main.py"):
|
||||||
|
print("❌ Please run this script from the project root directory")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check test directories exist
|
||||||
|
test_dirs = ["tests", "scripts"]
|
||||||
|
for dir_name in test_dirs:
|
||||||
|
if os.path.exists(dir_name):
|
||||||
|
print(f"✅ Directory {dir_name}/ exists")
|
||||||
|
else:
|
||||||
|
print(f"❌ Directory {dir_name}/ missing")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check key test files exist
|
||||||
|
test_files = [
|
||||||
|
"tests/test_theme_manager.py",
|
||||||
|
"scripts/test_menu_theming.py",
|
||||||
|
"scripts/integration_test.py",
|
||||||
|
"docs/TESTING.md",
|
||||||
|
]
|
||||||
|
|
||||||
|
for file_path in test_files:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
print(f"✅ File {file_path} exists")
|
||||||
|
else:
|
||||||
|
print(f"❌ File {file_path} missing")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check virtual environment
|
||||||
|
if os.path.exists(".venv/bin/python"):
|
||||||
|
print("✅ Virtual environment found")
|
||||||
|
else:
|
||||||
|
print("❌ Virtual environment not found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("\n📋 Test Structure Summary:")
|
||||||
|
print("Unit Tests: tests/")
|
||||||
|
print("Integration Tests: scripts/")
|
||||||
|
print("Interactive Demos: scripts/")
|
||||||
|
print("Documentation: docs/TESTING.md")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def run_test_verification():
|
||||||
|
"""Run basic test verification."""
|
||||||
|
print("\n🚀 Running Test Verification")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
success_count = 0
|
||||||
|
total_tests = 0
|
||||||
|
|
||||||
|
# Test 1: Unit test syntax check
|
||||||
|
total_tests += 1
|
||||||
|
if run_command(
|
||||||
|
"source .venv/bin/activate.fish && "
|
||||||
|
"python -m py_compile tests/test_theme_manager.py",
|
||||||
|
"Unit test syntax check",
|
||||||
|
):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
# Test 2: Integration test syntax check
|
||||||
|
total_tests += 1
|
||||||
|
if run_command(
|
||||||
|
"source .venv/bin/activate.fish && "
|
||||||
|
"python -m py_compile scripts/integration_test.py",
|
||||||
|
"Integration test syntax check",
|
||||||
|
):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
# Test 3: Demo script syntax check
|
||||||
|
total_tests += 1
|
||||||
|
if run_command(
|
||||||
|
"source .venv/bin/activate.fish && "
|
||||||
|
"python -m py_compile scripts/test_menu_theming.py",
|
||||||
|
"Demo script syntax check",
|
||||||
|
):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
# Test 4: Check if pytest is available
|
||||||
|
total_tests += 1
|
||||||
|
pytest_cmd = (
|
||||||
|
"source .venv/bin/activate.fish && "
|
||||||
|
"python -c 'import pytest; print(f\"pytest version: {pytest.__version__}\")'"
|
||||||
|
)
|
||||||
|
if run_command(pytest_cmd, "Pytest availability check"):
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
print(f"\n📊 Test Verification Results: {success_count}/{total_tests} passed")
|
||||||
|
|
||||||
|
if success_count == total_tests:
|
||||||
|
print("✅ All verification tests passed!")
|
||||||
|
print("\n🎯 Next Steps:")
|
||||||
|
print("1. Run unit tests: python -m pytest tests/ -v")
|
||||||
|
print("2. Run integration test: python scripts/integration_test.py")
|
||||||
|
print("3. Try interactive demo: python scripts/test_menu_theming.py")
|
||||||
|
else:
|
||||||
|
print("❌ Some verification tests failed. Check the output above.")
|
||||||
|
|
||||||
|
return success_count == total_tests
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("🧪 TheChart Consolidated Testing Verification")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Verify structure
|
||||||
|
if not verify_test_structure():
|
||||||
|
print("\n❌ Test structure verification failed")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Run verification tests
|
||||||
|
if not run_test_verification():
|
||||||
|
print("\n❌ Test verification failed")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("\n🎉 All verification checks passed!")
|
||||||
|
print("📚 See docs/TESTING.md for complete testing guide")
|
||||||
+293
-39
@@ -17,6 +17,8 @@ from medicine_management_window import MedicineManagementWindow
|
|||||||
from medicine_manager import MedicineManager
|
from medicine_manager import MedicineManager
|
||||||
from pathology_management_window import PathologyManagementWindow
|
from pathology_management_window import PathologyManagementWindow
|
||||||
from pathology_manager import PathologyManager
|
from pathology_manager import PathologyManager
|
||||||
|
from settings_window import SettingsWindow
|
||||||
|
from theme_manager import ThemeManager
|
||||||
from ui_manager import UIManager
|
from ui_manager import UIManager
|
||||||
|
|
||||||
|
|
||||||
@@ -44,6 +46,9 @@ class MedTrackerApp:
|
|||||||
|
|
||||||
logger.info(f"Log level: {LOG_LEVEL}")
|
logger.info(f"Log level: {LOG_LEVEL}")
|
||||||
|
|
||||||
|
# Initialize theme manager first
|
||||||
|
self.theme_manager: ThemeManager = ThemeManager(self.root, logger)
|
||||||
|
|
||||||
if LOG_LEVEL == "DEBUG":
|
if LOG_LEVEL == "DEBUG":
|
||||||
logger.debug(f"Script name: {sys.argv[0]}")
|
logger.debug(f"Script name: {sys.argv[0]}")
|
||||||
logger.debug(f"Logs path: {LOG_PATH}")
|
logger.debug(f"Logs path: {LOG_PATH}")
|
||||||
@@ -54,7 +59,11 @@ class MedTrackerApp:
|
|||||||
self.medicine_manager: MedicineManager = MedicineManager(logger=logger)
|
self.medicine_manager: MedicineManager = MedicineManager(logger=logger)
|
||||||
self.pathology_manager: PathologyManager = PathologyManager(logger=logger)
|
self.pathology_manager: PathologyManager = PathologyManager(logger=logger)
|
||||||
self.ui_manager: UIManager = UIManager(
|
self.ui_manager: UIManager = UIManager(
|
||||||
root, logger, self.medicine_manager, self.pathology_manager
|
root,
|
||||||
|
logger,
|
||||||
|
self.medicine_manager,
|
||||||
|
self.pathology_manager,
|
||||||
|
self.theme_manager,
|
||||||
)
|
)
|
||||||
self.data_manager: DataManager = DataManager(
|
self.data_manager: DataManager = DataManager(
|
||||||
self.filename, logger, self.medicine_manager, self.pathology_manager
|
self.filename, logger, self.medicine_manager, self.pathology_manager
|
||||||
@@ -72,6 +81,9 @@ class MedTrackerApp:
|
|||||||
# Add menu bar
|
# Add menu bar
|
||||||
self._setup_menu()
|
self._setup_menu()
|
||||||
|
|
||||||
|
# Setup keyboard shortcuts
|
||||||
|
self._setup_keyboard_shortcuts()
|
||||||
|
|
||||||
# Center the window on screen
|
# Center the window on screen
|
||||||
self._center_window()
|
self._center_window()
|
||||||
|
|
||||||
@@ -100,7 +112,7 @@ class MedTrackerApp:
|
|||||||
import tkinter.ttk as ttk
|
import tkinter.ttk as ttk
|
||||||
|
|
||||||
# --- Main Frame ---
|
# --- Main Frame ---
|
||||||
main_frame: ttk.Frame = ttk.Frame(self.root, padding="10")
|
main_frame: ttk.Frame = ttk.Frame(self.root, padding="10", style="Card.TFrame")
|
||||||
main_frame.grid(row=0, column=0, sticky="nsew")
|
main_frame.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
# Configure root window grid
|
# Configure root window grid
|
||||||
@@ -108,7 +120,7 @@ class MedTrackerApp:
|
|||||||
self.root.grid_columnconfigure(0, weight=1)
|
self.root.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
# Configure main frame grid for scaling
|
# Configure main frame grid for scaling
|
||||||
for i in range(2):
|
for i in range(3): # Changed from 2 to 3 to accommodate status bar
|
||||||
main_frame.grid_rowconfigure(i, weight=1 if i == 1 else 0)
|
main_frame.grid_rowconfigure(i, weight=1 if i == 1 else 0)
|
||||||
main_frame.grid_columnconfigure(i, weight=3 if i == 1 else 1)
|
main_frame.grid_columnconfigure(i, weight=3 if i == 1 else 1)
|
||||||
logger.debug("Main frame and root grid configured for scaling.")
|
logger.debug("Main frame and root grid configured for scaling.")
|
||||||
@@ -141,12 +153,12 @@ class MedTrackerApp:
|
|||||||
self.input_frame,
|
self.input_frame,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"text": "Add Entry",
|
"text": "Add Entry (Ctrl+S)",
|
||||||
"command": self.add_new_entry,
|
"command": self.add_new_entry,
|
||||||
"fill": "both",
|
"fill": "both",
|
||||||
"expand": True,
|
"expand": True,
|
||||||
},
|
},
|
||||||
{"text": "Quit", "command": self.handle_window_closing},
|
{"text": "Quit (Ctrl+Q)", "command": self.handle_window_closing},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -155,49 +167,216 @@ class MedTrackerApp:
|
|||||||
self.tree: ttk.Treeview = table_ui["tree"]
|
self.tree: ttk.Treeview = table_ui["tree"]
|
||||||
self.tree.bind("<Double-1>", self.handle_double_click)
|
self.tree.bind("<Double-1>", self.handle_double_click)
|
||||||
|
|
||||||
|
# --- Create Status Bar ---
|
||||||
|
self.status_bar = self.ui_manager.create_status_bar(main_frame)
|
||||||
|
|
||||||
# Load data
|
# Load data
|
||||||
self.refresh_data_display()
|
self.refresh_data_display()
|
||||||
|
|
||||||
|
# Initialize status bar with ready message
|
||||||
|
self.ui_manager.update_status("Application ready", "info")
|
||||||
|
|
||||||
def _setup_menu(self) -> None:
|
def _setup_menu(self) -> None:
|
||||||
"""Set up the menu bar."""
|
"""Set up the menu bar."""
|
||||||
menubar = tk.Menu(self.root)
|
menubar = self.theme_manager.create_themed_menu(self.root)
|
||||||
self.root.config(menu=menubar)
|
self.root.config(menu=menubar)
|
||||||
|
|
||||||
# File menu
|
# File menu
|
||||||
file_menu = tk.Menu(menubar, tearoff=0)
|
file_menu = self.theme_manager.create_themed_menu(menubar, tearoff=0)
|
||||||
menubar.add_cascade(label="File", menu=file_menu)
|
menubar.add_cascade(label="File", menu=file_menu)
|
||||||
file_menu.add_command(label="Export Data...", command=self._open_export_window)
|
file_menu.add_command(
|
||||||
|
label="Export Data...",
|
||||||
|
command=self._open_export_window,
|
||||||
|
accelerator="Ctrl+E",
|
||||||
|
)
|
||||||
file_menu.add_separator()
|
file_menu.add_separator()
|
||||||
file_menu.add_command(label="Exit", command=self.handle_window_closing)
|
file_menu.add_command(
|
||||||
|
label="Exit", command=self.handle_window_closing, accelerator="Ctrl+Q"
|
||||||
|
)
|
||||||
|
|
||||||
# Tools menu
|
# Tools menu
|
||||||
tools_menu = tk.Menu(menubar, tearoff=0)
|
tools_menu = self.theme_manager.create_themed_menu(menubar, tearoff=0)
|
||||||
menubar.add_cascade(label="Tools", menu=tools_menu)
|
menubar.add_cascade(label="Tools", menu=tools_menu)
|
||||||
tools_menu.add_command(
|
tools_menu.add_command(
|
||||||
label="Manage Pathologies...", command=self._open_pathology_manager
|
label="Manage Pathologies...",
|
||||||
|
command=self._open_pathology_manager,
|
||||||
|
accelerator="Ctrl+P",
|
||||||
)
|
)
|
||||||
tools_menu.add_command(
|
tools_menu.add_command(
|
||||||
label="Manage Medicines...", command=self._open_medicine_manager
|
label="Manage Medicines...",
|
||||||
|
command=self._open_medicine_manager,
|
||||||
|
accelerator="Ctrl+M",
|
||||||
)
|
)
|
||||||
|
tools_menu.add_separator()
|
||||||
|
tools_menu.add_command(
|
||||||
|
label="Clear Entries", command=self._clear_entries, accelerator="Ctrl+N"
|
||||||
|
)
|
||||||
|
tools_menu.add_command(
|
||||||
|
label="Refresh Data", command=self.refresh_data_display, accelerator="F5"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Theme menu
|
||||||
|
theme_menu = self.theme_manager.create_themed_menu(menubar, tearoff=0)
|
||||||
|
menubar.add_cascade(label="Theme", menu=theme_menu)
|
||||||
|
|
||||||
|
# Add quick theme options
|
||||||
|
available_themes = self.theme_manager.get_available_themes()
|
||||||
|
current_theme = self.theme_manager.get_current_theme()
|
||||||
|
|
||||||
|
for theme in available_themes:
|
||||||
|
theme_menu.add_radiobutton(
|
||||||
|
label=theme.title(),
|
||||||
|
command=lambda t=theme: self._change_theme(t),
|
||||||
|
value=theme == current_theme,
|
||||||
|
)
|
||||||
|
|
||||||
|
theme_menu.add_separator()
|
||||||
|
theme_menu.add_command(
|
||||||
|
label="More Settings...",
|
||||||
|
command=self._open_settings_window,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Help menu
|
||||||
|
help_menu = self.theme_manager.create_themed_menu(menubar, tearoff=0)
|
||||||
|
menubar.add_cascade(label="Help", menu=help_menu)
|
||||||
|
help_menu.add_command(
|
||||||
|
label="Settings...",
|
||||||
|
command=self._open_settings_window,
|
||||||
|
accelerator="F2",
|
||||||
|
)
|
||||||
|
help_menu.add_separator()
|
||||||
|
help_menu.add_command(
|
||||||
|
label="Keyboard Shortcuts",
|
||||||
|
command=self._show_keyboard_shortcuts,
|
||||||
|
accelerator="F1",
|
||||||
|
)
|
||||||
|
help_menu.add_command(label="About", command=self._show_about_dialog)
|
||||||
|
|
||||||
|
def _setup_keyboard_shortcuts(self) -> None:
|
||||||
|
"""Set up keyboard shortcuts for common actions."""
|
||||||
|
# Bind keyboard shortcuts to the main window
|
||||||
|
self.root.bind("<Control-s>", lambda e: self.add_new_entry())
|
||||||
|
self.root.bind("<Control-S>", lambda e: self.add_new_entry())
|
||||||
|
self.root.bind("<Control-q>", lambda e: self.handle_window_closing())
|
||||||
|
self.root.bind("<Control-Q>", lambda e: self.handle_window_closing())
|
||||||
|
self.root.bind("<Control-e>", lambda e: self._open_export_window())
|
||||||
|
self.root.bind("<Control-E>", lambda e: self._open_export_window())
|
||||||
|
self.root.bind("<Control-n>", lambda e: self._clear_entries())
|
||||||
|
self.root.bind("<Control-N>", lambda e: self._clear_entries())
|
||||||
|
self.root.bind("<Control-r>", lambda e: self.refresh_data_display())
|
||||||
|
self.root.bind("<Control-R>", lambda e: self.refresh_data_display())
|
||||||
|
self.root.bind("<F5>", lambda e: self.refresh_data_display())
|
||||||
|
self.root.bind("<Control-m>", lambda e: self._open_medicine_manager())
|
||||||
|
self.root.bind("<Control-M>", lambda e: self._open_medicine_manager())
|
||||||
|
self.root.bind("<Control-p>", lambda e: self._open_pathology_manager())
|
||||||
|
self.root.bind("<Control-P>", lambda e: self._open_pathology_manager())
|
||||||
|
self.root.bind("<Delete>", lambda e: self._delete_selected_entry())
|
||||||
|
self.root.bind("<Escape>", lambda e: self._clear_selection())
|
||||||
|
self.root.bind("<F1>", lambda e: self._show_keyboard_shortcuts())
|
||||||
|
self.root.bind("<F2>", lambda e: self._open_settings_window())
|
||||||
|
|
||||||
|
# Make the window focusable so it can receive key events
|
||||||
|
self.root.focus_set()
|
||||||
|
|
||||||
|
logger.info("Keyboard shortcuts configured:")
|
||||||
|
logger.info(" Ctrl+S: Save/Add new entry")
|
||||||
|
logger.info(" Ctrl+Q: Quit application")
|
||||||
|
logger.info(" Ctrl+E: Export data")
|
||||||
|
logger.info(" Ctrl+N: Clear entries")
|
||||||
|
logger.info(" Ctrl+R/F5: Refresh data")
|
||||||
|
logger.info(" Ctrl+M: Manage medicines")
|
||||||
|
logger.info(" Ctrl+P: Manage pathologies")
|
||||||
|
logger.info(" Delete: Delete selected entry")
|
||||||
|
logger.info(" Escape: Clear selection")
|
||||||
|
logger.info(" F1: Show keyboard shortcuts help")
|
||||||
|
|
||||||
|
def _show_keyboard_shortcuts(self) -> None:
|
||||||
|
"""Show a dialog with keyboard shortcuts information."""
|
||||||
|
shortcuts_text = """Keyboard Shortcuts:
|
||||||
|
|
||||||
|
File Operations:
|
||||||
|
• Ctrl+S: Save/Add new entry
|
||||||
|
• Ctrl+Q: Quit application
|
||||||
|
• Ctrl+E: Export data
|
||||||
|
|
||||||
|
Data Management:
|
||||||
|
• Ctrl+N: Clear entries
|
||||||
|
• Ctrl+R / F5: Refresh data
|
||||||
|
|
||||||
|
Window Management:
|
||||||
|
• Ctrl+M: Manage medicines
|
||||||
|
• Ctrl+P: Manage pathologies
|
||||||
|
|
||||||
|
Table Operations:
|
||||||
|
• Delete: Delete selected entry
|
||||||
|
• Escape: Clear selection
|
||||||
|
• Double-click: Edit entry
|
||||||
|
|
||||||
|
Help:
|
||||||
|
• F1: Show this help dialog
|
||||||
|
• F2: Open settings window"""
|
||||||
|
|
||||||
|
messagebox.showinfo("Keyboard Shortcuts", shortcuts_text, parent=self.root)
|
||||||
|
|
||||||
|
def _change_theme(self, theme_name: str) -> None:
|
||||||
|
"""Change the application theme."""
|
||||||
|
if self.theme_manager.apply_theme(theme_name):
|
||||||
|
self.ui_manager.update_status(
|
||||||
|
f"Theme changed to: {theme_name.title()}", "info"
|
||||||
|
)
|
||||||
|
# Refresh the menu to update radio button selection
|
||||||
|
self._setup_menu()
|
||||||
|
else:
|
||||||
|
self.ui_manager.update_status(
|
||||||
|
f"Failed to apply theme: {theme_name}", "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _show_about_dialog(self) -> None:
|
||||||
|
"""Show about dialog."""
|
||||||
|
about_text = """TheChart - Medication Tracker
|
||||||
|
|
||||||
|
A simple application for tracking medications and pathologies.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
• Add daily medication and pathology entries
|
||||||
|
• Visual graphs and charts
|
||||||
|
• Data export capabilities
|
||||||
|
• Keyboard shortcuts for efficiency
|
||||||
|
|
||||||
|
Use Ctrl+S to save entries and Ctrl+Q to quit."""
|
||||||
|
|
||||||
|
messagebox.showinfo("About TheChart", about_text, parent=self.root)
|
||||||
|
|
||||||
def _open_export_window(self) -> None:
|
def _open_export_window(self) -> None:
|
||||||
"""Open the export window."""
|
"""Open the export window."""
|
||||||
|
self.ui_manager.update_status("Opening export window", "info")
|
||||||
ExportWindow(self.root, self.export_manager)
|
ExportWindow(self.root, self.export_manager)
|
||||||
|
|
||||||
def _open_pathology_manager(self) -> None:
|
def _open_pathology_manager(self) -> None:
|
||||||
"""Open the pathology management window."""
|
"""Open the pathology management window."""
|
||||||
|
self.ui_manager.update_status("Opening pathology manager", "info")
|
||||||
PathologyManagementWindow(
|
PathologyManagementWindow(
|
||||||
self.root, self.pathology_manager, self._refresh_ui_after_config_change
|
self.root, self.pathology_manager, self._refresh_ui_after_config_change
|
||||||
)
|
)
|
||||||
|
|
||||||
def _open_medicine_manager(self) -> None:
|
def _open_medicine_manager(self) -> None:
|
||||||
"""Open the medicine management window."""
|
"""Open the medicine management window."""
|
||||||
|
self.ui_manager.update_status("Opening medicine manager", "info")
|
||||||
MedicineManagementWindow(
|
MedicineManagementWindow(
|
||||||
self.root, self.medicine_manager, self._refresh_ui_after_config_change
|
self.root, self.medicine_manager, self._refresh_ui_after_config_change
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _open_settings_window(self) -> None:
|
||||||
|
"""Open the settings window."""
|
||||||
|
self.ui_manager.update_status("Opening settings window", "info")
|
||||||
|
SettingsWindow(self.root, self.theme_manager, self.ui_manager)
|
||||||
|
|
||||||
def _refresh_ui_after_config_change(self) -> None:
|
def _refresh_ui_after_config_change(self) -> None:
|
||||||
"""Refresh UI components after pathology or medicine configuration changes."""
|
"""Refresh UI components after pathology or medicine configuration changes."""
|
||||||
|
self.ui_manager.update_status(
|
||||||
|
"Refreshing UI after configuration change", "info"
|
||||||
|
)
|
||||||
|
|
||||||
# Clear caches in optimized data manager
|
# Clear caches in optimized data manager
|
||||||
if hasattr(self.data_manager, "_invalidate_cache"):
|
if hasattr(self.data_manager, "_invalidate_cache"):
|
||||||
self.data_manager._invalidate_cache()
|
self.data_manager._invalidate_cache()
|
||||||
@@ -218,12 +397,12 @@ class MedTrackerApp:
|
|||||||
self.input_frame,
|
self.input_frame,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"text": "Add Entry",
|
"text": "Add Entry (Ctrl+S)",
|
||||||
"command": self.add_new_entry,
|
"command": self.add_new_entry,
|
||||||
"fill": "both",
|
"fill": "both",
|
||||||
"expand": True,
|
"expand": True,
|
||||||
},
|
},
|
||||||
{"text": "Quit", "command": self.handle_window_closing},
|
{"text": "Quit (Ctrl+Q)", "command": self.handle_window_closing},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -238,14 +417,59 @@ class MedTrackerApp:
|
|||||||
# Refresh data display
|
# Refresh data display
|
||||||
self.refresh_data_display()
|
self.refresh_data_display()
|
||||||
|
|
||||||
|
# Update status to show completion
|
||||||
|
self.ui_manager.update_status("UI refreshed successfully", "success")
|
||||||
|
|
||||||
|
def _delete_selected_entry(self) -> None:
|
||||||
|
"""Delete the currently selected entry in the table."""
|
||||||
|
selection = self.tree.selection()
|
||||||
|
if not selection:
|
||||||
|
self.ui_manager.update_status("No entry selected for deletion", "warning")
|
||||||
|
return
|
||||||
|
|
||||||
|
item_id = selection[0]
|
||||||
|
item_values = self.tree.item(item_id, "values")
|
||||||
|
|
||||||
|
if messagebox.askyesno(
|
||||||
|
"Delete Entry",
|
||||||
|
f"Are you sure you want to delete the entry for {item_values[0]}?",
|
||||||
|
parent=self.root,
|
||||||
|
):
|
||||||
|
date: str = item_values[0]
|
||||||
|
logger.debug(f"Deleting entry with date={date}")
|
||||||
|
|
||||||
|
self.ui_manager.update_status("Deleting entry...", "info")
|
||||||
|
if self.data_manager.delete_entry(date):
|
||||||
|
self.ui_manager.update_status("Entry deleted successfully!", "success")
|
||||||
|
messagebox.showinfo(
|
||||||
|
"Success", "Entry deleted successfully!", parent=self.root
|
||||||
|
)
|
||||||
|
self.refresh_data_display()
|
||||||
|
else:
|
||||||
|
self.ui_manager.update_status("Failed to delete entry", "error")
|
||||||
|
messagebox.showerror(
|
||||||
|
"Error", "Failed to delete entry", parent=self.root
|
||||||
|
)
|
||||||
|
|
||||||
|
def _clear_selection(self) -> None:
|
||||||
|
"""Clear the current selection in the table."""
|
||||||
|
if self.tree.selection():
|
||||||
|
self.tree.selection_remove(self.tree.selection())
|
||||||
|
self.ui_manager.update_status("Selection cleared", "info")
|
||||||
|
|
||||||
def handle_double_click(self, event: tk.Event) -> None:
|
def handle_double_click(self, event: tk.Event) -> None:
|
||||||
"""Handle double-click event to edit an entry."""
|
"""Handle double-click event to edit an entry."""
|
||||||
logger.debug("Double-click event triggered on treeview.")
|
logger.debug("Double-click event triggered on treeview.")
|
||||||
if len(self.tree.get_children()) > 0:
|
if len(self.tree.get_children()) > 0:
|
||||||
item_id = self.tree.selection()[0]
|
item_id = self.tree.selection()[0]
|
||||||
item_values = self.tree.item(item_id, "values")
|
item_values = self.tree.item(item_id, "values")
|
||||||
|
self.ui_manager.update_status(
|
||||||
|
f"Opening entry for {item_values[0]} for editing", "info"
|
||||||
|
)
|
||||||
logger.debug(f"Editing item_id={item_id}, values={item_values}")
|
logger.debug(f"Editing item_id={item_id}, values={item_values}")
|
||||||
self._create_edit_window(item_id, item_values)
|
self._create_edit_window(item_id, item_values)
|
||||||
|
else:
|
||||||
|
self.ui_manager.update_status("No entries to edit", "warning")
|
||||||
|
|
||||||
def _create_edit_window(self, item_id: str, values: tuple[str, ...]) -> None:
|
def _create_edit_window(self, item_id: str, values: tuple[str, ...]) -> None:
|
||||||
"""Create a new Toplevel window for editing an entry."""
|
"""Create a new Toplevel window for editing an entry."""
|
||||||
@@ -347,8 +571,10 @@ class MedTrackerApp:
|
|||||||
|
|
||||||
values.append(note)
|
values.append(note)
|
||||||
|
|
||||||
|
self.ui_manager.update_status("Saving changes...", "info")
|
||||||
if self.data_manager.update_entry(original_date, values):
|
if self.data_manager.update_entry(original_date, values):
|
||||||
edit_win.destroy()
|
edit_win.destroy()
|
||||||
|
self.ui_manager.update_status("Entry updated successfully!", "success")
|
||||||
messagebox.showinfo(
|
messagebox.showinfo(
|
||||||
"Success", "Entry updated successfully!", parent=self.root
|
"Success", "Entry updated successfully!", parent=self.root
|
||||||
)
|
)
|
||||||
@@ -358,6 +584,7 @@ class MedTrackerApp:
|
|||||||
# Check if it's a duplicate date issue
|
# Check if it's a duplicate date issue
|
||||||
df = self.data_manager.load_data()
|
df = self.data_manager.load_data()
|
||||||
if original_date != date and not df.empty and date in df["date"].values:
|
if original_date != date and not df.empty and date in df["date"].values:
|
||||||
|
self.ui_manager.update_status("Duplicate date found", "error")
|
||||||
messagebox.showerror(
|
messagebox.showerror(
|
||||||
"Error",
|
"Error",
|
||||||
f"An entry for date '{date}' already exists. "
|
f"An entry for date '{date}' already exists. "
|
||||||
@@ -365,6 +592,7 @@ class MedTrackerApp:
|
|||||||
parent=edit_win,
|
parent=edit_win,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
self.ui_manager.update_status("Failed to save changes", "error")
|
||||||
messagebox.showerror("Error", "Failed to save changes", parent=edit_win)
|
messagebox.showerror("Error", "Failed to save changes", parent=edit_win)
|
||||||
|
|
||||||
def handle_window_closing(self) -> None:
|
def handle_window_closing(self) -> None:
|
||||||
@@ -409,10 +637,13 @@ class MedTrackerApp:
|
|||||||
|
|
||||||
# Check if date is empty
|
# Check if date is empty
|
||||||
if not self.date_var.get().strip():
|
if not self.date_var.get().strip():
|
||||||
|
self.ui_manager.update_status("Please enter a date", "error")
|
||||||
messagebox.showerror("Error", "Please enter a date.", parent=self.root)
|
messagebox.showerror("Error", "Please enter a date.", parent=self.root)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.ui_manager.update_status("Adding new entry...", "info")
|
||||||
if self.data_manager.add_entry(entry):
|
if self.data_manager.add_entry(entry):
|
||||||
|
self.ui_manager.update_status("Entry added successfully!", "success")
|
||||||
messagebox.showinfo(
|
messagebox.showinfo(
|
||||||
"Success", "Entry added successfully!", parent=self.root
|
"Success", "Entry added successfully!", parent=self.root
|
||||||
)
|
)
|
||||||
@@ -422,6 +653,7 @@ class MedTrackerApp:
|
|||||||
# Check if it's a duplicate date by trying to load existing data
|
# Check if it's a duplicate date by trying to load existing data
|
||||||
df = self.data_manager.load_data()
|
df = self.data_manager.load_data()
|
||||||
if not df.empty and self.date_var.get() in df["date"].values:
|
if not df.empty and self.date_var.get() in df["date"].values:
|
||||||
|
self.ui_manager.update_status("Duplicate entry found", "error")
|
||||||
messagebox.showerror(
|
messagebox.showerror(
|
||||||
"Error",
|
"Error",
|
||||||
f"An entry for date '{self.date_var.get()}' already exists. "
|
f"An entry for date '{self.date_var.get()}' already exists. "
|
||||||
@@ -429,6 +661,7 @@ class MedTrackerApp:
|
|||||||
parent=self.root,
|
parent=self.root,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
self.ui_manager.update_status("Failed to add entry", "error")
|
||||||
messagebox.showerror("Error", "Failed to add entry", parent=self.root)
|
messagebox.showerror("Error", "Failed to add entry", parent=self.root)
|
||||||
|
|
||||||
def _delete_entry(self, edit_win: tk.Toplevel, item_id: str) -> None:
|
def _delete_entry(self, edit_win: tk.Toplevel, item_id: str) -> None:
|
||||||
@@ -443,13 +676,16 @@ class MedTrackerApp:
|
|||||||
date: str = self.tree.item(item_id, "values")[0]
|
date: str = self.tree.item(item_id, "values")[0]
|
||||||
logger.debug(f"Deleting entry with date={date}")
|
logger.debug(f"Deleting entry with date={date}")
|
||||||
|
|
||||||
|
self.ui_manager.update_status("Deleting entry...", "info")
|
||||||
if self.data_manager.delete_entry(date):
|
if self.data_manager.delete_entry(date):
|
||||||
edit_win.destroy()
|
edit_win.destroy()
|
||||||
|
self.ui_manager.update_status("Entry deleted successfully!", "success")
|
||||||
messagebox.showinfo(
|
messagebox.showinfo(
|
||||||
"Success", "Entry deleted successfully!", parent=self.root
|
"Success", "Entry deleted successfully!", parent=self.root
|
||||||
)
|
)
|
||||||
self.refresh_data_display()
|
self.refresh_data_display()
|
||||||
else:
|
else:
|
||||||
|
self.ui_manager.update_status("Failed to delete entry", "error")
|
||||||
messagebox.showerror("Error", "Failed to delete entry", parent=edit_win)
|
messagebox.showerror("Error", "Failed to delete entry", parent=edit_win)
|
||||||
|
|
||||||
def _clear_entries(self) -> None:
|
def _clear_entries(self) -> None:
|
||||||
@@ -471,38 +707,56 @@ class MedTrackerApp:
|
|||||||
if children:
|
if children:
|
||||||
self.tree.delete(*children)
|
self.tree.delete(*children)
|
||||||
|
|
||||||
# Load data from the CSV file
|
try:
|
||||||
df: pd.DataFrame = self.data_manager.load_data()
|
# Load data from the CSV file
|
||||||
|
df: pd.DataFrame = self.data_manager.load_data()
|
||||||
|
|
||||||
# Update the treeview with the data
|
# Update the treeview with the data
|
||||||
if not df.empty:
|
if not df.empty:
|
||||||
# Build display columns dynamically (exclude dose columns for table view)
|
# Build display columns dynamically
|
||||||
display_columns = ["date"]
|
# (exclude dose columns for table view)
|
||||||
|
display_columns = ["date"]
|
||||||
|
|
||||||
# Add pathology columns
|
# Add pathology columns
|
||||||
for pathology_key in self.pathology_manager.get_pathology_keys():
|
for pathology_key in self.pathology_manager.get_pathology_keys():
|
||||||
display_columns.append(pathology_key)
|
display_columns.append(pathology_key)
|
||||||
|
|
||||||
# Add medicine columns (without dose columns)
|
# Add medicine columns (without dose columns)
|
||||||
for medicine_key in self.medicine_manager.get_medicine_keys():
|
for medicine_key in self.medicine_manager.get_medicine_keys():
|
||||||
display_columns.append(medicine_key)
|
display_columns.append(medicine_key)
|
||||||
|
|
||||||
display_columns.append("note")
|
display_columns.append("note")
|
||||||
|
|
||||||
# Filter to only the columns we want to display
|
# Filter to only the columns we want to display
|
||||||
if all(col in df.columns for col in display_columns):
|
if all(col in df.columns for col in display_columns):
|
||||||
display_df = df[display_columns]
|
display_df = df[display_columns]
|
||||||
|
else:
|
||||||
|
# Fallback - just use all columns
|
||||||
|
display_df = df
|
||||||
|
|
||||||
|
# Batch insert for better performance with alternating row colors
|
||||||
|
for index, row in display_df.iterrows():
|
||||||
|
# Add alternating row tags for better visibility
|
||||||
|
tag = "evenrow" if index % 2 == 0 else "oddrow"
|
||||||
|
self.tree.insert(
|
||||||
|
parent="", index="end", values=list(row), tags=(tag,)
|
||||||
|
)
|
||||||
|
logger.debug(f"Loaded {len(display_df)} entries into treeview.")
|
||||||
|
|
||||||
|
# Update the graph
|
||||||
|
self.graph_manager.update_graph(df)
|
||||||
|
|
||||||
|
# Update status bar with file info
|
||||||
|
entry_count = len(df) if not df.empty else 0
|
||||||
|
self.ui_manager.update_file_info(self.filename, entry_count)
|
||||||
|
if entry_count == 0:
|
||||||
|
self.ui_manager.update_status("No data to display", "warning")
|
||||||
else:
|
else:
|
||||||
# Fallback - just use all columns
|
self.ui_manager.update_status("Data loaded successfully", "success")
|
||||||
display_df = df
|
|
||||||
|
|
||||||
# Batch insert for better performance
|
except Exception as e:
|
||||||
for _index, row in display_df.iterrows():
|
logger.error(f"Error loading data: {e}")
|
||||||
self.tree.insert(parent="", index="end", values=list(row))
|
self.ui_manager.update_status(f"Error loading data: {str(e)}", "error")
|
||||||
logger.debug(f"Loaded {len(display_df)} entries into treeview.")
|
|
||||||
|
|
||||||
# Update the graph
|
|
||||||
self.graph_manager.update_graph(df)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -0,0 +1,324 @@
|
|||||||
|
"""Settings window for TheChart application."""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import messagebox, ttk
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsWindow:
|
||||||
|
"""Settings window for application preferences."""
|
||||||
|
|
||||||
|
def __init__(self, parent: tk.Tk, theme_manager, ui_manager) -> None:
|
||||||
|
self.parent = parent
|
||||||
|
self.theme_manager = theme_manager
|
||||||
|
self.ui_manager = ui_manager
|
||||||
|
|
||||||
|
# Create window
|
||||||
|
self.window = tk.Toplevel(parent)
|
||||||
|
self.window.title("Settings - TheChart")
|
||||||
|
self.window.geometry("500x400")
|
||||||
|
self.window.resizable(False, False)
|
||||||
|
|
||||||
|
# Make window modal
|
||||||
|
self.window.transient(parent)
|
||||||
|
self.window.grab_set()
|
||||||
|
|
||||||
|
# Center the window
|
||||||
|
self._center_window()
|
||||||
|
|
||||||
|
# Setup UI
|
||||||
|
self._setup_ui()
|
||||||
|
|
||||||
|
# Set initial values
|
||||||
|
self._load_current_settings()
|
||||||
|
|
||||||
|
def _center_window(self) -> None:
|
||||||
|
"""Center the settings window on the parent."""
|
||||||
|
self.window.update_idletasks()
|
||||||
|
|
||||||
|
# Get window dimensions
|
||||||
|
window_width = self.window.winfo_reqwidth()
|
||||||
|
window_height = self.window.winfo_reqheight()
|
||||||
|
|
||||||
|
# Get parent window position and size
|
||||||
|
parent_x = self.parent.winfo_x()
|
||||||
|
parent_y = self.parent.winfo_y()
|
||||||
|
parent_width = self.parent.winfo_width()
|
||||||
|
parent_height = self.parent.winfo_height()
|
||||||
|
|
||||||
|
# Calculate centered position
|
||||||
|
x = parent_x + (parent_width // 2) - (window_width // 2)
|
||||||
|
y = parent_y + (parent_height // 2) - (window_height // 2)
|
||||||
|
|
||||||
|
self.window.geometry(f"{window_width}x{window_height}+{x}+{y}")
|
||||||
|
|
||||||
|
def _setup_ui(self) -> None:
|
||||||
|
"""Setup the settings UI."""
|
||||||
|
# Main container
|
||||||
|
main_frame = ttk.Frame(self.window, padding="20", style="Card.TFrame")
|
||||||
|
main_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title_label = ttk.Label(
|
||||||
|
main_frame,
|
||||||
|
text="Application Settings",
|
||||||
|
font=("TkDefaultFont", 16, "bold"),
|
||||||
|
)
|
||||||
|
title_label.pack(pady=(0, 20))
|
||||||
|
|
||||||
|
# Create notebook for different setting categories
|
||||||
|
notebook = ttk.Notebook(main_frame, style="Modern.TNotebook")
|
||||||
|
notebook.pack(fill="both", expand=True, pady=(0, 20))
|
||||||
|
|
||||||
|
# Theme settings tab
|
||||||
|
self._create_theme_tab(notebook)
|
||||||
|
|
||||||
|
# UI settings tab
|
||||||
|
self._create_ui_tab(notebook)
|
||||||
|
|
||||||
|
# About tab
|
||||||
|
self._create_about_tab(notebook)
|
||||||
|
|
||||||
|
# Button frame
|
||||||
|
button_frame = ttk.Frame(main_frame)
|
||||||
|
button_frame.pack(fill="x", pady=(10, 0))
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
ttk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="Apply",
|
||||||
|
command=self._apply_settings,
|
||||||
|
style="Action.TButton",
|
||||||
|
).pack(side="right", padx=(5, 0))
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="Cancel",
|
||||||
|
command=self._cancel,
|
||||||
|
style="Action.TButton",
|
||||||
|
).pack(side="right")
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="OK",
|
||||||
|
command=self._ok,
|
||||||
|
style="Action.TButton",
|
||||||
|
).pack(side="right", padx=(0, 5))
|
||||||
|
|
||||||
|
def _create_theme_tab(self, notebook: ttk.Notebook) -> None:
|
||||||
|
"""Create the theme settings tab."""
|
||||||
|
theme_frame = ttk.Frame(notebook, style="Card.TFrame")
|
||||||
|
notebook.add(theme_frame, text="Theme")
|
||||||
|
|
||||||
|
# Theme selection
|
||||||
|
theme_label_frame = ttk.LabelFrame(
|
||||||
|
theme_frame, text="Theme Selection", style="Card.TLabelframe"
|
||||||
|
)
|
||||||
|
theme_label_frame.pack(fill="x", padx=10, pady=10)
|
||||||
|
|
||||||
|
ttk.Label(
|
||||||
|
theme_label_frame,
|
||||||
|
text="Choose your preferred theme:",
|
||||||
|
font=("TkDefaultFont", 10),
|
||||||
|
).pack(anchor="w", padx=10, pady=(10, 5))
|
||||||
|
|
||||||
|
# Theme radio buttons
|
||||||
|
self.theme_var = tk.StringVar()
|
||||||
|
themes = self.theme_manager.get_available_themes()
|
||||||
|
|
||||||
|
theme_buttons_frame = ttk.Frame(theme_label_frame)
|
||||||
|
theme_buttons_frame.pack(fill="x", padx=10, pady=(0, 10))
|
||||||
|
|
||||||
|
# Create radio buttons in a grid
|
||||||
|
for i, theme in enumerate(themes):
|
||||||
|
row = i // 3
|
||||||
|
col = i % 3
|
||||||
|
|
||||||
|
ttk.Radiobutton(
|
||||||
|
theme_buttons_frame,
|
||||||
|
text=theme.title(),
|
||||||
|
variable=self.theme_var,
|
||||||
|
value=theme,
|
||||||
|
style="Modern.TCheckbutton",
|
||||||
|
).grid(row=row, column=col, sticky="w", padx=5, pady=2)
|
||||||
|
|
||||||
|
# Theme preview info
|
||||||
|
preview_frame = ttk.LabelFrame(
|
||||||
|
theme_frame, text="Theme Preview", style="Card.TLabelframe"
|
||||||
|
)
|
||||||
|
preview_frame.pack(fill="both", expand=True, padx=10, pady=(0, 10))
|
||||||
|
|
||||||
|
preview_text = tk.Text(
|
||||||
|
preview_frame,
|
||||||
|
height=6,
|
||||||
|
wrap="word",
|
||||||
|
font=("TkDefaultFont", 9),
|
||||||
|
state="disabled",
|
||||||
|
)
|
||||||
|
preview_text.pack(fill="both", expand=True, padx=10, pady=10)
|
||||||
|
|
||||||
|
# Theme change callback
|
||||||
|
def on_theme_change():
|
||||||
|
selected_theme = self.theme_var.get()
|
||||||
|
preview_text.config(state="normal")
|
||||||
|
preview_text.delete("1.0", "end")
|
||||||
|
preview_text.insert(
|
||||||
|
"1.0",
|
||||||
|
f"Selected theme: {selected_theme.title()}\\n\\n"
|
||||||
|
"Theme changes will be applied when you click 'Apply' or 'OK'. "
|
||||||
|
"The new theme will affect all windows and UI elements "
|
||||||
|
"in the application.",
|
||||||
|
)
|
||||||
|
preview_text.config(state="disabled")
|
||||||
|
|
||||||
|
self.theme_var.trace("w", lambda *args: on_theme_change())
|
||||||
|
|
||||||
|
def _create_ui_tab(self, notebook: ttk.Notebook) -> None:
|
||||||
|
"""Create the UI settings tab."""
|
||||||
|
ui_frame = ttk.Frame(notebook, style="Card.TFrame")
|
||||||
|
notebook.add(ui_frame, text="Interface")
|
||||||
|
|
||||||
|
# Font settings
|
||||||
|
font_frame = ttk.LabelFrame(
|
||||||
|
ui_frame, text="Font Settings", style="Card.TLabelframe"
|
||||||
|
)
|
||||||
|
font_frame.pack(fill="x", padx=10, pady=10)
|
||||||
|
|
||||||
|
ttk.Label(
|
||||||
|
font_frame,
|
||||||
|
text="Font size adjustments (requires restart):",
|
||||||
|
font=("TkDefaultFont", 10),
|
||||||
|
).pack(anchor="w", padx=10, pady=10)
|
||||||
|
|
||||||
|
# Font size scale
|
||||||
|
self.font_scale_var = tk.DoubleVar(value=1.0)
|
||||||
|
font_scale = ttk.Scale(
|
||||||
|
font_frame,
|
||||||
|
from_=0.8,
|
||||||
|
to=1.5,
|
||||||
|
variable=self.font_scale_var,
|
||||||
|
orient="horizontal",
|
||||||
|
style="Modern.Horizontal.TScale",
|
||||||
|
)
|
||||||
|
font_scale.pack(fill="x", padx=10, pady=(0, 10))
|
||||||
|
|
||||||
|
# Scale labels
|
||||||
|
scale_labels_frame = ttk.Frame(font_frame)
|
||||||
|
scale_labels_frame.pack(fill="x", padx=10, pady=(0, 10))
|
||||||
|
|
||||||
|
ttk.Label(scale_labels_frame, text="Small").pack(side="left")
|
||||||
|
ttk.Label(scale_labels_frame, text="Large").pack(side="right")
|
||||||
|
ttk.Label(scale_labels_frame, text="Normal").pack()
|
||||||
|
|
||||||
|
# Window settings
|
||||||
|
window_frame = ttk.LabelFrame(
|
||||||
|
ui_frame, text="Window Settings", style="Card.TLabelframe"
|
||||||
|
)
|
||||||
|
window_frame.pack(fill="x", padx=10, pady=(0, 10))
|
||||||
|
|
||||||
|
# Remember window size
|
||||||
|
self.remember_size_var = tk.BooleanVar(value=True)
|
||||||
|
ttk.Checkbutton(
|
||||||
|
window_frame,
|
||||||
|
text="Remember window size and position",
|
||||||
|
variable=self.remember_size_var,
|
||||||
|
style="Modern.TCheckbutton",
|
||||||
|
).pack(anchor="w", padx=10, pady=10)
|
||||||
|
|
||||||
|
# Always on top
|
||||||
|
self.always_on_top_var = tk.BooleanVar(value=False)
|
||||||
|
ttk.Checkbutton(
|
||||||
|
window_frame,
|
||||||
|
text="Keep window always on top",
|
||||||
|
variable=self.always_on_top_var,
|
||||||
|
style="Modern.TCheckbutton",
|
||||||
|
).pack(anchor="w", padx=10, pady=(0, 10))
|
||||||
|
|
||||||
|
def _create_about_tab(self, notebook: ttk.Notebook) -> None:
|
||||||
|
"""Create the about tab."""
|
||||||
|
about_frame = ttk.Frame(notebook, style="Card.TFrame")
|
||||||
|
notebook.add(about_frame, text="About")
|
||||||
|
|
||||||
|
# App info
|
||||||
|
info_frame = ttk.LabelFrame(
|
||||||
|
about_frame, text="Application Information", style="Card.TLabelframe"
|
||||||
|
)
|
||||||
|
info_frame.pack(fill="both", expand=True, padx=10, pady=10)
|
||||||
|
|
||||||
|
about_text = tk.Text(
|
||||||
|
info_frame,
|
||||||
|
wrap="word",
|
||||||
|
font=("TkDefaultFont", 10),
|
||||||
|
state="disabled",
|
||||||
|
bg=self.theme_manager.get_theme_colors()["bg"],
|
||||||
|
fg=self.theme_manager.get_theme_colors()["fg"],
|
||||||
|
)
|
||||||
|
about_text.pack(fill="both", expand=True, padx=10, pady=10)
|
||||||
|
|
||||||
|
about_content = """TheChart - Medication Tracker
|
||||||
|
|
||||||
|
Version: 1.9.5
|
||||||
|
Built with: Python, Tkinter, ttkthemes
|
||||||
|
|
||||||
|
Features:
|
||||||
|
• Modern themed interface with multiple themes
|
||||||
|
• Medication and pathology tracking
|
||||||
|
• Visual graphs and charts
|
||||||
|
• Data export capabilities
|
||||||
|
• Keyboard shortcuts for efficiency
|
||||||
|
• Customizable UI settings
|
||||||
|
|
||||||
|
This application helps you track your daily medications and health
|
||||||
|
conditions with an intuitive, modern interface.
|
||||||
|
|
||||||
|
Enhanced with ttkthemes for better visual appeal and user experience."""
|
||||||
|
|
||||||
|
about_text.config(state="normal")
|
||||||
|
about_text.insert("1.0", about_content)
|
||||||
|
about_text.config(state="disabled")
|
||||||
|
|
||||||
|
def _load_current_settings(self) -> None:
|
||||||
|
"""Load current application settings."""
|
||||||
|
# Set current theme
|
||||||
|
current_theme = self.theme_manager.get_current_theme()
|
||||||
|
self.theme_var.set(current_theme)
|
||||||
|
|
||||||
|
# Trigger theme change to update preview
|
||||||
|
if hasattr(self, "theme_var"):
|
||||||
|
self.theme_var.set(current_theme)
|
||||||
|
|
||||||
|
def _apply_settings(self) -> None:
|
||||||
|
"""Apply the selected settings."""
|
||||||
|
# Apply theme if changed
|
||||||
|
selected_theme = self.theme_var.get()
|
||||||
|
current_theme = self.theme_manager.get_current_theme()
|
||||||
|
|
||||||
|
if selected_theme != current_theme:
|
||||||
|
if self.theme_manager.apply_theme(selected_theme):
|
||||||
|
self.ui_manager.update_status(
|
||||||
|
f"Theme changed to: {selected_theme.title()}", "info"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Error",
|
||||||
|
f"Failed to apply theme: {selected_theme}",
|
||||||
|
parent=self.window,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Apply other settings (font size, window settings, etc.)
|
||||||
|
# These would typically be saved to a config file
|
||||||
|
|
||||||
|
messagebox.showinfo(
|
||||||
|
"Settings Applied",
|
||||||
|
"Settings have been applied successfully!",
|
||||||
|
parent=self.window,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _ok(self) -> None:
|
||||||
|
"""Apply settings and close window."""
|
||||||
|
self._apply_settings()
|
||||||
|
self.window.destroy()
|
||||||
|
|
||||||
|
def _cancel(self) -> None:
|
||||||
|
"""Close window without applying settings."""
|
||||||
|
self.window.destroy()
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
"""Theme manager for the application using ttkthemes."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
from ttkthemes import ThemedStyle
|
||||||
|
|
||||||
|
|
||||||
|
class ThemeManager:
|
||||||
|
"""Manages application themes and styling."""
|
||||||
|
|
||||||
|
def __init__(self, root: tk.Tk, logger: logging.Logger) -> None:
|
||||||
|
self.root = root
|
||||||
|
self.logger = logger
|
||||||
|
self.style: ThemedStyle | None = None
|
||||||
|
self.current_theme: str = "arc" # Default theme
|
||||||
|
|
||||||
|
# Available themes - these are some of the best looking ones
|
||||||
|
self.available_themes = [
|
||||||
|
"arc",
|
||||||
|
"equilux",
|
||||||
|
"adapta",
|
||||||
|
"yaru",
|
||||||
|
"ubuntu",
|
||||||
|
"plastik",
|
||||||
|
"breeze",
|
||||||
|
"elegance",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.initialize_theme()
|
||||||
|
|
||||||
|
def initialize_theme(self) -> None:
|
||||||
|
"""Initialize the themed style."""
|
||||||
|
try:
|
||||||
|
self.style = ThemedStyle(self.root)
|
||||||
|
self.apply_theme(self.current_theme)
|
||||||
|
self._configure_custom_styles()
|
||||||
|
self.logger.info(
|
||||||
|
f"Theme manager initialized with theme: {self.current_theme}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to initialize theme manager: {e}")
|
||||||
|
# Fallback to default ttk styling
|
||||||
|
self.style = ttk.Style()
|
||||||
|
|
||||||
|
def apply_theme(self, theme_name: str) -> bool:
|
||||||
|
"""Apply a specific theme."""
|
||||||
|
try:
|
||||||
|
if self.style and theme_name in self.get_available_themes():
|
||||||
|
self.style.set_theme(theme_name)
|
||||||
|
self.current_theme = theme_name
|
||||||
|
self._configure_custom_styles()
|
||||||
|
self.logger.info(f"Applied theme: {theme_name}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.logger.warning(f"Theme '{theme_name}' not available")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to apply theme '{theme_name}': {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_available_themes(self) -> list[str]:
|
||||||
|
"""Get list of available themes."""
|
||||||
|
if self.style:
|
||||||
|
try:
|
||||||
|
# Get all available themes from ttkthemes
|
||||||
|
all_themes = self.style.theme_names()
|
||||||
|
# Filter to only include our curated list
|
||||||
|
return [theme for theme in self.available_themes if theme in all_themes]
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to get available themes: {e}")
|
||||||
|
return self.available_themes
|
||||||
|
return self.available_themes
|
||||||
|
|
||||||
|
def get_current_theme(self) -> str:
|
||||||
|
"""Get the currently active theme."""
|
||||||
|
return self.current_theme
|
||||||
|
|
||||||
|
def _configure_custom_styles(self) -> None:
|
||||||
|
"""Configure custom styles for better appearance."""
|
||||||
|
if not self.style:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get current theme colors for consistent styling
|
||||||
|
colors = self.get_theme_colors()
|
||||||
|
|
||||||
|
# Configure frame styles with better padding and borders
|
||||||
|
self.style.configure(
|
||||||
|
"Card.TFrame",
|
||||||
|
relief="flat",
|
||||||
|
borderwidth=0,
|
||||||
|
background=colors["bg"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure label frame styles with modern appearance
|
||||||
|
self.style.configure(
|
||||||
|
"Card.TLabelframe",
|
||||||
|
relief="solid",
|
||||||
|
borderwidth=1,
|
||||||
|
background=colors["bg"],
|
||||||
|
foreground=colors["fg"],
|
||||||
|
padding=(10, 5, 10, 10),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.style.configure(
|
||||||
|
"Card.TLabelframe.Label",
|
||||||
|
background=colors["bg"],
|
||||||
|
foreground=colors["fg"],
|
||||||
|
font=("TkDefaultFont", 10, "bold"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure button styles for better appearance
|
||||||
|
self.style.configure(
|
||||||
|
"Action.TButton",
|
||||||
|
padding=(15, 8),
|
||||||
|
font=("TkDefaultFont", 9, "normal"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure entry styles with modern look
|
||||||
|
self.style.configure(
|
||||||
|
"Modern.TEntry",
|
||||||
|
padding=(8, 5),
|
||||||
|
borderwidth=1,
|
||||||
|
relief="solid",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure scale styles for pathology inputs
|
||||||
|
self.style.configure(
|
||||||
|
"Modern.Horizontal.TScale",
|
||||||
|
borderwidth=0,
|
||||||
|
background=colors["bg"],
|
||||||
|
troughcolor="#e0e0e0",
|
||||||
|
lightcolor=colors["select_bg"],
|
||||||
|
darkcolor=colors["select_bg"],
|
||||||
|
focuscolor=colors["select_bg"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure treeview for better data display
|
||||||
|
self.style.configure(
|
||||||
|
"Modern.Treeview",
|
||||||
|
rowheight=28,
|
||||||
|
borderwidth=1,
|
||||||
|
relief="solid",
|
||||||
|
background=colors["bg"],
|
||||||
|
foreground=colors["fg"],
|
||||||
|
fieldbackground=colors["bg"],
|
||||||
|
selectbackground=colors["select_bg"],
|
||||||
|
selectforeground=colors["select_fg"],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.style.configure(
|
||||||
|
"Modern.Treeview.Heading",
|
||||||
|
padding=(8, 6),
|
||||||
|
relief="flat",
|
||||||
|
borderwidth=1,
|
||||||
|
background=colors["select_bg"],
|
||||||
|
foreground=colors["select_fg"],
|
||||||
|
font=("TkDefaultFont", 9, "bold"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure comprehensive row selection colors for better visibility
|
||||||
|
self.style.map(
|
||||||
|
"Modern.Treeview",
|
||||||
|
background=[
|
||||||
|
("selected", colors["select_bg"]),
|
||||||
|
("active", colors["select_bg"]),
|
||||||
|
("focus", colors["select_bg"]),
|
||||||
|
("", colors["bg"]),
|
||||||
|
],
|
||||||
|
foreground=[
|
||||||
|
("selected", colors["select_fg"]),
|
||||||
|
("active", colors["select_fg"]),
|
||||||
|
("focus", colors["select_fg"]),
|
||||||
|
("", colors["fg"]),
|
||||||
|
],
|
||||||
|
selectbackground=[
|
||||||
|
("focus", colors["select_bg"]),
|
||||||
|
("", colors["select_bg"]),
|
||||||
|
],
|
||||||
|
selectforeground=[
|
||||||
|
("focus", colors["select_fg"]),
|
||||||
|
("", colors["select_fg"]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure notebook tabs with modern styling
|
||||||
|
self.style.configure(
|
||||||
|
"Modern.TNotebook.Tab",
|
||||||
|
padding=(15, 8),
|
||||||
|
borderwidth=1,
|
||||||
|
relief="flat",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.style.map(
|
||||||
|
"Modern.TNotebook.Tab",
|
||||||
|
background=[("selected", colors["select_bg"])],
|
||||||
|
foreground=[("selected", colors["select_fg"])],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure checkbutton for medicine selection
|
||||||
|
self.style.configure(
|
||||||
|
"Modern.TCheckbutton",
|
||||||
|
padding=(8, 4),
|
||||||
|
background=colors["bg"],
|
||||||
|
foreground=colors["fg"],
|
||||||
|
focuscolor=colors["select_bg"],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.debug("Enhanced custom styles configured")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to configure custom styles: {e}")
|
||||||
|
|
||||||
|
def get_menu_colors(self) -> dict[str, str]:
|
||||||
|
"""Get colors specifically for menu theming."""
|
||||||
|
colors = self.get_theme_colors()
|
||||||
|
|
||||||
|
# Use slightly different colors for menus to make them stand out
|
||||||
|
try:
|
||||||
|
# For menu background, use a slightly darker/lighter shade
|
||||||
|
if colors["bg"].startswith("#"):
|
||||||
|
rgb = tuple(int(colors["bg"][i : i + 2], 16) for i in (1, 3, 5))
|
||||||
|
if sum(rgb) > 384: # Light theme - make menu slightly darker
|
||||||
|
menu_bg = (
|
||||||
|
f"#{max(0, rgb[0] - 8):02x}"
|
||||||
|
f"{max(0, rgb[1] - 8):02x}"
|
||||||
|
f"{max(0, rgb[2] - 8):02x}"
|
||||||
|
)
|
||||||
|
else: # Dark theme - make menu slightly lighter
|
||||||
|
menu_bg = (
|
||||||
|
f"#{min(255, rgb[0] + 15):02x}"
|
||||||
|
f"{min(255, rgb[1] + 15):02x}"
|
||||||
|
f"{min(255, rgb[2] + 15):02x}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
menu_bg = colors["bg"]
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
menu_bg = colors["bg"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"bg": menu_bg,
|
||||||
|
"fg": colors["fg"],
|
||||||
|
"active_bg": colors["select_bg"],
|
||||||
|
"active_fg": colors["select_fg"],
|
||||||
|
"disabled_fg": colors.get("disabled_fg", "#888888"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure_menu(self, menu: "tk.Menu") -> None:
|
||||||
|
"""Apply theme colors to a menu widget."""
|
||||||
|
try:
|
||||||
|
menu_colors = self.get_menu_colors()
|
||||||
|
|
||||||
|
menu.configure(
|
||||||
|
background=menu_colors["bg"],
|
||||||
|
foreground=menu_colors["fg"],
|
||||||
|
activebackground=menu_colors["active_bg"],
|
||||||
|
activeforeground=menu_colors["active_fg"],
|
||||||
|
disabledforeground=menu_colors["disabled_fg"],
|
||||||
|
relief="flat",
|
||||||
|
borderwidth=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.debug(f"Applied theme to menu: {menu_colors}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to configure menu theme: {e}")
|
||||||
|
|
||||||
|
def create_themed_menu(self, parent: "tk.Widget", **kwargs) -> "tk.Menu":
|
||||||
|
"""Create a new menu with theme colors already applied."""
|
||||||
|
try:
|
||||||
|
menu = tk.Menu(parent, **kwargs)
|
||||||
|
self.configure_menu(menu)
|
||||||
|
return menu
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to create themed menu: {e}")
|
||||||
|
# Fallback to regular menu if theming fails
|
||||||
|
return tk.Menu(parent, **kwargs)
|
||||||
|
|
||||||
|
def configure_widget_style(self, widget: tk.Widget, style_name: str) -> None:
|
||||||
|
"""Apply a specific style to a widget."""
|
||||||
|
try:
|
||||||
|
if hasattr(widget, "configure") and self.style:
|
||||||
|
widget.configure(style=style_name)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to configure widget style '{style_name}': {e}")
|
||||||
|
|
||||||
|
def get_theme_colors(self) -> dict[str, str]:
|
||||||
|
"""Get current theme colors for custom widgets."""
|
||||||
|
if not self.style:
|
||||||
|
return {
|
||||||
|
"bg": "#ffffff",
|
||||||
|
"fg": "#000000",
|
||||||
|
"select_bg": "#3584e4",
|
||||||
|
"select_fg": "#ffffff",
|
||||||
|
"alt_bg": "#f5f5f5",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get colors from current theme
|
||||||
|
bg = self.style.lookup("TFrame", "background") or "#ffffff"
|
||||||
|
fg = self.style.lookup("TLabel", "foreground") or "#000000"
|
||||||
|
|
||||||
|
# Try to get better selection colors from different widget states
|
||||||
|
select_bg = (
|
||||||
|
self.style.lookup("TButton", "background", ["pressed"])
|
||||||
|
or self.style.lookup("TButton", "background", ["active"])
|
||||||
|
or self.style.lookup("Treeview", "selectbackground")
|
||||||
|
or "#0078d4" # Modern blue fallback
|
||||||
|
)
|
||||||
|
select_fg = (
|
||||||
|
self.style.lookup("TButton", "foreground", ["pressed"])
|
||||||
|
or self.style.lookup("TButton", "foreground", ["active"])
|
||||||
|
or self.style.lookup("Treeview", "selectforeground")
|
||||||
|
or "#ffffff" # White fallback
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure contrast - if selection colors are too similar to background,
|
||||||
|
# use fallbacks
|
||||||
|
if select_bg == bg or select_bg.lower() == bg.lower():
|
||||||
|
select_bg = "#0078d4" if bg != "#0078d4" else "#0066cc"
|
||||||
|
|
||||||
|
if select_fg == fg or select_fg.lower() == fg.lower():
|
||||||
|
select_fg = "#ffffff" if fg != "#ffffff" else "#000000"
|
||||||
|
|
||||||
|
# Calculate alternating row color
|
||||||
|
if bg.startswith("#"):
|
||||||
|
try:
|
||||||
|
rgb = tuple(int(bg[i : i + 2], 16) for i in (1, 3, 5))
|
||||||
|
if sum(rgb) > 384: # Light theme
|
||||||
|
alt_bg = (
|
||||||
|
f"#{max(0, rgb[0] - 10):02x}"
|
||||||
|
f"{max(0, rgb[1] - 10):02x}"
|
||||||
|
f"{max(0, rgb[2] - 10):02x}"
|
||||||
|
)
|
||||||
|
else: # Dark theme
|
||||||
|
alt_bg = (
|
||||||
|
f"#{min(255, rgb[0] + 10):02x}"
|
||||||
|
f"{min(255, rgb[1] + 10):02x}"
|
||||||
|
f"{min(255, rgb[2] + 10):02x}"
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
alt_bg = "#f5f5f5"
|
||||||
|
else:
|
||||||
|
alt_bg = "#f5f5f5"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"bg": bg,
|
||||||
|
"fg": fg,
|
||||||
|
"select_bg": select_bg,
|
||||||
|
"select_fg": select_fg,
|
||||||
|
"alt_bg": alt_bg, # Add alternating background color
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to get theme colors: {e}")
|
||||||
|
return {
|
||||||
|
"bg": "#ffffff",
|
||||||
|
"fg": "#000000",
|
||||||
|
"select_bg": "#3584e4",
|
||||||
|
"select_fg": "#ffffff",
|
||||||
|
"alt_bg": "#f5f5f5",
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
"""Tooltip system for enhanced user experience."""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
|
||||||
|
class ToolTip:
|
||||||
|
"""Create a tooltip for a given widget."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
widget: tk.Widget,
|
||||||
|
text: str,
|
||||||
|
delay: int = 500,
|
||||||
|
wrap_length: int = 250,
|
||||||
|
) -> None:
|
||||||
|
self.widget = widget
|
||||||
|
self.text = text
|
||||||
|
self.delay = delay
|
||||||
|
self.wrap_length = wrap_length
|
||||||
|
self.tooltip: tk.Toplevel | None = None
|
||||||
|
self.id_after: str | None = None
|
||||||
|
|
||||||
|
# Bind events
|
||||||
|
self.widget.bind("<Enter>", self._on_enter)
|
||||||
|
self.widget.bind("<Leave>", self._on_leave)
|
||||||
|
self.widget.bind("<ButtonPress>", self._on_leave)
|
||||||
|
|
||||||
|
def _on_enter(self, event: tk.Event | None = None) -> None:
|
||||||
|
"""Mouse entered widget - schedule tooltip."""
|
||||||
|
self._cancel_scheduled()
|
||||||
|
self.id_after = self.widget.after(self.delay, self._show_tooltip)
|
||||||
|
|
||||||
|
def _on_leave(self, event: tk.Event | None = None) -> None:
|
||||||
|
"""Mouse left widget - hide tooltip."""
|
||||||
|
self._cancel_scheduled()
|
||||||
|
self._hide_tooltip()
|
||||||
|
|
||||||
|
def _cancel_scheduled(self) -> None:
|
||||||
|
"""Cancel any scheduled tooltip."""
|
||||||
|
if self.id_after:
|
||||||
|
self.widget.after_cancel(self.id_after)
|
||||||
|
self.id_after = None
|
||||||
|
|
||||||
|
def _show_tooltip(self) -> None:
|
||||||
|
"""Display the tooltip."""
|
||||||
|
if self.tooltip:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get widget position
|
||||||
|
x = self.widget.winfo_rootx() + 25
|
||||||
|
y = self.widget.winfo_rooty() + 25
|
||||||
|
|
||||||
|
# Create tooltip window
|
||||||
|
self.tooltip = tk.Toplevel(self.widget)
|
||||||
|
self.tooltip.wm_overrideredirect(True)
|
||||||
|
self.tooltip.wm_geometry(f"+{x}+{y}")
|
||||||
|
|
||||||
|
# Create tooltip content
|
||||||
|
label = tk.Label(
|
||||||
|
self.tooltip,
|
||||||
|
text=self.text,
|
||||||
|
justify="left",
|
||||||
|
background="#ffffe0",
|
||||||
|
foreground="#000000",
|
||||||
|
relief="solid",
|
||||||
|
borderwidth=1,
|
||||||
|
font=("TkDefaultFont", "9", "normal"),
|
||||||
|
wraplength=self.wrap_length,
|
||||||
|
padx=8,
|
||||||
|
pady=6,
|
||||||
|
)
|
||||||
|
label.pack()
|
||||||
|
|
||||||
|
# Make sure tooltip appears above other windows
|
||||||
|
self.tooltip.lift()
|
||||||
|
|
||||||
|
def _hide_tooltip(self) -> None:
|
||||||
|
"""Hide the tooltip."""
|
||||||
|
if self.tooltip:
|
||||||
|
self.tooltip.destroy()
|
||||||
|
self.tooltip = None
|
||||||
|
|
||||||
|
def update_text(self, new_text: str) -> None:
|
||||||
|
"""Update the tooltip text."""
|
||||||
|
self.text = new_text
|
||||||
|
|
||||||
|
|
||||||
|
class TooltipManager:
|
||||||
|
"""Manages tooltips for UI elements."""
|
||||||
|
|
||||||
|
def __init__(self, theme_manager) -> None:
|
||||||
|
self.theme_manager = theme_manager
|
||||||
|
self.tooltips: list[ToolTip] = []
|
||||||
|
|
||||||
|
def add_tooltip(
|
||||||
|
self,
|
||||||
|
widget: tk.Widget,
|
||||||
|
text: str,
|
||||||
|
delay: int = 500,
|
||||||
|
wrap_length: int = 250,
|
||||||
|
) -> ToolTip:
|
||||||
|
"""Add a tooltip to a widget."""
|
||||||
|
tooltip = ToolTip(widget, text, delay, wrap_length)
|
||||||
|
self.tooltips.append(tooltip)
|
||||||
|
return tooltip
|
||||||
|
|
||||||
|
def add_scale_tooltip(self, scale_widget: tk.Widget, pathology_name: str) -> None:
|
||||||
|
"""Add a specialized tooltip for pathology scales."""
|
||||||
|
text = (
|
||||||
|
f"Adjust your {pathology_name} level\\n"
|
||||||
|
"• Drag the slider to set your current level\\n"
|
||||||
|
"• Higher values typically indicate worse symptoms\\n"
|
||||||
|
"• Use the full range for accurate tracking"
|
||||||
|
)
|
||||||
|
self.add_tooltip(scale_widget, text, delay=800)
|
||||||
|
|
||||||
|
def add_medicine_tooltip(self, widget: tk.Widget, medicine_name: str) -> None:
|
||||||
|
"""Add a specialized tooltip for medicine checkboxes."""
|
||||||
|
text = (
|
||||||
|
f"Mark if you took {medicine_name} today\\n"
|
||||||
|
"• Check the box when you've taken this medication\\n"
|
||||||
|
"• This helps track your medication adherence\\n"
|
||||||
|
"• You can add dose details when editing entries"
|
||||||
|
)
|
||||||
|
self.add_tooltip(widget, text, delay=600)
|
||||||
|
|
||||||
|
def add_button_tooltip(self, widget: tk.Widget, action: str) -> None:
|
||||||
|
"""Add a tooltip for action buttons."""
|
||||||
|
tooltips_map = {
|
||||||
|
"save": (
|
||||||
|
"Save your current entry (Ctrl+S)\\nThis will add a new daily record"
|
||||||
|
),
|
||||||
|
"export": (
|
||||||
|
"Export your data to various formats\\n"
|
||||||
|
"Supports CSV, PDF, and image exports"
|
||||||
|
),
|
||||||
|
"refresh": (
|
||||||
|
"Reload data from file (F5)\\nUpdates the display with latest changes"
|
||||||
|
),
|
||||||
|
"settings": (
|
||||||
|
"Open application settings (F2)\\nCustomize themes and preferences"
|
||||||
|
),
|
||||||
|
"quit": (
|
||||||
|
"Exit the application (Ctrl+Q)\\nYour data will be automatically saved"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
text = tooltips_map.get(action, f"Perform {action} action")
|
||||||
|
self.add_tooltip(widget, text, delay=400)
|
||||||
|
|
||||||
|
def add_menu_tooltip(self, widget: tk.Widget, menu_type: str) -> None:
|
||||||
|
"""Add tooltips for menu items."""
|
||||||
|
tooltips_map = {
|
||||||
|
"theme": (
|
||||||
|
"Quick theme selection\\nClick to instantly change the app's appearance"
|
||||||
|
),
|
||||||
|
"file": "File operations\\nExport data and manage files",
|
||||||
|
"tools": ("Data management tools\\nConfigure medicines and pathologies"),
|
||||||
|
"help": ("Get help and information\\nKeyboard shortcuts and about dialog"),
|
||||||
|
}
|
||||||
|
|
||||||
|
text = tooltips_map.get(menu_type, "Menu options")
|
||||||
|
self.add_tooltip(widget, text, delay=600)
|
||||||
+199
-15
@@ -11,6 +11,7 @@ from PIL import Image, ImageTk
|
|||||||
|
|
||||||
from medicine_manager import MedicineManager
|
from medicine_manager import MedicineManager
|
||||||
from pathology_manager import PathologyManager
|
from pathology_manager import PathologyManager
|
||||||
|
from tooltip_system import TooltipManager
|
||||||
|
|
||||||
|
|
||||||
class UIManager:
|
class UIManager:
|
||||||
@@ -22,11 +23,21 @@ class UIManager:
|
|||||||
logger: logging.Logger,
|
logger: logging.Logger,
|
||||||
medicine_manager: MedicineManager,
|
medicine_manager: MedicineManager,
|
||||||
pathology_manager: PathologyManager,
|
pathology_manager: PathologyManager,
|
||||||
|
theme_manager, # Import would create circular dependency
|
||||||
) -> None:
|
) -> None:
|
||||||
self.root: tk.Tk = root
|
self.root: tk.Tk = root
|
||||||
self.logger: logging.Logger = logger
|
self.logger: logging.Logger = logger
|
||||||
self.medicine_manager = medicine_manager
|
self.medicine_manager = medicine_manager
|
||||||
self.pathology_manager = pathology_manager
|
self.pathology_manager = pathology_manager
|
||||||
|
self.theme_manager = theme_manager
|
||||||
|
|
||||||
|
# Status bar attributes
|
||||||
|
self.status_bar: tk.Frame | None = None
|
||||||
|
self.status_label: tk.Label | None = None
|
||||||
|
self.file_info_label: tk.Label | None = None
|
||||||
|
|
||||||
|
# Initialize tooltip manager
|
||||||
|
self.tooltip_manager = TooltipManager(theme_manager)
|
||||||
|
|
||||||
def setup_application_icon(self, img_path: str) -> bool:
|
def setup_application_icon(self, img_path: str) -> bool:
|
||||||
"""Set up the application icon."""
|
"""Set up the application icon."""
|
||||||
@@ -65,13 +76,20 @@ class UIManager:
|
|||||||
def create_input_frame(self, parent_frame: ttk.Frame) -> dict[str, Any]:
|
def create_input_frame(self, parent_frame: ttk.Frame) -> dict[str, Any]:
|
||||||
"""Create and configure the input frame with all widgets."""
|
"""Create and configure the input frame with all widgets."""
|
||||||
# Create main container for the scrollable input frame
|
# Create main container for the scrollable input frame
|
||||||
main_container = ttk.LabelFrame(parent_frame, text="New Entry")
|
main_container = ttk.LabelFrame(
|
||||||
|
parent_frame, text="New Entry", style="Card.TLabelframe"
|
||||||
|
)
|
||||||
main_container.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
|
main_container.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
|
||||||
main_container.grid_rowconfigure(0, weight=1)
|
main_container.grid_rowconfigure(0, weight=1)
|
||||||
main_container.grid_columnconfigure(0, weight=1)
|
main_container.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
# Create canvas and scrollbar for scrolling
|
# Create canvas and scrollbar for scrolling
|
||||||
canvas = tk.Canvas(main_container, highlightthickness=0)
|
theme_colors = self.theme_manager.get_theme_colors()
|
||||||
|
canvas = tk.Canvas(
|
||||||
|
main_container,
|
||||||
|
highlightthickness=0,
|
||||||
|
bg=theme_colors["bg"],
|
||||||
|
)
|
||||||
scrollbar = ttk.Scrollbar(
|
scrollbar = ttk.Scrollbar(
|
||||||
main_container, orient="vertical", command=canvas.yview
|
main_container, orient="vertical", command=canvas.yview
|
||||||
)
|
)
|
||||||
@@ -159,7 +177,9 @@ class UIManager:
|
|||||||
ttk.Label(input_frame, text="Treatment:").grid(
|
ttk.Label(input_frame, text="Treatment:").grid(
|
||||||
row=medicine_row, column=0, sticky="w", padx=5, pady=2
|
row=medicine_row, column=0, sticky="w", padx=5, pady=2
|
||||||
)
|
)
|
||||||
medicine_frame = ttk.LabelFrame(input_frame, text="Medicine")
|
medicine_frame = ttk.LabelFrame(
|
||||||
|
input_frame, text="Medicine", style="Card.TLabelframe"
|
||||||
|
)
|
||||||
medicine_frame.grid(row=medicine_row, column=1, padx=0, pady=10, sticky="nsew")
|
medicine_frame.grid(row=medicine_row, column=1, padx=0, pady=10, sticky="nsew")
|
||||||
medicine_frame.grid_columnconfigure(0, weight=1)
|
medicine_frame.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
@@ -173,11 +193,19 @@ class UIManager:
|
|||||||
text = f"{medicine.display_name} {medicine.dosage_info}"
|
text = f"{medicine.display_name} {medicine.dosage_info}"
|
||||||
medicine_vars[medicine_key] = (var, text)
|
medicine_vars[medicine_key] = (var, text)
|
||||||
|
|
||||||
for idx, (_med_name, (var, text)) in enumerate(medicine_vars.items()):
|
for idx, (med_key, (var, text)) in enumerate(medicine_vars.items()):
|
||||||
# Just checkbox for medicine taken
|
# Just checkbox for medicine taken
|
||||||
ttk.Checkbutton(medicine_frame, text=text, variable=var).grid(
|
checkbox = ttk.Checkbutton(
|
||||||
row=idx, column=0, sticky="w", padx=5, pady=2
|
medicine_frame, text=text, variable=var, style="Modern.TCheckbutton"
|
||||||
)
|
)
|
||||||
|
checkbox.grid(row=idx, column=0, sticky="w", padx=5, pady=2)
|
||||||
|
|
||||||
|
# Add tooltip for medicine checkbox
|
||||||
|
medicine = self.medicine_manager.get_medicine(med_key)
|
||||||
|
if medicine:
|
||||||
|
self.tooltip_manager.add_medicine_tooltip(
|
||||||
|
checkbox, medicine.display_name
|
||||||
|
)
|
||||||
|
|
||||||
# Note and Date fields - adjust row numbers
|
# Note and Date fields - adjust row numbers
|
||||||
note_row = medicine_row + 1
|
note_row = medicine_row + 1
|
||||||
@@ -189,16 +217,19 @@ class UIManager:
|
|||||||
ttk.Label(input_frame, text="Note:").grid(
|
ttk.Label(input_frame, text="Note:").grid(
|
||||||
row=note_row, column=0, sticky="w", padx=5, pady=2
|
row=note_row, column=0, sticky="w", padx=5, pady=2
|
||||||
)
|
)
|
||||||
ttk.Entry(input_frame, textvariable=note_var).grid(
|
ttk.Entry(input_frame, textvariable=note_var, style="Modern.TEntry").grid(
|
||||||
row=note_row, column=1, sticky="ew", padx=5, pady=2
|
row=note_row, column=1, sticky="ew", padx=5, pady=2
|
||||||
)
|
)
|
||||||
|
|
||||||
ttk.Label(input_frame, text="Date (mm/dd/yyyy):").grid(
|
ttk.Label(input_frame, text="Date (mm/dd/yyyy):").grid(
|
||||||
row=date_row, column=0, sticky="w", padx=5, pady=2
|
row=date_row, column=0, sticky="w", padx=5, pady=2
|
||||||
)
|
)
|
||||||
ttk.Entry(input_frame, textvariable=date_var, justify="center").grid(
|
ttk.Entry(
|
||||||
row=date_row, column=1, sticky="ew", padx=5, pady=2
|
input_frame,
|
||||||
)
|
textvariable=date_var,
|
||||||
|
justify="center",
|
||||||
|
style="Modern.TEntry",
|
||||||
|
).grid(row=date_row, column=1, sticky="ew", padx=5, pady=2)
|
||||||
|
|
||||||
# Set default date to today
|
# Set default date to today
|
||||||
date_var.set(datetime.now().strftime("%m/%d/%Y"))
|
date_var.set(datetime.now().strftime("%m/%d/%Y"))
|
||||||
@@ -220,7 +251,7 @@ class UIManager:
|
|||||||
def create_table_frame(self, parent_frame: ttk.Frame) -> dict[str, Any]:
|
def create_table_frame(self, parent_frame: ttk.Frame) -> dict[str, Any]:
|
||||||
"""Create and configure the table frame with a treeview."""
|
"""Create and configure the table frame with a treeview."""
|
||||||
table_frame: ttk.LabelFrame = ttk.LabelFrame(
|
table_frame: ttk.LabelFrame = ttk.LabelFrame(
|
||||||
parent_frame, text="Log (Double-click to edit)"
|
parent_frame, text="Log (Double-click to edit)", style="Card.TLabelframe"
|
||||||
)
|
)
|
||||||
table_frame.grid(row=1, column=1, padx=10, pady=10, sticky="nsew")
|
table_frame.grid(row=1, column=1, padx=10, pady=10, sticky="nsew")
|
||||||
|
|
||||||
@@ -253,7 +284,34 @@ class UIManager:
|
|||||||
col_labels.append("Note")
|
col_labels.append("Note")
|
||||||
col_settings.append(("Note", 300, "w"))
|
col_settings.append(("Note", 300, "w"))
|
||||||
|
|
||||||
tree: ttk.Treeview = ttk.Treeview(table_frame, columns=columns, show="headings")
|
tree: ttk.Treeview = ttk.Treeview(
|
||||||
|
table_frame, columns=columns, show="headings", style="Modern.Treeview"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure treeview selection behavior
|
||||||
|
tree.configure(selectmode="browse") # Single selection mode
|
||||||
|
|
||||||
|
# Configure row tags for alternating colors
|
||||||
|
theme_colors = self.theme_manager.get_theme_colors()
|
||||||
|
tree.tag_configure("evenrow", background=theme_colors["bg"])
|
||||||
|
tree.tag_configure("oddrow", background=theme_colors["alt_bg"])
|
||||||
|
|
||||||
|
# Configure selection highlighting
|
||||||
|
tree.tag_configure(
|
||||||
|
"selected",
|
||||||
|
background=theme_colors["select_bg"],
|
||||||
|
foreground=theme_colors["select_fg"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bind selection events to ensure proper highlighting
|
||||||
|
def on_selection_change(event):
|
||||||
|
"""Handle treeview selection changes to ensure proper highlighting."""
|
||||||
|
selection = tree.selection()
|
||||||
|
if selection:
|
||||||
|
# Force focus to ensure selection is visible
|
||||||
|
tree.focus(selection[0])
|
||||||
|
|
||||||
|
tree.bind("<<TreeviewSelect>>", on_selection_change)
|
||||||
|
|
||||||
for col, label in zip(columns, col_labels, strict=False):
|
for col, label in zip(columns, col_labels, strict=False):
|
||||||
tree.heading(col, text=label)
|
tree.heading(col, text=label)
|
||||||
@@ -272,7 +330,9 @@ class UIManager:
|
|||||||
|
|
||||||
def create_graph_frame(self, parent_frame: ttk.Frame) -> ttk.LabelFrame:
|
def create_graph_frame(self, parent_frame: ttk.Frame) -> ttk.LabelFrame:
|
||||||
"""Create and configure the graph frame."""
|
"""Create and configure the graph frame."""
|
||||||
graph_frame: ttk.LabelFrame = ttk.LabelFrame(parent_frame, text="Evolution")
|
graph_frame: ttk.LabelFrame = ttk.LabelFrame(
|
||||||
|
parent_frame, text="Evolution", style="Card.TLabelframe"
|
||||||
|
)
|
||||||
graph_frame.grid(row=0, column=0, columnspan=2, padx=10, pady=10, sticky="nsew")
|
graph_frame.grid(row=0, column=0, columnspan=2, padx=10, pady=10, sticky="nsew")
|
||||||
return graph_frame
|
return graph_frame
|
||||||
|
|
||||||
@@ -284,19 +344,137 @@ class UIManager:
|
|||||||
button_frame.grid(row=7, column=0, columnspan=2, pady=10)
|
button_frame.grid(row=7, column=0, columnspan=2, pady=10)
|
||||||
|
|
||||||
for btn_config in buttons_config:
|
for btn_config in buttons_config:
|
||||||
ttk.Button(
|
button = ttk.Button(
|
||||||
button_frame,
|
button_frame,
|
||||||
text=btn_config["text"],
|
text=btn_config["text"],
|
||||||
command=btn_config["command"],
|
command=btn_config["command"],
|
||||||
).pack(
|
style="Action.TButton",
|
||||||
|
)
|
||||||
|
button.pack(
|
||||||
side="left",
|
side="left",
|
||||||
padx=5,
|
padx=5,
|
||||||
fill=btn_config.get("fill", None),
|
fill=btn_config.get("fill", None),
|
||||||
expand=btn_config.get("expand", False),
|
expand=btn_config.get("expand", False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add tooltips based on button text
|
||||||
|
button_text = btn_config["text"].lower()
|
||||||
|
if "add" in button_text or "save" in button_text:
|
||||||
|
self.tooltip_manager.add_button_tooltip(button, "save")
|
||||||
|
elif "quit" in button_text or "exit" in button_text:
|
||||||
|
self.tooltip_manager.add_button_tooltip(button, "quit")
|
||||||
|
|
||||||
return button_frame
|
return button_frame
|
||||||
|
|
||||||
|
def create_status_bar(self, parent_frame: tk.Widget) -> tk.Frame:
|
||||||
|
"""Create and configure the status bar at the bottom of the application."""
|
||||||
|
# Get theme colors for consistent styling
|
||||||
|
theme_colors = self.theme_manager.get_theme_colors()
|
||||||
|
|
||||||
|
# Create the status bar frame
|
||||||
|
self.status_bar = tk.Frame(
|
||||||
|
parent_frame,
|
||||||
|
relief=tk.SUNKEN,
|
||||||
|
bd=1,
|
||||||
|
bg=theme_colors["bg"],
|
||||||
|
)
|
||||||
|
self.status_bar.grid(row=2, column=0, columnspan=2, sticky="ew", padx=5, pady=2)
|
||||||
|
|
||||||
|
# Configure the parent to make the status bar stretch
|
||||||
|
parent_frame.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# Create status message label (left side)
|
||||||
|
self.status_label = tk.Label(
|
||||||
|
self.status_bar,
|
||||||
|
text="Ready",
|
||||||
|
anchor=tk.W,
|
||||||
|
font=("TkDefaultFont", 9),
|
||||||
|
padx=10,
|
||||||
|
pady=2,
|
||||||
|
bg=theme_colors["bg"],
|
||||||
|
fg=theme_colors["fg"],
|
||||||
|
)
|
||||||
|
self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||||
|
|
||||||
|
# Create file info label (right side)
|
||||||
|
self.file_info_label = tk.Label(
|
||||||
|
self.status_bar,
|
||||||
|
text="",
|
||||||
|
anchor=tk.E,
|
||||||
|
font=("TkDefaultFont", 9),
|
||||||
|
padx=10,
|
||||||
|
pady=2,
|
||||||
|
bg=theme_colors["bg"],
|
||||||
|
fg=theme_colors["fg"],
|
||||||
|
)
|
||||||
|
self.file_info_label.pack(side=tk.RIGHT)
|
||||||
|
|
||||||
|
return self.status_bar
|
||||||
|
|
||||||
|
def update_status(self, message: str, message_type: str = "info") -> None:
|
||||||
|
"""
|
||||||
|
Update the status bar with a message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: The message to display
|
||||||
|
message_type: Type of message ('info', 'success', 'warning', 'error')
|
||||||
|
"""
|
||||||
|
if not self.status_label:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Color mapping for different message types
|
||||||
|
colors = {
|
||||||
|
"info": "#000000", # Black
|
||||||
|
"success": "#28A745", # Green
|
||||||
|
"warning": "#FFC107", # Yellow/Orange
|
||||||
|
"error": "#DC3545", # Red
|
||||||
|
}
|
||||||
|
|
||||||
|
color = colors.get(message_type, "#000000")
|
||||||
|
self.status_label.config(text=message, fg=color)
|
||||||
|
|
||||||
|
# Clear the message after 5 seconds for non-info messages
|
||||||
|
if message_type != "info":
|
||||||
|
self.root.after(5000, lambda: self.update_status("Ready", "info"))
|
||||||
|
|
||||||
|
def update_file_info(self, filename: str, entry_count: int = 0) -> None:
|
||||||
|
"""
|
||||||
|
Update the file information in the status bar.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: Name of the current data file
|
||||||
|
entry_count: Number of entries in the file
|
||||||
|
"""
|
||||||
|
if not self.file_info_label:
|
||||||
|
return
|
||||||
|
|
||||||
|
file_display = os.path.basename(filename) if filename else "No file"
|
||||||
|
info_text = f"{file_display}"
|
||||||
|
if entry_count > 0:
|
||||||
|
info_text += f" ({entry_count} entries)"
|
||||||
|
|
||||||
|
self.file_info_label.config(text=info_text)
|
||||||
|
|
||||||
|
def show_status_message(self, message: str, duration: int = 3000) -> None:
|
||||||
|
"""
|
||||||
|
Show a temporary status message for a specific duration.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: The message to display
|
||||||
|
duration: How long to show the message in milliseconds
|
||||||
|
"""
|
||||||
|
if not self.status_label:
|
||||||
|
return
|
||||||
|
|
||||||
|
original_text = self.status_label.cget("text")
|
||||||
|
original_color = self.status_label.cget("fg")
|
||||||
|
|
||||||
|
self.status_label.config(text=message, fg="#2E86AB")
|
||||||
|
self.root.after(
|
||||||
|
duration,
|
||||||
|
lambda: self.status_label.config(text=original_text, fg=original_color),
|
||||||
|
)
|
||||||
|
|
||||||
def create_edit_window(
|
def create_edit_window(
|
||||||
self, values: tuple[str, ...], callbacks: dict[str, Callable]
|
self, values: tuple[str, ...], callbacks: dict[str, Callable]
|
||||||
) -> tk.Toplevel:
|
) -> tk.Toplevel:
|
||||||
@@ -691,9 +869,15 @@ class UIManager:
|
|||||||
variable=vars_dict[key],
|
variable=vars_dict[key],
|
||||||
orient=tk.HORIZONTAL,
|
orient=tk.HORIZONTAL,
|
||||||
length=250,
|
length=250,
|
||||||
|
style="Modern.Horizontal.TScale",
|
||||||
)
|
)
|
||||||
scale.grid(row=0, column=1, sticky="ew")
|
scale.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
|
# Add tooltip for the scale
|
||||||
|
pathology = self.pathology_manager.get_pathology(key)
|
||||||
|
if pathology:
|
||||||
|
self.tooltip_manager.add_scale_tooltip(scale, pathology.display_name)
|
||||||
|
|
||||||
# Scale labels
|
# Scale labels
|
||||||
labels_frame = ttk.Frame(scale_container)
|
labels_frame = ttk.Frame(scale_container)
|
||||||
labels_frame.grid(row=1, column=0, sticky="ew", pady=(5, 0))
|
labels_frame.grid(row=1, column=0, sticky="ew", pady=(5, 0))
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
"""
|
||||||
|
Tests for theme manager menu functionality.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
# Add src to path for imports
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||||
|
|
||||||
|
from theme_manager import ThemeManager
|
||||||
|
|
||||||
|
|
||||||
|
class TestThemeManagerMenu(unittest.TestCase):
|
||||||
|
"""Test cases for theme manager menu functionality."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up test fixtures."""
|
||||||
|
self.root = tk.Tk()
|
||||||
|
self.root.withdraw() # Hide the window during testing
|
||||||
|
self.mock_logger = Mock()
|
||||||
|
self.theme_manager = ThemeManager(self.root, self.mock_logger)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Clean up after tests."""
|
||||||
|
if self.root:
|
||||||
|
self.root.destroy()
|
||||||
|
|
||||||
|
def test_get_menu_colors(self):
|
||||||
|
"""Test that get_menu_colors returns valid color dictionary."""
|
||||||
|
colors = self.theme_manager.get_menu_colors()
|
||||||
|
|
||||||
|
# Check that all required keys are present
|
||||||
|
required_keys = ["bg", "fg", "active_bg", "active_fg", "disabled_fg"]
|
||||||
|
for key in required_keys:
|
||||||
|
self.assertIn(key, colors)
|
||||||
|
|
||||||
|
# Check that colors are valid hex strings or named colors
|
||||||
|
for key, color in colors.items():
|
||||||
|
self.assertIsInstance(color, str)
|
||||||
|
self.assertTrue(len(color) > 0)
|
||||||
|
|
||||||
|
def test_configure_menu(self):
|
||||||
|
"""Test that configure_menu applies theme to menu widget."""
|
||||||
|
menu = tk.Menu(self.root)
|
||||||
|
|
||||||
|
# Configure the menu
|
||||||
|
self.theme_manager.configure_menu(menu)
|
||||||
|
|
||||||
|
# Check that menu configuration was called
|
||||||
|
# Note: We can't directly test the visual appearance, but we can
|
||||||
|
# verify that no exceptions were raised
|
||||||
|
self.assertIsNotNone(menu)
|
||||||
|
|
||||||
|
def test_create_themed_menu(self):
|
||||||
|
"""Test that create_themed_menu creates and themes a menu."""
|
||||||
|
menu = self.theme_manager.create_themed_menu(self.root, tearoff=0)
|
||||||
|
|
||||||
|
# Check that a menu was created
|
||||||
|
self.assertIsInstance(menu, tk.Menu)
|
||||||
|
|
||||||
|
# Check that the menu has the tearoff option set
|
||||||
|
self.assertEqual(menu['tearoff'], 0)
|
||||||
|
|
||||||
|
def test_menu_colors_consistency(self):
|
||||||
|
"""Test that menu colors are consistent across theme changes."""
|
||||||
|
original_colors = self.theme_manager.get_menu_colors()
|
||||||
|
|
||||||
|
# Try to apply a different theme
|
||||||
|
available_themes = self.theme_manager.get_available_themes()
|
||||||
|
if len(available_themes) > 1:
|
||||||
|
# Apply a different theme
|
||||||
|
other_theme = available_themes[1] if available_themes[0] == self.theme_manager.current_theme else available_themes[0]
|
||||||
|
self.theme_manager.apply_theme(other_theme)
|
||||||
|
|
||||||
|
# Get new colors
|
||||||
|
new_colors = self.theme_manager.get_menu_colors()
|
||||||
|
|
||||||
|
# Colors should still have the same structure
|
||||||
|
self.assertEqual(set(original_colors.keys()), set(new_colors.keys()))
|
||||||
|
|
||||||
|
# Colors might be different (which is expected)
|
||||||
|
# Just ensure they're still valid
|
||||||
|
for color in new_colors.values():
|
||||||
|
self.assertIsInstance(color, str)
|
||||||
|
self.assertTrue(len(color) > 0)
|
||||||
|
|
||||||
|
@patch('tkinter.Menu')
|
||||||
|
def test_create_themed_menu_error_handling(self, mock_menu_class):
|
||||||
|
"""Test that create_themed_menu handles errors gracefully."""
|
||||||
|
# Make the Menu constructor raise an exception
|
||||||
|
mock_menu_class.side_effect = Exception("Test error")
|
||||||
|
|
||||||
|
# This should not raise an exception but return a fallback menu
|
||||||
|
menu = self.theme_manager.create_themed_menu(self.root)
|
||||||
|
|
||||||
|
# The method should still return something (fallback behavior)
|
||||||
|
self.assertIsNotNone(menu)
|
||||||
|
|
||||||
|
def test_menu_theme_integration(self):
|
||||||
|
"""Test complete menu theming workflow."""
|
||||||
|
# Create a menu structure similar to the main application
|
||||||
|
menubar = self.theme_manager.create_themed_menu(self.root)
|
||||||
|
file_menu = self.theme_manager.create_themed_menu(menubar, tearoff=0)
|
||||||
|
theme_menu = self.theme_manager.create_themed_menu(menubar, tearoff=0)
|
||||||
|
|
||||||
|
# Add some menu items
|
||||||
|
file_menu.add_command(label="Test Item 1")
|
||||||
|
file_menu.add_separator()
|
||||||
|
file_menu.add_command(label="Test Item 2")
|
||||||
|
|
||||||
|
# Add theme selection items
|
||||||
|
available_themes = self.theme_manager.get_available_themes()
|
||||||
|
for theme in available_themes:
|
||||||
|
theme_menu.add_radiobutton(label=theme.title())
|
||||||
|
|
||||||
|
# Verify structure was created successfully
|
||||||
|
self.assertIsInstance(menubar, tk.Menu)
|
||||||
|
self.assertIsInstance(file_menu, tk.Menu)
|
||||||
|
self.assertIsInstance(theme_menu, tk.Menu)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 3
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -757,7 +757,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thechart"
|
name = "thechart"
|
||||||
version = "1.8.5"
|
version = "1.9.5"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorlog" },
|
{ name = "colorlog" },
|
||||||
@@ -767,6 +767,7 @@ dependencies = [
|
|||||||
{ name = "pandas" },
|
{ name = "pandas" },
|
||||||
{ name = "reportlab" },
|
{ name = "reportlab" },
|
||||||
{ name = "tk" },
|
{ name = "tk" },
|
||||||
|
{ name = "ttkthemes" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
@@ -789,6 +790,7 @@ requires-dist = [
|
|||||||
{ name = "pandas", specifier = ">=2.3.1" },
|
{ name = "pandas", specifier = ">=2.3.1" },
|
||||||
{ name = "reportlab", specifier = ">=4.4.3" },
|
{ name = "reportlab", specifier = ">=4.4.3" },
|
||||||
{ name = "tk", specifier = ">=0.1.0" },
|
{ name = "tk", specifier = ">=0.1.0" },
|
||||||
|
{ name = "ttkthemes", specifier = ">=3.2.2" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
@@ -811,6 +813,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/1e/0b/029cbdb868bb555fed99bf6540fff072d500b3f895873709f25084e85e33/tk-0.1.0-py3-none-any.whl", hash = "sha256:703a69ff0d5ba2bd2f7440582ad10160e4a6561595d33457dc6caa79b9bf4930", size = 3879, upload-time = "2019-07-08T06:51:55.175Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/0b/029cbdb868bb555fed99bf6540fff072d500b3f895873709f25084e85e33/tk-0.1.0-py3-none-any.whl", hash = "sha256:703a69ff0d5ba2bd2f7440582ad10160e4a6561595d33457dc6caa79b9bf4930", size = 3879, upload-time = "2019-07-08T06:51:55.175Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ttkthemes"
|
||||||
|
version = "3.2.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pillow" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fa/45/ab8ada55281af99a03bc0f8be53a502eb37ee34b94819a9ced89e8b0c12f/ttkthemes-3.2.2.tar.gz", hash = "sha256:01daed001f2ff0e4f32832a0d9ea48176c0c505203b030756bdde3bd1bcb21d2", size = 891159, upload-time = "2021-02-15T12:57:14.719Z" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tzdata"
|
name = "tzdata"
|
||||||
version = "2025.2"
|
version = "2025.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user