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,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))
|