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,250 @@
1
+ """Give the controls of the reproduced Git Extensions windows the look Windows 10 gives them in a light theme.
2
+
3
+ A dark palette keeps the native Fusion controls: the colours below were sampled on light captures and would glare on a
4
+ dark desktop.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ from PySide6.QtGui import QColor, QPalette
12
+ from PySide6.QtWidgets import QApplication, QFrame
13
+
14
+ from gitpane.ui.look.theme_color import is_dark_palette, theme_line_color
15
+ from gitpane.ui.look.theme_palette import BODY_LIGHT_COLOR, WINDOWS_CONTROL_LIGHT_COLOR
16
+
17
+ if TYPE_CHECKING:
18
+ from PySide6.QtWidgets import QComboBox, QLabel, QLineEdit, QPlainTextEdit, QPushButton, QWidget
19
+
20
+ # Windows 10 push buttons as the Git Extensions windows show them: grey face and border, and a two-pixel accent frame on
21
+ # the default button (all sampled on the captures). Hover, pressed and disabled states are the system's own colors.
22
+ WINDOWS_BUTTON_FACE_COLOR = "#e1e1e1"
23
+ WINDOWS_BUTTON_BORDER_COLOR = "#adadad"
24
+ WINDOWS_BUTTON_TEXT_COLOR = "#000000"
25
+ WINDOWS_BUTTON_BORDER_WIDTH = 1
26
+ WINDOWS_DEFAULT_BUTTON_BORDER_COLOR = "#0078d7"
27
+ WINDOWS_DEFAULT_BUTTON_BORDER_WIDTH = 2
28
+ WINDOWS_BUTTON_HOVER_FACE_COLOR = "#e5f1fb"
29
+ WINDOWS_BUTTON_PRESSED_FACE_COLOR = "#cce4f7"
30
+ WINDOWS_BUTTON_PRESSED_BORDER_COLOR = "#005499"
31
+ WINDOWS_BUTTON_DISABLED_FACE_COLOR = "#cccccc"
32
+ WINDOWS_BUTTON_DISABLED_BORDER_COLOR = "#bfbfbf"
33
+ # Windows' grey text (SystemColors.GrayText): disabled button captions and secondary text such as the "-13" commit
34
+ # counter beside a revision field.
35
+ WINDOWS_GRAY_TEXT_COLOR = "#6d6d6d"
36
+ WINDOWS_BUTTON_DISABLED_TEXT_COLOR = WINDOWS_GRAY_TEXT_COLOR
37
+ # Visible height of a Windows 10 single-line text field in the Git Extensions captures, border included.
38
+ WINDOWS_EDIT_HEIGHT = 23
39
+ # Height of the Segoe UI 9 pt text line the captures were taken with. A field keeps the captures' room around a taller
40
+ # line, so a larger system text size makes the fields and their buttons taller instead of cutting the text.
41
+ WINDOWS_CAPTURE_TEXT_HEIGHT = 16
42
+ # Windows 10 text fields: one-pixel grey border, the accent border while focused, a light grey one when disabled.
43
+ WINDOWS_EDIT_BORDER_COLOR = "#7a7a7a"
44
+ WINDOWS_EDIT_DISABLED_BORDER_COLOR = "#cccccc"
45
+ WINDOWS_EDIT_BORDER_WIDTH = 1
46
+ # Border of the Windows 10 multi-line text box, as the message area of an annotated tag shows it in Git Extensions.
47
+ WINDOWS_MESSAGE_BORDER_COLOR = "#646464"
48
+ # Border of the Windows 10 list boxes, sampled on the merge parent list of Cherry pick commit and on the check box list of
49
+ # Select multiple branches.
50
+ WINDOWS_LIST_BORDER_LIGHT_COLOR = "#828790"
51
+ # Windows 10 list boxes have a one-pixel border.
52
+ WINDOWS_LIST_BORDER_WIDTH = 1
53
+ # Share of the way from the text color to the background color for secondary text on a dark palette: visibly paler than
54
+ # the main text, and still readable on the background.
55
+ SECONDARY_TEXT_DARK_BLEND_RATIO = 0.35
56
+ # Room between a push button's text and each side of its frame when the button grows with its text: the padding of the
57
+ # native Fusion button, whose width is its text plus 14 px (a 1 px border and 6 px of padding on each side), as the
58
+ # question windows show it. Without it, a style-sheeted button is only as wide as its text and the text touches the frame.
59
+ BUTTON_TEXT_SIDE_PADDING = 6
60
+ # Space between a drop-down list's border and its text: the Create tag capture writes the type from 4 px inside the list.
61
+ WINDOWS_COMBO_TEXT_PADDING = 3
62
+
63
+
64
+ def plain_form_color(palette: QPalette) -> QColor:
65
+ """Return the background of the Git Extensions windows that have no button band: Windows' control grey in a light theme.
66
+
67
+ Those windows are one grey surface carrying their fields and buttons. A dark palette keeps its own window color,
68
+ by the same light or dark rule as the banded windows, so a dark desktop never gets the light grey.
69
+ """
70
+ if is_dark_palette(palette):
71
+ return palette.color(QPalette.ColorRole.Window)
72
+ return QColor(WINDOWS_CONTROL_LIGHT_COLOR)
73
+
74
+
75
+ def windows_button_style_sheet(text_side_padding: int = 0) -> str:
76
+ """Return the style sheet that draws a push button like a Windows 10 one, the look of the Git Extensions windows.
77
+
78
+ Only the button's own frame and face are styled; a push button has no child widget the sheet could also reach.
79
+ ``text_side_padding`` is the room kept between the text and the left and right sides of the frame: none for a
80
+ button sized from a capture, ``BUTTON_TEXT_SIDE_PADDING`` for one that grows with its text.
81
+ """
82
+ return (f"QPushButton {{ background-color: {WINDOWS_BUTTON_FACE_COLOR}; color: {WINDOWS_BUTTON_TEXT_COLOR}; padding: 0px {text_side_padding}px;"
83
+ f" border: {WINDOWS_BUTTON_BORDER_WIDTH}px solid {WINDOWS_BUTTON_BORDER_COLOR}; }}"
84
+ f" QPushButton:default {{ border: {WINDOWS_DEFAULT_BUTTON_BORDER_WIDTH}px solid {WINDOWS_DEFAULT_BUTTON_BORDER_COLOR}; }}"
85
+ f" QPushButton:hover {{ background-color: {WINDOWS_BUTTON_HOVER_FACE_COLOR}; border-color: {WINDOWS_DEFAULT_BUTTON_BORDER_COLOR}; }}"
86
+ f" QPushButton:pressed {{ background-color: {WINDOWS_BUTTON_PRESSED_FACE_COLOR}; border-color: {WINDOWS_BUTTON_PRESSED_BORDER_COLOR}; }}"
87
+ f" QPushButton:disabled {{ background-color: {WINDOWS_BUTTON_DISABLED_FACE_COLOR}; color: {WINDOWS_BUTTON_DISABLED_TEXT_COLOR};"
88
+ f" border: {WINDOWS_BUTTON_BORDER_WIDTH}px solid {WINDOWS_BUTTON_DISABLED_BORDER_COLOR}; }}")
89
+
90
+
91
+ def apply_windows_button_look(button: QPushButton, text_side_padding: int = 0) -> None:
92
+ """Draw a button like the Windows 10 buttons of the Git Extensions windows in a light theme, the native one in a dark theme.
93
+
94
+ A dark palette keeps the native Fusion button, which already derives its face, frame and default outline from the
95
+ palette; the Windows colors are light-theme colors and would glare on a dark desktop, so a sheet laid in light is
96
+ taken off. The choice follows the application palette, never the button's, which its own sheet changes. Calling it
97
+ again, at any theme switch, gives the same result. ``text_side_padding`` goes to ``windows_button_style_sheet``; the
98
+ native dark button keeps its own padding.
99
+ """
100
+ if is_dark_palette(QApplication.palette()):
101
+ button.setStyleSheet("")
102
+ return
103
+ button.setStyleSheet(windows_button_style_sheet(text_side_padding))
104
+
105
+
106
+ def windows_line_edit_style_sheet() -> str:
107
+ """Return the style sheet that draws a single-line text field like a Windows 10 one, as the Git Extensions forms show it."""
108
+ return (f"QLineEdit {{ background-color: {BODY_LIGHT_COLOR}; border: {WINDOWS_EDIT_BORDER_WIDTH}px solid {WINDOWS_EDIT_BORDER_COLOR}; }}"
109
+ f" QLineEdit:focus {{ border-color: {WINDOWS_DEFAULT_BUTTON_BORDER_COLOR}; }}"
110
+ f" QLineEdit:disabled {{ background-color: {WINDOWS_CONTROL_LIGHT_COLOR}; border-color: {WINDOWS_EDIT_DISABLED_BORDER_COLOR}; }}")
111
+
112
+
113
+ def windows_field_height(widget: QWidget) -> int:
114
+ """Return the height of a control on a field row: the captures' height, taller by as much as its text line is taller than theirs.
115
+
116
+ It is measured on the font rather than on the style's size hint, so the default text size gives exactly the
117
+ captures' height in both themes (the dark Fusion frame asks for one more pixel), while a larger system text size
118
+ never cuts the text.
119
+ """
120
+ return WINDOWS_EDIT_HEIGHT + max(0, widget.fontMetrics().height() - WINDOWS_CAPTURE_TEXT_HEIGHT)
121
+
122
+
123
+ def fit_windows_field_height(widget: QWidget) -> None:
124
+ """Give a field-row control the height of ``windows_field_height`` for its current font; call it again after a font change."""
125
+ widget.setFixedHeight(windows_field_height(widget))
126
+
127
+
128
+ def apply_windows_line_edit_look(edit: QLineEdit) -> None:
129
+ """Draw a text field like the Windows 10 fields of the Git Extensions windows in a light theme, at their height.
130
+
131
+ A dark palette keeps the Fusion field, whose frame and background already follow the palette; the Windows colors
132
+ are light-theme colors and would glare on a dark desktop, so a sheet laid in light is taken off. The choice follows
133
+ the application palette, and calling it again at any theme switch gives the same result. The height is the
134
+ captures' in both themes at the default text size, and grows with a larger one.
135
+ """
136
+ fit_windows_field_height(edit)
137
+ if is_dark_palette(QApplication.palette()):
138
+ edit.setStyleSheet("")
139
+ return
140
+ edit.setStyleSheet(windows_line_edit_style_sheet())
141
+
142
+
143
+ def windows_combo_style_sheet(*, editable: bool) -> str:
144
+ """Return the style sheet that draws a drop-down list like a Windows 10 one, as the Git Extensions forms show it.
145
+
146
+ A list the user cannot type into looks like a button (grey face and border); one the user can type into looks
147
+ like a text field. The arrow is left to Qt's default drop-down part, which the style sheet keeps drawing.
148
+ """
149
+ if editable:
150
+ face_color, border_color, disabled_face_color = BODY_LIGHT_COLOR, WINDOWS_EDIT_BORDER_COLOR, WINDOWS_CONTROL_LIGHT_COLOR
151
+ else:
152
+ face_color, border_color, disabled_face_color = WINDOWS_BUTTON_FACE_COLOR, WINDOWS_BUTTON_BORDER_COLOR, WINDOWS_BUTTON_DISABLED_FACE_COLOR
153
+ return (f"QComboBox {{ background-color: {face_color}; color: {WINDOWS_BUTTON_TEXT_COLOR}; padding-left: {WINDOWS_COMBO_TEXT_PADDING}px;"
154
+ f" border: {WINDOWS_EDIT_BORDER_WIDTH}px solid {border_color}; }}"
155
+ f" QComboBox:focus {{ border-color: {WINDOWS_DEFAULT_BUTTON_BORDER_COLOR}; }}"
156
+ f" QComboBox:disabled {{ background-color: {disabled_face_color}; color: {WINDOWS_GRAY_TEXT_COLOR}; border-color: {WINDOWS_EDIT_DISABLED_BORDER_COLOR}; }}")
157
+
158
+
159
+ def apply_windows_combo_look(combo: QComboBox, *, editable: bool) -> None:
160
+ """Draw a drop-down list like the Windows 10 lists of the Git Extensions windows in a light theme, at their height.
161
+
162
+ ``editable`` says whether the list takes typed text, which Windows draws as a field rather than as a button. A dark
163
+ palette keeps the Fusion list, whose colors already follow the palette, so a sheet laid in light is taken off. The
164
+ choice follows the application palette, and calling it again at any theme switch gives the same result. The height
165
+ is the captures' in both themes at the default text size, and grows with a larger one like the text fields beside it.
166
+ """
167
+ fit_windows_field_height(combo)
168
+ if is_dark_palette(QApplication.palette()):
169
+ combo.setStyleSheet("")
170
+ return
171
+ combo.setStyleSheet(windows_combo_style_sheet(editable=editable))
172
+
173
+
174
+ def windows_message_style_sheet() -> str:
175
+ """Return the style sheet that draws a multi-line text box like the Windows 10 one of the Git Extensions forms."""
176
+ return f"QPlainTextEdit {{ background-color: {BODY_LIGHT_COLOR}; border: {WINDOWS_EDIT_BORDER_WIDTH}px solid {WINDOWS_MESSAGE_BORDER_COLOR}; }}"
177
+
178
+
179
+ def hidden_message_style_sheet(background: QColor) -> str:
180
+ """Return the style sheet that blends a multi-line text box into the window: no border, the window's own background."""
181
+ return f"QPlainTextEdit {{ background-color: {background.name()}; border: none; }}"
182
+
183
+
184
+ def apply_windows_message_look(edit: QPlainTextEdit) -> None:
185
+ """Draw a multi-line text box like the Git Extensions ones, following whether it is enabled; call it after each change.
186
+
187
+ An enabled box has the Windows 10 border and white background in a light theme and the Fusion frame in a dark one.
188
+ A disabled box has no border and the window's own background in both themes, so it disappears into the window,
189
+ as Git Extensions shows a text box without border style. The theme is the application palette's, so calling it
190
+ again at a theme switch lays the new theme's look.
191
+ """
192
+ source = QApplication.palette()
193
+ if not edit.isEnabled():
194
+ edit.setFrameShape(QFrame.Shape.NoFrame)
195
+ edit.setStyleSheet(hidden_message_style_sheet(plain_form_color(source)))
196
+ return
197
+ edit.setFrameShape(QFrame.Shape.StyledPanel)
198
+ if is_dark_palette(source):
199
+ edit.setStyleSheet("")
200
+ return
201
+ edit.setStyleSheet(windows_message_style_sheet())
202
+
203
+
204
+ def apply_secondary_text_color(label: QLabel) -> None:
205
+ """Write a label in Windows' grey text in a light theme, like the commit counter of Git Extensions beside a revision field.
206
+
207
+ A dark palette keeps its own text color: the grey was sampled on a white body, and a palette's placeholder color is
208
+ not a safe substitute because Qt leaves it half-transparent black when the palette does not set it, which cannot
209
+ be read on a dark body. A label greyed in light gets back the palette it inherits, so calling it again at a theme
210
+ switch gives the new theme's color.
211
+ """
212
+ if is_dark_palette(QApplication.palette()):
213
+ label.setPalette(QPalette())
214
+ return
215
+ palette = label.palette()
216
+ palette.setColor(QPalette.ColorRole.WindowText, QColor(WINDOWS_GRAY_TEXT_COLOR))
217
+ label.setPalette(palette)
218
+
219
+
220
+ def secondary_text_color(palette: QPalette) -> QColor:
221
+ """Return the color of secondary text written on a list or field background: Windows' grey text in a light theme.
222
+
223
+ A dark palette gets its text color moved part of the way towards its input background, so the secondary text reads
224
+ paler than the main text and stays readable. The palette's placeholder color is not used, for the same reason as in
225
+ ``apply_secondary_text_color``: Qt leaves it half-transparent black when the palette does not set it. That function
226
+ is not replaced: it keeps the full text color in a dark theme for the window that uses it.
227
+ """
228
+ if not is_dark_palette(palette):
229
+ return QColor(WINDOWS_GRAY_TEXT_COLOR)
230
+ text, background = palette.color(QPalette.ColorRole.Text), palette.color(QPalette.ColorRole.Base)
231
+ channels: list[int] = []
232
+ for text_channel, background_channel in ((text.red(), background.red()), (text.green(), background.green()), (text.blue(), background.blue())):
233
+ channels.append(round(text_channel + (background_channel - text_channel) * SECONDARY_TEXT_DARK_BLEND_RATIO))
234
+ red, green, blue = channels
235
+ return QColor(red, green, blue)
236
+
237
+
238
+ def windows_list_style_sheet(palette: QPalette, selector: str, *, hide_focus_outline: bool) -> str:
239
+ """Return the style sheet that draws a widget like a Windows 10 list box: one-pixel list border on white in a light theme.
240
+
241
+ A dark palette keeps its own input background and takes the theme's line grey for the border, so the list stays
242
+ readable, shows its edge on the body and does not glare. ``selector`` names the widget class the rule applies to.
243
+ Whether the keyboard focus rectangle is hidden is each window's choice: a list reached with the keyboard keeps it.
244
+ """
245
+ if is_dark_palette(palette):
246
+ border, background = theme_line_color(palette), palette.color(QPalette.ColorRole.Base)
247
+ else:
248
+ border, background = QColor(WINDOWS_LIST_BORDER_LIGHT_COLOR), QColor(BODY_LIGHT_COLOR)
249
+ outline = " outline: 0;" if hide_focus_outline else ""
250
+ return f"{selector} {{ border: {WINDOWS_LIST_BORDER_WIDTH}px solid {border.name()}; background-color: {background.name()};{outline} }}"
File without changes
@@ -0,0 +1,129 @@
1
+ """Search the visible commit message without changing the repository filter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from PySide6.QtCore import Qt
8
+ from PySide6.QtGui import QKeySequence, QShortcut, QTextOption
9
+ from PySide6.QtWidgets import QHBoxLayout, QLineEdit, QPushButton, QTextEdit, QVBoxLayout, QWidget
10
+
11
+ from gitpane.config import LAYOUT_SPACING, SharedText
12
+ from gitpane.ui.reader.search_result import ActiveSelectionKeeper, SearchResultLabel
13
+ from gitpane.ui.reader.source_text import SourceTextEdit
14
+ from gitpane.ui.widget.system_shortcut import FIND_NEXT_ACTION, FIND_PREVIOUS_ACTION, find_shortcuts
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Callable
18
+
19
+
20
+ class CommitMessage(QWidget):
21
+ """Keep literal message text and a local, wrapping search in one reading area."""
22
+
23
+ def __init__(self, text_edit: QTextEdit | SourceTextEdit | None = None) -> None:
24
+ """Build a hidden search bar with keyboard navigation and a visible result state."""
25
+ super().__init__()
26
+ layout = QVBoxLayout(self)
27
+ layout.setContentsMargins(0, 0, 0, 0)
28
+ layout.setSpacing(LAYOUT_SPACING)
29
+ self.search_bar = QWidget()
30
+ search_layout = QHBoxLayout(self.search_bar)
31
+ search_layout.setContentsMargins(0, 0, 0, 0)
32
+ self.find_edit = QLineEdit()
33
+ self.find_edit.setPlaceholderText("Find in this commit message")
34
+ self.find_edit.setAccessibleName("Find in commit message")
35
+ self.find_edit.setToolTip(SharedText.TEXT_SEARCH_HELP.value)
36
+ self.result_label = SearchResultLabel()
37
+ self.previous_button = QPushButton("Previous")
38
+ self.next_button = QPushButton("Next")
39
+ self.close_button = QPushButton("Close")
40
+ search_layout.addWidget(self.find_edit, 1)
41
+ search_layout.addWidget(self.result_label)
42
+ for button in (self.previous_button, self.next_button, self.close_button):
43
+ search_layout.addWidget(button)
44
+ self.text_edit = text_edit if text_edit is not None else QTextEdit()
45
+ if isinstance(self.text_edit, QTextEdit):
46
+ self.text_edit.setAcceptRichText(False)
47
+ self.text_edit.setUndoRedoEnabled(False)
48
+ self.text_edit.setReadOnly(True)
49
+ self.text_edit.setWordWrapMode(QTextOption.WrapMode.WrapAtWordBoundaryOrAnywhere)
50
+ self.text_edit.setAccessibleName("Full commit message")
51
+ self.selection_keeper = ActiveSelectionKeeper(self.text_edit)
52
+ layout.addWidget(self.search_bar)
53
+ layout.addWidget(self.text_edit, 1)
54
+ self.search_bar.hide()
55
+ self.find_edit.textChanged.connect(self._restart_search)
56
+ self.text_edit.textChanged.connect(self._restart_search)
57
+ self.find_edit.returnPressed.connect(self.find_next)
58
+ self.previous_button.clicked.connect(self.find_previous)
59
+ self.next_button.clicked.connect(self.find_next)
60
+ self.close_button.clicked.connect(self.close_search)
61
+ self.shortcuts: list[QShortcut] = []
62
+ callback_by_action = {FIND_NEXT_ACTION: self.find_next, FIND_PREVIOUS_ACTION: self.find_previous}
63
+ key_callbacks: list[tuple[str, Callable[[], None]]] = []
64
+ for key, action in find_shortcuts():
65
+ key_callbacks.append((key, callback_by_action[action]))
66
+ key_callbacks.append(("Shift+Return", self.find_previous))
67
+ key_callbacks.append(("Escape", self.close_search))
68
+ for key, callback in key_callbacks:
69
+ shortcut = QShortcut(QKeySequence(key), self)
70
+ shortcut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut)
71
+ shortcut.activated.connect(callback)
72
+ self.shortcuts.append(shortcut)
73
+
74
+ def open_search(self) -> None:
75
+ """Reveal the search field while retaining its query and current match."""
76
+ self.search_bar.show()
77
+ self.result_label.show_result(self.text_edit, self.find_edit.text())
78
+ self.text_edit.setExtraSelections(self.result_label.highlight_selections(self.text_edit, self.find_edit.text()))
79
+ self.find_edit.setFocus(Qt.FocusReason.ShortcutFocusReason)
80
+ self.find_edit.selectAll()
81
+
82
+ def close_search(self) -> None:
83
+ """Hide search without altering the message or the history filter."""
84
+ self.search_bar.hide()
85
+ self.text_edit.setExtraSelections([])
86
+ self.text_edit.setFocus(Qt.FocusReason.ShortcutFocusReason)
87
+
88
+ def find_next(self) -> None:
89
+ """Select the next match, wrapping at the end of the message."""
90
+ self._find(False)
91
+
92
+ def find_previous(self) -> None:
93
+ """Select the previous match, wrapping at the start of the message."""
94
+ self._find(True)
95
+
96
+ def _restart_search(self) -> None:
97
+ """Discard an old match when the query or selected commit changes."""
98
+ self.text_edit.setExtraSelections([])
99
+ self.result_label.clear()
100
+ cursor = self.text_edit.textCursor()
101
+ cursor.clearSelection()
102
+ self.text_edit.setTextCursor(cursor)
103
+ if self.search_bar.isHidden() or not self.find_edit.text():
104
+ return
105
+ self._find(False, restart=True)
106
+
107
+ def _find(self, backward: bool, restart: bool = False) -> None:
108
+ """Use document cursors so Unicode offsets and read-only text remain intact."""
109
+ query = self.find_edit.text()
110
+ if self.search_bar.isHidden() or not query:
111
+ return
112
+ self.result_label.show_result(self.text_edit, query)
113
+ document = self.text_edit.document()
114
+ cursor = self.text_edit.textCursor()
115
+ start = cursor.selectionStart() if backward else cursor.selectionEnd()
116
+ if restart:
117
+ start = 0
118
+ match = self.result_label.find_match(self.text_edit, query, start, backward)
119
+ if match.isNull():
120
+ start = document.characterCount() - 1 if backward else 0
121
+ match = self.result_label.find_match(self.text_edit, query, start, backward)
122
+ self.text_edit.setExtraSelections([])
123
+ if match.isNull():
124
+ cursor.clearSelection()
125
+ self.text_edit.setTextCursor(cursor)
126
+ return
127
+ self.text_edit.setTextCursor(match)
128
+ self.text_edit.ensureCursorVisible()
129
+ self.text_edit.setExtraSelections(self.result_label.highlight_selections(self.text_edit, query, match))