diff --git a/Documentation.md b/Documentation.md index 4bdd649..50641f1 100644 --- a/Documentation.md +++ b/Documentation.md @@ -1,52 +1,46 @@ -# PNG Metadata Editor - Technical Documentation +# PNG Metadata Editor - Technical Documentation v2.3.0 ## Table of Contents 1. [Overview](#overview) 2. [Build System](#build-system) -3. [Code Structure](#code-structure) -4. [Class Reference](#class-reference) -5. [Function Documentation](#function-documentation) -6. [Data Flow](#data-flow) -7. [Usage Examples](#usage-examples) -8. [Troubleshooting](#troubleshooting) +3. [Architecture](#architecture) +4. [Code Structure](#code-structure) +5. [Class Reference](#class-reference) +6. [Function Documentation](#function-documentation) +7. [Data Flow](#data-flow) +8. [Theme System](#theme-system) +9. [Usage Examples](#usage-examples) +10. [Troubleshooting](#troubleshooting) ## Overview -PNG Metadata Editor is a graphical application built with Python and Tkinter that allows users to view, edit, add, and delete metadata in PNG files. The application leverages the Pillow library for PNG manipulation. +PNG Metadata Editor v2.3.0 is a graphical application built with Python and Tkinter that allows users to view, edit, add, and delete metadata in PNG files. The application features an image browser with thumbnail previews, automatic dark mode detection, and intelligent JSON handling. + +### Key Technologies +- **Python 3.6+**: Core language +- **Tkinter**: GUI framework +- **Pillow (PIL)**: PNG image manipulation and metadata extraction +- **subprocess**: System theme detection + +### Version 2.3.0 Features +- Image browser with thumbnail previews +- Automatic dark mode detection (macOS, Windows, Linux) +- Smart JSON flattening (preserves original format) +- Enhanced file handle management +- Trackpad and mousewheel scrolling support +- Resizable UI panes ## Build System ### build.py Functionality -The `build.py` script provides a unified way to build the application across platforms: - -1. **Dependency Management**: - - Checks for `requirements.txt` and installs dependencies - - Falls back to default dependencies if file not found - -2. **PyInstaller Handling**: - - Verifies PyInstaller is installed - - Automatically installs it if missing - - Provides clear error messages if installation fails - -3. **Platform-Specific Builds**: - - Detects operating system (Windows, macOS, Linux) - - Applies appropriate build options for each platform - - Includes version information from `version.txt` when available - -4. **Build Process**: - ```python - # Main build workflow: - 1. Check/install PyInstaller - 2. Install dependencies - 3. Determine platform options - 4. Execute PyInstaller build - ``` +The `build.py` script provides a unified way to build the application across platforms with automatic dependency management, PyInstaller handling, and platform-specific build options. ### Build Requirements - Python 3.6+ - PyInstaller (auto-installed if missing) +- Pillow library - Platform-specific icon files: - `AppIcon.ico` for Windows - `AppIcon.icns` for macOS @@ -57,167 +51,286 @@ The `build.py` script provides a unified way to build the application across pla - Temporary build files are stored in `build/` - Version information is embedded from `version.txt` +## Architecture + +### Component Overview + +``` ++-----------------------------------------------+ +| PNG Metadata Editor v2.3.0 | ++-----------------------------------------------+ +| +-----------+ +------------------------+ | +| | Image | | Metadata Editor | | +| | Browser | | +------------------+ | | +| | | | | Tree View | | | +| | Thumbnails| | +------------------+ | | +| | + Scroll | | | Detail Pane | | | +| +-----------+ | +------------------+ | | +| | Edit/Add/Delete | | +| +------------------------+ | ++-----------------------------------------------+ +| Status Bar & Controls | ++-----------------------------------------------+ +``` + +### Design Patterns + +1. **MVC Pattern**: + - Model: `metadata_dict` + - View: Tkinter UI components + - Controller: Event handlers and methods + +2. **Observer Pattern**: UI updates when metadata changes via `refresh_tree()` + +3. **Strategy Pattern**: Platform-specific theme detection + ## Code Structure ``` png-meta-editor.py -├── Constants (APP_VERSION, APP_NAME) -├── PNGMetadataEditor (Main Class) -│ ├── __init__() - Initialization -│ ├── setup_ui() - UI Construction -│ ├── open_file() - File Operations -│ └── Various helper methods -└── Supporting Functions - ├── format_value_for_display() - ├── truncate_value() - └── mark_as_modified() +| ++-- Constants +| +-- APP_VERSION = "2.3.0" +| +-- APP_NAME = "PNG Metadata Editor" +| ++-- PNGMetadataEditor (Main Class) + +-- __init__() - Initialization + +-- Theme System + | +-- detect_dark_mode() + | +-- get_theme_colors() + +-- UI Construction + | +-- setup_ui() + | +-- Image Browser (left pane) + | +-- Metadata Editor (right pane) + +-- Image Browser + | +-- browse_directory() + | +-- load_directory_thumbnails() + | +-- load_file_from_path() + | +-- scroll_to_thumbnail() + +-- Metadata Operations + | +-- open_file() + | +-- save_changes() + | +-- add_field() + | +-- edit_entry() + | +-- delete_field() + +-- JSON Handling + | +-- format_value_for_display() + | +-- flatten_json_if_valid() + +-- Helper Methods + +-- refresh_tree() + +-- set_status() + +-- mark_as_modified() + ``` ## Class Reference ### PNGMetadataEditor Class -```python -class PNGMetadataEditor: - def __init__(self, root): - # Initialize application state - self.current_file = None # Currently loaded PNG file path - self.metadata_dict = {} # Dictionary storing all metadata fields - self.has_unsaved_changes = False # Track unsaved modifications - self.status_timer = None # Timer for status messages -``` +Instance variables: +- `current_file`: Currently loaded PNG file path +- `current_directory`: Current directory for browser +- `metadata_dict`: Dictionary storing metadata fields +- `has_unsaved_changes`: Track unsaved modifications +- `status_timer`: Timer for status messages +- `thumbnail_images`: PhotoImage references for thumbnails +- `thumbnail_frames`: Frame references for highlighting +- `theme_colors`: Current theme color scheme ### Public Methods | Method | Description | |--------|-------------| -| `open_file()` | Loads PNG file and extracts metadata | +| `detect_dark_mode()` | Detects OS dark mode setting | +| `get_theme_colors()` | Returns color scheme based on theme | +| `browse_directory()` | Opens directory browser for thumbnails | +| `load_directory_thumbnails(directory)` | Loads PNG thumbnails from directory | +| `load_file_from_path(filepath)` | Loads specific PNG file metadata | +| `open_file()` | Opens file dialog and loads PNG | | `save_changes()` | Saves current metadata to file | | `add_field()` | Opens dialog to add new metadata field | | `edit_entry(event=None)` | Opens dialog to edit selected field | | `delete_field()` | Removes selected metadata field | -| `copy_value()` | Copies selected metadata value to clipboard | -| `show_about()` | Displays application about dialog | - -### Helper Methods - -```python -def format_value_for_display(self, value): - """Try to parse as JSON for pretty printing""" - try: - return json.dumps(json.loads(value), indent=2) - except: - return str(value) - -def truncate_value(self, value, max_length=80): - """Shorten long values for tree display""" - return str(value)[:max_length] + "..." if len(str(value)) > max_length else str(value) - -def mark_as_modified(self): - """Mark the document as having unsaved changes""" - if not self.has_unsaved_changes: - self.has_unsaved_changes = True - self.update_title() - self.changes_label.config(text="Unsaved changes") -``` +| `copy_value()` | Copies selected value to clipboard | +| `flatten_json_if_valid(value)` | Flattens JSON to preserve format | ## Function Documentation -### Core Functions +### Theme System -1. **`open_file()`** - - Opens a file dialog to select PNG file - - Extracts text chunks using PIL's `Image.text` attribute - - Populates `metadata_dict` with key-value pairs - - Refreshes UI to display new metadata +**detect_dark_mode()** +- Detects if OS is in dark mode +- Platform-specific commands with 1-second timeout +- Returns: `bool` (True if dark mode, False otherwise) -2. **`save_changes()`** - - Creates new `PngInfo` object with current metadata - - Saves image with updated metadata using PIL's `img.save()` - - Marks changes as saved and updates UI state +**get_theme_colors()** +- Returns color scheme dictionary based on detected theme +- Dark mode: #2b2b2b backgrounds, #ffffff text +- Light mode: #f0f0f0 backgrounds, #000000 text -3. **`refresh_tree()`** - - Clears existing tree view items - - Repopulates with current metadata from `metadata_dict` - - Truncates long values for display in tree +### Image Browser Functions -4. **`on_selection_change(event)`** - - Updates detail view when selection changes - - Formats selected value for display (JSON pretty-printing) - - Handles both JSON and plain text values +**load_directory_thumbnails(directory)** +- Loads 150x150 thumbnails for all PNG files +- Shows loading indicator with progress +- Updates scroll region 3 times (0ms, 100ms, 300ms) -### UI Management Functions +**load_file_from_path(filepath, auto_scroll=True)** +- Opens image with PIL +- Extracts text chunks to metadata_dict +- **Critical**: Closes image file handle with `img.close()` +- Refreshes UI and scrolls to thumbnail -1. **`set_status(message, duration=3000, color="")`** - - Displays temporary message in status bar - - Automatically clears after specified duration (ms) - - Supports colored messages for different status types +**scroll_to_thumbnail(filepath)** +- Centers selected thumbnail in viewport +- Uses `yview_moveto()` for smooth scrolling -2. **`update_title()`** - - Updates window title based on current state - - Shows filename when file is loaded - - Adds asterisk (*) for unsaved changes +### JSON Handling Functions -3. **`show_about()`** - - Displays application information dialog - - Shows version, description, author, and license - - Uses `messagebox.showinfo()` for standard dialog +**format_value_for_display(value)** +- Pretty-prints JSON with `indent=2` for editor display +- Falls back to original string if not valid JSON -### Data Handling Functions - -1. **`format_value_for_display(value)`** - - Attempts to parse value as JSON - - Returns pretty-printed JSON if successful - - Falls back to string representation otherwise - -2. **`truncate_value(value, max_length=80)`** - - Shortens long values for tree display - - Adds ellipsis (...) to truncated values - - Preserves full value in detail view - -3. **`mark_as_modified()`** - - Sets unsaved changes flag - - Updates UI indicators (title, label) - - Prevents duplicate change notifications +**flatten_json_if_valid(value)** +- Flattens JSON to single line: `separators=(',', ':')` +- Preserves original compact format before saving +- Falls back to original string if not valid JSON ## Data Flow -1. **File Opening**: - ```mermaid - sequenceDiagram - User->>App: Click "Open PNG File" - App->>FileDialog: Show open dialog - User->>App: Select PNG file - App->>PIL: Image.open(filepath) - PIL->>App: Extract text chunks - App->>metadata_dict: Store key-value pairs - App->>UI: Refresh tree and detail view - ``` +### 1. File Opening with Image Browser -2. **Editing Process**: - ```mermaid - sequenceDiagram - User->>App: Select field in tree - App->>on_selection_change: Update detail view - User->>App: Click "Edit Field" - App->>Dialog: Show edit dialog - User->>App: Modify value - User->>App: Click "Save" - App->>metadata_dict: Update value - App->>mark_as_modified: Set unsaved flag - App->>UI: Refresh tree view - ``` +```mermaid +sequenceDiagram + participant User + participant App + participant FileDialog + participant PIL + participant UI -3. **Saving**: - ```mermaid - sequenceDiagram - User->>App: Click "Save Changes" - App->>PIL: Image.open(current_file) - App->>PngInfo: Create new metadata object - App->>PIL: img.save(pnginfo=metadata) - App->>mark_as_saved: Clear unsaved flag - App->>UI: Update status and title - ``` + User->>App: Click "Browse Directory" + App->>FileDialog: Show directory dialog + User->>App: Select directory + App->>App: load_directory_thumbnails() + App->>UI: Show loading indicator + loop For each PNG file + App->>PIL: Open and create thumbnail + App->>UI: Create thumbnail frame + end + App->>UI: Update scroll region (3x) + User->>App: Click thumbnail + App->>App: load_file_from_path() + App->>PIL: Image.open(filepath) + PIL->>App: Extract text chunks + App->>PIL: img.close() <- IMPORTANT + App->>App: Store in metadata_dict + App->>UI: Refresh tree and detail view + App->>UI: Scroll to selected thumbnail +``` + +### 2. Editing Process with JSON Flattening + +```mermaid +sequenceDiagram + participant User + participant App + participant Dialog + participant metadata_dict + participant UI + + User->>App: Select field in tree + App->>App: on_selection_change() + App->>App: format_value_for_display() + Note over App: Pretty-print JSON with indent=2 + App->>UI: Show formatted value + User->>App: Click "Edit Field" + App->>Dialog: Show edit dialog with formatted JSON + User->>Dialog: Modify value + User->>Dialog: Click "Save" + Dialog->>App: Return edited value + App->>App: flatten_json_if_valid() + Note over App: Flatten to single line + App->>metadata_dict: Update with flattened value + App->>App: mark_as_modified() + App->>UI: Refresh tree view +``` + +### 3. Saving Metadata + +```mermaid +sequenceDiagram + participant User + participant App + participant PIL + participant metadata_dict + participant File + + User->>App: Click "Save Changes" + App->>PIL: Image.open(current_file) + App->>PIL: Create PngInfo object + loop For each metadata field + App->>metadata_dict: Get key-value pair + Note over App: Values already flattened + App->>PIL: metadata.add_text(key, value) + end + App->>PIL: img.save(pnginfo=metadata) + PIL->>File: Write PNG with metadata + App->>App: mark_as_saved() + App->>App: Update UI (remove asterisk) + App->>App: Show success status +``` + +### 4. Theme Detection Flow + +```mermaid +sequenceDiagram + participant App + participant detect_dark_mode + participant subprocess + participant winreg + participant get_theme_colors + participant UI + + App->>detect_dark_mode: Check OS theme + alt macOS + detect_dark_mode->>subprocess: defaults read AppleInterfaceStyle + subprocess-->>detect_dark_mode: "Dark" or error + else Windows + detect_dark_mode->>winreg: Read AppsUseLightTheme + winreg-->>detect_dark_mode: 0=Dark, 1=Light + else Linux + detect_dark_mode->>subprocess: gsettings get gtk-theme + subprocess-->>detect_dark_mode: theme name + end + detect_dark_mode-->>App: Return boolean (True=Dark) + App->>get_theme_colors: Get color scheme + get_theme_colors-->>App: Return color dict + App->>UI: Apply colors to canvas/frames +``` + +## Theme System + +### Color Schemes + +**Dark Mode** (when OS theme is dark): +- canvas_bg: #2b2b2b (dark gray) +- frame_bg: #2b2b2b +- text_fg: #ffffff (white) +- loading_fg: #6bb6ff (light blue) + +**Light Mode** (when OS theme is light): +- canvas_bg: #f0f0f0 (light gray) +- frame_bg: #f0f0f0 +- text_fg: #000000 (black) +- loading_fg: #4a90d9 (blue) + +### Platform-Specific Detection + +| Platform | Method | Command/Registry | +|----------|--------|------------------| +| macOS | `defaults` | `defaults read -g AppleInterfaceStyle` | +| Windows | Registry | `HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme` | +| Linux | `gsettings` | `gsettings get org.gnome.desktop.interface gtk-theme` | ## Usage Examples @@ -227,28 +340,31 @@ def mark_as_modified(self): root = tk.Tk() app = PNGMetadataEditor(root) -# Open a file programmatically (simulated) -app.current_file = "example.png" -img = Image.open("example.png") -app.metadata_dict = dict(img.text) -app.refresh_tree() +# Browse directory (simulated) +app.current_directory = "/path/to/images" +app.load_directory_thumbnails("/path/to/images") -# Edit a field programmatically -app.edit_entry() # Would normally be triggered by UI event +# Load specific file +app.load_file_from_path(Path("/path/to/image.png")) + +# Edit metadata +app.metadata_dict["prompt"] = '{"text":"a cat","steps":20}' +app.mark_as_modified() +app.refresh_tree() # Save changes app.save_changes() ``` -### Programmatic Access +### Programmatic Metadata Access ```python -# Get all metadata as dictionary +# Get all metadata metadata = app.metadata_dict -# Add new field programmatically -app.metadata_dict["NewField"] = "Value" +# Add field with JSON (will be flattened on save) +json_value = json.dumps({"key": "value"}, indent=2) +app.metadata_dict["NewField"] = json_value app.mark_as_modified() -app.refresh_tree() # Check for unsaved changes if app.has_unsaved_changes: @@ -257,50 +373,102 @@ if app.has_unsaved_changes: ## Troubleshooting +### Fixed in v2.3.0 + +**Can't Load Second File** +- Status: FIXED +- Cause: Image file handle not closed +- Solution: Added `img.close()` after reading metadata + +**JSON Format Changes After Edit** +- Status: FIXED +- Cause: Pretty-printed JSON was saved as-is +- Solution: `flatten_json_if_valid()` restores compact format + +**Scroll Stops Before End of Thumbnails** +- Status: FIXED +- Cause: Scroll region not updated after thumbnail loading +- Solution: Triple scroll region update (0ms, 100ms, 300ms) + ### Common Issues -1. **File Not Opening**: - - Ensure file is a valid PNG - - Check file permissions -2. **Metadata Not Displaying**: - - Verify PNG contains text chunks - - Check for special characters in keys/values +**File Not Opening** +- Ensure file is a valid PNG +- Check file permissions +- Verify file isn't locked by another process -3. **Save Failures**: - - Ensure file isn't read-only - - Verify write permissions to directory +**Metadata Not Displaying** +- Verify PNG contains text chunks (tEXt, zTXt, iTXt) +- Check for special characters in keys/values -### Debugging Tips -- Check status bar for error messages -- Review console output for exceptions -- Verify metadata_dict contents in debugger +**Save Failures** +- Ensure file isn't read-only +- Verify write permissions to directory +- Check available disk space + +**Dark Mode Not Working** +- Verify theme detection command access +- Check terminal for subprocess errors +- Ensure permissions for `defaults` or `gsettings` ## Development Notes -### Build System Improvements -1. **Automatic Dependency Installation**: - - Script checks for `requirements.txt` - - Installs dependencies automatically +### v2.3.0 Architecture Decisions -2. **PyInstaller Management**: - - Verifies PyInstaller installation - - Auto-installs if missing +1. **JSON Flattening Strategy**: + - Display formatted JSON for readability + - Store flattened JSON to preserve original format + - Automatic conversion in save workflows -3. **Platform Detection**: - - Automatically applies correct build options - - Handles version information consistently +2. **File Handle Management**: + - Always close PIL Image objects after reading + - Prevents file locking issues + - Enables reliable multi-file loading + +3. **Scroll Region Updates**: + - Triple update strategy (0ms, 100ms, 300ms) + - Handles timing issues with tkinter geometry + - Ensures scroll region encompasses all thumbnails + +4. **Theme Detection**: + - Platform-specific detection methods + - Fallback to light mode if detection fails + - Applied once at startup (not reactive) ### Future Improvements + - Batch processing for multiple files - Metadata validation rules - Export/import to JSON - Advanced search functionality +- Undo/redo support +- Reactive theme changes (watch for OS theme changes) +- Async thumbnail loading +- Virtual scrolling for large directories ## Contributing When contributing to this project: -1. Fork the repository -2. Create a feature branch -3. Submit a pull request with tests -For major changes, please open an issue first to discuss what you would like to change. \ No newline at end of file +1. **Code Style**: Follow PEP 8, use type hints, document methods +2. **Testing**: Test on all platforms, verify dark mode, test large directories +3. **Pull Requests**: Fork, create feature branch, submit with clear description +4. **Issues**: Include version, reproduction steps, sample PNG if relevant + +For major changes, please open an issue first to discuss. + +## Version History + +- **v2.3.0 (2026-01-07)**: Major UI update + - Added image browser with thumbnail previews + - Implemented dark mode detection for all platforms + - Smart JSON flattening (preserves original format) + - Fixed file loading with proper handle management + - Enhanced scroll region updates + - Unicode support in UI + +- **v1.0.0 (2026-01-05)**: Initial release + - Basic metadata viewing and editing + - Tree view with detail pane + - Add, edit, delete operations + - JSON pretty-printing \ No newline at end of file