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.
Files changed (353) hide show
  1. gitpane/__init__.py +0 -0
  2. gitpane/account/__init__.py +0 -0
  3. gitpane/account/account_choice.py +297 -0
  4. gitpane/account/address_owner.py +67 -0
  5. gitpane/account/authentication.py +1811 -0
  6. gitpane/account/authentication_worker.py +836 -0
  7. gitpane/account/credential_manager.py +480 -0
  8. gitpane/account/credential_manager_install.py +171 -0
  9. gitpane/account/keyring_reader.py +135 -0
  10. gitpane/account/keyring_state.py +158 -0
  11. gitpane/account/ssh.py +312 -0
  12. gitpane/account/ssh_askpass.py +216 -0
  13. gitpane/account/ssh_prompt.py +222 -0
  14. gitpane/application.py +684 -0
  15. gitpane/commit/__init__.py +0 -0
  16. gitpane/commit/commit_metadata.py +88 -0
  17. gitpane/commit/commit_push.py +276 -0
  18. gitpane/commit/commit_write.py +830 -0
  19. gitpane/config.py +1066 -0
  20. gitpane/controller/__init__.py +0 -0
  21. gitpane/controller/account_controller.py +677 -0
  22. gitpane/controller/clone_controller.py +776 -0
  23. gitpane/controller/close_readiness.py +212 -0
  24. gitpane/controller/commit_timing_controller.py +566 -0
  25. gitpane/controller/conflict_controller.py +885 -0
  26. gitpane/controller/console_registry.py +38 -0
  27. gitpane/controller/emoji_window_controller.py +95 -0
  28. gitpane/controller/file_action_controller.py +479 -0
  29. gitpane/controller/history_read_controller.py +671 -0
  30. gitpane/controller/history_rewrite_controller.py +739 -0
  31. gitpane/controller/navigation_controller.py +658 -0
  32. gitpane/controller/opening_controller.py +375 -0
  33. gitpane/controller/operation_policy.py +76 -0
  34. gitpane/controller/rebase_controller.py +797 -0
  35. gitpane/controller/rebase_editor_controller.py +309 -0
  36. gitpane/controller/reference_form_controller.py +1677 -0
  37. gitpane/controller/reference_read_controller.py +242 -0
  38. gitpane/controller/refresh_controller.py +477 -0
  39. gitpane/controller/replay_controller.py +805 -0
  40. gitpane/controller/session_registry.py +337 -0
  41. gitpane/controller/setting_controller.py +592 -0
  42. gitpane/controller/shutdown_controller.py +345 -0
  43. gitpane/controller/staging_controller.py +199 -0
  44. gitpane/controller/staging_session_registry.py +47 -0
  45. gitpane/controller/stash_controller.py +367 -0
  46. gitpane/controller/transport_controller.py +813 -0
  47. gitpane/controller/welcome_controller.py +102 -0
  48. gitpane/controller/window_activation.py +116 -0
  49. gitpane/controller/worktree_state_controller.py +169 -0
  50. gitpane/emoji/__init__.py +0 -0
  51. gitpane/emoji/emoji_catalogue.py +156 -0
  52. gitpane/git_config/__init__.py +0 -0
  53. gitpane/git_config/git_setting.py +441 -0
  54. gitpane/history/__init__.py +0 -0
  55. gitpane/history/avatar.py +241 -0
  56. gitpane/history/file_history.py +359 -0
  57. gitpane/history/graph.py +145 -0
  58. gitpane/history/history_filter.py +31 -0
  59. gitpane/history/history_parse.py +605 -0
  60. gitpane/history/repository.py +1600 -0
  61. gitpane/history/repository_value.py +341 -0
  62. gitpane/history/revision.py +180 -0
  63. gitpane/history/revision_file.py +182 -0
  64. gitpane/history_rewrite/__init__.py +0 -0
  65. gitpane/history_rewrite/graph.py +210 -0
  66. gitpane/history_rewrite/guard.py +288 -0
  67. gitpane/history_rewrite/index.py +125 -0
  68. gitpane/history_rewrite/prepare.py +378 -0
  69. gitpane/history_rewrite/protocol.py +320 -0
  70. gitpane/history_rewrite/publish.py +444 -0
  71. gitpane/history_rewrite/recovery.py +481 -0
  72. gitpane/history_rewrite/value.py +229 -0
  73. gitpane/operation/__init__.py +0 -0
  74. gitpane/operation/console_line.py +313 -0
  75. gitpane/operation/console_tone.py +76 -0
  76. gitpane/operation/diagnostic.py +954 -0
  77. gitpane/operation/launch_signature.py +254 -0
  78. gitpane/operation/operation_status.py +87 -0
  79. gitpane/operation/operation_trace.py +766 -0
  80. gitpane/operation/repository_operation.py +781 -0
  81. gitpane/preference/__init__.py +0 -0
  82. gitpane/preference/preference_location.py +40 -0
  83. gitpane/preference/preference_store.py +590 -0
  84. gitpane/process/__init__.py +0 -0
  85. gitpane/process/exact_arguments.py +77 -0
  86. gitpane/process/git_output_text.py +20 -0
  87. gitpane/process/process_group.py +851 -0
  88. gitpane/process/process_log.py +114 -0
  89. gitpane/process/process_runner.py +1033 -0
  90. gitpane/process/released_child_watch.py +138 -0
  91. gitpane/recent/__init__.py +0 -0
  92. gitpane/recent/mount_table.py +422 -0
  93. gitpane/recent/recent_availability.py +414 -0
  94. gitpane/recent/recent_name.py +11 -0
  95. gitpane/recent/recent_search.py +114 -0
  96. gitpane/reference/__init__.py +0 -0
  97. gitpane/reference/reference_edit.py +2030 -0
  98. gitpane/reference/reference_snapshot.py +196 -0
  99. gitpane/reference/stash.py +730 -0
  100. gitpane/reference/stash_list.py +164 -0
  101. gitpane/sequencer/__init__.py +0 -0
  102. gitpane/sequencer/commit_message_rule.py +154 -0
  103. gitpane/sequencer/conflict.py +506 -0
  104. gitpane/sequencer/merge_recovery.py +163 -0
  105. gitpane/sequencer/merge_state.py +166 -0
  106. gitpane/sequencer/mergetool.py +856 -0
  107. gitpane/sequencer/rebase.py +750 -0
  108. gitpane/sequencer/rebase_edit_file.py +224 -0
  109. gitpane/sequencer/rebase_state.py +523 -0
  110. gitpane/sequencer/replay.py +1435 -0
  111. gitpane/sequencer/replay_legacy.py +253 -0
  112. gitpane/sequencer/replay_state.py +747 -0
  113. gitpane/submodule/__init__.py +0 -0
  114. gitpane/submodule/checkout_refusal.py +102 -0
  115. gitpane/submodule/checkout_refusal_notice.py +111 -0
  116. gitpane/submodule/cloned_without_checkout.py +65 -0
  117. gitpane/submodule/submodule_discovery.py +1180 -0
  118. gitpane/submodule/submodule_loss.py +218 -0
  119. gitpane/system/__init__.py +0 -0
  120. gitpane/system/appearance_portal.py +125 -0
  121. gitpane/system/application_identity.py +48 -0
  122. gitpane/system/command_line.py +36 -0
  123. gitpane/system/desktop_icon_fact.py +128 -0
  124. gitpane/system/desktop_icon_support.py +270 -0
  125. gitpane/system/desktop_shortcut.py +536 -0
  126. gitpane/system/icns_image.py +35 -0
  127. gitpane/system/installed_launcher.py +136 -0
  128. gitpane/system/linux_distribution.py +151 -0
  129. gitpane/system/network_folder.py +46 -0
  130. gitpane/system/path_identity.py +95 -0
  131. gitpane/system/private_tree_removal.py +47 -0
  132. gitpane/system/process_program.py +63 -0
  133. gitpane/system/qt_folder.py +38 -0
  134. gitpane/system/ready_channel_watch.py +67 -0
  135. gitpane/system/startup_activation.py +65 -0
  136. gitpane/system/terminal_detach.py +384 -0
  137. gitpane/system/xdg_session.py +85 -0
  138. gitpane/text/__init__.py +0 -0
  139. gitpane/text/text_query.py +97 -0
  140. gitpane/text/typed_path.py +75 -0
  141. gitpane/text/typed_text.py +17 -0
  142. gitpane/thread/__init__.py +0 -0
  143. gitpane/thread/weak_signal.py +93 -0
  144. gitpane/tool/__init__.py +0 -0
  145. gitpane/tool/app_bundle.py +164 -0
  146. gitpane/tool/desktop_text_editor.py +156 -0
  147. gitpane/tool/difftool.py +429 -0
  148. gitpane/tool/editor.py +445 -0
  149. gitpane/tool/editor_bridge.py +193 -0
  150. gitpane/tool/editor_recipe.py +481 -0
  151. gitpane/tool/file_reveal.py +94 -0
  152. gitpane/tool/git_executable.py +264 -0
  153. gitpane/tool/git_version.py +62 -0
  154. gitpane/tool/kdiff3.py +41 -0
  155. gitpane/tool/kdiff3_install.py +162 -0
  156. gitpane/tool/launch_services_text_editor.py +104 -0
  157. gitpane/tool/mergetool_recipe.py +84 -0
  158. gitpane/tool/system_editor_default.py +80 -0
  159. gitpane/tool/system_text_editor.py +44 -0
  160. gitpane/tool/system_tool.py +816 -0
  161. gitpane/tool/terminal_recipe.py +390 -0
  162. gitpane/transport/__init__.py +0 -0
  163. gitpane/transport/branch_exchange.py +1690 -0
  164. gitpane/transport/clone.py +1273 -0
  165. gitpane/transport/credential_read_retry.py +35 -0
  166. gitpane/transport/git_refusal.py +207 -0
  167. gitpane/transport/push_tracking.py +121 -0
  168. gitpane/transport/refusal_notice.py +225 -0
  169. gitpane/transport/remote.py +747 -0
  170. gitpane/transport/remote_config.py +544 -0
  171. gitpane/ui/__init__.py +0 -0
  172. gitpane/ui/console/__init__.py +0 -0
  173. gitpane/ui/console/error_dialog.py +332 -0
  174. gitpane/ui/console/operation_dialog.py +408 -0
  175. gitpane/ui/console/operation_output.py +91 -0
  176. gitpane/ui/console/operation_output_view.py +373 -0
  177. gitpane/ui/emoji/__init__.py +0 -0
  178. gitpane/ui/emoji/emoji_font.py +257 -0
  179. gitpane/ui/emoji/emoji_grid.py +125 -0
  180. gitpane/ui/emoji/emoji_window.py +154 -0
  181. gitpane/ui/emoji/file_action_icon.py +131 -0
  182. gitpane/ui/emoji/opentype_family_name.py +182 -0
  183. gitpane/ui/history_rewrite/__init__.py +0 -0
  184. gitpane/ui/history_rewrite/details.py +78 -0
  185. gitpane/ui/history_rewrite/preview.py +565 -0
  186. gitpane/ui/history_rewrite/preview_model.py +291 -0
  187. gitpane/ui/history_rewrite/question.py +87 -0
  188. gitpane/ui/history_rewrite/recovery_dialog.py +262 -0
  189. gitpane/ui/look/__init__.py +0 -0
  190. gitpane/ui/look/action_icon.py +504 -0
  191. gitpane/ui/look/application_icon.py +78 -0
  192. gitpane/ui/look/application_style.py +317 -0
  193. gitpane/ui/look/avatar_label.py +53 -0
  194. gitpane/ui/look/color_contrast.py +43 -0
  195. gitpane/ui/look/desktop_appearance_follower.py +118 -0
  196. gitpane/ui/look/fixed_font.py +21 -0
  197. gitpane/ui/look/gtk_settings.py +116 -0
  198. gitpane/ui/look/icon_density.py +16 -0
  199. gitpane/ui/look/reference_font.py +19 -0
  200. gitpane/ui/look/theme_color.py +63 -0
  201. gitpane/ui/look/theme_look.py +95 -0
  202. gitpane/ui/look/theme_palette.py +173 -0
  203. gitpane/ui/look/warning_icon.py +86 -0
  204. gitpane/ui/look/windows_control_look.py +250 -0
  205. gitpane/ui/reader/__init__.py +0 -0
  206. gitpane/ui/reader/commit_message.py +129 -0
  207. gitpane/ui/reader/commit_panel.py +378 -0
  208. gitpane/ui/reader/diff_panel.py +626 -0
  209. gitpane/ui/reader/file_action.py +138 -0
  210. gitpane/ui/reader/file_tree_panel.py +235 -0
  211. gitpane/ui/reader/reader_toolbar.py +56 -0
  212. gitpane/ui/reader/revision_tree.py +80 -0
  213. gitpane/ui/reader/search_result.py +165 -0
  214. gitpane/ui/reader/source_text.py +134 -0
  215. gitpane/ui/reader/source_view.py +151 -0
  216. gitpane/ui/reader/syntax_span.py +67 -0
  217. gitpane/ui/reference/__init__.py +0 -0
  218. gitpane/ui/reference/branch_selector.py +106 -0
  219. gitpane/ui/reference/checkout_dialog.py +419 -0
  220. gitpane/ui/reference/checkout_revision_dialog.py +182 -0
  221. gitpane/ui/reference/commit_summary.py +339 -0
  222. gitpane/ui/reference/create_branch_dialog.py +346 -0
  223. gitpane/ui/reference/create_tag_dialog.py +283 -0
  224. gitpane/ui/reference/delete_local_branch_dialog.py +323 -0
  225. gitpane/ui/reference/delete_tag_dialog.py +394 -0
  226. gitpane/ui/reference/form_opening.py +53 -0
  227. gitpane/ui/reference/reference_dialog.py +417 -0
  228. gitpane/ui/reference/reference_feedback.py +62 -0
  229. gitpane/ui/reference/reference_form.py +139 -0
  230. gitpane/ui/reference/rename_branch_dialog.py +179 -0
  231. gitpane/ui/reference/reset_branch_dialog.py +430 -0
  232. gitpane/ui/reference/reset_dialog.py +263 -0
  233. gitpane/ui/reference/revision_dialog.py +121 -0
  234. gitpane/ui/reference/revision_picker.py +227 -0
  235. gitpane/ui/reference/select_branches_dialog.py +199 -0
  236. gitpane/ui/reference/stash_dialog.py +171 -0
  237. gitpane/ui/repository/__init__.py +0 -0
  238. gitpane/ui/repository/graph_delegate.py +298 -0
  239. gitpane/ui/repository/history_avatar.py +130 -0
  240. gitpane/ui/repository/history_model.py +179 -0
  241. gitpane/ui/repository/repository_list_popup.py +319 -0
  242. gitpane/ui/repository/repository_window.py +2580 -0
  243. gitpane/ui/resource/ArrowDown.png +0 -0
  244. gitpane/ui/resource/ArrowUp.png +0 -0
  245. gitpane/ui/resource/BranchCheckout.png +0 -0
  246. gitpane/ui/resource/BranchCreate.png +0 -0
  247. gitpane/ui/resource/BranchDelete.png +0 -0
  248. gitpane/ui/resource/CopyToClipboard.png +0 -0
  249. gitpane/ui/resource/GitPane-16.png +0 -0
  250. gitpane/ui/resource/GitPane-24.png +0 -0
  251. gitpane/ui/resource/GitPane-256.png +0 -0
  252. gitpane/ui/resource/GitPane-32.png +0 -0
  253. gitpane/ui/resource/GitPane-48.png +0 -0
  254. gitpane/ui/resource/GitPane-64.png +0 -0
  255. gitpane/ui/resource/GitPane.ico +0 -0
  256. gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
  257. gitpane/ui/resource/NotoColorEmoji_WindowsCompatible.ttf +0 -0
  258. gitpane/ui/resource/Renamed.png +0 -0
  259. gitpane/ui/resource/ResetAnotherBranchToHere.png +0 -0
  260. gitpane/ui/resource/ResetCurrentBranchToHere.png +0 -0
  261. gitpane/ui/resource/Stash.png +0 -0
  262. gitpane/ui/resource/Tag.png +0 -0
  263. gitpane/ui/resource/TagCreate.png +0 -0
  264. gitpane/ui/resource/TagDelete.png +0 -0
  265. gitpane/ui/resource/Unmerged.png +0 -0
  266. gitpane/ui/resource/checkout.png +0 -0
  267. gitpane/ui/sequencer/__init__.py +0 -0
  268. gitpane/ui/sequencer/conflict_dialog.py +532 -0
  269. gitpane/ui/sequencer/merge_recovery_dialog.py +67 -0
  270. gitpane/ui/sequencer/rebase_dialog.py +461 -0
  271. gitpane/ui/sequencer/rebase_editor_window.py +220 -0
  272. gitpane/ui/sequencer/replay_conflict_prompt.py +105 -0
  273. gitpane/ui/sequencer/replay_dialog.py +501 -0
  274. gitpane/ui/setting/__init__.py +0 -0
  275. gitpane/ui/setting/account_page.py +724 -0
  276. gitpane/ui/setting/add_account_dialog.py +1005 -0
  277. gitpane/ui/setting/credential_manager_dialog.py +151 -0
  278. gitpane/ui/setting/git_setting_widget.py +388 -0
  279. gitpane/ui/setting/setting_block.py +19 -0
  280. gitpane/ui/setting/setting_dialog.py +750 -0
  281. gitpane/ui/setting/ssh_widget.py +232 -0
  282. gitpane/ui/setting/utility_page.py +240 -0
  283. gitpane/ui/staging/__init__.py +0 -0
  284. gitpane/ui/staging/staging_action.py +356 -0
  285. gitpane/ui/staging/staging_window.py +745 -0
  286. gitpane/ui/transport/__init__.py +0 -0
  287. gitpane/ui/transport/clone_dialog.py +879 -0
  288. gitpane/ui/transport/push_dialog.py +580 -0
  289. gitpane/ui/transport/push_rejection_prompt.py +100 -0
  290. gitpane/ui/transport/push_tracking_question.py +88 -0
  291. gitpane/ui/transport/remote_dialog.py +943 -0
  292. gitpane/ui/welcome/__init__.py +0 -0
  293. gitpane/ui/welcome/invalid_repository_prompt.py +114 -0
  294. gitpane/ui/welcome/welcome_window.py +1137 -0
  295. gitpane/ui/widget/__init__.py +0 -0
  296. gitpane/ui/widget/busy_indicator.py +106 -0
  297. gitpane/ui/widget/commit_date_text.py +55 -0
  298. gitpane/ui/widget/content_growth.py +262 -0
  299. gitpane/ui/widget/content_measure.py +99 -0
  300. gitpane/ui/widget/dialog_layout.py +668 -0
  301. gitpane/ui/widget/drop_down_wheel_filter.py +32 -0
  302. gitpane/ui/widget/flow_layout.py +110 -0
  303. gitpane/ui/widget/long_text.py +542 -0
  304. gitpane/ui/widget/mnemonic.py +13 -0
  305. gitpane/ui/widget/numbered_text_edit.py +127 -0
  306. gitpane/ui/widget/plain_label.py +15 -0
  307. gitpane/ui/widget/plain_tooltip.py +19 -0
  308. gitpane/ui/widget/refresh_advice.py +25 -0
  309. gitpane/ui/widget/result_label.py +227 -0
  310. gitpane/ui/widget/result_status_bar.py +53 -0
  311. gitpane/ui/widget/scrolled_text.py +69 -0
  312. gitpane/ui/widget/shortcut_text.py +31 -0
  313. gitpane/ui/widget/standard_icon_label.py +55 -0
  314. gitpane/ui/widget/system_shortcut.py +75 -0
  315. gitpane/ui/widget/unavailable_reason.py +29 -0
  316. gitpane/ui/widget/wheel_safe_drop_down.py +24 -0
  317. gitpane/ui/window_control/__init__.py +0 -0
  318. gitpane/ui/window_control/button_focus.py +42 -0
  319. gitpane/ui/window_control/compositor_answer.py +152 -0
  320. gitpane/ui/window_control/compositor_growth_cap.py +73 -0
  321. gitpane/ui/window_control/desktop_activation.py +203 -0
  322. gitpane/ui/window_control/dialog_modality.py +122 -0
  323. gitpane/ui/window_control/item_activation.py +42 -0
  324. gitpane/ui/window_control/modal_focus_relay.py +124 -0
  325. gitpane/ui/window_control/wayland_activation.py +695 -0
  326. gitpane/ui/window_control/window_placement.py +305 -0
  327. gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
  328. gitpane/vendor/w32.exe +0 -0
  329. gitpane/vendor/w64-arm.exe +0 -0
  330. gitpane/vendor/w64.exe +0 -0
  331. gitpane/worktree/__init__.py +0 -0
  332. gitpane/worktree/command_sequence.py +110 -0
  333. gitpane/worktree/file_action_target.py +61 -0
  334. gitpane/worktree/file_menu_action.py +52 -0
  335. gitpane/worktree/historical_restore.py +361 -0
  336. gitpane/worktree/ignore_rule.py +266 -0
  337. gitpane/worktree/mutation.py +1539 -0
  338. gitpane/worktree/partial_patch.py +435 -0
  339. gitpane/worktree/staged_reset.py +447 -0
  340. gitpane/worktree/staging.py +1418 -0
  341. gitpane/worktree/trash.py +302 -0
  342. gitpane/worktree/worktree_file.py +406 -0
  343. gitpane/worktree/worktree_service.py +584 -0
  344. gitpane/worktree/worktree_snapshot.py +630 -0
  345. gitpane-1.0.0.dist-info/METADATA +141 -0
  346. gitpane-1.0.0.dist-info/RECORD +353 -0
  347. gitpane-1.0.0.dist-info/WHEEL +5 -0
  348. gitpane-1.0.0.dist-info/entry_points.txt +2 -0
  349. gitpane-1.0.0.dist-info/licenses/LICENSE +21 -0
  350. gitpane-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +109 -0
  351. gitpane-1.0.0.dist-info/licenses/src/gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
  352. gitpane-1.0.0.dist-info/licenses/src/gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
  353. 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