Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ make test
|
|||||||
|
|
||||||
## 📚 Documentation
|
## 📚 Documentation
|
||||||
- **[Features Guide](docs/FEATURES.md)** - Complete feature documentation
|
- **[Features Guide](docs/FEATURES.md)** - Complete feature documentation
|
||||||
|
- **[Keyboard Shortcuts](docs/KEYBOARD_SHORTCUTS.md)** - Keyboard shortcuts for efficient navigation
|
||||||
- **[Export System](docs/EXPORT_SYSTEM.md)** - Data export functionality and formats
|
- **[Export System](docs/EXPORT_SYSTEM.md)** - Data export functionality and formats
|
||||||
- **[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 feature evolution
|
||||||
@@ -483,6 +484,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,40 @@ 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.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
|
||||||
|
|||||||
@@ -159,6 +159,37 @@ 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
|
### 🏗️ Modular Design
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -11,6 +11,11 @@ Welcome to TheChart documentation! This guide will help you navigate the availab
|
|||||||
- Advanced Dose Tracking
|
- Advanced Dose Tracking
|
||||||
- Graph Visualizations
|
- Graph Visualizations
|
||||||
- Data Management
|
- Data Management
|
||||||
|
- Keyboard Shortcuts
|
||||||
|
- **[Keyboard Shortcuts](KEYBOARD_SHORTCUTS.md)** - Comprehensive shortcut reference
|
||||||
|
- File operations shortcuts
|
||||||
|
- Data management shortcuts
|
||||||
|
- Navigation shortcuts
|
||||||
|
|
||||||
### For Developers
|
### For Developers
|
||||||
- **[Development Guide](DEVELOPMENT.md)** - Development setup and testing
|
- **[Development Guide](DEVELOPMENT.md)** - Development setup and testing
|
||||||
|
|||||||
+1
-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"
|
||||||
|
|||||||
@@ -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)
|
||||||
+228
-34
@@ -72,6 +72,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()
|
||||||
|
|
||||||
@@ -108,7 +111,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 +144,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,9 +158,15 @@ 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 = tk.Menu(self.root)
|
||||||
@@ -166,38 +175,152 @@ class MedTrackerApp:
|
|||||||
# File menu
|
# File menu
|
||||||
file_menu = tk.Menu(menubar, tearoff=0)
|
file_menu = tk.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 = tk.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"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Help menu
|
||||||
|
help_menu = tk.Menu(menubar, tearoff=0)
|
||||||
|
menubar.add_cascade(label="Help", menu=help_menu)
|
||||||
|
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())
|
||||||
|
|
||||||
|
# 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"""
|
||||||
|
|
||||||
|
messagebox.showinfo("Keyboard Shortcuts", shortcuts_text, parent=self.root)
|
||||||
|
|
||||||
|
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 _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 +341,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 +361,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 +515,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 +528,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 +536,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 +581,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 +597,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 +605,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 +620,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 +651,52 @@ 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
|
||||||
|
for _index, row in display_df.iterrows():
|
||||||
|
self.tree.insert(parent="", index="end", values=list(row))
|
||||||
|
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__":
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ class UIManager:
|
|||||||
self.medicine_manager = medicine_manager
|
self.medicine_manager = medicine_manager
|
||||||
self.pathology_manager = pathology_manager
|
self.pathology_manager = pathology_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
|
||||||
|
|
||||||
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."""
|
||||||
try:
|
try:
|
||||||
@@ -297,6 +302,103 @@ class UIManager:
|
|||||||
|
|
||||||
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."""
|
||||||
|
# Create the status bar frame
|
||||||
|
self.status_bar = tk.Frame(parent_frame, relief=tk.SUNKEN, bd=1)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
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:
|
||||||
|
|||||||
Reference in New Issue
Block a user