1 """Main UI for authoring commits and other Git Cola interactions"""
3 from functools
import partial
5 from qtpy
import QtCore
7 from qtpy
import QtWidgets
8 from qtpy
.QtCore
import Qt
9 from qtpy
.QtCore
import Signal
11 from ..compat
import uchr
12 from ..compat
import WIN32
14 from ..interaction
import Interaction
15 from ..models
import prefs
16 from ..qtutils
import get
19 from .. import guicmds
21 from .. import gitcmds
22 from .. import hotkeys
24 from .. import qtutils
25 from .. import resources
27 from .. import version
31 from . import bookmarks
33 from . import submodules
35 from . import cfgactions
37 from . import commitmsg
40 from . import createbranch
41 from . import createtag
46 from . import editremotes
50 from . import prefs
as prefs_widget
54 from . import standard
60 class MainView(standard
.MainWindow
):
61 config_actions_changed
= Signal(object)
63 def __init__(self
, context
, parent
=None):
64 # pylint: disable=too-many-statements,too-many-locals
65 standard
.MainWindow
.__init
__(self
, parent
)
66 self
.setAttribute(Qt
.WA_DeleteOnClose
)
68 self
.context
= context
69 self
.git
= context
.git
71 self
.model
= context
.model
72 self
.prefs_model
= prefs_model
= prefs
.PreferencesModel(context
)
73 self
.toolbar_state
= toolbar
.ToolBarState(context
, self
)
75 # The widget version is used by import/export_state().
76 # Change this whenever dockwidgets are removed.
77 self
.widget_version
= 2
79 create_dock
= qtutils
.create_dock
81 self
.browser_dockable
= cfg
.get('cola.browserdockable')
82 if self
.browser_dockable
:
83 browser
= browse
.worktree_browser(
84 context
, parent
=self
, show
=False, update
=False
86 self
.browserdock
= create_dock(
87 'Browser', N_('Browser'), self
, widget
=browser
91 self
.actionswidget
= action
.ActionButtons(context
, self
)
92 self
.actionsdock
= create_dock(
93 'Actions', N_('Actions'), self
, widget
=self
.actionswidget
95 qtutils
.hide_dock(self
.actionsdock
)
97 # "Repository Status" widget
98 self
.statusdock
= create_dock(
102 func
=lambda dock
: status
.StatusWidget(context
, dock
.titleBarWidget(), dock
),
104 self
.statuswidget
= self
.statusdock
.widget()
106 # "Switch Repository" widgets
107 self
.bookmarksdock
= create_dock(
111 func
=lambda dock
: bookmarks
.bookmark(context
, dock
),
113 bookmarkswidget
= self
.bookmarksdock
.widget()
114 qtutils
.hide_dock(self
.bookmarksdock
)
116 self
.recentdock
= create_dock(
120 func
=lambda dock
: bookmarks
.recent(context
, dock
),
122 recentwidget
= self
.recentdock
.widget()
123 qtutils
.hide_dock(self
.recentdock
)
124 bookmarkswidget
.connect_to(recentwidget
)
127 self
.branchdock
= create_dock(
131 func
=partial(branch
.BranchesWidget
, context
),
133 self
.branchwidget
= self
.branchdock
.widget()
134 titlebar
= self
.branchdock
.titleBarWidget()
135 titlebar
.add_corner_widget(self
.branchwidget
.filter_button
)
136 titlebar
.add_corner_widget(self
.branchwidget
.sort_order_button
)
138 # "Submodule" widgets
139 self
.submodulesdock
= create_dock(
143 func
=partial(submodules
.SubmodulesWidget
, context
),
145 self
.submoduleswidget
= self
.submodulesdock
.widget()
147 # "Commit Message Editor" widget
148 self
.position_label
= QtWidgets
.QLabel()
149 self
.position_label
.setAlignment(Qt
.AlignCenter
)
150 font
= qtutils
.default_monospace_font()
151 font
.setPointSize(int(font
.pointSize() * 0.8))
152 self
.position_label
.setFont(font
)
154 # make the position label fixed size to avoid layout issues
155 metrics
= self
.position_label
.fontMetrics()
156 width
= metrics
.width('99:999') + defs
.spacing
157 self
.position_label
.setMinimumWidth(width
)
159 editor
= commitmsg
.CommitMessageEditor(context
, self
)
160 self
.commiteditor
= editor
161 self
.commitdock
= create_dock('Commit', N_('Commit'), self
, widget
=editor
)
162 titlebar
= self
.commitdock
.titleBarWidget()
163 titlebar
.add_corner_widget(self
.position_label
)
166 self
.logwidget
= log
.LogWidget(context
)
167 self
.logdock
= create_dock(
168 'Console', N_('Console'), self
, widget
=self
.logwidget
170 qtutils
.hide_dock(self
.logdock
)
172 # "Diff Viewer" widget
173 self
.diffdock
= create_dock(
177 func
=lambda dock
: diff
.Viewer(context
, parent
=dock
),
179 self
.diffviewer
= self
.diffdock
.widget()
180 self
.diffviewer
.set_diff_type(self
.model
.diff_type
)
181 self
.diffviewer
.enable_filename_tracking()
182 self
.diffeditor
= self
.diffviewer
.text
183 titlebar
= self
.diffdock
.titleBarWidget()
184 titlebar
.add_title_widget(self
.diffviewer
.filename
)
185 titlebar
.add_corner_widget(self
.diffviewer
.options
)
188 add_action
= qtutils
.add_action
189 add_action_bool
= qtutils
.add_action_bool
191 self
.commit_amend_action
= add_action_bool(
193 N_('Amend Last Commit'),
194 partial(cmds
.do
, cmds
.AmendMode
, context
),
197 self
.commit_amend_action
.setIcon(icons
.edit())
198 self
.commit_amend_action
.setShortcut(hotkeys
.AMEND
)
199 self
.commit_amend_action
.setShortcutContext(Qt
.WidgetShortcut
)
201 self
.unstage_all_action
= add_action(
202 self
, N_('Unstage All'), cmds
.run(cmds
.UnstageAll
, context
)
204 self
.unstage_all_action
.setIcon(icons
.remove())
206 self
.undo_commit_action
= add_action(
207 self
, N_('Undo Last Commit'), cmds
.run(cmds
.UndoLastCommit
, context
)
209 self
.undo_commit_action
.setIcon(icons
.style_dialog_discard())
211 self
.unstage_selected_action
= add_action(
212 self
, N_('Unstage From Commit'), cmds
.run(cmds
.UnstageSelected
, context
)
214 self
.unstage_selected_action
.setIcon(icons
.remove())
216 self
.show_diffstat_action
= add_action(
217 self
, N_('Diffstat'), self
.statuswidget
.select_header
, hotkeys
.DIFFSTAT
219 self
.show_diffstat_action
.setIcon(icons
.diff())
221 self
.stage_modified_action
= add_action(
223 N_('Stage Changed Files To Commit'),
224 cmds
.run(cmds
.StageModified
, context
),
225 hotkeys
.STAGE_MODIFIED
,
227 self
.stage_modified_action
.setIcon(icons
.add())
229 self
.stage_untracked_action
= add_action(
231 N_('Stage All Untracked'),
232 cmds
.run(cmds
.StageUntracked
, context
),
233 hotkeys
.STAGE_UNTRACKED
,
235 self
.stage_untracked_action
.setIcon(icons
.add())
237 self
.apply_patches_action
= add_action(
238 self
, N_('Apply Patches...'), partial(diff
.apply_patches
, context
)
240 self
.apply_patches_action
.setIcon(icons
.diff())
242 self
.apply_patches_abort_action
= qtutils
.add_action_with_tooltip(
244 N_('Abort Applying Patches...'),
245 N_('Abort the current "git am" patch session'),
246 cmds
.run(cmds
.AbortApplyPatch
, context
),
248 self
.apply_patches_abort_action
.setIcon(icons
.style_dialog_discard())
250 self
.apply_patches_continue_action
= qtutils
.add_action_with_tooltip(
252 N_('Continue Applying Patches'),
253 N_('Commit the current state and continue applying patches'),
254 cmds
.run(cmds
.ApplyPatchesContinue
, context
),
256 self
.apply_patches_continue_action
.setIcon(icons
.commit())
258 self
.apply_patches_skip_action
= qtutils
.add_action_with_tooltip(
260 N_('Skip Current Patch'),
261 N_('Skip applying the current patch and continue applying patches'),
262 cmds
.run(cmds
.ApplyPatchesContinue
, context
),
264 self
.apply_patches_skip_action
.setIcon(icons
.discard())
266 self
.export_patches_action
= add_action(
268 N_('Export Patches...'),
269 partial(guicmds
.export_patches
, context
),
272 self
.export_patches_action
.setIcon(icons
.save())
274 self
.new_repository_action
= add_action(
275 self
, N_('New Repository...'), partial(guicmds
.open_new_repo
, context
)
277 self
.new_repository_action
.setIcon(icons
.new())
279 self
.new_bare_repository_action
= add_action(
280 self
, N_('New Bare Repository...'), partial(guicmds
.new_bare_repo
, context
)
282 self
.new_bare_repository_action
.setIcon(icons
.new())
284 prefs_func
= partial(
285 prefs_widget
.preferences
, context
, parent
=self
, model
=prefs_model
287 self
.preferences_action
= add_action(
288 self
, N_('Preferences'), prefs_func
, QtGui
.QKeySequence
.Preferences
290 self
.preferences_action
.setIcon(icons
.configure())
292 self
.edit_remotes_action
= add_action(
293 self
, N_('Edit Remotes...'), partial(editremotes
.editor
, context
)
295 self
.edit_remotes_action
.setIcon(icons
.edit())
297 self
.rescan_action
= add_action(
300 cmds
.run(cmds
.Refresh
, context
),
301 *hotkeys
.REFRESH_HOTKEYS
,
303 self
.rescan_action
.setIcon(icons
.sync())
305 self
.find_files_action
= add_action(
308 partial(finder
.finder
, context
),
311 self
.find_files_action
.setIcon(icons
.search())
313 self
.browse_recently_modified_action
= add_action(
315 N_('Recently Modified Files...'),
316 partial(recent
.browse_recent_files
, context
),
317 hotkeys
.EDIT_SECONDARY
,
319 self
.browse_recently_modified_action
.setIcon(icons
.directory())
321 self
.cherry_pick_action
= add_action(
323 N_('Cherry-Pick...'),
324 partial(guicmds
.cherry_pick
, context
),
327 self
.cherry_pick_action
.setIcon(icons
.cherry_pick())
328 self
.cherry_pick_abort_action
= add_action(
329 self
, N_('Abort Cherry-Pick...'), cmds
.run(cmds
.AbortCherryPick
, context
)
331 self
.cherry_pick_abort_action
.setIcon(icons
.style_dialog_discard())
333 self
.load_commitmsg_action
= add_action(
334 self
, N_('Load Commit Message...'), partial(guicmds
.load_commitmsg
, context
)
336 self
.load_commitmsg_action
.setIcon(icons
.file_text())
338 self
.prepare_commitmsg_hook_action
= add_action(
340 N_('Prepare Commit Message'),
341 cmds
.run(cmds
.PrepareCommitMessageHook
, context
),
342 hotkeys
.PREPARE_COMMIT_MESSAGE
,
345 self
.save_tarball_action
= add_action(
346 self
, N_('Save As Tarball/Zip...'), partial(archive
.save_archive
, context
)
348 self
.save_tarball_action
.setIcon(icons
.file_zip())
350 self
.quit_action
= add_action(self
, N_('Quit'), self
.close
, hotkeys
.QUIT
)
352 self
.grep_action
= add_action(
353 self
, N_('Grep'), partial(grep
.grep
, context
), hotkeys
.GREP
355 self
.grep_action
.setIcon(icons
.search())
357 self
.merge_local_action
= add_action(
358 self
, N_('Merge...'), partial(merge
.local_merge
, context
), hotkeys
.MERGE
360 self
.merge_local_action
.setIcon(icons
.merge())
362 self
.merge_abort_action
= add_action(
363 self
, N_('Abort Merge...'), cmds
.run(cmds
.AbortMerge
, context
)
365 self
.merge_abort_action
.setIcon(icons
.style_dialog_discard())
367 self
.update_submodules_action
= add_action(
369 N_('Update All Submodules...'),
370 cmds
.run(cmds
.SubmodulesUpdate
, context
),
372 self
.update_submodules_action
.setIcon(icons
.sync())
374 self
.add_submodule_action
= add_action(
376 N_('Add Submodule...'),
377 partial(submodules
.add_submodule
, context
, parent
=self
),
379 self
.add_submodule_action
.setIcon(icons
.add())
381 self
.fetch_action
= qtutils
.add_action_with_tooltip(
384 N_('Fetch from one or more remotes using "git fetch"'),
385 partial(remote
.fetch
, context
),
388 self
.fetch_action
.setIcon(icons
.download())
390 self
.push_action
= qtutils
.add_action_with_tooltip(
393 N_('Push to one or more remotes using "git push"'),
394 partial(remote
.push
, context
),
397 self
.push_action
.setIcon(icons
.push())
399 self
.pull_action
= qtutils
.add_action_with_tooltip(
402 N_('Integrate changes using "git pull"'),
403 partial(remote
.pull
, context
),
406 self
.pull_action
.setIcon(icons
.pull())
408 self
.open_repo_action
= add_action(
409 self
, N_('Open...'), partial(guicmds
.open_repo
, context
), hotkeys
.OPEN
411 self
.open_repo_action
.setIcon(icons
.folder())
413 self
.open_repo_new_action
= add_action(
415 N_('Open in New Window...'),
416 partial(guicmds
.open_repo_in_new_window
, context
),
418 self
.open_repo_new_action
.setIcon(icons
.folder())
420 self
.stash_action
= qtutils
.add_action_with_tooltip(
423 N_('Temporarily stash away uncommitted changes using "git stash"'),
424 partial(stash
.view
, context
),
427 self
.stash_action
.setIcon(icons
.commit())
429 self
.reset_soft_action
= qtutils
.add_action_with_tooltip(
431 N_('Reset Branch (Soft)'),
432 cmds
.ResetSoft
.tooltip('<commit>'),
433 partial(guicmds
.reset_soft
, context
),
435 self
.reset_soft_action
.setIcon(icons
.style_dialog_reset())
437 self
.reset_mixed_action
= qtutils
.add_action_with_tooltip(
439 N_('Reset Branch and Stage (Mixed)'),
440 cmds
.ResetMixed
.tooltip('<commit>'),
441 partial(guicmds
.reset_mixed
, context
),
443 self
.reset_mixed_action
.setIcon(icons
.style_dialog_reset())
445 self
.reset_keep_action
= qtutils
.add_action_with_tooltip(
447 N_('Restore Worktree and Reset All (Keep Unstaged Changes)'),
448 cmds
.ResetKeep
.tooltip('<commit>'),
449 partial(guicmds
.reset_keep
, context
),
451 self
.reset_keep_action
.setIcon(icons
.style_dialog_reset())
453 self
.reset_merge_action
= qtutils
.add_action_with_tooltip(
455 N_('Restore Worktree and Reset All (Merge)'),
456 cmds
.ResetMerge
.tooltip('<commit>'),
457 partial(guicmds
.reset_merge
, context
),
459 self
.reset_merge_action
.setIcon(icons
.style_dialog_reset())
461 self
.reset_hard_action
= qtutils
.add_action_with_tooltip(
463 N_('Restore Worktree and Reset All (Hard)'),
464 cmds
.ResetHard
.tooltip('<commit>'),
465 partial(guicmds
.reset_hard
, context
),
467 self
.reset_hard_action
.setIcon(icons
.style_dialog_reset())
469 self
.restore_worktree_action
= qtutils
.add_action_with_tooltip(
471 N_('Restore Worktree'),
472 cmds
.RestoreWorktree
.tooltip('<commit>'),
473 partial(guicmds
.restore_worktree
, context
),
475 self
.restore_worktree_action
.setIcon(icons
.edit())
477 self
.clone_repo_action
= add_action(
478 self
, N_('Clone...'), partial(clone
.clone
, context
)
480 self
.clone_repo_action
.setIcon(icons
.repo())
482 self
.help_docs_action
= add_action(
485 resources
.show_html_docs
,
486 QtGui
.QKeySequence
.HelpContents
,
489 self
.help_shortcuts_action
= add_action(
490 self
, N_('Keyboard Shortcuts'), about
.show_shortcuts
, hotkeys
.QUESTION
493 self
.visualize_current_action
= add_action(
495 N_('Visualize Current Branch...'),
496 cmds
.run(cmds
.VisualizeCurrent
, context
),
498 self
.visualize_current_action
.setIcon(icons
.visualize())
500 self
.visualize_all_action
= add_action(
501 self
, N_('Visualize All Branches...'), cmds
.run(cmds
.VisualizeAll
, context
)
503 self
.visualize_all_action
.setIcon(icons
.visualize())
505 self
.search_commits_action
= add_action(
506 self
, N_('Search...'), partial(search
.search
, context
)
508 self
.search_commits_action
.setIcon(icons
.search())
510 self
.browse_branch_action
= add_action(
512 N_('Browse Current Branch...'),
513 partial(guicmds
.browse_current
, context
),
515 self
.browse_branch_action
.setIcon(icons
.directory())
517 self
.browse_other_branch_action
= add_action(
518 self
, N_('Browse Other Branch...'), partial(guicmds
.browse_other
, context
)
520 self
.browse_other_branch_action
.setIcon(icons
.directory())
522 self
.load_commitmsg_template_action
= add_action(
524 N_('Get Commit Message Template'),
525 cmds
.run(cmds
.LoadCommitMessageFromTemplate
, context
),
527 self
.load_commitmsg_template_action
.setIcon(icons
.style_dialog_apply())
529 self
.help_about_action
= add_action(
530 self
, N_('About'), partial(about
.about_dialog
, context
)
533 self
.diff_against_commit_action
= add_action(
535 N_('Against Commit... (Diff Mode)'),
536 partial(guicmds
.diff_against_commit
, context
),
538 self
.diff_against_commit_action
.setIcon(icons
.compare())
540 self
.exit_diff_mode_action
= add_action(
541 self
, N_('Exit Diff Mode'), cmds
.run(cmds
.ResetMode
, context
)
543 self
.exit_diff_mode_action
.setIcon(icons
.compare())
545 self
.diff_expression_action
= add_action(
546 self
, N_('Expression...'), partial(guicmds
.diff_expression
, context
)
548 self
.diff_expression_action
.setIcon(icons
.compare())
550 self
.branch_compare_action
= add_action(
551 self
, N_('Branches...'), partial(compare
.compare_branches
, context
)
553 self
.branch_compare_action
.setIcon(icons
.compare())
555 self
.create_tag_action
= add_action(
558 partial(createtag
.create_tag
, context
),
560 self
.create_tag_action
.setIcon(icons
.tag())
562 self
.create_branch_action
= add_action(
565 partial(createbranch
.create_new_branch
, context
),
568 self
.create_branch_action
.setIcon(icons
.branch())
570 self
.delete_branch_action
= add_action(
571 self
, N_('Delete...'), partial(guicmds
.delete_branch
, context
)
573 self
.delete_branch_action
.setIcon(icons
.discard())
575 self
.delete_remote_branch_action
= add_action(
577 N_('Delete Remote Branch...'),
578 partial(guicmds
.delete_remote_branch
, context
),
580 self
.delete_remote_branch_action
.setIcon(icons
.discard())
582 self
.rename_branch_action
= add_action(
583 self
, N_('Rename Branch...'), partial(guicmds
.rename_branch
, context
)
585 self
.rename_branch_action
.setIcon(icons
.edit())
587 self
.checkout_branch_action
= add_action(
590 partial(guicmds
.checkout_branch
, context
),
593 self
.checkout_branch_action
.setIcon(icons
.branch())
595 self
.branch_review_action
= add_action(
596 self
, N_('Review...'), partial(guicmds
.review_branch
, context
)
598 self
.branch_review_action
.setIcon(icons
.compare())
600 self
.browse_action
= add_action(
601 self
, N_('File Browser...'), partial(browse
.worktree_browser
, context
)
603 self
.browse_action
.setIcon(icons
.cola())
605 self
.dag_action
= add_action(self
, N_('DAG...'), self
.git_dag
)
606 self
.dag_action
.setIcon(icons
.cola())
608 self
.rebase_start_action
= add_action(
610 N_('Start Interactive Rebase...'),
611 cmds
.run(cmds
.Rebase
, context
),
612 hotkeys
.REBASE_START_AND_CONTINUE
,
614 self
.rebase_start_action
.setIcon(icons
.play())
616 self
.rebase_edit_todo_action
= add_action(
617 self
, N_('Edit...'), cmds
.run(cmds
.RebaseEditTodo
, context
)
619 self
.rebase_edit_todo_action
.setIcon(icons
.edit())
621 self
.rebase_continue_action
= add_action(
624 cmds
.run(cmds
.RebaseContinue
, context
),
625 hotkeys
.REBASE_START_AND_CONTINUE
,
627 self
.rebase_continue_action
.setIcon(icons
.play())
629 self
.rebase_skip_action
= add_action(
630 self
, N_('Skip Current Patch'), cmds
.run(cmds
.RebaseSkip
, context
)
632 self
.rebase_skip_action
.setIcon(icons
.delete())
634 self
.rebase_abort_action
= add_action(
635 self
, N_('Abort'), cmds
.run(cmds
.RebaseAbort
, context
)
637 self
.rebase_abort_action
.setIcon(icons
.close())
639 # For "Start Rebase" only, reverse the first argument to setEnabled()
640 # so that we can operate on it as a group.
641 # We can do this because can_rebase == not is_rebasing
642 self
.rebase_start_action_proxy
= utils
.Proxy(
643 self
.rebase_start_action
,
644 setEnabled
=lambda x
: self
.rebase_start_action
.setEnabled(not x
),
647 self
.rebase_group
= utils
.Group(
648 self
.rebase_start_action_proxy
,
649 self
.rebase_edit_todo_action
,
650 self
.rebase_continue_action
,
651 self
.rebase_skip_action
,
652 self
.rebase_abort_action
,
655 self
.annex_init_action
= qtutils
.add_action(
656 self
, N_('Initialize Git Annex'), cmds
.run(cmds
.AnnexInit
, context
)
659 self
.lfs_init_action
= qtutils
.add_action(
660 self
, N_('Initialize Git LFS'), cmds
.run(cmds
.LFSInstall
, context
)
663 self
.lock_layout_action
= add_action_bool(
664 self
, N_('Lock Layout'), self
.set_lock_layout
, False
667 self
.reset_layout_action
= add_action(
668 self
, N_('Reset Layout'), self
.reset_layout
671 self
.quick_repository_search
= add_action(
674 lambda: guicmds
.open_quick_repo_search(self
.context
, parent
=self
),
675 hotkeys
.OPEN_REPO_SEARCH
,
677 self
.quick_repository_search
.setIcon(icons
.search())
679 self
.terminal_action
= common
.terminal_action(
680 context
, self
, hotkey
=hotkeys
.TERMINAL
683 # Create the application menu
684 self
.menubar
= QtWidgets
.QMenuBar(self
)
685 self
.setMenuBar(self
.menubar
)
688 add_menu
= qtutils
.add_menu
689 self
.file_menu
= add_menu(N_('&File'), self
.menubar
)
690 self
.file_menu
.addAction(self
.quick_repository_search
)
691 # File->Open Recent menu
692 self
.open_recent_menu
= self
.file_menu
.addMenu(N_('Open Recent'))
693 self
.open_recent_menu
.setIcon(icons
.folder())
694 self
.file_menu
.addAction(self
.open_repo_action
)
695 self
.file_menu
.addAction(self
.open_repo_new_action
)
696 self
.file_menu
.addSeparator()
697 self
.file_menu
.addAction(self
.new_repository_action
)
698 self
.file_menu
.addAction(self
.new_bare_repository_action
)
699 self
.file_menu
.addAction(self
.clone_repo_action
)
700 self
.file_menu
.addSeparator()
701 self
.file_menu
.addAction(self
.rescan_action
)
702 self
.file_menu
.addAction(self
.find_files_action
)
703 self
.file_menu
.addAction(self
.edit_remotes_action
)
704 self
.file_menu
.addAction(self
.browse_recently_modified_action
)
705 self
.file_menu
.addSeparator()
706 self
.file_menu
.addAction(self
.save_tarball_action
)
708 self
.patches_menu
= self
.file_menu
.addMenu(N_('Patches'))
709 self
.patches_menu
.setIcon(icons
.diff())
710 self
.patches_menu
.addAction(self
.export_patches_action
)
711 self
.patches_menu
.addAction(self
.apply_patches_action
)
712 self
.patches_menu
.addAction(self
.apply_patches_continue_action
)
713 self
.patches_menu
.addAction(self
.apply_patches_skip_action
)
714 self
.patches_menu
.addAction(self
.apply_patches_abort_action
)
716 # Git Annex / Git LFS
717 annex
= core
.find_executable('git-annex')
718 lfs
= core
.find_executable('git-lfs')
720 self
.file_menu
.addSeparator()
722 self
.file_menu
.addAction(self
.annex_init_action
)
724 self
.file_menu
.addAction(self
.lfs_init_action
)
726 self
.file_menu
.addSeparator()
727 self
.file_menu
.addAction(self
.preferences_action
)
728 self
.file_menu
.addAction(self
.quit_action
)
731 self
.edit_proxy
= edit_proxy
= FocusProxy(
732 editor
, editor
.summary
, editor
.description
740 bookmarkswidget
.tree
,
743 select_widgets
= copy_widgets
+ (self
.statuswidget
.tree
,)
744 edit_proxy
.override('copy', copy_widgets
)
745 edit_proxy
.override('selectAll', select_widgets
)
747 edit_menu
= self
.edit_menu
= add_menu(N_('&Edit'), self
.menubar
)
748 undo
= add_action(edit_menu
, N_('Undo'), edit_proxy
.undo
, hotkeys
.UNDO
)
749 undo
.setIcon(icons
.undo())
750 redo
= add_action(edit_menu
, N_('Redo'), edit_proxy
.redo
, hotkeys
.REDO
)
751 redo
.setIcon(icons
.redo())
752 edit_menu
.addSeparator()
753 cut
= add_action(edit_menu
, N_('Cut'), edit_proxy
.cut
, hotkeys
.CUT
)
754 cut
.setIcon(icons
.cut())
755 copy
= add_action(edit_menu
, N_('Copy'), edit_proxy
.copy
, hotkeys
.COPY
)
756 copy
.setIcon(icons
.copy())
757 paste
= add_action(edit_menu
, N_('Paste'), edit_proxy
.paste
, hotkeys
.PASTE
)
758 paste
.setIcon(icons
.paste())
759 delete
= add_action(edit_menu
, N_('Delete'), edit_proxy
.delete
, hotkeys
.DELETE
)
760 delete
.setIcon(icons
.delete())
761 edit_menu
.addSeparator()
762 select_all
= add_action(
763 edit_menu
, N_('Select All'), edit_proxy
.selectAll
, hotkeys
.SELECT_ALL
765 select_all
.setIcon(icons
.select_all())
766 edit_menu
.addSeparator()
767 qtutils
.add_menu_actions(edit_menu
, self
.commiteditor
.menu_actions
)
770 self
.actions_menu
= add_menu(N_('Actions'), self
.menubar
)
771 if self
.terminal_action
is not None:
772 self
.actions_menu
.addAction(self
.terminal_action
)
773 self
.actions_menu
.addAction(self
.fetch_action
)
774 self
.actions_menu
.addAction(self
.push_action
)
775 self
.actions_menu
.addAction(self
.pull_action
)
776 self
.actions_menu
.addAction(self
.stash_action
)
777 self
.actions_menu
.addSeparator()
778 self
.actions_menu
.addAction(self
.create_tag_action
)
779 self
.actions_menu
.addAction(self
.cherry_pick_action
)
780 self
.actions_menu
.addAction(self
.cherry_pick_abort_action
)
781 self
.actions_menu
.addAction(self
.merge_local_action
)
782 self
.actions_menu
.addAction(self
.merge_abort_action
)
783 self
.actions_menu
.addSeparator()
784 self
.actions_menu
.addAction(self
.update_submodules_action
)
785 self
.actions_menu
.addAction(self
.add_submodule_action
)
786 self
.actions_menu
.addSeparator()
787 self
.actions_menu
.addAction(self
.grep_action
)
788 self
.actions_menu
.addAction(self
.search_commits_action
)
791 self
.commit_menu
= add_menu(N_('Commit@@verb'), self
.menubar
)
792 self
.commit_menu
.setTitle(N_('Commit@@verb'))
793 self
.commit_menu
.addAction(self
.commiteditor
.commit_action
)
794 self
.commit_menu
.addAction(self
.commit_amend_action
)
795 self
.commit_menu
.addAction(self
.undo_commit_action
)
796 self
.commit_menu
.addSeparator()
797 self
.commit_menu
.addAction(self
.statuswidget
.tree
.process_selection_action
)
798 self
.commit_menu
.addAction(self
.statuswidget
.tree
.stage_or_unstage_all_action
)
799 self
.commit_menu
.addAction(self
.stage_modified_action
)
800 self
.commit_menu
.addAction(self
.stage_untracked_action
)
801 self
.commit_menu
.addSeparator()
802 self
.commit_menu
.addAction(self
.unstage_all_action
)
803 self
.commit_menu
.addAction(self
.unstage_selected_action
)
804 self
.commit_menu
.addSeparator()
805 self
.commit_menu
.addAction(self
.load_commitmsg_action
)
806 self
.commit_menu
.addAction(self
.load_commitmsg_template_action
)
807 self
.commit_menu
.addAction(self
.prepare_commitmsg_hook_action
)
810 self
.diff_menu
= add_menu(N_('Diff'), self
.menubar
)
811 self
.diff_menu
.addAction(self
.diff_expression_action
)
812 self
.diff_menu
.addAction(self
.branch_compare_action
)
813 self
.diff_menu
.addAction(self
.show_diffstat_action
)
814 self
.diff_menu
.addSeparator()
815 self
.diff_menu
.addAction(self
.diff_against_commit_action
)
816 self
.diff_menu
.addAction(self
.exit_diff_mode_action
)
819 self
.branch_menu
= add_menu(N_('Branch'), self
.menubar
)
820 self
.branch_menu
.addAction(self
.branch_review_action
)
821 self
.branch_menu
.addSeparator()
822 self
.branch_menu
.addAction(self
.create_branch_action
)
823 self
.branch_menu
.addAction(self
.checkout_branch_action
)
824 self
.branch_menu
.addAction(self
.delete_branch_action
)
825 self
.branch_menu
.addAction(self
.delete_remote_branch_action
)
826 self
.branch_menu
.addAction(self
.rename_branch_action
)
827 self
.branch_menu
.addSeparator()
828 self
.branch_menu
.addAction(self
.browse_branch_action
)
829 self
.branch_menu
.addAction(self
.browse_other_branch_action
)
830 self
.branch_menu
.addSeparator()
831 self
.branch_menu
.addAction(self
.visualize_current_action
)
832 self
.branch_menu
.addAction(self
.visualize_all_action
)
835 self
.rebase_menu
= add_menu(N_('Rebase'), self
.menubar
)
836 self
.rebase_menu
.addAction(self
.rebase_start_action
)
837 self
.rebase_menu
.addAction(self
.rebase_edit_todo_action
)
838 self
.rebase_menu
.addSeparator()
839 self
.rebase_menu
.addAction(self
.rebase_continue_action
)
840 self
.rebase_menu
.addAction(self
.rebase_skip_action
)
841 self
.rebase_menu
.addSeparator()
842 self
.rebase_menu
.addAction(self
.rebase_abort_action
)
845 self
.reset_menu
= add_menu(N_('Reset'), self
.menubar
)
846 self
.reset_menu
.addAction(self
.unstage_all_action
)
847 self
.reset_menu
.addAction(self
.undo_commit_action
)
848 self
.reset_menu
.addSeparator()
849 self
.reset_menu
.addAction(self
.reset_soft_action
)
850 self
.reset_menu
.addAction(self
.reset_mixed_action
)
851 self
.reset_menu
.addAction(self
.restore_worktree_action
)
852 self
.reset_menu
.addSeparator()
853 self
.reset_menu
.addAction(self
.reset_keep_action
)
854 self
.reset_menu
.addAction(self
.reset_merge_action
)
855 self
.reset_menu
.addAction(self
.reset_hard_action
)
858 self
.view_menu
= add_menu(N_('View'), self
.menubar
)
859 # pylint: disable=no-member
860 self
.view_menu
.aboutToShow
.connect(lambda: self
.build_view_menu(self
.view_menu
))
861 self
.setup_dockwidget_view_menu()
862 if utils
.is_darwin():
863 # The native macOS menu doesn't show empty entries.
864 self
.build_view_menu(self
.view_menu
)
867 self
.help_menu
= add_menu(N_('Help'), self
.menubar
)
868 self
.help_menu
.addAction(self
.help_docs_action
)
869 self
.help_menu
.addAction(self
.help_shortcuts_action
)
870 self
.help_menu
.addAction(self
.help_about_action
)
872 # Arrange dock widgets
873 bottom
= Qt
.BottomDockWidgetArea
874 top
= Qt
.TopDockWidgetArea
876 self
.addDockWidget(top
, self
.statusdock
)
877 self
.addDockWidget(top
, self
.commitdock
)
878 if self
.browser_dockable
:
879 self
.addDockWidget(top
, self
.browserdock
)
880 self
.tabifyDockWidget(self
.browserdock
, self
.commitdock
)
882 self
.addDockWidget(top
, self
.branchdock
)
883 self
.addDockWidget(top
, self
.submodulesdock
)
884 self
.addDockWidget(top
, self
.bookmarksdock
)
885 self
.addDockWidget(top
, self
.recentdock
)
887 self
.tabifyDockWidget(self
.branchdock
, self
.submodulesdock
)
888 self
.tabifyDockWidget(self
.submodulesdock
, self
.bookmarksdock
)
889 self
.tabifyDockWidget(self
.bookmarksdock
, self
.recentdock
)
890 self
.branchdock
.raise_()
892 self
.addDockWidget(bottom
, self
.diffdock
)
893 self
.addDockWidget(bottom
, self
.actionsdock
)
894 self
.addDockWidget(bottom
, self
.logdock
)
895 self
.tabifyDockWidget(self
.actionsdock
, self
.logdock
)
897 # Listen for model notifications
898 self
.model
.updated
.connect(self
.refresh
, type=Qt
.QueuedConnection
)
899 self
.model
.mode_changed
.connect(
900 lambda mode
: self
.refresh(), type=Qt
.QueuedConnection
903 prefs_model
.config_updated
.connect(self
._config
_updated
)
905 # Set a default value
906 self
.show_cursor_position(1, 0)
908 self
.commit_menu
.aboutToShow
.connect(self
.update_menu_actions
)
909 self
.open_recent_menu
.aboutToShow
.connect(self
.build_recent_menu
)
910 self
.commiteditor
.cursor_changed
.connect(self
.show_cursor_position
)
912 self
.diffeditor
.options_changed
.connect(self
.statuswidget
.refresh
)
913 self
.diffeditor
.up
.connect(self
.statuswidget
.move_up
)
914 self
.diffeditor
.down
.connect(self
.statuswidget
.move_down
)
916 self
.commiteditor
.up
.connect(self
.statuswidget
.move_up
)
917 self
.commiteditor
.down
.connect(self
.statuswidget
.move_down
)
919 self
.config_actions_changed
.connect(
920 lambda names_and_shortcuts
: _install_config_actions(
925 type=Qt
.QueuedConnection
,
927 self
.init_state(context
.settings
, self
.set_initial_size
)
929 # Route command output here
930 Interaction
.log_status
= self
.logwidget
.log_status
931 Interaction
.log
= self
.logwidget
.log
932 # Focus the status widget; this must be deferred
933 QtCore
.QTimer
.singleShot(0, self
.initialize
)
935 def initialize(self
):
936 context
= self
.context
937 git_version
= version
.git_version_str(context
)
941 git_version
+ '\n' + N_('git cola version %s') % version
.version()
945 error_msg
= N_('error: unable to execute git')
946 Interaction
.log(error_msg
)
949 self
.statuswidget
.setFocus()
951 title
= N_('error: unable to execute git')
955 details
= git
.win32_git_error_hint()
956 Interaction
.critical(title
, message
=msg
, details
=details
)
957 self
.context
.app
.exit(2)
959 def set_initial_size(self
):
960 # Default size; this is thrown out when save/restore is used
961 width
, height
= qtutils
.desktop_size()
962 self
.resize((width
* 3) // 4, height
)
963 self
.statuswidget
.set_initial_size()
964 self
.commiteditor
.set_initial_size()
966 def set_filter(self
, txt
):
967 self
.statuswidget
.set_filter(txt
)
970 def closeEvent(self
, event
):
971 """Save state in the settings"""
972 commit_msg
= self
.commiteditor
.commit_message(raw
=True)
973 self
.model
.save_commitmsg(msg
=commit_msg
)
974 for browser
in list(self
.context
.browser_windows
):
976 standard
.MainWindow
.closeEvent(self
, event
)
978 def create_view_menu(self
):
979 menu
= qtutils
.create_menu(N_('View'), self
)
980 self
.build_view_menu(menu
)
983 def build_view_menu(self
, menu
):
985 menu
.addAction(self
.browse_action
)
986 menu
.addAction(self
.dag_action
)
989 popup_menu
= self
.createPopupMenu()
990 for menu_action
in popup_menu
.actions():
991 menu_action
.setParent(menu
)
992 menu
.addAction(menu_action
)
994 context
= self
.context
995 menu_action
= menu
.addAction(
996 N_('New Toolbar'), partial(toolbar
.add_toolbar
, context
, self
)
998 menu_action
.setIcon(icons
.add())
1010 self
.submodulesdock
,
1012 if self
.browser_dockable
:
1013 dockwidgets
.append(self
.browserdock
)
1015 for dockwidget
in dockwidgets
:
1016 # Associate the action with the shortcut
1017 toggleview
= dockwidget
.toggleViewAction()
1018 menu
.addAction(toggleview
)
1021 menu
.addAction(self
.lock_layout_action
)
1022 menu
.addAction(self
.reset_layout_action
)
1026 def contextMenuEvent(self
, event
):
1027 menu
= self
.create_view_menu()
1028 menu
.exec_(event
.globalPos())
1030 def build_recent_menu(self
):
1032 context
= self
.context
1033 settings
= context
.settings
1035 menu
= self
.open_recent_menu
1037 worktree
= context
.git
.worktree()
1039 for entry
in settings
.recent
:
1040 directory
= entry
['path']
1041 if directory
== worktree
:
1042 # Omit the current worktree from the "Open Recent" menu.
1044 name
= entry
['name']
1045 text
= f
'{name} {uchr(0x2192)} {directory}'
1046 menu
.addAction(text
, cmds
.run(cmd
, context
, directory
))
1049 mode
= property(lambda self
: self
.model
.mode
)
1051 def _config_updated(self
, _source
, config
, value
):
1052 if config
== prefs
.FONTDIFF
:
1054 font
= QtGui
.QFont()
1055 if not font
.fromString(value
):
1057 self
.logwidget
.setFont(font
)
1058 self
.diffeditor
.setFont(font
)
1059 self
.commiteditor
.setFont(font
)
1061 elif config
== prefs
.TABWIDTH
:
1062 # This can be set locally or globally, so we have to use the
1063 # effective value otherwise we'll update when we shouldn't.
1064 # For example, if this value is overridden locally, and the
1065 # global value is tweaked, we should not update.
1066 value
= prefs
.tabwidth(self
.context
)
1067 self
.diffeditor
.set_tabwidth(value
)
1068 self
.commiteditor
.set_tabwidth(value
)
1070 elif config
== prefs
.EXPANDTAB
:
1071 self
.commiteditor
.set_expandtab(value
)
1073 elif config
== prefs
.LINEBREAK
:
1074 # enables automatic line breaks
1075 self
.commiteditor
.set_linebreak(value
)
1077 elif config
== prefs
.SORT_BOOKMARKS
:
1078 self
.bookmarksdock
.widget().reload_bookmarks()
1080 elif config
== prefs
.TEXTWIDTH
:
1081 # Use the effective value for the same reason as tabwidth.
1082 value
= prefs
.textwidth(self
.context
)
1083 self
.commiteditor
.set_textwidth(value
)
1085 elif config
== prefs
.SHOW_PATH
:
1086 # the path in the window title was toggled
1087 self
.refresh_window_title()
1089 def start(self
, context
):
1090 """Do the expensive "get_config_actions()" call in the background"""
1091 # Install .git-config-defined actions
1092 task
= qtutils
.SimpleTask(self
.get_config_actions
)
1093 context
.runtask
.start(task
)
1095 def get_config_actions(self
):
1096 actions
= cfgactions
.get_config_actions(self
.context
)
1097 self
.config_actions_changed
.emit(actions
)
1100 """Update the title with the current branch and directory name."""
1101 curbranch
= self
.model
.currentbranch
1102 is_merging
= self
.model
.is_merging
1103 is_rebasing
= self
.model
.is_rebasing
1104 is_applying_patch
= self
.model
.is_applying_patch
1105 is_cherry_picking
= self
.model
.is_rebasing
1107 curdir
= core
.getcwd()
1108 msg
= N_('Repository: %s') % curdir
1110 msg
+= N_('Branch: %s') % curbranch
1115 'This repository is currently being rebased.\n'
1116 'Resolve conflicts, commit changes, and run:\n'
1117 ' Rebase > Continue'
1119 elif is_applying_patch
:
1122 'This repository has unresolved conflicts after applying a patch.\n'
1123 'Resolve conflicts and commit changes.'
1125 elif is_cherry_picking
:
1128 'This repository is in the middle of a cherry-pick.\n'
1129 'Resolve conflicts and commit changes.'
1134 'This repository is in the middle of a merge.\n'
1135 'Resolve conflicts and commit changes.'
1138 self
.refresh_window_title()
1140 if self
.mode
== self
.model
.mode_amend
:
1141 self
.commit_amend_action
.setChecked(True)
1143 self
.commit_amend_action
.setChecked(False)
1145 self
.commitdock
.setToolTip(msg
)
1147 self
.actionswidget
.set_mode(self
.mode
)
1148 self
.commiteditor
.set_mode(self
.mode
)
1149 self
.statuswidget
.set_mode(self
.mode
)
1151 self
.update_actions()
1153 def refresh_window_title(self
):
1154 """Refresh the window title when state changes"""
1157 project
= self
.model
.project
1158 curbranch
= self
.model
.currentbranch
1159 is_cherry_picking
= self
.model
.is_cherry_picking
1160 is_merging
= self
.model
.is_merging
1161 is_rebasing
= self
.model
.is_rebasing
1162 is_applying_patch
= self
.model
.is_applying_patch
1163 is_diff_mode
= self
.model
.is_diff_mode()
1164 is_amend_mode
= self
.mode
== self
.model
.mode_amend
1170 alerts
.append(N_('Amending'))
1172 alerts
.append(N_('Diff Mode'))
1173 elif is_cherry_picking
:
1174 alerts
.append(N_('Cherry-picking'))
1176 alerts
.append(N_('Merging'))
1178 alerts
.append(N_('Rebasing'))
1179 elif is_applying_patch
:
1180 alerts
.append(N_('Applying Patch'))
1183 alert_text
= (prefix
+ ' %s ' + suffix
+ ' ') % ', '.join(alerts
)
1187 if self
.model
.cfg
.get(prefs
.SHOW_PATH
, True):
1188 path_text
= self
.git
.worktree()
1192 title
= f
'{project}: {curbranch} {alert_text}{path_text}'
1193 self
.setWindowTitle(title
)
1195 def update_actions(self
):
1196 is_rebasing
= self
.model
.is_rebasing
1197 self
.rebase_group
.setEnabled(is_rebasing
)
1199 enabled
= not self
.model
.is_empty_repository()
1200 self
.rename_branch_action
.setEnabled(enabled
)
1201 self
.delete_branch_action
.setEnabled(enabled
)
1203 self
.annex_init_action
.setEnabled(not self
.model
.annex
)
1204 self
.lfs_init_action
.setEnabled(not self
.model
.lfs
)
1205 self
.merge_abort_action
.setEnabled(self
.model
.is_merging
)
1206 self
.cherry_pick_abort_action
.setEnabled(self
.model
.is_cherry_picking
)
1207 self
.apply_patches_continue_action
.setEnabled(self
.model
.is_applying_patch
)
1208 self
.apply_patches_skip_action
.setEnabled(self
.model
.is_applying_patch
)
1209 self
.apply_patches_abort_action
.setEnabled(self
.model
.is_applying_patch
)
1211 diff_mode
= self
.model
.mode
== self
.model
.mode_diff
1212 self
.exit_diff_mode_action
.setEnabled(diff_mode
)
1214 def update_menu_actions(self
):
1215 # Enable the Prepare Commit Message action if the hook exists
1216 hook
= gitcmds
.prepare_commit_message_hook(self
.context
)
1217 enabled
= os
.path
.exists(hook
)
1218 self
.prepare_commitmsg_hook_action
.setEnabled(enabled
)
1220 def export_state(self
):
1221 state
= standard
.MainWindow
.export_state(self
)
1222 show_status_filter
= self
.statuswidget
.filter_widget
.isVisible()
1223 state
['show_status_filter'] = show_status_filter
1224 state
['toolbars'] = self
.toolbar_state
.export_state()
1225 state
['ref_sort'] = self
.model
.ref_sort
1226 self
.diffviewer
.export_state(state
)
1230 def apply_state(self
, state
):
1231 """Imports data for save/restore"""
1232 base_ok
= standard
.MainWindow
.apply_state(self
, state
)
1233 lock_layout
= state
.get('lock_layout', False)
1234 self
.lock_layout_action
.setChecked(lock_layout
)
1236 show_status_filter
= state
.get('show_status_filter', False)
1237 self
.statuswidget
.filter_widget
.setVisible(show_status_filter
)
1239 toolbars
= state
.get('toolbars', [])
1240 self
.toolbar_state
.apply_state(toolbars
)
1242 sort_key
= state
.get('ref_sort', 0)
1243 self
.model
.set_ref_sort(sort_key
)
1245 diff_ok
= self
.diffviewer
.apply_state(state
)
1246 return base_ok
and diff_ok
1248 def setup_dockwidget_view_menu(self
):
1249 # Hotkeys for toggling the dock widgets
1250 if utils
.is_darwin():
1255 (optkey
+ '+0', self
.logdock
),
1256 (optkey
+ '+1', self
.commitdock
),
1257 (optkey
+ '+2', self
.statusdock
),
1258 (optkey
+ '+3', self
.diffdock
),
1259 (optkey
+ '+4', self
.actionsdock
),
1260 (optkey
+ '+5', self
.bookmarksdock
),
1261 (optkey
+ '+6', self
.recentdock
),
1262 (optkey
+ '+7', self
.branchdock
),
1263 (optkey
+ '+8', self
.submodulesdock
),
1265 for shortcut
, dockwidget
in dockwidgets
:
1266 # Associate the action with the shortcut
1267 toggleview
= dockwidget
.toggleViewAction()
1268 toggleview
.setShortcut('Shift+' + shortcut
)
1270 def showdock(show
, dockwidget
=dockwidget
):
1273 dockwidget
.widget().setFocus()
1277 self
.addAction(toggleview
)
1278 qtutils
.connect_action_bool(toggleview
, showdock
)
1280 # Create a new shortcut Shift+<shortcut> that gives focus
1281 toggleview
= QtWidgets
.QAction(self
)
1282 toggleview
.setShortcut(shortcut
)
1284 def focusdock(dockwidget
=dockwidget
):
1285 focus_dock(dockwidget
)
1287 self
.addAction(toggleview
)
1288 qtutils
.connect_action(toggleview
, focusdock
)
1290 # These widgets warrant home-row hotkey status
1293 'Focus Commit Message',
1294 lambda: focus_dock(self
.commitdock
),
1300 'Focus Status Window',
1301 lambda: focus_dock(self
.statusdock
),
1302 hotkeys
.FOCUS_STATUS
,
1307 'Focus Diff Editor',
1308 lambda: focus_dock(self
.diffdock
),
1313 self
.dag
= dag
.git_dag(self
.context
, existing_view
=self
.dag
)
1315 def show_cursor_position(self
, rows
, cols
):
1316 display_content
= '%02d:%02d' % (rows
, cols
)
1323 background-color: yellow;
1327 background-color: #f83;
1331 background-color: red;
1339 cls
= 'second-warning'
1341 cls
= 'first-warning'
1344 div
= f
'<div class="{cls}">{display_content}</div>'
1345 self
.position_label
.setText(css
+ div
)
1349 """Proxy over child widgets and operate on the focused widget"""
1351 def __init__(self
, *widgets
):
1352 self
.widgets
= widgets
1355 def override(self
, name
, widgets
):
1356 self
.overrides
[name
] = widgets
1358 def focus(self
, name
):
1359 """Return the currently focused widget"""
1360 widgets
= self
.overrides
.get(name
, self
.widgets
)
1361 # The parent must be the parent of all the proxied widgets
1363 # The first widget is used as a fallback
1364 fallback
= widgets
[1]
1365 # We ignore the parent when delegating to child widgets
1366 widgets
= widgets
[1:]
1368 focus
= parent
.focusWidget()
1369 if focus
not in widgets
:
1373 def __getattr__(self
, name
):
1374 """Return a callback that calls a common child method"""
1377 focus
= self
.focus(name
)
1378 func
= getattr(focus
, name
, None)
1385 """Specialized delete() to deal with QLineEdit vs QTextEdit"""
1386 focus
= self
.focus('delete')
1387 if hasattr(focus
, 'del_'):
1389 elif hasattr(focus
, 'textCursor'):
1390 focus
.textCursor().deleteChar()
1393 def show_dock(dockwidget
):
1395 dockwidget
.widget().setFocus()
1398 def focus_dock(dockwidget
):
1399 if get(dockwidget
.toggleViewAction()):
1400 show_dock(dockwidget
)
1402 dockwidget
.toggleViewAction().trigger()
1405 def _install_config_actions(context
, menu
, names_and_shortcuts
):
1406 """Install .gitconfig-defined actions"""
1407 if not names_and_shortcuts
:
1411 for name
, shortcut
in names_and_shortcuts
:
1412 sub_menu
, action_name
= build_menus(name
, menu
, cache
)
1413 callback
= cmds
.run(cmds
.RunConfigAction
, context
, name
)
1414 menu_action
= sub_menu
.addAction(action_name
, callback
)
1416 menu_action
.setShortcut(shortcut
)
1419 def build_menus(name
, menu
, cache
):
1420 """Create a chain of QMenu entries parented under a root QMenu
1422 A name of "a/b/c" create a menu chain of menu -> QMenu("a") -> QMenu("b")
1423 and returns a tuple of (QMenu("b"), "c").
1425 :param name: The full entry path, ex: "a/b/c" where "a/b" is the menu chain.
1426 :param menu: The root menu under which to create the menu chain.
1427 :param cache: A dict cache of previously created menus to avoid duplicates.
1430 # NOTE: utils.split() and friends are used instead of os.path.split() because
1431 # slash '/' is the only supported "<menu>/name" separator. Use of os.path.split()
1432 # would introduce differences in behavior across platforms.
1434 # If the menu_path is empty then no parent menus need to be created.
1435 # The action will be added to the root menu.
1436 menu_path
, text
= utils
.split(utils
.normalize_slash(name
))
1440 # When menu_path contains ex: "a/b" we will create two menus: "a" and "b".
1441 # The root menu is the parent of "a" and "a" is the parent of "b".
1442 # The menu returned to the caller is "b".
1444 # Loop over the individual menu basenames alongside the full subpath returned by
1445 # pathset(). The subpath is a cache key for finding previously created menus.
1446 menu_names
= utils
.splitpath(menu_path
) # ['a', 'b']
1447 menu_pathset
= utils
.pathset(menu_path
) # ['a', 'a/b']
1448 for menu_name
, menu_id
in zip(menu_names
, menu_pathset
):
1450 menu
= cache
[menu_id
]
1452 menu
= cache
[menu_id
] = menu
.addMenu(menu_name
)