iOpenPod 2.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- device_registry/__init__.py +68 -0
- device_registry/authority.py +671 -0
- device_registry/data/__init__.py +1 -0
- device_registry/data/catalog.py +1115 -0
- device_registry/identifiers.py +44 -0
- device_registry/metadata.py +352 -0
- device_registry/models.py +403 -0
- device_registry/py.typed +1 -0
- device_registry/registry.py +389 -0
- iOpenPod/GUI/PROTOTYPE.md +218 -0
- iOpenPod/GUI/delegates/album_card_delegate.py +155 -0
- iOpenPod/GUI/delegates/collection_card_delegate.py +124 -0
- iOpenPod/GUI/delegates/collection_list_delegate.py +172 -0
- iOpenPod/GUI/delegates/photo_album_membership_delegate.py +168 -0
- iOpenPod/GUI/delegates/photo_card_delegate.py +142 -0
- iOpenPod/GUI/delegates/podcast_delegates.py +726 -0
- iOpenPod/GUI/delegates/selection_group_delegate.py +326 -0
- iOpenPod/GUI/delegates/track_artwork_delegate.py +92 -0
- iOpenPod/GUI/dialogs/artwork_editor.py +816 -0
- iOpenPod/GUI/dialogs/device_picker.py +396 -0
- iOpenPod/GUI/dialogs/external_playlist_files.py +286 -0
- iOpenPod/GUI/dialogs/library_review.py +379 -0
- iOpenPod/GUI/dialogs/linux_identity_setup.py +182 -0
- iOpenPod/GUI/dialogs/media_folders.py +440 -0
- iOpenPod/GUI/dialogs/media_tools_setup.py +175 -0
- iOpenPod/GUI/dialogs/metadata_editor.py +819 -0
- iOpenPod/GUI/dialogs/photo_album_editor.py +122 -0
- iOpenPod/GUI/dialogs/photo_album_manager.py +143 -0
- iOpenPod/GUI/dialogs/photo_metadata_editor.py +365 -0
- iOpenPod/GUI/dialogs/playlist_editor.py +1064 -0
- iOpenPod/GUI/dialogs/playlist_export.py +148 -0
- iOpenPod/GUI/dialogs/podcast_search.py +326 -0
- iOpenPod/GUI/dialogs/podcast_sync_settings.py +224 -0
- iOpenPod/GUI/dialogs/scrobble_report.py +68 -0
- iOpenPod/GUI/dialogs/tag_normalizer.py +489 -0
- iOpenPod/GUI/host_library_browser.py +312 -0
- iOpenPod/GUI/main_window.py +2069 -0
- iOpenPod/GUI/navigation.py +113 -0
- iOpenPod/GUI/pages/backup_page.py +1254 -0
- iOpenPod/GUI/pages/collection_page.py +504 -0
- iOpenPod/GUI/pages/library_page.py +227 -0
- iOpenPod/GUI/pages/photo_page.py +753 -0
- iOpenPod/GUI/pages/placeholder_page.py +75 -0
- iOpenPod/GUI/pages/playlist_page.py +486 -0
- iOpenPod/GUI/pages/podcast_page.py +1049 -0
- iOpenPod/GUI/pages/settings_page.py +950 -0
- iOpenPod/GUI/pages/sync_execution_page.py +589 -0
- iOpenPod/GUI/pages/sync_plan_page.py +401 -0
- iOpenPod/GUI/pages/synesthesia_page.py +175 -0
- iOpenPod/GUI/pages/track_page.py +90 -0
- iOpenPod/GUI/presentation/application_icon.py +13 -0
- iOpenPod/GUI/presentation/artwork.py +128 -0
- iOpenPod/GUI/presentation/artwork_provider.py +317 -0
- iOpenPod/GUI/presentation/backup_messages.py +480 -0
- iOpenPod/GUI/presentation/collection_text.py +27 -0
- iOpenPod/GUI/presentation/device_images.py +81 -0
- iOpenPod/GUI/presentation/display.py +20 -0
- iOpenPod/GUI/presentation/eta.py +61 -0
- iOpenPod/GUI/presentation/i18n/manager.py +146 -0
- iOpenPod/GUI/presentation/i18n/text.py +155 -0
- iOpenPod/GUI/presentation/i18n/translations/iopenpod_de.qm +0 -0
- iOpenPod/GUI/presentation/i18n/translations/iopenpod_de.ts +10060 -0
- iOpenPod/GUI/presentation/i18n/translations/iopenpod_es.qm +0 -0
- iOpenPod/GUI/presentation/i18n/translations/iopenpod_es.ts +10060 -0
- iOpenPod/GUI/presentation/i18n/workflow.py +24 -0
- iOpenPod/GUI/presentation/icons.py +51 -0
- iOpenPod/GUI/presentation/image_color.py +136 -0
- iOpenPod/GUI/presentation/library_card.py +252 -0
- iOpenPod/GUI/presentation/middle_mouse_scrolling.py +208 -0
- iOpenPod/GUI/presentation/photo.py +47 -0
- iOpenPod/GUI/presentation/photo_provider.py +182 -0
- iOpenPod/GUI/presentation/podcast_artwork_provider.py +83 -0
- iOpenPod/GUI/presentation/podcast_collection_artwork.py +91 -0
- iOpenPod/GUI/presentation/selection_checkbox.py +148 -0
- iOpenPod/GUI/presentation/theme/backup_styles.py +84 -0
- iOpenPod/GUI/presentation/theme/checkbox_style.py +173 -0
- iOpenPod/GUI/presentation/theme/dynamic.py +187 -0
- iOpenPod/GUI/presentation/theme/manager.py +235 -0
- iOpenPod/GUI/presentation/theme/podcast_styles.py +163 -0
- iOpenPod/GUI/presentation/theme/stylesheet.py +2322 -0
- iOpenPod/GUI/presentation/theme/tokens.py +631 -0
- iOpenPod/GUI/presentation/track_drag_preview.py +229 -0
- iOpenPod/GUI/sync_workspace.py +795 -0
- iOpenPod/GUI/synesthesia/__init__.py +36 -0
- iOpenPod/GUI/synesthesia/conductor.py +677 -0
- iOpenPod/GUI/synesthesia/motion_phases.py +81 -0
- iOpenPod/GUI/synesthesia/persistent_world.py +751 -0
- iOpenPod/GUI/synesthesia/preview.py +76 -0
- iOpenPod/GUI/synesthesia/render_resolution.py +104 -0
- iOpenPod/GUI/synesthesia/renderer.py +1653 -0
- iOpenPod/GUI/synesthesia/scene_director.py +1632 -0
- iOpenPod/GUI/synesthesia/shader_sources/comets.frag +21 -0
- iOpenPod/GUI/synesthesia/shader_sources/comets.frag.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/comets.vert +98 -0
- iOpenPod/GUI/synesthesia/shader_sources/comets.vert.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/events.frag +166 -0
- iOpenPod/GUI/synesthesia/shader_sources/events.frag.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/feedback.frag +144 -0
- iOpenPod/GUI/synesthesia/shader_sources/feedback.frag.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/field_sim.comp +200 -0
- iOpenPod/GUI/synesthesia/shader_sources/field_sim.comp.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/filaments.frag +21 -0
- iOpenPod/GUI/synesthesia/shader_sources/filaments.frag.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/filaments.vert +95 -0
- iOpenPod/GUI/synesthesia/shader_sources/filaments.vert.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/fullscreen.vert +13 -0
- iOpenPod/GUI/synesthesia/shader_sources/fullscreen.vert.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/particles.frag +25 -0
- iOpenPod/GUI/synesthesia/shader_sources/particles.frag.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/particles.vert +113 -0
- iOpenPod/GUI/synesthesia/shader_sources/particles.vert.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/present.frag +95 -0
- iOpenPod/GUI/synesthesia/shader_sources/present.frag.qsb +0 -0
- iOpenPod/GUI/synesthesia/shader_sources/scenes.frag +758 -0
- iOpenPod/GUI/synesthesia/shader_sources/scenes.frag.qsb +0 -0
- iOpenPod/GUI/synesthesia/visual_style.py +129 -0
- iOpenPod/GUI/widgets/album_grid.py +86 -0
- iOpenPod/GUI/widgets/app_combo_box.py +158 -0
- iOpenPod/GUI/widgets/artwork_view.py +124 -0
- iOpenPod/GUI/widgets/browser_chrome.py +147 -0
- iOpenPod/GUI/widgets/collection_detail.py +321 -0
- iOpenPod/GUI/widgets/collection_grid.py +87 -0
- iOpenPod/GUI/widgets/elided_label.py +171 -0
- iOpenPod/GUI/widgets/equalized_grid.py +160 -0
- iOpenPod/GUI/widgets/eta_label.py +61 -0
- iOpenPod/GUI/widgets/host_media_drop.py +144 -0
- iOpenPod/GUI/widgets/ipod_preferences.py +96 -0
- iOpenPod/GUI/widgets/library_content.py +193 -0
- iOpenPod/GUI/widgets/library_drag_grid.py +102 -0
- iOpenPod/GUI/widgets/library_toolbar.py +257 -0
- iOpenPod/GUI/widgets/media_tools_settings.py +145 -0
- iOpenPod/GUI/widgets/metadata_field_editor.py +416 -0
- iOpenPod/GUI/widgets/photo_album_membership_grid.py +70 -0
- iOpenPod/GUI/widgets/photo_grid.py +131 -0
- iOpenPod/GUI/widgets/photo_inspector.py +612 -0
- iOpenPod/GUI/widgets/playback_pane.py +881 -0
- iOpenPod/GUI/widgets/player_bar.py +1087 -0
- iOpenPod/GUI/widgets/playlist_artwork_banner.py +203 -0
- iOpenPod/GUI/widgets/playlist_tree.py +790 -0
- iOpenPod/GUI/widgets/podcast_artwork.py +153 -0
- iOpenPod/GUI/widgets/scrobbling_settings.py +210 -0
- iOpenPod/GUI/widgets/search_field.py +99 -0
- iOpenPod/GUI/widgets/selection_grid.py +55 -0
- iOpenPod/GUI/widgets/selection_group_transition.py +298 -0
- iOpenPod/GUI/widgets/setting_group.py +75 -0
- iOpenPod/GUI/widgets/sidebar.py +695 -0
- iOpenPod/GUI/widgets/status_controls.py +94 -0
- iOpenPod/GUI/widgets/status_list.py +386 -0
- iOpenPod/GUI/widgets/sync_preparation_progress.py +130 -0
- iOpenPod/GUI/widgets/sync_review_group.py +285 -0
- iOpenPod/GUI/widgets/sync_selection_actions.py +101 -0
- iOpenPod/GUI/widgets/sync_settings.py +510 -0
- iOpenPod/GUI/widgets/sync_storage_bar.py +241 -0
- iOpenPod/GUI/widgets/themed_buttons.py +351 -0
- iOpenPod/GUI/widgets/track_actions.py +861 -0
- iOpenPod/GUI/widgets/track_list_header.py +214 -0
- iOpenPod/GUI/widgets/track_table.py +483 -0
- iOpenPod/__main__.py +6 -0
- iOpenPod/app/app.py +195 -0
- iOpenPod/app/artwork_controller.py +254 -0
- iOpenPod/app/artwork_import.py +195 -0
- iOpenPod/app/backup_controller.py +591 -0
- iOpenPod/app/backups/__init__.py +77 -0
- iOpenPod/app/backups/_json.py +60 -0
- iOpenPod/app/backups/archive.py +500 -0
- iOpenPod/app/backups/models.py +628 -0
- iOpenPod/app/backups/original_archive.py +249 -0
- iOpenPod/app/backups/outcomes.py +419 -0
- iOpenPod/app/backups/recovery.py +183 -0
- iOpenPod/app/backups/repository.py +2139 -0
- iOpenPod/app/backups/scope.py +57 -0
- iOpenPod/app/backups/service.py +1281 -0
- iOpenPod/app/chaptered_conversion.py +258 -0
- iOpenPod/app/chaptered_conversion_controller.py +228 -0
- iOpenPod/app/context.py +236 -0
- iOpenPod/app/core/certifi_ssl.py +9 -0
- iOpenPod/app/core/eta.py +340 -0
- iOpenPod/app/core/logging.py +63 -0
- iOpenPod/app/core/runtime.py +52 -0
- iOpenPod/app/core/settings/definitions.py +299 -0
- iOpenPod/app/core/settings/paths.py +43 -0
- iOpenPod/app/core/settings/service.py +134 -0
- iOpenPod/app/core/settings/stores.py +250 -0
- iOpenPod/app/core/settings/transcoding.py +152 -0
- iOpenPod/app/core/status.py +205 -0
- iOpenPod/app/core/version.py +14 -0
- iOpenPod/app/device_controller.py +562 -0
- iOpenPod/app/display_text.py +58 -0
- iOpenPod/app/export_tagging.py +301 -0
- iOpenPod/app/host_media_controller.py +250 -0
- iOpenPod/app/host_media_fingerprint.py +206 -0
- iOpenPod/app/host_media_folders.py +162 -0
- iOpenPod/app/host_media_library.py +2396 -0
- iOpenPod/app/host_playlists.py +232 -0
- iOpenPod/app/ipod_media_controller.py +146 -0
- iOpenPod/app/library_export.py +714 -0
- iOpenPod/app/library_export_controller.py +271 -0
- iOpenPod/app/library_sync_helper.py +1108 -0
- iOpenPod/app/library_workspace.py +1294 -0
- iOpenPod/app/library_write.py +130 -0
- iOpenPod/app/library_write_controller.py +608 -0
- iOpenPod/app/library_write_inspection.py +424 -0
- iOpenPod/app/lyrics_controller.py +161 -0
- iOpenPod/app/media/__init__.py +20 -0
- iOpenPod/app/media/content_type.py +42 -0
- iOpenPod/app/media/importing.py +252 -0
- iOpenPod/app/media/inspection.py +368 -0
- iOpenPod/app/media/lyrics.py +143 -0
- iOpenPod/app/media/lyrics_reader.py +96 -0
- iOpenPod/app/media/models.py +95 -0
- iOpenPod/app/media/music_paths.py +95 -0
- iOpenPod/app/media/photo_sync.py +266 -0
- iOpenPod/app/media/progress.py +23 -0
- iOpenPod/app/media/sync_artwork.py +58 -0
- iOpenPod/app/media/tags.py +429 -0
- iOpenPod/app/media/transcoding.py +1031 -0
- iOpenPod/app/media_tools_controller.py +235 -0
- iOpenPod/app/metadata_fields.py +212 -0
- iOpenPod/app/models/album_list_model.py +285 -0
- iOpenPod/app/models/artwork.py +49 -0
- iOpenPod/app/models/artwork_seed.py +13 -0
- iOpenPod/app/models/collection_list_model.py +332 -0
- iOpenPod/app/models/device.py +138 -0
- iOpenPod/app/models/filter_compat.py +22 -0
- iOpenPod/app/models/ipod_preferences.py +27 -0
- iOpenPod/app/models/keyed_projection.py +101 -0
- iOpenPod/app/models/library_drag.py +141 -0
- iOpenPod/app/models/library_filter_models.py +425 -0
- iOpenPod/app/models/photo_album_membership_model.py +189 -0
- iOpenPod/app/models/photo_list_model.py +554 -0
- iOpenPod/app/models/photos.py +62 -0
- iOpenPod/app/models/playback_models.py +525 -0
- iOpenPod/app/models/playlist_tree_model.py +295 -0
- iOpenPod/app/models/podcast_list_models.py +355 -0
- iOpenPod/app/models/selection_grouping.py +323 -0
- iOpenPod/app/models/sync_plan_table_model.py +383 -0
- iOpenPod/app/models/sync_selection.py +274 -0
- iOpenPod/app/models/track_columns.py +982 -0
- iOpenPod/app/models/track_order.py +19 -0
- iOpenPod/app/models/track_table_model.py +330 -0
- iOpenPod/app/music_import_controller.py +205 -0
- iOpenPod/app/photo_controller.py +350 -0
- iOpenPod/app/playback/__init__.py +21 -0
- iOpenPod/app/playback/_qt_io.py +54 -0
- iOpenPod/app/playback/_qt_logging.py +101 -0
- iOpenPod/app/playback/backend.py +92 -0
- iOpenPod/app/playback/qt_backend.py +224 -0
- iOpenPod/app/playback/runtime_check.py +82 -0
- iOpenPod/app/playback/system_media/__init__.py +25 -0
- iOpenPod/app/playback/system_media/bridge.py +284 -0
- iOpenPod/app/playback/system_media/factory.py +66 -0
- iOpenPod/app/playback/system_media/linux.py +699 -0
- iOpenPod/app/playback/system_media/macos.py +430 -0
- iOpenPod/app/playback/system_media/session.py +145 -0
- iOpenPod/app/playback/system_media/windows.py +554 -0
- iOpenPod/app/playback_controller.py +352 -0
- iOpenPod/app/podcasts/__init__.py +59 -0
- iOpenPod/app/podcasts/artwork_controller.py +215 -0
- iOpenPod/app/podcasts/catalog.py +630 -0
- iOpenPod/app/podcasts/controller.py +610 -0
- iOpenPod/app/podcasts/documents.py +292 -0
- iOpenPod/app/podcasts/feed_client.py +460 -0
- iOpenPod/app/podcasts/identity.py +115 -0
- iOpenPod/app/podcasts/media.py +151 -0
- iOpenPod/app/podcasts/models.py +294 -0
- iOpenPod/app/podcasts/store.py +236 -0
- iOpenPod/app/podcasts/sync.py +459 -0
- iOpenPod/app/podcasts/sync_artwork.py +98 -0
- iOpenPod/app/podcasts/sync_preparation.py +172 -0
- iOpenPod/app/scrobbling/__init__.py +1 -0
- iOpenPod/app/scrobbling/_json.py +25 -0
- iOpenPod/app/scrobbling/clients.py +373 -0
- iOpenPod/app/scrobbling/controller.py +304 -0
- iOpenPod/app/scrobbling/credentials.py +98 -0
- iOpenPod/app/scrobbling/models.py +80 -0
- iOpenPod/app/scrobbling/queue.py +219 -0
- iOpenPod/app/scrobbling/reporting.py +132 -0
- iOpenPod/app/scrobbling/service.py +191 -0
- iOpenPod/app/scrobbling/settings.py +22 -0
- iOpenPod/app/scrobbling/timestamps.py +56 -0
- iOpenPod/app/services/device_coordinator.py +4175 -0
- iOpenPod/app/services/device_time.py +42 -0
- iOpenPod/app/services/ipod_preferences.py +359 -0
- iOpenPod/app/services/library_resources.py +677 -0
- iOpenPod/app/services/linux_identity.py +259 -0
- iOpenPod/app/services/media_tools.py +273 -0
- iOpenPod/app/services/volume_presentation.py +206 -0
- iOpenPod/app/smart_playlist_preview.py +264 -0
- iOpenPod/app/sync_controller.py +677 -0
- iOpenPod/app/sync_execution.py +2319 -0
- iOpenPod/app/sync_plan.py +680 -0
- iOpenPod/app/sync_storage.py +131 -0
- iOpenPod/app/synesthesia/__init__.py +143 -0
- iOpenPod/app/synesthesia/_rhythm.py +167 -0
- iOpenPod/app/synesthesia/_signals.py +226 -0
- iOpenPod/app/synesthesia/_structure.py +229 -0
- iOpenPod/app/synesthesia/audio.py +227 -0
- iOpenPod/app/synesthesia/backend.py +202 -0
- iOpenPod/app/synesthesia/controller.py +356 -0
- iOpenPod/app/synesthesia/dsp.py +882 -0
- iOpenPod/app/synesthesia/enrichment.py +407 -0
- iOpenPod/app/synesthesia/models.py +796 -0
- iOpenPod/app/tag_normalization_controller.py +197 -0
- iOpenPod/app/tag_normalizer.py +314 -0
- iOpenPod/app/track_conversion.py +110 -0
- iOpenPod/app/track_playback_policy.py +60 -0
- iOpenPod/app/updates/__init__.py +1 -0
- iOpenPod/app/updates/backend.py +97 -0
- iOpenPod/app/updates/bootstrap.py +116 -0
- iOpenPod/app/updates/controller.py +386 -0
- iOpenPod/app/updates/github.py +222 -0
- iOpenPod/app/updates/helper.py +228 -0
- iOpenPod/app/updates/installation.py +94 -0
- iOpenPod/app/updates/platform.py +156 -0
- iOpenPod/app/updates/portable.py +430 -0
- iOpenPod/app/updates/processes.py +127 -0
- iOpenPod/app/updates/processes_windows.py +229 -0
- iOpenPod/app/updates/pypi.py +288 -0
- iOpenPod/app/updates/python_helper.py +352 -0
- iOpenPod/app/updates/python_installation.py +420 -0
- iOpenPod/app/updates/releases.py +276 -0
- iOpenPod/app/updates/sparkle.py +111 -0
- iOpenPod/app/updates/transport.py +183 -0
- iOpenPod/app/updates/windows.py +252 -0
- iOpenPod/assets/glyphs/README.md +6 -0
- iOpenPod/assets/glyphs/album.svg +19 -0
- iOpenPod/assets/glyphs/alert.svg +3 -0
- iOpenPod/assets/glyphs/bell.svg +4 -0
- iOpenPod/assets/glyphs/book.svg +4 -0
- iOpenPod/assets/glyphs/box.svg +3 -0
- iOpenPod/assets/glyphs/broadcast.svg +8 -0
- iOpenPod/assets/glyphs/check-circle.svg +4 -0
- iOpenPod/assets/glyphs/chevron-left.svg +1 -0
- iOpenPod/assets/glyphs/eject.svg +3 -0
- iOpenPod/assets/glyphs/film.svg +4 -0
- iOpenPod/assets/glyphs/folder.svg +3 -0
- iOpenPod/assets/glyphs/grid.svg +6 -0
- iOpenPod/assets/glyphs/list.svg +4 -0
- iOpenPod/assets/glyphs/minus.svg +3 -0
- iOpenPod/assets/glyphs/monitor.svg +4 -0
- iOpenPod/assets/glyphs/music.svg +5 -0
- iOpenPod/assets/glyphs/pause.svg +4 -0
- iOpenPod/assets/glyphs/photo.svg +3 -0
- iOpenPod/assets/glyphs/play-last.svg +6 -0
- iOpenPod/assets/glyphs/play-next.svg +6 -0
- iOpenPod/assets/glyphs/play.svg +3 -0
- iOpenPod/assets/glyphs/playlist.svg +7 -0
- iOpenPod/assets/glyphs/plus.svg +3 -0
- iOpenPod/assets/glyphs/refresh.svg +3 -0
- iOpenPod/assets/glyphs/search.svg +3 -0
- iOpenPod/assets/glyphs/settings-sliders.svg +5 -0
- iOpenPod/assets/glyphs/skip-back.svg +4 -0
- iOpenPod/assets/glyphs/skip-forward.svg +4 -0
- iOpenPod/assets/glyphs/smart-playlist.svg +5 -0
- iOpenPod/assets/glyphs/synesthesia.svg +8 -0
- iOpenPod/assets/glyphs/user.svg +4 -0
- iOpenPod/assets/glyphs/video.svg +4 -0
- iOpenPod/assets/glyphs/volume.svg +3 -0
- iOpenPod/assets/glyphs/x.svg +3 -0
- iOpenPod/assets/icons/DJShott-icon.txt +21 -0
- iOpenPod/assets/icons/README.md +13 -0
- iOpenPod/assets/icons/icon-128.png +0 -0
- iOpenPod/assets/icons/icon-16.png +0 -0
- iOpenPod/assets/icons/icon-24.png +0 -0
- iOpenPod/assets/icons/icon-256.png +0 -0
- iOpenPod/assets/icons/icon-32.png +0 -0
- iOpenPod/assets/icons/icon-48.png +0 -0
- iOpenPod/assets/icons/icon-64.png +0 -0
- iOpenPod/assets/icons/icon.ico +0 -0
- iOpenPod/assets/ipod_images/iPod1.png +0 -0
- iOpenPod/assets/ipod_images/iPod11-Black.png +0 -0
- iOpenPod/assets/ipod_images/iPod11-Silver.png +0 -0
- iOpenPod/assets/ipod_images/iPod11B-Black.png +0 -0
- iOpenPod/assets/ipod_images/iPod12-Black.png +0 -0
- iOpenPod/assets/ipod_images/iPod12-Blue.png +0 -0
- iOpenPod/assets/ipod_images/iPod12-Green.png +0 -0
- iOpenPod/assets/ipod_images/iPod12-Pink.png +0 -0
- iOpenPod/assets/ipod_images/iPod12-Red.png +0 -0
- iOpenPod/assets/ipod_images/iPod12-Silver.png +0 -0
- iOpenPod/assets/ipod_images/iPod15-Black.png +0 -0
- iOpenPod/assets/ipod_images/iPod15-Blue.png +0 -0
- iOpenPod/assets/ipod_images/iPod15-Green.png +0 -0
- iOpenPod/assets/ipod_images/iPod15-Orange.png +0 -0
- iOpenPod/assets/ipod_images/iPod15-Pink.png +0 -0
- iOpenPod/assets/ipod_images/iPod15-Purple.png +0 -0
- iOpenPod/assets/ipod_images/iPod15-Red.png +0 -0
- iOpenPod/assets/ipod_images/iPod15-Silver.png +0 -0
- iOpenPod/assets/ipod_images/iPod15-Yellow.png +0 -0
- iOpenPod/assets/ipod_images/iPod16-Black.png +0 -0
- iOpenPod/assets/ipod_images/iPod16-Blue.png +0 -0
- iOpenPod/assets/ipod_images/iPod16-Green.png +0 -0
- iOpenPod/assets/ipod_images/iPod16-Orange.png +0 -0
- iOpenPod/assets/ipod_images/iPod16-Pink.png +0 -0
- iOpenPod/assets/ipod_images/iPod16-Purple.png +0 -0
- iOpenPod/assets/ipod_images/iPod16-Red.png +0 -0
- iOpenPod/assets/ipod_images/iPod16-Silver.png +0 -0
- iOpenPod/assets/ipod_images/iPod16-Yellow.png +0 -0
- iOpenPod/assets/ipod_images/iPod17-Blue.png +0 -0
- iOpenPod/assets/ipod_images/iPod17-DarkGray.png +0 -0
- iOpenPod/assets/ipod_images/iPod17-Green.png +0 -0
- iOpenPod/assets/ipod_images/iPod17-Orange.png +0 -0
- iOpenPod/assets/ipod_images/iPod17-Pink.png +0 -0
- iOpenPod/assets/ipod_images/iPod17-Red.png +0 -0
- iOpenPod/assets/ipod_images/iPod17-Silver.png +0 -0
- iOpenPod/assets/ipod_images/iPod18-Blue.png +0 -0
- iOpenPod/assets/ipod_images/iPod18-DarkGray.png +0 -0
- iOpenPod/assets/ipod_images/iPod18-Green.png +0 -0
- iOpenPod/assets/ipod_images/iPod18-Pink.png +0 -0
- iOpenPod/assets/ipod_images/iPod18-Purple.png +0 -0
- iOpenPod/assets/ipod_images/iPod18-Red.png +0 -0
- iOpenPod/assets/ipod_images/iPod18-Silver.png +0 -0
- iOpenPod/assets/ipod_images/iPod18-SpaceGray.png +0 -0
- iOpenPod/assets/ipod_images/iPod18-Yellow.png +0 -0
- iOpenPod/assets/ipod_images/iPod18A-Blue.png +0 -0
- iOpenPod/assets/ipod_images/iPod18A-Gold.png +0 -0
- iOpenPod/assets/ipod_images/iPod18A-Pink.png +0 -0
- iOpenPod/assets/ipod_images/iPod18A-Red.png +0 -0
- iOpenPod/assets/ipod_images/iPod18A-Silver.png +0 -0
- iOpenPod/assets/ipod_images/iPod18A-SpaceGray.png +0 -0
- iOpenPod/assets/ipod_images/iPod2.png +0 -0
- iOpenPod/assets/ipod_images/iPod3-Blue.png +0 -0
- iOpenPod/assets/ipod_images/iPod3-Gold.png +0 -0
- iOpenPod/assets/ipod_images/iPod3-Green.png +0 -0
- iOpenPod/assets/ipod_images/iPod3-Pink.png +0 -0
- iOpenPod/assets/ipod_images/iPod3-Silver.png +0 -0
- iOpenPod/assets/ipod_images/iPod3B-Blue.png +0 -0
- iOpenPod/assets/ipod_images/iPod3B-Green.png +0 -0
- iOpenPod/assets/ipod_images/iPod3B-Pink.png +0 -0
- iOpenPod/assets/ipod_images/iPod4-BlackRed.png +0 -0
- iOpenPod/assets/ipod_images/iPod4-White.png +0 -0
- iOpenPod/assets/ipod_images/iPod5-BlackRed.png +0 -0
- iOpenPod/assets/ipod_images/iPod5-White.png +0 -0
- iOpenPod/assets/ipod_images/iPod6-Black.png +0 -0
- iOpenPod/assets/ipod_images/iPod6-BlackRed.png +0 -0
- iOpenPod/assets/ipod_images/iPod6-White.png +0 -0
- iOpenPod/assets/ipod_images/iPod7-Black.png +0 -0
- iOpenPod/assets/ipod_images/iPod7-White.png +0 -0
- iOpenPod/assets/ipod_images/iPod9-Black.png +0 -0
- iOpenPod/assets/ipod_images/iPod9-Blue.png +0 -0
- iOpenPod/assets/ipod_images/iPod9-Green.png +0 -0
- iOpenPod/assets/ipod_images/iPod9-Pink.png +0 -0
- iOpenPod/assets/ipod_images/iPod9-Red.png +0 -0
- iOpenPod/assets/ipod_images/iPod9-Silver.png +0 -0
- iOpenPod/assets/ipod_images/iPodGeneric.png +0 -0
- iOpenPod/assets/linux/61-iopenpod.rules +24 -0
- iPodDB/ArtworkDB/builder/build_ArtworkDB.py +180 -0
- iPodDB/ArtworkDB/ithmb.py +453 -0
- iPodDB/ArtworkDB/ithmb_writer.py +170 -0
- iPodDB/ArtworkDB/parser/mhod_parsers/container_mhod.py +38 -0
- iPodDB/ArtworkDB/parser/mhod_parsers/opaque_mhod.py +12 -0
- iPodDB/ArtworkDB/parser/mhod_parsers/string_mhod.py +33 -0
- iPodDB/ArtworkDB/parser/mhod_payload_spec_registry.py +75 -0
- iPodDB/ArtworkDB/parser/parse_ArtworkDB.py +14 -0
- iPodDB/ArtworkDB/parser/parser_definition.py +31 -0
- iPodDB/ArtworkDB/shared/artwork_index.py +163 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhaf.py +23 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhba.py +44 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhfd.py +36 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhia.py +24 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhif.py +27 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhii.py +34 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhla.py +16 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhlf.py +16 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhli.py +16 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhni.py +34 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhod.py +27 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhod_payloads/container_mhod.py +12 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhod_payloads/opaque_mhod.py +14 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhod_payloads/string_mhod.py +43 -0
- iPodDB/ArtworkDB/shared/chunk_defs/mhsd.py +51 -0
- iPodDB/ArtworkDB/shared/constants.py +24 -0
- iPodDB/ArtworkDB/shared/database_definition.py +36 -0
- iPodDB/ArtworkDB/shared/mhod_payload_spec_registry.py +88 -0
- iPodDB/ArtworkDB/writer/mhod_encoder.py +172 -0
- iPodDB/ArtworkDB/writer/mhod_serializer.py +72 -0
- iPodDB/ArtworkDB/writer/write_ArtworkDB.py +24 -0
- iPodDB/PhotosDB/builder/build_PhotosDB.py +181 -0
- iPodDB/PhotosDB/parser/mhod_parsers/container_mhod.py +38 -0
- iPodDB/PhotosDB/parser/mhod_parsers/opaque_mhod.py +12 -0
- iPodDB/PhotosDB/parser/mhod_parsers/string_mhod.py +33 -0
- iPodDB/PhotosDB/parser/mhod_payload_spec_registry.py +75 -0
- iPodDB/PhotosDB/parser/parse_PhotosDB.py +14 -0
- iPodDB/PhotosDB/parser/parser_definition.py +29 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhaf.py +17 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhba.py +22 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhfd.py +38 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhia.py +17 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhif.py +18 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhii.py +19 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhla.py +16 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhlf.py +16 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhli.py +16 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhni.py +18 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhod.py +17 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhod_payloads/container_mhod.py +7 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhod_payloads/opaque_mhod.py +8 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhod_payloads/string_mhod.py +36 -0
- iPodDB/PhotosDB/shared/chunk_defs/mhsd.py +43 -0
- iPodDB/PhotosDB/shared/constants.py +31 -0
- iPodDB/PhotosDB/shared/database_definition.py +36 -0
- iPodDB/PhotosDB/shared/mhod_payload_spec_registry.py +21 -0
- iPodDB/PhotosDB/writer/write_PhotosDB.py +22 -0
- iPodDB/SQLiteDB/artifacts.py +27 -0
- iPodDB/SQLiteDB/checksum.py +68 -0
- iPodDB/SQLiteDB/database.py +1089 -0
- iPodDB/device_time.py +143 -0
- iPodDB/iTunesDB/builder/build_iTunesDB.py +94 -0
- iPodDB/iTunesDB/cdb.py +136 -0
- iPodDB/iTunesDB/parser/mhod_parsers/chapter_data_mhod.py +327 -0
- iPodDB/iTunesDB/parser/mhod_parsers/contextual_100_mhod.py +17 -0
- iPodDB/iTunesDB/parser/mhod_parsers/library_index_mhod.py +31 -0
- iPodDB/iTunesDB/parser/mhod_parsers/library_jump_table_mhod.py +36 -0
- iPodDB/iTunesDB/parser/mhod_parsers/opaque_mhod.py +14 -0
- iPodDB/iTunesDB/parser/mhod_parsers/plist_mhod.py +52 -0
- iPodDB/iTunesDB/parser/mhod_parsers/settings_mhod.py +28 -0
- iPodDB/iTunesDB/parser/mhod_parsers/smart_prefs_mhod.py +17 -0
- iPodDB/iTunesDB/parser/mhod_parsers/smart_rules_mhod.py +180 -0
- iPodDB/iTunesDB/parser/mhod_parsers/string_mhod.py +40 -0
- iPodDB/iTunesDB/parser/mhod_parsers/url_mhod.py +18 -0
- iPodDB/iTunesDB/parser/mhod_parsers/video_details_mhod.py +16 -0
- iPodDB/iTunesDB/parser/mhod_payload_spec_registry.py +128 -0
- iPodDB/iTunesDB/parser/parse_iTunesDB.py +23 -0
- iPodDB/iTunesDB/parser/parser_definition.py +29 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhbd.py +47 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhia.py +33 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhii.py +28 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhip.py +34 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhit.py +131 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhla.py +16 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhli.py +16 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhlp.py +16 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhlt.py +16 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod.py +26 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/chapter_data_mhod.py +93 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/contextual_100_mhod.py +28 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/library_index_mhod.py +19 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/library_jump_table_mhod.py +27 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/opaque_mhod.py +10 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/plist_mhod.py +52 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/settings_mhod.py +18 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/smart_prefs_mhod.py +28 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/smart_rules_mhod.py +173 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/string_mhod.py +19 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/url_mhod.py +10 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/video_details_mhod.py +13 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhsd.py +44 -0
- iPodDB/iTunesDB/shared/chunk_defs/mhyp.py +50 -0
- iPodDB/iTunesDB/shared/constants.py +205 -0
- iPodDB/iTunesDB/shared/database_definition.py +36 -0
- iPodDB/iTunesDB/shared/device_time.py +39 -0
- iPodDB/iTunesDB/shared/field_converters.py +22 -0
- iPodDB/iTunesDB/shared/mhod_payload_spec_registry.py +165 -0
- iPodDB/iTunesDB/writer/calcHashAB.NOTICE +20 -0
- iPodDB/iTunesDB/writer/calcHashAB.wasm.b64 +150 -0
- iPodDB/iTunesDB/writer/mhod_encoder.py +607 -0
- iPodDB/iTunesDB/writer/signature.py +291 -0
- iPodDB/iTunesDB/writer/write_iTunesDB.py +67 -0
- iPodDB/library/__init__.py +218 -0
- iPodDB/library/_album_index.py +375 -0
- iPodDB/library/_artwork_verification.py +171 -0
- iPodDB/library/_artwork_writing.py +652 -0
- iPodDB/library/_browse_groups.py +68 -0
- iPodDB/library/_browse_index.py +103 -0
- iPodDB/library/_document_edit.py +23 -0
- iPodDB/library/_field_policy.py +387 -0
- iPodDB/library/_folder_validation.py +78 -0
- iPodDB/library/_folder_writing.py +163 -0
- iPodDB/library/_group_verification.py +305 -0
- iPodDB/library/_hidden_writing.py +461 -0
- iPodDB/library/_hierarchy.py +36 -0
- iPodDB/library/_identities.py +40 -0
- iPodDB/library/_index_verification.py +126 -0
- iPodDB/library/_lyrics_resources.py +71 -0
- iPodDB/library/_native_values.py +72 -0
- iPodDB/library/_photo_analysis.py +575 -0
- iPodDB/library/_photo_projection.py +178 -0
- iPodDB/library/_photo_resources.py +238 -0
- iPodDB/library/_photo_writing.py +376 -0
- iPodDB/library/_playlist_datasets.py +263 -0
- iPodDB/library/_playlist_projection.py +207 -0
- iPodDB/library/_playlist_verification.py +622 -0
- iPodDB/library/_playlist_writing.py +244 -0
- iPodDB/library/_podcast_writing.py +272 -0
- iPodDB/library/_projection.py +422 -0
- iPodDB/library/_reconcile.py +597 -0
- iPodDB/library/_resolve_write.py +360 -0
- iPodDB/library/_resolved_write.py +36 -0
- iPodDB/library/_sidecar_projection.py +168 -0
- iPodDB/library/_smart_projection.py +332 -0
- iPodDB/library/_smart_writing.py +321 -0
- iPodDB/library/_source_warnings.py +87 -0
- iPodDB/library/_time_warnings.py +83 -0
- iPodDB/library/_track_header_verification.py +168 -0
- iPodDB/library/_track_verification.py +206 -0
- iPodDB/library/_track_writing.py +467 -0
- iPodDB/library/_write_analysis.py +415 -0
- iPodDB/library/_write_effects.py +363 -0
- iPodDB/library/_write_logging.py +201 -0
- iPodDB/library/_write_preparation.py +1052 -0
- iPodDB/library/_write_verification.py +147 -0
- iPodDB/library/artwork.py +112 -0
- iPodDB/library/database.py +414 -0
- iPodDB/library/editing.py +114 -0
- iPodDB/library/media.py +48 -0
- iPodDB/library/models.py +254 -0
- iPodDB/library/photos.py +285 -0
- iPodDB/library/playlists.py +437 -0
- iPodDB/library/signing.py +64 -0
- iPodDB/library/smart_rules.py +236 -0
- iPodDB/library/sqlite.py +5 -0
- iPodDB/library/writing.py +305 -0
- iPodDB/preferences/__init__.py +58 -0
- iPodDB/preferences/_source.py +9 -0
- iPodDB/preferences/codec.py +175 -0
- iPodDB/preferences/definitions.py +104 -0
- iPodDB/preferences/itunes.py +145 -0
- iPodDB/preferences/itunes_definitions.py +93 -0
- iPodDB/preferences/models.py +81 -0
- iPodDB/preferences/timezones.py +220 -0
- iPodDB/py.typed +0 -0
- iPodDB/shared/binary_struct.py +271 -0
- iPodDB/shared/chunk.py +408 -0
- iPodDB/shared/chunk_builder.py +46 -0
- iPodDB/shared/chunk_field.py +178 -0
- iPodDB/shared/chunk_reader.py +433 -0
- iPodDB/shared/chunk_writer.py +325 -0
- iPodDB/shared/errors.py +42 -0
- iPodDB/shared/measure_memory.py +107 -0
- iPodDB/shared/to_json.py +91 -0
- iPodDB/shared/types.py +377 -0
- iPodDB/sidecars/__init__.py +41 -0
- iPodDB/sidecars/codec.py +225 -0
- iPodDB/sidecars/models.py +63 -0
- iPodDB/sidecars/remapping.py +64 -0
- iPodDB/tools/chunk_browser.py +1051 -0
- iopenpod-2.0.4.dist-info/METADATA +327 -0
- iopenpod-2.0.4.dist-info/RECORD +681 -0
- iopenpod-2.0.4.dist-info/WHEEL +4 -0
- iopenpod-2.0.4.dist-info/entry_points.txt +2 -0
- iopenpod-2.0.4.dist-info/licenses/ACKNOWLEDGEMENTS.md +44 -0
- iopenpod-2.0.4.dist-info/licenses/COPYING.md +47 -0
- iopenpod-2.0.4.dist-info/licenses/LICENSE +675 -0
- iopenpod_launcher.py +283 -0
- storage/__init__.py +177 -0
- storage/_filesystem.py +238 -0
- storage/_transaction_journal.py +242 -0
- storage/_transactions.py +1426 -0
- storage/_writer_lock.py +189 -0
- storage/device_capture.py +39 -0
- storage/errors.py +111 -0
- storage/host_capture.py +139 -0
- storage/host_directory.py +242 -0
- storage/host_files.py +221 -0
- storage/host_input.py +417 -0
- storage/host_installation.py +147 -0
- storage/host_tools.py +124 -0
- storage/host_usage.py +119 -0
- storage/media_probe.py +161 -0
- storage/media_processing.py +527 -0
- storage/models.py +364 -0
- storage/paths.py +97 -0
- storage/platform/__init__.py +1 -0
- storage/platform/base.py +58 -0
- storage/platform/common.py +88 -0
- storage/platform/host_install_windows.py +201 -0
- storage/platform/linux.py +619 -0
- storage/platform/linux_eject.py +392 -0
- storage/platform/macos.py +974 -0
- storage/platform/macos_eject.py +90 -0
- storage/platform/macos_volume_metadata.py +116 -0
- storage/platform/macos_vpd.py +546 -0
- storage/platform/native.py +29 -0
- storage/platform/windows.py +667 -0
- storage/platform/windows_eject.py +386 -0
- storage/portal.py +403 -0
- storage/py.typed +1 -0
- storage/scsi_vpd.py +127 -0
- storage/session.py +1626 -0
- storage/testing.py +282 -0
- storage/transactions.py +209 -0
- storage/volume_metadata.py +40 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Aspect-preserving Photo painting shared by cards and the inspector."""
|
|
2
|
+
|
|
3
|
+
from PySide6.QtCore import QRectF, Qt
|
|
4
|
+
from PySide6.QtGui import QColor, QPainter, QPainterPath, QPen, QPixmap
|
|
5
|
+
|
|
6
|
+
from iOpenPod.GUI.presentation.theme.tokens import ThemeTokens
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def paint_photo_pixmap(
|
|
10
|
+
painter: QPainter,
|
|
11
|
+
rect: QRectF,
|
|
12
|
+
pixmap: QPixmap,
|
|
13
|
+
tokens: ThemeTokens,
|
|
14
|
+
radius: float,
|
|
15
|
+
) -> None:
|
|
16
|
+
"""Letterbox one Photo within ``rect`` while retaining its aspect ratio."""
|
|
17
|
+
|
|
18
|
+
clip = QPainterPath()
|
|
19
|
+
clip.addRoundedRect(rect, radius, radius)
|
|
20
|
+
source = QRectF(pixmap.rect())
|
|
21
|
+
if source.isEmpty():
|
|
22
|
+
return
|
|
23
|
+
scale = min(rect.width() / source.width(), rect.height() / source.height())
|
|
24
|
+
width = source.width() * scale
|
|
25
|
+
height = source.height() * scale
|
|
26
|
+
target = QRectF(
|
|
27
|
+
rect.left() + (rect.width() - width) / 2.0,
|
|
28
|
+
rect.top() + (rect.height() - height) / 2.0,
|
|
29
|
+
width,
|
|
30
|
+
height,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
painter.save()
|
|
34
|
+
painter.setClipPath(clip)
|
|
35
|
+
painter.fillRect(rect, QColor(tokens.surface_alt))
|
|
36
|
+
painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True)
|
|
37
|
+
painter.drawPixmap(target, pixmap, source)
|
|
38
|
+
painter.restore()
|
|
39
|
+
|
|
40
|
+
border = QPen(QColor(tokens.border))
|
|
41
|
+
border.setWidthF(1.0)
|
|
42
|
+
painter.setPen(border)
|
|
43
|
+
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
44
|
+
painter.drawRoundedRect(rect, radius, radius)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
__all__ = ["paint_photo_pixmap"]
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""GUI-thread conversion of lazily decoded Photo images into display pixmaps."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from math import ceil
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from PySide6.QtCore import QObject, Signal, Slot
|
|
10
|
+
from PySide6.QtGui import QImage, QPixmap, QPixmapCache
|
|
11
|
+
|
|
12
|
+
from iOpenPod.app.models.photos import PhotoImage
|
|
13
|
+
from iOpenPod.app.photo_controller import PhotoLoadFailure, PhotoUnavailable
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from iOpenPod.app.photo_controller import PhotoController
|
|
17
|
+
|
|
18
|
+
_CACHE_PREFIX = "iOpenPod/photo/"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PhotoPixmapState(StrEnum):
|
|
22
|
+
"""Presentation state for one automatic or exact Photo representation."""
|
|
23
|
+
|
|
24
|
+
IDLE = "idle"
|
|
25
|
+
LOADING = "loading"
|
|
26
|
+
READY = "ready"
|
|
27
|
+
UNAVAILABLE = "unavailable"
|
|
28
|
+
FAILED = "failed"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PhotoPixmapProvider(QObject):
|
|
32
|
+
"""Bridge owned Photo RGB pixels to Qt's bounded display-pixmap cache."""
|
|
33
|
+
|
|
34
|
+
photoChanged = Signal(object)
|
|
35
|
+
cleared = Signal()
|
|
36
|
+
capacityAvailable = Signal()
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
controller: PhotoController,
|
|
41
|
+
parent: QObject | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
super().__init__(parent)
|
|
44
|
+
self._controller = controller
|
|
45
|
+
self._qt_cache_keys: set[str] = set()
|
|
46
|
+
self._states: dict[tuple[int, int | None], PhotoPixmapState] = {}
|
|
47
|
+
self._closed = False
|
|
48
|
+
controller.photoReady.connect(self._photo_ready)
|
|
49
|
+
controller.photoUnavailable.connect(self._photo_unavailable)
|
|
50
|
+
controller.photoFailed.connect(self._photo_failed)
|
|
51
|
+
controller.generationChanged.connect(self._generation_changed)
|
|
52
|
+
controller.capacityAvailable.connect(self._capacity_available)
|
|
53
|
+
|
|
54
|
+
def pixmap(
|
|
55
|
+
self,
|
|
56
|
+
photo_id: int,
|
|
57
|
+
logical_size: int,
|
|
58
|
+
device_pixel_ratio: float,
|
|
59
|
+
*,
|
|
60
|
+
format_id: int | None = None,
|
|
61
|
+
) -> QPixmap | None:
|
|
62
|
+
"""Return a pixmap now, queuing automatic or exact-format decode on miss."""
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
self._closed
|
|
66
|
+
or photo_id <= 0
|
|
67
|
+
or logical_size <= 0
|
|
68
|
+
or (format_id is not None and format_id < 0)
|
|
69
|
+
):
|
|
70
|
+
return None
|
|
71
|
+
target_px = max(1, ceil(logical_size * max(1.0, device_pixel_ratio)))
|
|
72
|
+
image = self._controller.request(
|
|
73
|
+
photo_id,
|
|
74
|
+
target_px,
|
|
75
|
+
format_id=format_id,
|
|
76
|
+
)
|
|
77
|
+
if image is None:
|
|
78
|
+
key = (photo_id, format_id)
|
|
79
|
+
if self._states.get(key) not in (
|
|
80
|
+
PhotoPixmapState.UNAVAILABLE,
|
|
81
|
+
PhotoPixmapState.FAILED,
|
|
82
|
+
):
|
|
83
|
+
self._states[key] = PhotoPixmapState.LOADING
|
|
84
|
+
return None
|
|
85
|
+
self._states[(photo_id, format_id)] = PhotoPixmapState.READY
|
|
86
|
+
return self._pixmap_for(image)
|
|
87
|
+
|
|
88
|
+
def state(
|
|
89
|
+
self,
|
|
90
|
+
photo_id: int,
|
|
91
|
+
*,
|
|
92
|
+
format_id: int | None = None,
|
|
93
|
+
) -> PhotoPixmapState:
|
|
94
|
+
"""Return the last observed presentation state without starting I/O."""
|
|
95
|
+
|
|
96
|
+
return self._states.get((photo_id, format_id), PhotoPixmapState.IDLE)
|
|
97
|
+
|
|
98
|
+
def retry(self, photo_id: int, *, format_id: int | None = None) -> None:
|
|
99
|
+
"""Clear one terminal state so the next paint retries the request."""
|
|
100
|
+
|
|
101
|
+
if self._closed or photo_id <= 0 or (format_id is not None and format_id < 0):
|
|
102
|
+
return
|
|
103
|
+
self._controller.retry(photo_id, format_id=format_id)
|
|
104
|
+
self._states.pop((photo_id, format_id), None)
|
|
105
|
+
self.photoChanged.emit(photo_id)
|
|
106
|
+
|
|
107
|
+
def shutdown(self) -> None:
|
|
108
|
+
"""Release presentation cache entries before the owning window closes."""
|
|
109
|
+
|
|
110
|
+
if self._closed:
|
|
111
|
+
return
|
|
112
|
+
self._closed = True
|
|
113
|
+
self._clear_pixmaps()
|
|
114
|
+
self._states.clear()
|
|
115
|
+
|
|
116
|
+
@Slot(object)
|
|
117
|
+
def _photo_ready(self, value: object) -> None:
|
|
118
|
+
if self._closed or not isinstance(value, PhotoImage):
|
|
119
|
+
return
|
|
120
|
+
self._pixmap_for(value)
|
|
121
|
+
for key in tuple(self._states):
|
|
122
|
+
if key[0] == value.photo_id and (
|
|
123
|
+
key[1] is None or key[1] == value.format_id
|
|
124
|
+
):
|
|
125
|
+
self._states[key] = PhotoPixmapState.READY
|
|
126
|
+
self.photoChanged.emit(value.photo_id)
|
|
127
|
+
|
|
128
|
+
@Slot(object)
|
|
129
|
+
def _photo_unavailable(self, value: object) -> None:
|
|
130
|
+
if self._closed or not isinstance(value, PhotoUnavailable):
|
|
131
|
+
return
|
|
132
|
+
self._states[(value.photo_id, value.format_id)] = PhotoPixmapState.UNAVAILABLE
|
|
133
|
+
self.photoChanged.emit(value.photo_id)
|
|
134
|
+
|
|
135
|
+
@Slot(object)
|
|
136
|
+
def _photo_failed(self, value: object) -> None:
|
|
137
|
+
if self._closed or not isinstance(value, PhotoLoadFailure):
|
|
138
|
+
return
|
|
139
|
+
self._states[(value.photo_id, value.format_id)] = PhotoPixmapState.FAILED
|
|
140
|
+
self.photoChanged.emit(value.photo_id)
|
|
141
|
+
|
|
142
|
+
@Slot(int)
|
|
143
|
+
def _generation_changed(self, _generation: int) -> None:
|
|
144
|
+
if self._closed:
|
|
145
|
+
return
|
|
146
|
+
self._clear_pixmaps()
|
|
147
|
+
self._states.clear()
|
|
148
|
+
self.cleared.emit()
|
|
149
|
+
|
|
150
|
+
@Slot()
|
|
151
|
+
def _capacity_available(self) -> None:
|
|
152
|
+
if not self._closed:
|
|
153
|
+
self.capacityAvailable.emit()
|
|
154
|
+
|
|
155
|
+
def _clear_pixmaps(self) -> None:
|
|
156
|
+
for cache_key in self._qt_cache_keys:
|
|
157
|
+
QPixmapCache.remove(cache_key)
|
|
158
|
+
self._qt_cache_keys.clear()
|
|
159
|
+
|
|
160
|
+
def _pixmap_for(self, image: PhotoImage) -> QPixmap:
|
|
161
|
+
cache_key = f"{_CACHE_PREFIX}{image.cache_key}"
|
|
162
|
+
cached = QPixmap()
|
|
163
|
+
if QPixmapCache.find(cache_key, cached) and not cached.isNull():
|
|
164
|
+
return cached
|
|
165
|
+
|
|
166
|
+
wrapped = QImage(
|
|
167
|
+
image.rgb888,
|
|
168
|
+
image.width,
|
|
169
|
+
image.height,
|
|
170
|
+
image.width * 3,
|
|
171
|
+
QImage.Format.Format_RGB888,
|
|
172
|
+
)
|
|
173
|
+
# QImage's raw-buffer constructor borrows Python memory. Detach from that
|
|
174
|
+
# buffer before crossing into Qt's independently retained pixmap cache.
|
|
175
|
+
owned = wrapped.copy()
|
|
176
|
+
pixmap = QPixmap.fromImage(owned)
|
|
177
|
+
QPixmapCache.insert(cache_key, pixmap)
|
|
178
|
+
self._qt_cache_keys.add(cache_key)
|
|
179
|
+
return pixmap
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
__all__ = ["PhotoPixmapProvider", "PhotoPixmapState"]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""DPR-aware GUI pixmaps for asynchronously fetched Podcast artwork."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from math import ceil
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from PySide6.QtCore import QObject, Signal, Slot
|
|
9
|
+
from PySide6.QtGui import QImage, QPixmap, QPixmapCache
|
|
10
|
+
|
|
11
|
+
from iOpenPod.app.podcasts.models import PodcastArtworkImage
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from iOpenPod.app.podcasts.artwork_controller import PodcastArtworkController
|
|
15
|
+
|
|
16
|
+
_CACHE_PREFIX = "iOpenPod/podcast-artwork/"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PodcastArtworkPixmapProvider(QObject):
|
|
20
|
+
"""Bridge remote Podcast RGB pixels to the GUI-owned pixmap cache."""
|
|
21
|
+
|
|
22
|
+
artworkChanged = Signal(str)
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
controller: PodcastArtworkController,
|
|
27
|
+
parent: QObject | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
super().__init__(parent)
|
|
30
|
+
self._controller = controller
|
|
31
|
+
self._qt_cache_keys: set[str] = set()
|
|
32
|
+
self._closed = False
|
|
33
|
+
controller.artworkReady.connect(self._artwork_ready)
|
|
34
|
+
controller.artworkUnavailable.connect(self.artworkChanged)
|
|
35
|
+
|
|
36
|
+
def pixmap(
|
|
37
|
+
self,
|
|
38
|
+
source_url: str,
|
|
39
|
+
logical_size: int,
|
|
40
|
+
device_pixel_ratio: float,
|
|
41
|
+
) -> QPixmap | None:
|
|
42
|
+
if self._closed or not source_url.strip() or logical_size <= 0:
|
|
43
|
+
return None
|
|
44
|
+
target_px = max(1, ceil(logical_size * max(1.0, device_pixel_ratio)))
|
|
45
|
+
image = self._controller.request(source_url, target_px)
|
|
46
|
+
return self._pixmap_for(image) if image is not None else None
|
|
47
|
+
|
|
48
|
+
def shutdown(self) -> None:
|
|
49
|
+
if self._closed:
|
|
50
|
+
return
|
|
51
|
+
self._closed = True
|
|
52
|
+
self._controller.artworkReady.disconnect(self._artwork_ready)
|
|
53
|
+
self._controller.artworkUnavailable.disconnect(self.artworkChanged)
|
|
54
|
+
for cache_key in self._qt_cache_keys:
|
|
55
|
+
QPixmapCache.remove(cache_key)
|
|
56
|
+
self._qt_cache_keys.clear()
|
|
57
|
+
|
|
58
|
+
@Slot(object)
|
|
59
|
+
def _artwork_ready(self, value: object) -> None:
|
|
60
|
+
if self._closed or not isinstance(value, PodcastArtworkImage):
|
|
61
|
+
return
|
|
62
|
+
self._pixmap_for(value)
|
|
63
|
+
self.artworkChanged.emit(value.source_url)
|
|
64
|
+
|
|
65
|
+
def _pixmap_for(self, image: PodcastArtworkImage) -> QPixmap:
|
|
66
|
+
cache_key = f"{_CACHE_PREFIX}{image.cache_key}"
|
|
67
|
+
cached = QPixmap()
|
|
68
|
+
if QPixmapCache.find(cache_key, cached) and not cached.isNull():
|
|
69
|
+
return cached
|
|
70
|
+
wrapped = QImage(
|
|
71
|
+
image.rgb888,
|
|
72
|
+
image.width,
|
|
73
|
+
image.height,
|
|
74
|
+
image.width * 3,
|
|
75
|
+
QImage.Format.Format_RGB888,
|
|
76
|
+
)
|
|
77
|
+
pixmap = QPixmap.fromImage(wrapped.copy())
|
|
78
|
+
QPixmapCache.insert(cache_key, pixmap)
|
|
79
|
+
self._qt_cache_keys.add(cache_key)
|
|
80
|
+
return pixmap
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
__all__ = ["PodcastArtworkPixmapProvider"]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Shared four-cover artwork treatment for the All Podcasts projection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import QRectF
|
|
8
|
+
from PySide6.QtGui import QColor, QPainter, QPen
|
|
9
|
+
|
|
10
|
+
from iOpenPod.app.models.artwork_seed import stable_artwork_seed
|
|
11
|
+
from iOpenPod.GUI.presentation.artwork import (
|
|
12
|
+
paint_artwork_pixmap,
|
|
13
|
+
paint_artwork_placeholder,
|
|
14
|
+
)
|
|
15
|
+
from iOpenPod.GUI.presentation.theme.tokens import LAYOUT
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Sequence
|
|
19
|
+
|
|
20
|
+
from iOpenPod.app.podcasts.models import PodcastSubscription
|
|
21
|
+
from iOpenPod.GUI.presentation.artwork_provider import ArtworkPixmapProvider
|
|
22
|
+
from iOpenPod.GUI.presentation.podcast_artwork_provider import (
|
|
23
|
+
PodcastArtworkPixmapProvider,
|
|
24
|
+
)
|
|
25
|
+
from iOpenPod.GUI.presentation.theme.tokens import ThemeTokens
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def paint_podcast_collection_artwork(
|
|
29
|
+
painter: QPainter,
|
|
30
|
+
rect: QRectF,
|
|
31
|
+
subscriptions: Sequence[PodcastSubscription],
|
|
32
|
+
provider: PodcastArtworkPixmapProvider,
|
|
33
|
+
tokens: ThemeTokens,
|
|
34
|
+
device_pixel_ratio: float,
|
|
35
|
+
device_provider: ArtworkPixmapProvider | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Paint the first four Podcast covers as a fixed 2-by-2 collection grid."""
|
|
38
|
+
|
|
39
|
+
gap = float(LAYOUT.space_2xs)
|
|
40
|
+
tile_size = (min(rect.width(), rect.height()) - gap) / 2.0
|
|
41
|
+
tile_radius = float(LAYOUT.space_2xs)
|
|
42
|
+
for position in range(4):
|
|
43
|
+
column = position % 2
|
|
44
|
+
row = position // 2
|
|
45
|
+
tile_rect = QRectF(
|
|
46
|
+
rect.left() + column * (tile_size + gap),
|
|
47
|
+
rect.top() + row * (tile_size + gap),
|
|
48
|
+
tile_size,
|
|
49
|
+
tile_size,
|
|
50
|
+
)
|
|
51
|
+
if position >= len(subscriptions):
|
|
52
|
+
painter.setPen(QPen(QColor(tokens.border)))
|
|
53
|
+
painter.setBrush(QColor(tokens.surface_alt))
|
|
54
|
+
painter.drawRoundedRect(tile_rect, tile_radius, tile_radius)
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
subscription = subscriptions[position]
|
|
58
|
+
pixmap = provider.pixmap(
|
|
59
|
+
subscription.artwork_url,
|
|
60
|
+
round(tile_size),
|
|
61
|
+
device_pixel_ratio,
|
|
62
|
+
)
|
|
63
|
+
if (
|
|
64
|
+
pixmap is None
|
|
65
|
+
and device_provider is not None
|
|
66
|
+
and subscription.artwork_id > 0
|
|
67
|
+
):
|
|
68
|
+
pixmap = device_provider.pixmap(
|
|
69
|
+
subscription.artwork_id,
|
|
70
|
+
round(tile_size),
|
|
71
|
+
device_pixel_ratio,
|
|
72
|
+
)
|
|
73
|
+
if pixmap is None:
|
|
74
|
+
paint_artwork_placeholder(
|
|
75
|
+
painter,
|
|
76
|
+
tile_rect,
|
|
77
|
+
stable_artwork_seed(subscription.subscription_id),
|
|
78
|
+
tokens,
|
|
79
|
+
tile_radius,
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
paint_artwork_pixmap(
|
|
83
|
+
painter,
|
|
84
|
+
tile_rect,
|
|
85
|
+
pixmap,
|
|
86
|
+
tokens,
|
|
87
|
+
tile_radius,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
__all__ = ["paint_podcast_collection_artwork"]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Hallmark · component: Library selection checkbox · genre: modern-minimal
|
|
2
|
+
# theme: iOpenPod · states: unchecked · checked · mixed · hover · focus · disabled
|
|
3
|
+
# contrast: pass · pre-emit critique: P5 H5 E5 S5 R5 V4
|
|
4
|
+
"""Shared circular selection control for virtualized Library cards."""
|
|
5
|
+
|
|
6
|
+
from PySide6.QtCore import (
|
|
7
|
+
QAbstractItemModel,
|
|
8
|
+
QEvent,
|
|
9
|
+
QLineF,
|
|
10
|
+
QModelIndex,
|
|
11
|
+
QPersistentModelIndex,
|
|
12
|
+
QRectF,
|
|
13
|
+
Qt,
|
|
14
|
+
)
|
|
15
|
+
from PySide6.QtGui import QColor, QKeyEvent, QMouseEvent, QPainter, QPainterPath, QPen
|
|
16
|
+
from PySide6.QtWidgets import QStyle, QStyleOptionViewItem
|
|
17
|
+
|
|
18
|
+
from iOpenPod.GUI.presentation.library_card import (
|
|
19
|
+
library_card_artwork_rect,
|
|
20
|
+
library_card_rect,
|
|
21
|
+
)
|
|
22
|
+
from iOpenPod.GUI.presentation.theme.tokens import ThemeTokens, TypographyTokens
|
|
23
|
+
|
|
24
|
+
_CHECKBOX_EXTENT = 36.0
|
|
25
|
+
_CHECKBOX_MARGIN = 8.0
|
|
26
|
+
_CHECKBOX_BORDER_WIDTH = 2.0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def library_card_checkbox_rect(
|
|
30
|
+
option: QStyleOptionViewItem,
|
|
31
|
+
typography: TypographyTokens,
|
|
32
|
+
) -> QRectF:
|
|
33
|
+
artwork = library_card_artwork_rect(option, typography)
|
|
34
|
+
return QRectF(
|
|
35
|
+
artwork.right() - _CHECKBOX_MARGIN - _CHECKBOX_EXTENT,
|
|
36
|
+
artwork.top() + _CHECKBOX_MARGIN,
|
|
37
|
+
_CHECKBOX_EXTENT,
|
|
38
|
+
_CHECKBOX_EXTENT,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def paint_library_card_checkbox(
|
|
43
|
+
painter: QPainter,
|
|
44
|
+
option: QStyleOptionViewItem,
|
|
45
|
+
state: Qt.CheckState,
|
|
46
|
+
tokens: ThemeTokens,
|
|
47
|
+
typography: TypographyTokens,
|
|
48
|
+
) -> None:
|
|
49
|
+
checked = state is Qt.CheckState.Checked
|
|
50
|
+
partial = state is Qt.CheckState.PartiallyChecked
|
|
51
|
+
active = checked or partial
|
|
52
|
+
enabled = bool(option.state & QStyle.StateFlag.State_Enabled)
|
|
53
|
+
hovered = bool(option.state & QStyle.StateFlag.State_MouseOver)
|
|
54
|
+
focused = bool(option.state & QStyle.StateFlag.State_HasFocus)
|
|
55
|
+
rect = library_card_checkbox_rect(option, typography)
|
|
56
|
+
fill = (
|
|
57
|
+
tokens.accent_hover
|
|
58
|
+
if active and hovered
|
|
59
|
+
else tokens.accent
|
|
60
|
+
if active
|
|
61
|
+
else tokens.surface_hover
|
|
62
|
+
if hovered
|
|
63
|
+
else tokens.surface
|
|
64
|
+
)
|
|
65
|
+
if not enabled:
|
|
66
|
+
fill = tokens.surface_alt
|
|
67
|
+
border = tokens.text_disabled if not enabled else tokens.border_strong
|
|
68
|
+
mark = tokens.text_disabled if not enabled else tokens.accent_ink
|
|
69
|
+
|
|
70
|
+
painter.save()
|
|
71
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
|
72
|
+
if focused:
|
|
73
|
+
painter.setPen(QPen(QColor(tokens.focus), _CHECKBOX_BORDER_WIDTH))
|
|
74
|
+
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
75
|
+
painter.drawEllipse(rect.adjusted(-3.0, -3.0, 3.0, 3.0))
|
|
76
|
+
painter.setPen(QPen(QColor(border), _CHECKBOX_BORDER_WIDTH))
|
|
77
|
+
painter.setBrush(QColor(fill))
|
|
78
|
+
painter.drawEllipse(rect)
|
|
79
|
+
if checked:
|
|
80
|
+
path = QPainterPath()
|
|
81
|
+
path.moveTo(rect.left() + 9.0, rect.center().y())
|
|
82
|
+
path.lineTo(rect.left() + 15.0, rect.bottom() - 10.0)
|
|
83
|
+
path.lineTo(rect.right() - 8.0, rect.top() + 10.0)
|
|
84
|
+
pen = QPen(QColor(mark), 3.0)
|
|
85
|
+
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
|
|
86
|
+
pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
|
|
87
|
+
painter.setPen(pen)
|
|
88
|
+
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
89
|
+
painter.drawPath(path)
|
|
90
|
+
elif partial:
|
|
91
|
+
pen = QPen(QColor(mark), 3.0)
|
|
92
|
+
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
|
|
93
|
+
painter.setPen(pen)
|
|
94
|
+
painter.drawLine(
|
|
95
|
+
QLineF(
|
|
96
|
+
rect.left() + 9.0,
|
|
97
|
+
rect.center().y(),
|
|
98
|
+
rect.right() - 9.0,
|
|
99
|
+
rect.center().y(),
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
painter.restore()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def handle_library_card_check_event(
|
|
106
|
+
event: QEvent,
|
|
107
|
+
model: QAbstractItemModel,
|
|
108
|
+
option: QStyleOptionViewItem,
|
|
109
|
+
index: QModelIndex | QPersistentModelIndex,
|
|
110
|
+
typography: TypographyTokens,
|
|
111
|
+
*,
|
|
112
|
+
checkbox_only: bool = True,
|
|
113
|
+
) -> bool:
|
|
114
|
+
if not index.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
|
115
|
+
return False
|
|
116
|
+
activate = False
|
|
117
|
+
if isinstance(event, QMouseEvent):
|
|
118
|
+
hit_rect = (
|
|
119
|
+
library_card_checkbox_rect(option, typography)
|
|
120
|
+
if checkbox_only
|
|
121
|
+
else library_card_rect(option, typography)
|
|
122
|
+
)
|
|
123
|
+
activate = (
|
|
124
|
+
event.type() is QEvent.Type.MouseButtonRelease
|
|
125
|
+
and event.button() is Qt.MouseButton.LeftButton
|
|
126
|
+
and hit_rect.contains(event.position())
|
|
127
|
+
)
|
|
128
|
+
elif isinstance(event, QKeyEvent):
|
|
129
|
+
activate = event.type() is QEvent.Type.KeyPress and event.key() in {
|
|
130
|
+
Qt.Key.Key_Space,
|
|
131
|
+
Qt.Key.Key_Select,
|
|
132
|
+
}
|
|
133
|
+
if not activate:
|
|
134
|
+
return False
|
|
135
|
+
state = index.data(Qt.ItemDataRole.CheckStateRole)
|
|
136
|
+
next_state = (
|
|
137
|
+
Qt.CheckState.Unchecked
|
|
138
|
+
if state == Qt.CheckState.Checked
|
|
139
|
+
else Qt.CheckState.Checked
|
|
140
|
+
)
|
|
141
|
+
return model.setData(index, next_state, Qt.ItemDataRole.CheckStateRole)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
__all__ = [
|
|
145
|
+
"handle_library_card_check_event",
|
|
146
|
+
"library_card_checkbox_rect",
|
|
147
|
+
"paint_library_card_checkbox",
|
|
148
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Semantic Qt styles for the Backup Snapshot browser."""
|
|
2
|
+
|
|
3
|
+
from iOpenPod.GUI.presentation.theme.tokens import (
|
|
4
|
+
LAYOUT,
|
|
5
|
+
ThemeTokens,
|
|
6
|
+
TypographyTokens,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def render_backup_page_style(
|
|
11
|
+
tokens: ThemeTokens,
|
|
12
|
+
typography: TypographyTokens,
|
|
13
|
+
) -> str:
|
|
14
|
+
"""Render the archive browser using the application theme contract."""
|
|
15
|
+
|
|
16
|
+
return f"""/* Hallmark · macrostructure: Saved backup list · genre: quiet desktop utility
|
|
17
|
+
* pre-emit critique: P4 H5 E4 S5 R5 V4 · theme: iOpenPod semantic
|
|
18
|
+
* design-system: docs/gui-design-language.md
|
|
19
|
+
* structure: iPod source list · device context · compact cards with optional details
|
|
20
|
+
*/
|
|
21
|
+
QWidget#backupsPage,
|
|
22
|
+
QWidget#backupBrowserPage,
|
|
23
|
+
QWidget#backupEmptyPage,
|
|
24
|
+
QWidget#backupDetail,
|
|
25
|
+
QWidget#backupSnapshotHost {{
|
|
26
|
+
color: {tokens.text};
|
|
27
|
+
background-color: {tokens.window};
|
|
28
|
+
}}
|
|
29
|
+
QFrame#backupRecoveryBanner {{
|
|
30
|
+
color: {tokens.text};
|
|
31
|
+
background-color: {tokens.surface_selected};
|
|
32
|
+
border: none;
|
|
33
|
+
border-bottom: 1px solid {tokens.warning};
|
|
34
|
+
}}
|
|
35
|
+
QLabel#backupRecoveryText {{
|
|
36
|
+
color: {tokens.text};
|
|
37
|
+
font-weight: 600;
|
|
38
|
+
}}
|
|
39
|
+
QLabel#backupDeviceName,
|
|
40
|
+
QLabel#backupEmptyTitle {{
|
|
41
|
+
color: {tokens.text};
|
|
42
|
+
font-family: "{typography.display}";
|
|
43
|
+
font-size: {typography.title_pt:g}pt;
|
|
44
|
+
font-weight: 700;
|
|
45
|
+
}}
|
|
46
|
+
QLabel#backupDeviceModel,
|
|
47
|
+
QLabel#backupDeviceSummary,
|
|
48
|
+
QLabel#backupSnapshotsCount,
|
|
49
|
+
QLabel#backupSnapshotSummary,
|
|
50
|
+
QLabel#backupProgressFile,
|
|
51
|
+
QLabel#backupEmptyDetail,
|
|
52
|
+
QLabel#backupArchiveEmpty {{
|
|
53
|
+
color: {tokens.text_secondary};
|
|
54
|
+
}}
|
|
55
|
+
QFrame#backupHero {{
|
|
56
|
+
color: {tokens.text};
|
|
57
|
+
background-color: {tokens.surface};
|
|
58
|
+
border-bottom: 1px solid {tokens.border};
|
|
59
|
+
}}
|
|
60
|
+
QLabel#backupSnapshotsLabel,
|
|
61
|
+
QLabel#backupSnapshotDate,
|
|
62
|
+
QLabel#backupProgressTitle {{
|
|
63
|
+
color: {tokens.text};
|
|
64
|
+
font-family: "{typography.display}";
|
|
65
|
+
font-size: {typography.heading_pt:g}pt;
|
|
66
|
+
font-weight: 700;
|
|
67
|
+
}}
|
|
68
|
+
QFrame#backupSnapshotCard,
|
|
69
|
+
QFrame#backupProgressPanel {{
|
|
70
|
+
color: {tokens.text};
|
|
71
|
+
background-color: {tokens.surface};
|
|
72
|
+
border: 1px solid {tokens.border};
|
|
73
|
+
border-radius: {LAYOUT.radius_panel}px;
|
|
74
|
+
}}
|
|
75
|
+
QLabel#backupSnapshotNote {{
|
|
76
|
+
color: {tokens.text};
|
|
77
|
+
}}
|
|
78
|
+
QLabel#backupSnapshotInvalid {{
|
|
79
|
+
color: {tokens.danger};
|
|
80
|
+
}}
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
__all__ = ["render_backup_page_style"]
|