gitpane 1.0.0__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.
- gitpane/__init__.py +0 -0
- gitpane/account/__init__.py +0 -0
- gitpane/account/account_choice.py +297 -0
- gitpane/account/address_owner.py +67 -0
- gitpane/account/authentication.py +1811 -0
- gitpane/account/authentication_worker.py +836 -0
- gitpane/account/credential_manager.py +480 -0
- gitpane/account/credential_manager_install.py +171 -0
- gitpane/account/keyring_reader.py +135 -0
- gitpane/account/keyring_state.py +158 -0
- gitpane/account/ssh.py +312 -0
- gitpane/account/ssh_askpass.py +216 -0
- gitpane/account/ssh_prompt.py +222 -0
- gitpane/application.py +684 -0
- gitpane/commit/__init__.py +0 -0
- gitpane/commit/commit_metadata.py +88 -0
- gitpane/commit/commit_push.py +276 -0
- gitpane/commit/commit_write.py +830 -0
- gitpane/config.py +1066 -0
- gitpane/controller/__init__.py +0 -0
- gitpane/controller/account_controller.py +677 -0
- gitpane/controller/clone_controller.py +776 -0
- gitpane/controller/close_readiness.py +212 -0
- gitpane/controller/commit_timing_controller.py +566 -0
- gitpane/controller/conflict_controller.py +885 -0
- gitpane/controller/console_registry.py +38 -0
- gitpane/controller/emoji_window_controller.py +95 -0
- gitpane/controller/file_action_controller.py +479 -0
- gitpane/controller/history_read_controller.py +671 -0
- gitpane/controller/history_rewrite_controller.py +739 -0
- gitpane/controller/navigation_controller.py +658 -0
- gitpane/controller/opening_controller.py +375 -0
- gitpane/controller/operation_policy.py +76 -0
- gitpane/controller/rebase_controller.py +797 -0
- gitpane/controller/rebase_editor_controller.py +309 -0
- gitpane/controller/reference_form_controller.py +1677 -0
- gitpane/controller/reference_read_controller.py +242 -0
- gitpane/controller/refresh_controller.py +477 -0
- gitpane/controller/replay_controller.py +805 -0
- gitpane/controller/session_registry.py +337 -0
- gitpane/controller/setting_controller.py +592 -0
- gitpane/controller/shutdown_controller.py +345 -0
- gitpane/controller/staging_controller.py +199 -0
- gitpane/controller/staging_session_registry.py +47 -0
- gitpane/controller/stash_controller.py +367 -0
- gitpane/controller/transport_controller.py +813 -0
- gitpane/controller/welcome_controller.py +102 -0
- gitpane/controller/window_activation.py +116 -0
- gitpane/controller/worktree_state_controller.py +169 -0
- gitpane/emoji/__init__.py +0 -0
- gitpane/emoji/emoji_catalogue.py +156 -0
- gitpane/git_config/__init__.py +0 -0
- gitpane/git_config/git_setting.py +441 -0
- gitpane/history/__init__.py +0 -0
- gitpane/history/avatar.py +241 -0
- gitpane/history/file_history.py +359 -0
- gitpane/history/graph.py +145 -0
- gitpane/history/history_filter.py +31 -0
- gitpane/history/history_parse.py +605 -0
- gitpane/history/repository.py +1600 -0
- gitpane/history/repository_value.py +341 -0
- gitpane/history/revision.py +180 -0
- gitpane/history/revision_file.py +182 -0
- gitpane/history_rewrite/__init__.py +0 -0
- gitpane/history_rewrite/graph.py +210 -0
- gitpane/history_rewrite/guard.py +288 -0
- gitpane/history_rewrite/index.py +125 -0
- gitpane/history_rewrite/prepare.py +378 -0
- gitpane/history_rewrite/protocol.py +320 -0
- gitpane/history_rewrite/publish.py +444 -0
- gitpane/history_rewrite/recovery.py +481 -0
- gitpane/history_rewrite/value.py +229 -0
- gitpane/operation/__init__.py +0 -0
- gitpane/operation/console_line.py +313 -0
- gitpane/operation/console_tone.py +76 -0
- gitpane/operation/diagnostic.py +954 -0
- gitpane/operation/launch_signature.py +254 -0
- gitpane/operation/operation_status.py +87 -0
- gitpane/operation/operation_trace.py +766 -0
- gitpane/operation/repository_operation.py +781 -0
- gitpane/preference/__init__.py +0 -0
- gitpane/preference/preference_location.py +40 -0
- gitpane/preference/preference_store.py +590 -0
- gitpane/process/__init__.py +0 -0
- gitpane/process/exact_arguments.py +77 -0
- gitpane/process/git_output_text.py +20 -0
- gitpane/process/process_group.py +851 -0
- gitpane/process/process_log.py +114 -0
- gitpane/process/process_runner.py +1033 -0
- gitpane/process/released_child_watch.py +138 -0
- gitpane/recent/__init__.py +0 -0
- gitpane/recent/mount_table.py +422 -0
- gitpane/recent/recent_availability.py +414 -0
- gitpane/recent/recent_name.py +11 -0
- gitpane/recent/recent_search.py +114 -0
- gitpane/reference/__init__.py +0 -0
- gitpane/reference/reference_edit.py +2030 -0
- gitpane/reference/reference_snapshot.py +196 -0
- gitpane/reference/stash.py +730 -0
- gitpane/reference/stash_list.py +164 -0
- gitpane/sequencer/__init__.py +0 -0
- gitpane/sequencer/commit_message_rule.py +154 -0
- gitpane/sequencer/conflict.py +506 -0
- gitpane/sequencer/merge_recovery.py +163 -0
- gitpane/sequencer/merge_state.py +166 -0
- gitpane/sequencer/mergetool.py +856 -0
- gitpane/sequencer/rebase.py +750 -0
- gitpane/sequencer/rebase_edit_file.py +224 -0
- gitpane/sequencer/rebase_state.py +523 -0
- gitpane/sequencer/replay.py +1435 -0
- gitpane/sequencer/replay_legacy.py +253 -0
- gitpane/sequencer/replay_state.py +747 -0
- gitpane/submodule/__init__.py +0 -0
- gitpane/submodule/checkout_refusal.py +102 -0
- gitpane/submodule/checkout_refusal_notice.py +111 -0
- gitpane/submodule/cloned_without_checkout.py +65 -0
- gitpane/submodule/submodule_discovery.py +1180 -0
- gitpane/submodule/submodule_loss.py +218 -0
- gitpane/system/__init__.py +0 -0
- gitpane/system/appearance_portal.py +125 -0
- gitpane/system/application_identity.py +48 -0
- gitpane/system/command_line.py +36 -0
- gitpane/system/desktop_icon_fact.py +128 -0
- gitpane/system/desktop_icon_support.py +270 -0
- gitpane/system/desktop_shortcut.py +536 -0
- gitpane/system/icns_image.py +35 -0
- gitpane/system/installed_launcher.py +136 -0
- gitpane/system/linux_distribution.py +151 -0
- gitpane/system/network_folder.py +46 -0
- gitpane/system/path_identity.py +95 -0
- gitpane/system/private_tree_removal.py +47 -0
- gitpane/system/process_program.py +63 -0
- gitpane/system/qt_folder.py +38 -0
- gitpane/system/ready_channel_watch.py +67 -0
- gitpane/system/startup_activation.py +65 -0
- gitpane/system/terminal_detach.py +384 -0
- gitpane/system/xdg_session.py +85 -0
- gitpane/text/__init__.py +0 -0
- gitpane/text/text_query.py +97 -0
- gitpane/text/typed_path.py +75 -0
- gitpane/text/typed_text.py +17 -0
- gitpane/thread/__init__.py +0 -0
- gitpane/thread/weak_signal.py +93 -0
- gitpane/tool/__init__.py +0 -0
- gitpane/tool/app_bundle.py +164 -0
- gitpane/tool/desktop_text_editor.py +156 -0
- gitpane/tool/difftool.py +429 -0
- gitpane/tool/editor.py +445 -0
- gitpane/tool/editor_bridge.py +193 -0
- gitpane/tool/editor_recipe.py +481 -0
- gitpane/tool/file_reveal.py +94 -0
- gitpane/tool/git_executable.py +264 -0
- gitpane/tool/git_version.py +62 -0
- gitpane/tool/kdiff3.py +41 -0
- gitpane/tool/kdiff3_install.py +162 -0
- gitpane/tool/launch_services_text_editor.py +104 -0
- gitpane/tool/mergetool_recipe.py +84 -0
- gitpane/tool/system_editor_default.py +80 -0
- gitpane/tool/system_text_editor.py +44 -0
- gitpane/tool/system_tool.py +816 -0
- gitpane/tool/terminal_recipe.py +390 -0
- gitpane/transport/__init__.py +0 -0
- gitpane/transport/branch_exchange.py +1690 -0
- gitpane/transport/clone.py +1273 -0
- gitpane/transport/credential_read_retry.py +35 -0
- gitpane/transport/git_refusal.py +207 -0
- gitpane/transport/push_tracking.py +121 -0
- gitpane/transport/refusal_notice.py +225 -0
- gitpane/transport/remote.py +747 -0
- gitpane/transport/remote_config.py +544 -0
- gitpane/ui/__init__.py +0 -0
- gitpane/ui/console/__init__.py +0 -0
- gitpane/ui/console/error_dialog.py +332 -0
- gitpane/ui/console/operation_dialog.py +408 -0
- gitpane/ui/console/operation_output.py +91 -0
- gitpane/ui/console/operation_output_view.py +373 -0
- gitpane/ui/emoji/__init__.py +0 -0
- gitpane/ui/emoji/emoji_font.py +257 -0
- gitpane/ui/emoji/emoji_grid.py +125 -0
- gitpane/ui/emoji/emoji_window.py +154 -0
- gitpane/ui/emoji/file_action_icon.py +131 -0
- gitpane/ui/emoji/opentype_family_name.py +182 -0
- gitpane/ui/history_rewrite/__init__.py +0 -0
- gitpane/ui/history_rewrite/details.py +78 -0
- gitpane/ui/history_rewrite/preview.py +565 -0
- gitpane/ui/history_rewrite/preview_model.py +291 -0
- gitpane/ui/history_rewrite/question.py +87 -0
- gitpane/ui/history_rewrite/recovery_dialog.py +262 -0
- gitpane/ui/look/__init__.py +0 -0
- gitpane/ui/look/action_icon.py +504 -0
- gitpane/ui/look/application_icon.py +78 -0
- gitpane/ui/look/application_style.py +317 -0
- gitpane/ui/look/avatar_label.py +53 -0
- gitpane/ui/look/color_contrast.py +43 -0
- gitpane/ui/look/desktop_appearance_follower.py +118 -0
- gitpane/ui/look/fixed_font.py +21 -0
- gitpane/ui/look/gtk_settings.py +116 -0
- gitpane/ui/look/icon_density.py +16 -0
- gitpane/ui/look/reference_font.py +19 -0
- gitpane/ui/look/theme_color.py +63 -0
- gitpane/ui/look/theme_look.py +95 -0
- gitpane/ui/look/theme_palette.py +173 -0
- gitpane/ui/look/warning_icon.py +86 -0
- gitpane/ui/look/windows_control_look.py +250 -0
- gitpane/ui/reader/__init__.py +0 -0
- gitpane/ui/reader/commit_message.py +129 -0
- gitpane/ui/reader/commit_panel.py +378 -0
- gitpane/ui/reader/diff_panel.py +626 -0
- gitpane/ui/reader/file_action.py +138 -0
- gitpane/ui/reader/file_tree_panel.py +235 -0
- gitpane/ui/reader/reader_toolbar.py +56 -0
- gitpane/ui/reader/revision_tree.py +80 -0
- gitpane/ui/reader/search_result.py +165 -0
- gitpane/ui/reader/source_text.py +134 -0
- gitpane/ui/reader/source_view.py +151 -0
- gitpane/ui/reader/syntax_span.py +67 -0
- gitpane/ui/reference/__init__.py +0 -0
- gitpane/ui/reference/branch_selector.py +106 -0
- gitpane/ui/reference/checkout_dialog.py +419 -0
- gitpane/ui/reference/checkout_revision_dialog.py +182 -0
- gitpane/ui/reference/commit_summary.py +339 -0
- gitpane/ui/reference/create_branch_dialog.py +346 -0
- gitpane/ui/reference/create_tag_dialog.py +283 -0
- gitpane/ui/reference/delete_local_branch_dialog.py +323 -0
- gitpane/ui/reference/delete_tag_dialog.py +394 -0
- gitpane/ui/reference/form_opening.py +53 -0
- gitpane/ui/reference/reference_dialog.py +417 -0
- gitpane/ui/reference/reference_feedback.py +62 -0
- gitpane/ui/reference/reference_form.py +139 -0
- gitpane/ui/reference/rename_branch_dialog.py +179 -0
- gitpane/ui/reference/reset_branch_dialog.py +430 -0
- gitpane/ui/reference/reset_dialog.py +263 -0
- gitpane/ui/reference/revision_dialog.py +121 -0
- gitpane/ui/reference/revision_picker.py +227 -0
- gitpane/ui/reference/select_branches_dialog.py +199 -0
- gitpane/ui/reference/stash_dialog.py +171 -0
- gitpane/ui/repository/__init__.py +0 -0
- gitpane/ui/repository/graph_delegate.py +298 -0
- gitpane/ui/repository/history_avatar.py +130 -0
- gitpane/ui/repository/history_model.py +179 -0
- gitpane/ui/repository/repository_list_popup.py +319 -0
- gitpane/ui/repository/repository_window.py +2580 -0
- gitpane/ui/resource/ArrowDown.png +0 -0
- gitpane/ui/resource/ArrowUp.png +0 -0
- gitpane/ui/resource/BranchCheckout.png +0 -0
- gitpane/ui/resource/BranchCreate.png +0 -0
- gitpane/ui/resource/BranchDelete.png +0 -0
- gitpane/ui/resource/CopyToClipboard.png +0 -0
- gitpane/ui/resource/GitPane-16.png +0 -0
- gitpane/ui/resource/GitPane-24.png +0 -0
- gitpane/ui/resource/GitPane-256.png +0 -0
- gitpane/ui/resource/GitPane-32.png +0 -0
- gitpane/ui/resource/GitPane-48.png +0 -0
- gitpane/ui/resource/GitPane-64.png +0 -0
- gitpane/ui/resource/GitPane.ico +0 -0
- gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
- gitpane/ui/resource/NotoColorEmoji_WindowsCompatible.ttf +0 -0
- gitpane/ui/resource/Renamed.png +0 -0
- gitpane/ui/resource/ResetAnotherBranchToHere.png +0 -0
- gitpane/ui/resource/ResetCurrentBranchToHere.png +0 -0
- gitpane/ui/resource/Stash.png +0 -0
- gitpane/ui/resource/Tag.png +0 -0
- gitpane/ui/resource/TagCreate.png +0 -0
- gitpane/ui/resource/TagDelete.png +0 -0
- gitpane/ui/resource/Unmerged.png +0 -0
- gitpane/ui/resource/checkout.png +0 -0
- gitpane/ui/sequencer/__init__.py +0 -0
- gitpane/ui/sequencer/conflict_dialog.py +532 -0
- gitpane/ui/sequencer/merge_recovery_dialog.py +67 -0
- gitpane/ui/sequencer/rebase_dialog.py +461 -0
- gitpane/ui/sequencer/rebase_editor_window.py +220 -0
- gitpane/ui/sequencer/replay_conflict_prompt.py +105 -0
- gitpane/ui/sequencer/replay_dialog.py +501 -0
- gitpane/ui/setting/__init__.py +0 -0
- gitpane/ui/setting/account_page.py +724 -0
- gitpane/ui/setting/add_account_dialog.py +1005 -0
- gitpane/ui/setting/credential_manager_dialog.py +151 -0
- gitpane/ui/setting/git_setting_widget.py +388 -0
- gitpane/ui/setting/setting_block.py +19 -0
- gitpane/ui/setting/setting_dialog.py +750 -0
- gitpane/ui/setting/ssh_widget.py +232 -0
- gitpane/ui/setting/utility_page.py +240 -0
- gitpane/ui/staging/__init__.py +0 -0
- gitpane/ui/staging/staging_action.py +356 -0
- gitpane/ui/staging/staging_window.py +745 -0
- gitpane/ui/transport/__init__.py +0 -0
- gitpane/ui/transport/clone_dialog.py +879 -0
- gitpane/ui/transport/push_dialog.py +580 -0
- gitpane/ui/transport/push_rejection_prompt.py +100 -0
- gitpane/ui/transport/push_tracking_question.py +88 -0
- gitpane/ui/transport/remote_dialog.py +943 -0
- gitpane/ui/welcome/__init__.py +0 -0
- gitpane/ui/welcome/invalid_repository_prompt.py +114 -0
- gitpane/ui/welcome/welcome_window.py +1137 -0
- gitpane/ui/widget/__init__.py +0 -0
- gitpane/ui/widget/busy_indicator.py +106 -0
- gitpane/ui/widget/commit_date_text.py +55 -0
- gitpane/ui/widget/content_growth.py +262 -0
- gitpane/ui/widget/content_measure.py +99 -0
- gitpane/ui/widget/dialog_layout.py +668 -0
- gitpane/ui/widget/drop_down_wheel_filter.py +32 -0
- gitpane/ui/widget/flow_layout.py +110 -0
- gitpane/ui/widget/long_text.py +542 -0
- gitpane/ui/widget/mnemonic.py +13 -0
- gitpane/ui/widget/numbered_text_edit.py +127 -0
- gitpane/ui/widget/plain_label.py +15 -0
- gitpane/ui/widget/plain_tooltip.py +19 -0
- gitpane/ui/widget/refresh_advice.py +25 -0
- gitpane/ui/widget/result_label.py +227 -0
- gitpane/ui/widget/result_status_bar.py +53 -0
- gitpane/ui/widget/scrolled_text.py +69 -0
- gitpane/ui/widget/shortcut_text.py +31 -0
- gitpane/ui/widget/standard_icon_label.py +55 -0
- gitpane/ui/widget/system_shortcut.py +75 -0
- gitpane/ui/widget/unavailable_reason.py +29 -0
- gitpane/ui/widget/wheel_safe_drop_down.py +24 -0
- gitpane/ui/window_control/__init__.py +0 -0
- gitpane/ui/window_control/button_focus.py +42 -0
- gitpane/ui/window_control/compositor_answer.py +152 -0
- gitpane/ui/window_control/compositor_growth_cap.py +73 -0
- gitpane/ui/window_control/desktop_activation.py +203 -0
- gitpane/ui/window_control/dialog_modality.py +122 -0
- gitpane/ui/window_control/item_activation.py +42 -0
- gitpane/ui/window_control/modal_focus_relay.py +124 -0
- gitpane/ui/window_control/wayland_activation.py +695 -0
- gitpane/ui/window_control/window_placement.py +305 -0
- gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
- gitpane/vendor/w32.exe +0 -0
- gitpane/vendor/w64-arm.exe +0 -0
- gitpane/vendor/w64.exe +0 -0
- gitpane/worktree/__init__.py +0 -0
- gitpane/worktree/command_sequence.py +110 -0
- gitpane/worktree/file_action_target.py +61 -0
- gitpane/worktree/file_menu_action.py +52 -0
- gitpane/worktree/historical_restore.py +361 -0
- gitpane/worktree/ignore_rule.py +266 -0
- gitpane/worktree/mutation.py +1539 -0
- gitpane/worktree/partial_patch.py +435 -0
- gitpane/worktree/staged_reset.py +447 -0
- gitpane/worktree/staging.py +1418 -0
- gitpane/worktree/trash.py +302 -0
- gitpane/worktree/worktree_file.py +406 -0
- gitpane/worktree/worktree_service.py +584 -0
- gitpane/worktree/worktree_snapshot.py +630 -0
- gitpane-1.0.0.dist-info/METADATA +141 -0
- gitpane-1.0.0.dist-info/RECORD +353 -0
- gitpane-1.0.0.dist-info/WHEEL +5 -0
- gitpane-1.0.0.dist-info/entry_points.txt +2 -0
- gitpane-1.0.0.dist-info/licenses/LICENSE +21 -0
- gitpane-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +109 -0
- gitpane-1.0.0.dist-info/licenses/src/gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
- gitpane-1.0.0.dist-info/licenses/src/gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
- gitpane-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Show the welcome window of GitPane, where a repository is chosen."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from gitpane.config import SharedText
|
|
9
|
+
from gitpane.history.file_history import WindowCapability
|
|
10
|
+
from gitpane.ui.welcome.welcome_window import WelcomeWindow
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from PySide6.QtWidgets import QWidget
|
|
14
|
+
|
|
15
|
+
from gitpane.controller.clone_controller import CloneController
|
|
16
|
+
from gitpane.controller.opening_controller import OpeningController
|
|
17
|
+
from gitpane.controller.operation_policy import OperationPolicy
|
|
18
|
+
from gitpane.controller.session_registry import SessionRegistry
|
|
19
|
+
from gitpane.controller.setting_controller import SettingController
|
|
20
|
+
from gitpane.controller.window_activation import WindowActivationFilter
|
|
21
|
+
from gitpane.history.repository import GitRepository
|
|
22
|
+
from gitpane.operation.diagnostic import DiagnosticLog
|
|
23
|
+
from gitpane.preference.preference_store import PreferenceStore
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WelcomeController:
|
|
27
|
+
"""The single welcome window of GitPane, shown again when no usable Git is found or a repository window asks for it."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, *, session_registry_controller: SessionRegistry, operation_policy_controller: OperationPolicy, window_activation_controller: WindowActivationFilter, setting_controller: SettingController, clone_controller: CloneController,
|
|
30
|
+
opening_controller: OpeningController, preference: PreferenceStore, repository: GitRepository, diagnostic_log: DiagnosticLog | None) -> None:
|
|
31
|
+
"""Receive the services and the lower controllers the welcome window works with; the wiring builds it after the opening controller, and Settings tells it through the wiring when no usable Git was found."""
|
|
32
|
+
self.session_registry_controller = session_registry_controller
|
|
33
|
+
self.operation_policy_controller = operation_policy_controller
|
|
34
|
+
self.window_activation_controller = window_activation_controller
|
|
35
|
+
self.setting_controller = setting_controller
|
|
36
|
+
self.clone_controller = clone_controller
|
|
37
|
+
self.opening_controller = opening_controller
|
|
38
|
+
self.preference = preference
|
|
39
|
+
self.repository = repository
|
|
40
|
+
self.diagnostic_log = diagnostic_log
|
|
41
|
+
|
|
42
|
+
def show_welcome(self, anchor: QWidget | None = None) -> WelcomeWindow:
|
|
43
|
+
"""Focus the single chooser or reopen it using current shared preferences.
|
|
44
|
+
|
|
45
|
+
A new chooser opens centred on ``anchor``, the repository window it was asked from, or on the screen without one.
|
|
46
|
+
A chooser already open is only brought to the front, where it is.
|
|
47
|
+
"""
|
|
48
|
+
if self.session_registry_controller.welcome_by_session:
|
|
49
|
+
window = next(iter(self.session_registry_controller.welcome_by_session.values()))
|
|
50
|
+
window.showNormal()
|
|
51
|
+
window.raise_()
|
|
52
|
+
window.activateWindow()
|
|
53
|
+
return window
|
|
54
|
+
session_id = uuid.uuid4().hex
|
|
55
|
+
window = WelcomeWindow(self.preference.read_recent(), anchor, diagnostic_log=self.diagnostic_log)
|
|
56
|
+
self.session_registry_controller.welcome_by_session[session_id] = window
|
|
57
|
+
window.open_requested.connect(lambda repository_dir: self.opening_controller.open_from_welcome(session_id, repository_dir))
|
|
58
|
+
window.clone_requested.connect(lambda: self.clone_controller.show_clone(session_id))
|
|
59
|
+
window.cancel_requested.connect(lambda: self.opening_controller.cancel_open(session_id))
|
|
60
|
+
window.closed.connect(lambda: self._close_welcome(session_id))
|
|
61
|
+
window.setting_requested.connect(self.setting_controller.show_setting)
|
|
62
|
+
window.installEventFilter(self.window_activation_controller)
|
|
63
|
+
window.show()
|
|
64
|
+
if self.diagnostic_log is not None and self.diagnostic_log.warning:
|
|
65
|
+
window.show_status(self.diagnostic_log.warning)
|
|
66
|
+
elif self.preference.warning:
|
|
67
|
+
window.show_status(self.preference.warning)
|
|
68
|
+
return window
|
|
69
|
+
|
|
70
|
+
def show_missing_git(self) -> None:
|
|
71
|
+
"""Keep repository input available and expose a direct path to Git correction."""
|
|
72
|
+
welcome = self.show_welcome()
|
|
73
|
+
welcome.show_status("Git was not found\nNo usable Git executable was found. Choose its path in Settings.")
|
|
74
|
+
welcome.setting_button.setText(SharedText.SETTING_BUTTON_MISSING_GIT.value)
|
|
75
|
+
dialog = self.setting_controller.show_setting()
|
|
76
|
+
dialog.status_label.setText(welcome.status_label.text())
|
|
77
|
+
|
|
78
|
+
def reopen_welcome(self, session_id: str | None = None) -> WelcomeWindow | None:
|
|
79
|
+
"""Expose the repository-window action that restores the shared chooser."""
|
|
80
|
+
if session_id is not None and not self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE):
|
|
81
|
+
return None
|
|
82
|
+
return self.show_welcome(self.session_registry_controller.window_by_session.get(session_id) if session_id is not None else None)
|
|
83
|
+
|
|
84
|
+
def _close_welcome(self, session_id: str) -> None:
|
|
85
|
+
"""Drop a closed chooser without cancelling a successfully opened repository.
|
|
86
|
+
|
|
87
|
+
The chooser and its clone form are handed to Qt for deletion, so they end at a safe moment of the event loop
|
|
88
|
+
instead of wherever their last Python reference happens to go away, such as on return from the close event.
|
|
89
|
+
"""
|
|
90
|
+
welcome = self.session_registry_controller.welcome_by_session.pop(session_id, None)
|
|
91
|
+
if welcome is not None:
|
|
92
|
+
self.clone_controller.cancel_clone(session_id)
|
|
93
|
+
dialog = self.session_registry_controller.clone_dialog_by_session.pop(session_id, None)
|
|
94
|
+
if dialog is not None:
|
|
95
|
+
dialog.close()
|
|
96
|
+
dialog.deleteLater()
|
|
97
|
+
self.session_registry_controller.identity_by_session.pop(session_id, None)
|
|
98
|
+
self.repository.cancel(session_id)
|
|
99
|
+
self.session_registry_controller.generation_by_session.pop(session_id, None)
|
|
100
|
+
welcome.deleteLater()
|
|
101
|
+
self.setting_controller.forget_waiting_opening(session_id)
|
|
102
|
+
self.session_registry_controller.opening_dir_by_session.pop(session_id, None)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Follow the windows of GitPane as the system shows, activates or deactivates them."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import QEvent, QObject, Qt, QTimer
|
|
8
|
+
from PySide6.QtWidgets import QWidget
|
|
9
|
+
from shiboken6 import isValid
|
|
10
|
+
|
|
11
|
+
from gitpane.controller.session_registry import WindowDisplayBinding
|
|
12
|
+
from gitpane.history.file_history import WindowCapability
|
|
13
|
+
from gitpane.worktree.worktree_snapshot import WorktreeKey
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from PySide6.QtWidgets import QApplication
|
|
17
|
+
|
|
18
|
+
from gitpane.controller.operation_policy import OperationPolicy
|
|
19
|
+
from gitpane.controller.session_registry import SessionRegistry
|
|
20
|
+
from gitpane.controller.staging_session_registry import StagingSessionRegistry
|
|
21
|
+
from gitpane.preference.preference_store import PreferenceStore
|
|
22
|
+
|
|
23
|
+
# The only events the filter acts on; it runs on every event of the application, so it lets any other one through at once.
|
|
24
|
+
FOLLOWED_EVENT_TYPE_SET = frozenset({QEvent.Type.Show, QEvent.Type.WindowActivate, QEvent.Type.WindowDeactivate})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WindowActivationFilter(QObject):
|
|
28
|
+
"""The event filter of the application and of every window of GitPane, which tells each preparation session whether one of its windows is active."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, *, session_registry_controller: SessionRegistry, operation_policy_controller: OperationPolicy, staging_session_registry_controller: StagingSessionRegistry,
|
|
31
|
+
application: QApplication, preference: PreferenceStore, parent: QObject | None = None) -> None:
|
|
32
|
+
"""Receive the application, the preferences and the registries the filter reads; the wiring installs it on the application before the placement filter."""
|
|
33
|
+
super().__init__(parent)
|
|
34
|
+
self.session_registry_controller = session_registry_controller
|
|
35
|
+
self.operation_policy_controller = operation_policy_controller
|
|
36
|
+
self.staging_session_registry_controller = staging_session_registry_controller
|
|
37
|
+
self.application = application
|
|
38
|
+
self.preference = preference
|
|
39
|
+
self.activation_update_pending = False
|
|
40
|
+
|
|
41
|
+
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
|
|
42
|
+
"""Follow the screen and the activation of the windows of GitPane, letting every event through."""
|
|
43
|
+
if event.type() not in FOLLOWED_EVENT_TYPE_SET or not isinstance(watched, QWidget) or not watched.isWindow():
|
|
44
|
+
return super().eventFilter(watched, event)
|
|
45
|
+
owner = self._top_level_owner(watched)
|
|
46
|
+
owned = (owner in self.session_registry_controller.welcome_by_session.values() or owner in self.session_registry_controller.window_by_session.values()
|
|
47
|
+
or owner is self.session_registry_controller.setting_dialog or any(owner is staging.window for staging in self.staging_session_registry_controller.staging_by_key.values()))
|
|
48
|
+
if not owned:
|
|
49
|
+
return super().eventFilter(watched, event)
|
|
50
|
+
if event.type() == QEvent.Type.Show and hasattr(watched, "refresh_metrics") and not hasattr(watched, "display_binding"):
|
|
51
|
+
watched.display_binding = WindowDisplayBinding(watched, self.application)
|
|
52
|
+
watched.display_binding.schedule_refresh()
|
|
53
|
+
if event.type() == QEvent.Type.WindowActivate:
|
|
54
|
+
self.preference.refresh()
|
|
55
|
+
self.schedule_activation_update()
|
|
56
|
+
if event.type() == QEvent.Type.WindowDeactivate:
|
|
57
|
+
self.schedule_activation_update()
|
|
58
|
+
return super().eventFilter(watched, event)
|
|
59
|
+
|
|
60
|
+
def _top_level_owner(self, widget: QWidget) -> QWidget:
|
|
61
|
+
"""Answer the top level window a widget belongs to, walking up its parents."""
|
|
62
|
+
owner = widget
|
|
63
|
+
while owner.parentWidget() is not None:
|
|
64
|
+
owner = owner.parentWidget()
|
|
65
|
+
return owner
|
|
66
|
+
|
|
67
|
+
def schedule_activation_update(self, *arguments: object) -> None:
|
|
68
|
+
"""Ask for one reading of which window is active, once the current burst of events is over.
|
|
69
|
+
|
|
70
|
+
Activation events arrive twice for a window that carries the filter itself, and in bursts when a dialog opens
|
|
71
|
+
or closes. The state is therefore read once, after them, instead of being toggled event by event.
|
|
72
|
+
"""
|
|
73
|
+
if self.activation_update_pending or self.session_registry_controller.shutdown_requested:
|
|
74
|
+
return
|
|
75
|
+
self.activation_update_pending = True
|
|
76
|
+
QTimer.singleShot(0, self, self._update_window_activation)
|
|
77
|
+
|
|
78
|
+
def _worktree_key_of_window(self, owner: QWidget | None) -> WorktreeKey | None:
|
|
79
|
+
"""Answer the worktree a top level window belongs to, or nothing when it belongs to none."""
|
|
80
|
+
if owner is None:
|
|
81
|
+
return None
|
|
82
|
+
for session_id, window in self.session_registry_controller.window_by_session.items():
|
|
83
|
+
if window is owner and isValid(window) and self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE) and not window.context.is_bare:
|
|
84
|
+
return WorktreeKey.from_context(window.context)
|
|
85
|
+
for staging in self.staging_session_registry_controller.staging_by_key.values():
|
|
86
|
+
if staging.window is owner:
|
|
87
|
+
return staging.key
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
def _worktree_has_open_window(self, key: WorktreeKey) -> bool:
|
|
91
|
+
"""Tell whether this worktree still has a window of its own on screen."""
|
|
92
|
+
for session_id, window in self.session_registry_controller.window_by_session.items():
|
|
93
|
+
if not isValid(window) or window.is_closed or window.context.is_bare:
|
|
94
|
+
continue
|
|
95
|
+
if self.operation_policy_controller.allows(session_id, WindowCapability.MANAGE) and WorktreeKey.from_context(window.context) == key:
|
|
96
|
+
return True
|
|
97
|
+
staging = self.staging_session_registry_controller.staging_by_key.get(key)
|
|
98
|
+
return staging is not None and staging.window is not None
|
|
99
|
+
|
|
100
|
+
def _update_window_activation(self) -> None:
|
|
101
|
+
"""Publish to every preparation session whether one of its own windows is the active window."""
|
|
102
|
+
self.activation_update_pending = False
|
|
103
|
+
if self.session_registry_controller.shutdown_requested:
|
|
104
|
+
return
|
|
105
|
+
active_window = self.application.activeWindow()
|
|
106
|
+
# Between closing a window and activating the next one, Qt has no active window for one turn of the loop while
|
|
107
|
+
# the application is still active. Reading that as a loss of activation would make closing any dialog read again.
|
|
108
|
+
between_windows = active_window is None and self.application.applicationState() == Qt.ApplicationState.ApplicationActive
|
|
109
|
+
active_key = self._worktree_key_of_window(self._top_level_owner(active_window)) if active_window is not None else None
|
|
110
|
+
for staging in tuple(self.staging_session_registry_controller.staging_by_key.values()):
|
|
111
|
+
if not self._worktree_has_open_window(staging.key):
|
|
112
|
+
staging.set_window_active(False)
|
|
113
|
+
continue
|
|
114
|
+
if between_windows:
|
|
115
|
+
continue
|
|
116
|
+
staging.set_window_active(staging.key == active_key)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Show the state of each worktree in the repository windows that manage it."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from PySide6.QtCore import QObject, Signal
|
|
9
|
+
from shiboken6 import isValid
|
|
10
|
+
|
|
11
|
+
from gitpane.controller.refresh_controller import RefreshScope
|
|
12
|
+
from gitpane.history.file_history import WindowCapability
|
|
13
|
+
from gitpane.ui.look.action_icon import action_icon
|
|
14
|
+
from gitpane.worktree.worktree_snapshot import WorktreeColorState, WorktreeKey
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from gitpane.controller.reference_read_controller import ReferenceReadController
|
|
18
|
+
from gitpane.controller.refresh_controller import RefreshController
|
|
19
|
+
from gitpane.controller.session_registry import SessionRegistry
|
|
20
|
+
from gitpane.controller.staging_session_registry import StagingSessionRegistry
|
|
21
|
+
from gitpane.history.repository_value import RepositoryContext
|
|
22
|
+
from gitpane.operation.repository_operation import RepositoryOperationService
|
|
23
|
+
from gitpane.sequencer.conflict import ConflictService
|
|
24
|
+
from gitpane.sequencer.mergetool import MergetoolService
|
|
25
|
+
from gitpane.sequencer.rebase_state import RebaseSnapshot
|
|
26
|
+
from gitpane.sequencer.replay_state import ReplaySnapshot
|
|
27
|
+
from gitpane.ui.repository.repository_window import RepositoryWindow
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def same_pause_state(first: RebaseSnapshot | ReplaySnapshot | None, second: RebaseSnapshot | ReplaySnapshot | None) -> bool:
|
|
31
|
+
"""Tell whether two published pause states show the same thing, the read number aside."""
|
|
32
|
+
if first is None or second is None:
|
|
33
|
+
return False
|
|
34
|
+
return replace(second, generation=first.generation) == first
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class WorktreeStateController(QObject):
|
|
38
|
+
"""The counters, the merge banner and the pause state of every worktree, shown in each repository window that manages it."""
|
|
39
|
+
|
|
40
|
+
# Asks for an automatic look at the rebase and replay pause state of a window, the session id given: only what the
|
|
41
|
+
# periodic worktree read already found may be shown without reading Git again.
|
|
42
|
+
automatic_observation_requested = Signal(str)
|
|
43
|
+
# Tells that a window of a worktree has just shown the conflict state of a working tree read: the session id, the
|
|
44
|
+
# WorktreeKey and the conflict state, for the conflict window and the conflict prompts of that window.
|
|
45
|
+
window_conflict_state_shown = Signal(str, object, object)
|
|
46
|
+
# Tells that a fresh conflict state of a worktree was read, the WorktreeKey given, once every managing window showed
|
|
47
|
+
# it: a stash kept until its conflicts were solved may now be dropped, and what a Stash pop left is read again.
|
|
48
|
+
worktree_conflict_state_read = Signal(object)
|
|
49
|
+
|
|
50
|
+
def __init__(self, *, session_registry_controller: SessionRegistry, staging_session_registry_controller: StagingSessionRegistry, reference_read_controller: ReferenceReadController, refresh_controller: RefreshController,
|
|
51
|
+
repository_operation: RepositoryOperationService, conflict: ConflictService, mergetool: MergetoolService, parent: QObject | None = None) -> None:
|
|
52
|
+
"""Receive the services and the lower controllers the state is read from; the wiring builds it after the refresh controller, and the preparation sessions, the rebase and the replay tell it what changed."""
|
|
53
|
+
super().__init__(parent)
|
|
54
|
+
self.session_registry_controller = session_registry_controller
|
|
55
|
+
self.staging_session_registry_controller = staging_session_registry_controller
|
|
56
|
+
self.reference_read_controller = reference_read_controller
|
|
57
|
+
self.refresh_controller = refresh_controller
|
|
58
|
+
self.repository_operation = repository_operation
|
|
59
|
+
self.conflict = conflict
|
|
60
|
+
self.mergetool = mergetool
|
|
61
|
+
# The last pause state told to the availability views, per worktree; an equal state is not told again.
|
|
62
|
+
self.relayed_rebase_state_by_key: dict[WorktreeKey, RebaseSnapshot] = {}
|
|
63
|
+
self.relayed_replay_state_by_key: dict[WorktreeKey, ReplaySnapshot] = {}
|
|
64
|
+
|
|
65
|
+
def _relay_pause_state(self, relayed_state_by_key: dict[WorktreeKey, RebaseSnapshot] | dict[WorktreeKey, ReplaySnapshot],
|
|
66
|
+
state: RebaseSnapshot | ReplaySnapshot) -> None:
|
|
67
|
+
"""Tell the availability views about a published pause state only when it changed; reservations tell them on their own."""
|
|
68
|
+
# Every emission recomputes the availability of every dialog and reads the native files again for each staging session.
|
|
69
|
+
if same_pause_state(relayed_state_by_key.get(state.key), state):
|
|
70
|
+
return
|
|
71
|
+
relayed_state_by_key[state.key] = state
|
|
72
|
+
self.repository_operation.changed.emit()
|
|
73
|
+
|
|
74
|
+
def relay_rebase_state(self, state: RebaseSnapshot) -> None:
|
|
75
|
+
"""Relay one published native rebase state to the availability views."""
|
|
76
|
+
self._relay_pause_state(self.relayed_rebase_state_by_key, state)
|
|
77
|
+
|
|
78
|
+
def relay_replay_state(self, state: ReplaySnapshot) -> None:
|
|
79
|
+
"""Relay one published native Cherry-pick or Revert state to the availability views."""
|
|
80
|
+
self._relay_pause_state(self.relayed_replay_state_by_key, state)
|
|
81
|
+
|
|
82
|
+
def managed_windows_of_worktree(self, key: WorktreeKey) -> list[tuple[str, RepositoryWindow]]:
|
|
83
|
+
"""List the open repository windows that manage this worktree: the ones showing its counters and its pause banners."""
|
|
84
|
+
windows: list[tuple[str, RepositoryWindow]] = []
|
|
85
|
+
for session_id, window in self.session_registry_controller.window_by_session.items():
|
|
86
|
+
session = self.session_registry_controller.session_by_id[session_id]
|
|
87
|
+
if session.is_closed or not isValid(window) or window.is_closed or not isValid(window.commit_action):
|
|
88
|
+
continue
|
|
89
|
+
if WorktreeKey.from_context(session.repository_context) != key or WindowCapability.MANAGE not in session.capabilities:
|
|
90
|
+
continue
|
|
91
|
+
windows.append((session_id, window))
|
|
92
|
+
return windows
|
|
93
|
+
|
|
94
|
+
def mark_pause_observed(self, key: WorktreeKey, observation: str) -> None:
|
|
95
|
+
"""Tell the worktree's session that an observation of its pause state starts, so its periodic refresh asks again only when something moves afterwards."""
|
|
96
|
+
staging = self.staging_session_registry_controller.staging_by_key.get(key)
|
|
97
|
+
if staging is None:
|
|
98
|
+
return
|
|
99
|
+
staging.mark_pause_observed(observation)
|
|
100
|
+
|
|
101
|
+
def observe_worktree_pause(self, key: WorktreeKey) -> None:
|
|
102
|
+
"""Look at the rebase and replay state when the session reports a pause marker while nothing else moved."""
|
|
103
|
+
for session_id, _window in self.managed_windows_of_worktree(key):
|
|
104
|
+
self.automatic_observation_requested.emit(session_id)
|
|
105
|
+
|
|
106
|
+
def follow_moved_head(self, key: WorktreeKey) -> None:
|
|
107
|
+
"""Bring the graph of this worktree's windows to the HEAD a worktree read found moved by another program, without a Refresh.
|
|
108
|
+
|
|
109
|
+
The working tree and the pause state were read by that very read, so only references and graph are read again.
|
|
110
|
+
A window already refreshing, a refresh the read belongs to, an operation of GitPane on the worktree, or a graph
|
|
111
|
+
that already shows this HEAD needs nothing more.
|
|
112
|
+
"""
|
|
113
|
+
staging = self.staging_session_registry_controller.staging_by_key.get(key)
|
|
114
|
+
if staging is None or staging.snapshot is None or not staging.snapshot.fresh or staging.snapshot.probe is None:
|
|
115
|
+
return
|
|
116
|
+
if self.repository_operation.owns_worktree(key) or self.refresh_controller.full_refresh_reads(key, staging):
|
|
117
|
+
return
|
|
118
|
+
probe = staging.snapshot.probe
|
|
119
|
+
for session_id, _window in self.managed_windows_of_worktree(key):
|
|
120
|
+
if self.session_registry_controller.session_by_id[session_id].refresh_active:
|
|
121
|
+
continue
|
|
122
|
+
displayed = self.reference_read_controller.reference_snapshot_by_session.get(session_id)
|
|
123
|
+
if displayed is not None and displayed.head_oid == probe.head_oid and displayed.head_ref == probe.branch_head:
|
|
124
|
+
continue
|
|
125
|
+
self.refresh_controller.refresh(session_id, scope=RefreshScope.REFERENCES, outside_already_read=True)
|
|
126
|
+
|
|
127
|
+
def _merge_banner_busy(self, context: RepositoryContext) -> bool:
|
|
128
|
+
"""Tell whether a merge tool or a reserved operation holds this worktree, while the merge banner's Continue and Abort must wait."""
|
|
129
|
+
return self.mergetool.owns_repository(context.common_git_dir) or self.repository_operation.owns_worktree(WorktreeKey.from_context(context))
|
|
130
|
+
|
|
131
|
+
def merge_banner_actions_changed(self) -> None:
|
|
132
|
+
"""Redraw Continue and Abort of every blue merge banner from the state its window already shows, when a merge tool series or an operation starts or ends.
|
|
133
|
+
|
|
134
|
+
No Git read: the banner used to be redrawn only when a worktree read was published, and the merge tool publishes
|
|
135
|
+
its last read while its series still holds the repository; a later identical read publishes nothing, so the
|
|
136
|
+
buttons stayed greyed until a Refresh.
|
|
137
|
+
"""
|
|
138
|
+
for key, staging in tuple(self.staging_session_registry_controller.staging_by_key.items()):
|
|
139
|
+
busy = self._merge_banner_busy(staging.context)
|
|
140
|
+
for _session_id, window in self.managed_windows_of_worktree(key):
|
|
141
|
+
if not window.merge_banner_shown or window.conflict_state is None:
|
|
142
|
+
continue
|
|
143
|
+
window.show_conflict_state(window.conflict_state, merge_state=window.merge_banner_state, busy=busy)
|
|
144
|
+
|
|
145
|
+
def show_worktree_state(self, key: WorktreeKey) -> None:
|
|
146
|
+
"""Refresh matching counters and conflicts from the shared working-tree read."""
|
|
147
|
+
staging = self.staging_session_registry_controller.staging_by_key[key]
|
|
148
|
+
snapshot = staging.snapshot
|
|
149
|
+
conflict_state = self.conflict.observe(key, snapshot, staging.diagnostic.cause if staging.diagnostic is not None else "")
|
|
150
|
+
# The refresh this read belongs to observes the pause state itself; a second observation would only repeat it.
|
|
151
|
+
pause_observed_by_refresh = self.refresh_controller.full_refresh_covers(key, staging)
|
|
152
|
+
# A merge tool or an operation at work on the worktree changes the state the merge banner's buttons act on.
|
|
153
|
+
busy = self._merge_banner_busy(staging.context)
|
|
154
|
+
for session_id, window in self.managed_windows_of_worktree(key):
|
|
155
|
+
count = str(snapshot.count) if snapshot is not None and snapshot.fresh else "?"
|
|
156
|
+
window.commit_action.setText(f"Commit({count})")
|
|
157
|
+
state = snapshot.color_state() if snapshot is not None else WorktreeColorState.UNKNOWN
|
|
158
|
+
# Every working-tree read lands here; the icon only needs replacing when its color changes, and the
|
|
159
|
+
# window redraws it from change_state when the screen density changes.
|
|
160
|
+
if window.commit_action.property("change_state") != state:
|
|
161
|
+
window.commit_action.setIcon(action_icon("commit", window.devicePixelRatioF(), state))
|
|
162
|
+
window.commit_action.setProperty("change_state", state)
|
|
163
|
+
window.commit_action.setToolTip(staging.diagnostic.cause if staging.diagnostic is not None else "Prepare local changes")
|
|
164
|
+
window.show_conflict_state(conflict_state, merge_state=staging.merge_banner_state, busy=busy)
|
|
165
|
+
if not pause_observed_by_refresh:
|
|
166
|
+
self.automatic_observation_requested.emit(session_id)
|
|
167
|
+
self.window_conflict_state_shown.emit(session_id, key, conflict_state)
|
|
168
|
+
if conflict_state.fresh:
|
|
169
|
+
self.worktree_conflict_state_read.emit(key)
|
|
File without changes
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Closed list of the symbols offered by the emoji keyboard.
|
|
2
|
+
|
|
3
|
+
Both keyboard grids read this list, so it is the only place a symbol is added or removed. The packaged font is the
|
|
4
|
+
complete Noto Color Emoji build, not a subset made from this list: adding a symbol needs no new font, and
|
|
5
|
+
tests/test_emoji_font.py checks that the font draws every entry in colour. Pure data without any Qt import. Sequences are written with escapes because zero-width joiners (U+200D) and variation
|
|
6
|
+
selectors (U+FE0F) are invisible and silently lost when the characters are copied by hand.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class EmojiEntry:
|
|
16
|
+
"""One symbol of the keyboard: the exact text a click produces, its English name and what the button shows."""
|
|
17
|
+
|
|
18
|
+
text: str
|
|
19
|
+
name: str
|
|
20
|
+
display_text: str = ""
|
|
21
|
+
|
|
22
|
+
def shown_text(self) -> str:
|
|
23
|
+
"""Return what the button draws; combining marks are shown on a sample so they have something to sit on."""
|
|
24
|
+
return self.display_text or self.text
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
COMBINING_MARK_SAMPLE_BEFORE = "\U0001F98B"
|
|
28
|
+
COMBINING_MARK_SAMPLE_AFTER = "\U0001F497"
|
|
29
|
+
COMBINING_MARK_TITLE = "Combining Diacritical Marks"
|
|
30
|
+
|
|
31
|
+
# Themes follow each other without headings: status, highlight, pointers, tools, documents, faces, people and hands,
|
|
32
|
+
# then coloured circles and squares from red to purple. Symbols written side by side without a zero-width joiner in the
|
|
33
|
+
# original list (stop sign and blue diamond, eye and mouth, alien and laughing face) are separate symbols with their own button.
|
|
34
|
+
EMOJI_ENTRIES: tuple[EmojiEntry, ...] = (
|
|
35
|
+
EmojiEntry("✅", "Check mark button"),
|
|
36
|
+
EmojiEntry("✔️", "Check mark"),
|
|
37
|
+
EmojiEntry("❌", "Cross mark"),
|
|
38
|
+
EmojiEntry("⛔", "No entry"),
|
|
39
|
+
EmojiEntry("\U0001F6AB", "Prohibited"),
|
|
40
|
+
EmojiEntry("\U0001F6D1", "Stop sign"),
|
|
41
|
+
EmojiEntry("⚠️", "Warning"),
|
|
42
|
+
EmojiEntry("\U0001F6A8", "Police car light"),
|
|
43
|
+
EmojiEntry("\U0001F6A7", "Construction"),
|
|
44
|
+
EmojiEntry("\U0001F691", "Ambulance"),
|
|
45
|
+
EmojiEntry("‼️", "Double exclamation mark"),
|
|
46
|
+
EmojiEntry("❓", "Red question mark"),
|
|
47
|
+
EmojiEntry("❔", "White question mark"),
|
|
48
|
+
EmojiEntry("\U0001F4AF", "Hundred points"),
|
|
49
|
+
EmojiEntry("\U0001F31F", "Glowing star"),
|
|
50
|
+
EmojiEntry("⭐", "Star"),
|
|
51
|
+
EmojiEntry("\u2728", "Sparkles"),
|
|
52
|
+
EmojiEntry("\U0001F525", "Fire"),
|
|
53
|
+
EmojiEntry("\U0001F4A5", "Collision"),
|
|
54
|
+
EmojiEntry("⚡️", "High voltage"),
|
|
55
|
+
EmojiEntry("\U0001F3AF", "Bullseye"),
|
|
56
|
+
EmojiEntry("\U0001F680", "Rocket"),
|
|
57
|
+
EmojiEntry("\U0001F48E", "Gem stone"),
|
|
58
|
+
EmojiEntry("\U0001F300", "Cyclone"),
|
|
59
|
+
EmojiEntry("\U0001F4A4", "Zzz"),
|
|
60
|
+
EmojiEntry("\U0001F4A2", "Anger symbol"),
|
|
61
|
+
EmojiEntry("\U0001F5FF", "Moai"),
|
|
62
|
+
EmojiEntry("\U0001F3F3️", "White flag"),
|
|
63
|
+
EmojiEntry("\U0001F449", "Backhand index pointing right"),
|
|
64
|
+
EmojiEntry("\U0001F448", "Backhand index pointing left"),
|
|
65
|
+
EmojiEntry("\U0001FAF5", "Index pointing at the viewer"),
|
|
66
|
+
EmojiEntry("➤", "Black rightwards arrowhead"),
|
|
67
|
+
EmojiEntry("\U0001F53A", "Red triangle pointed up"),
|
|
68
|
+
EmojiEntry("\U0001F53B", "Red triangle pointed down"),
|
|
69
|
+
EmojiEntry("\U0001F537", "Large blue diamond"),
|
|
70
|
+
EmojiEntry("\U0001F500", "Shuffle tracks button"),
|
|
71
|
+
EmojiEntry("\U0001F4CC", "Pushpin"),
|
|
72
|
+
EmojiEntry("⚙️", "Gear"),
|
|
73
|
+
EmojiEntry("\U0001F6E0️", "Hammer and wrench"),
|
|
74
|
+
EmojiEntry("⚔️", "Crossed swords"),
|
|
75
|
+
EmojiEntry("\U0001F6E1️", "Shield"),
|
|
76
|
+
EmojiEntry("\U0001F5DD️", "Old key"),
|
|
77
|
+
EmojiEntry("\U0001F517", "Link"),
|
|
78
|
+
EmojiEntry("\U0001F3F7️", "Label"),
|
|
79
|
+
EmojiEntry("\U0001F50E", "Magnifying glass tilted right"),
|
|
80
|
+
EmojiEntry("\U0001F9EA", "Test tube"),
|
|
81
|
+
EmojiEntry("\U0001F9E0", "Brain"),
|
|
82
|
+
EmojiEntry("\U0001F916", "Robot"),
|
|
83
|
+
EmojiEntry("\U0001F40D", "Snake"),
|
|
84
|
+
EmojiEntry("\U0001F3A8", "Artist palette"),
|
|
85
|
+
EmojiEntry("\U0001F4C1", "File folder"),
|
|
86
|
+
EmojiEntry("\U0001F4CB", "Clipboard"),
|
|
87
|
+
EmojiEntry("\U0001F4DD", "Memo"),
|
|
88
|
+
EmojiEntry("✏️", "Pencil"),
|
|
89
|
+
EmojiEntry("\U0001F4D2", "Ledger"),
|
|
90
|
+
EmojiEntry("\U0001F4D5", "Closed book"),
|
|
91
|
+
EmojiEntry("\U0001F4DA", "Books"),
|
|
92
|
+
EmojiEntry("\U0001F4DC", "Scroll"),
|
|
93
|
+
EmojiEntry("\U0001F4CA", "Bar chart"),
|
|
94
|
+
EmojiEntry("\U0001F4C8", "Chart increasing"),
|
|
95
|
+
EmojiEntry("\U0001F522", "Input numbers"),
|
|
96
|
+
EmojiEntry("\U0001D48A", "Mathematical bold italic small i"),
|
|
97
|
+
EmojiEntry("\U0001F4E4", "Outbox tray"),
|
|
98
|
+
EmojiEntry("\U0001F4EC", "Open mailbox with raised flag"),
|
|
99
|
+
EmojiEntry("\U0001F4E2", "Loudspeaker"),
|
|
100
|
+
EmojiEntry("\U0001F5D3️", "Spiral calendar"),
|
|
101
|
+
EmojiEntry("⌚", "Watch"),
|
|
102
|
+
EmojiEntry("⌛", "Hourglass done"),
|
|
103
|
+
EmojiEntry("\U0001F4B5", "Dollar banknote"),
|
|
104
|
+
EmojiEntry("\U0001F917", "Smiling face with open hands"),
|
|
105
|
+
EmojiEntry("\U0001F601", "Beaming face with smiling eyes"),
|
|
106
|
+
EmojiEntry("\U0001F923", "Rolling on the floor laughing"),
|
|
107
|
+
EmojiEntry("\U0001F914", "Thinking face"),
|
|
108
|
+
EmojiEntry("\U0001F9D0", "Face with monocle"),
|
|
109
|
+
EmojiEntry("\U0001F97A", "Pleading face"),
|
|
110
|
+
EmojiEntry("\U0001F62C", "Grimacing face"),
|
|
111
|
+
EmojiEntry("\U0001F628", "Fearful face"),
|
|
112
|
+
EmojiEntry("\U0001F631", "Face screaming in fear"),
|
|
113
|
+
EmojiEntry("\U0001F62D", "Loudly crying face"),
|
|
114
|
+
EmojiEntry("\U0001F47F", "Angry face with horns"),
|
|
115
|
+
EmojiEntry("\U0001F92C", "Face with symbols on mouth"),
|
|
116
|
+
EmojiEntry("\U0001F621", "Enraged face"),
|
|
117
|
+
EmojiEntry("\U0001F921", "Clown face"),
|
|
118
|
+
EmojiEntry("\U0001F47D", "Alien"),
|
|
119
|
+
EmojiEntry("\U0001F441️", "Eye"),
|
|
120
|
+
EmojiEntry("\U0001F444", "Mouth"),
|
|
121
|
+
EmojiEntry("\U0001F926♀️", "Woman facepalming"),
|
|
122
|
+
EmojiEntry("\U0001F937", "Person shrugging"),
|
|
123
|
+
EmojiEntry("\U0001F46E\U0001F3FB♂️", "Man police officer: light skin tone"),
|
|
124
|
+
EmojiEntry("✋", "Raised hand"),
|
|
125
|
+
EmojiEntry("\U0001F919", "Call me hand"),
|
|
126
|
+
EmojiEntry("\U0001F64C", "Raising hands"),
|
|
127
|
+
EmojiEntry("\U0001F44C", "OK hand"),
|
|
128
|
+
EmojiEntry("\U0001F64F", "Folded hands"),
|
|
129
|
+
EmojiEntry("\U0001F595", "Middle finger"),
|
|
130
|
+
EmojiEntry("\U0001F534", "Red circle"),
|
|
131
|
+
EmojiEntry("\U0001F7E0", "Orange circle"),
|
|
132
|
+
EmojiEntry("\U0001F7E1", "Yellow circle"),
|
|
133
|
+
EmojiEntry("\U0001F7E2", "Green circle"),
|
|
134
|
+
EmojiEntry("\U0001F535", "Blue circle"),
|
|
135
|
+
EmojiEntry("\U0001F7E3", "Purple circle"),
|
|
136
|
+
EmojiEntry("\U0001F7E5", "Red square"),
|
|
137
|
+
EmojiEntry("\U0001F7E7", "Orange square"),
|
|
138
|
+
EmojiEntry("\U0001F7E8", "Yellow square"),
|
|
139
|
+
EmojiEntry("\U0001F7E9", "Green square"),
|
|
140
|
+
EmojiEntry("\U0001F7E6", "Blue square"),
|
|
141
|
+
EmojiEntry("\U0001F7EA", "Purple square"),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def combining_mark_entry(mark: str, name: str) -> EmojiEntry:
|
|
146
|
+
"""Build a mark entry whose click gives the mark alone while the button shows it between two emoji."""
|
|
147
|
+
return EmojiEntry(mark, name, COMBINING_MARK_SAMPLE_BEFORE + mark + COMBINING_MARK_SAMPLE_AFTER)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
COMBINING_MARK_ENTRIES: tuple[EmojiEntry, ...] = (
|
|
151
|
+
combining_mark_entry("⃘", "Combining ring overlay"),
|
|
152
|
+
combining_mark_entry("⃟", "Combining enclosing diamond"),
|
|
153
|
+
combining_mark_entry("⃤", "Combining enclosing upward pointing triangle"),
|
|
154
|
+
combining_mark_entry("⃫", "Combining long double solidus overlay"),
|
|
155
|
+
combining_mark_entry("⃒", "Combining long vertical line overlay"),
|
|
156
|
+
)
|
|
File without changes
|