1 """Main UI for authoring commits and other Git Cola interactions"""
2 from __future__
import absolute_import
, division
, print_function
, unicode_literals
4 from functools
import partial
6 from qtpy
import QtCore
8 from qtpy
import QtWidgets
9 from qtpy
.QtCore
import Qt
10 from qtpy
.QtCore
import Signal
12 from ..compat
import uchr
13 from ..compat
import WIN32
15 from ..interaction
import Interaction
16 from ..models
import prefs
17 from ..qtutils
import get
20 from .. import guicmds
22 from .. import gitcmds
23 from .. import hotkeys
25 from .. import qtutils
26 from .. import resources
28 from .. import version
32 from . import bookmarks
34 from . import submodules
36 from . import cfgactions
38 from . import commitmsg
41 from . import createbranch
42 from . import createtag
47 from . import editremotes
52 from . import prefs
as prefs_widget
56 from . import standard
62 class MainView(standard
.MainWindow
):
63 config_actions_changed
= Signal(object)
65 def __init__(self
, context
, parent
=None):
66 # pylint: disable=too-many-statements,too-many-locals
67 standard
.MainWindow
.__init
__(self
, parent
)
68 self
.setAttribute(Qt
.WA_DeleteOnClose
)
70 self
.context
= context
71 self
.git
= context
.git
73 self
.model
= context
.model
74 self
.prefs_model
= prefs_model
= prefs
.PreferencesModel(context
)
75 self
.toolbar_state
= toolbar
.ToolBarState(context
, self
)
77 # The widget version is used by import/export_state().
78 # Change this whenever dockwidgets are removed.
79 self
.widget_version
= 2
81 create_dock
= qtutils
.create_dock
83 self
.browser_dockable
= cfg
.get('cola.browserdockable')
84 if self
.browser_dockable
:
85 browser
= browse
.worktree_browser(
86 context
, parent
=self
, show
=False, update
=False
88 self
.browserdock
= create_dock(
89 'Browser', N_('Browser'), self
, widget
=browser
93 self
.actionswidget
= action
.ActionButtons(context
, self
)
94 self
.actionsdock
= create_dock(
95 'Actions', N_('Actions'), self
, widget
=self
.actionswidget
97 qtutils
.hide_dock(self
.actionsdock
)
99 # "Repository Status" widget
100 self
.statusdock
= create_dock(
104 fn
=lambda dock
: status
.StatusWidget(context
, dock
.titleBarWidget(), dock
),
106 self
.statuswidget
= self
.statusdock
.widget()
108 # "Switch Repository" widgets
109 self
.bookmarksdock
= create_dock(
113 fn
=lambda dock
: bookmarks
.bookmark(context
, dock
),
115 bookmarkswidget
= self
.bookmarksdock
.widget()
116 qtutils
.hide_dock(self
.bookmarksdock
)
118 self
.recentdock
= create_dock(
122 fn
=lambda dock
: bookmarks
.recent(context
, dock
),
124 recentwidget
= self
.recentdock
.widget()
125 qtutils
.hide_dock(self
.recentdock
)
126 bookmarkswidget
.connect_to(recentwidget
)
129 self
.branchdock
= create_dock(
130 'Branches', N_('Branches'), self
, fn
=partial(branch
.BranchesWidget
, context
)
132 self
.branchwidget
= self
.branchdock
.widget()
133 titlebar
= self
.branchdock
.titleBarWidget()
134 titlebar
.add_corner_widget(self
.branchwidget
.filter_button
)
135 titlebar
.add_corner_widget(self
.branchwidget
.sort_order_button
)
137 # "Submodule" widgets
138 self
.submodulesdock
= create_dock(
142 fn
=partial(submodules
.SubmodulesWidget
, context
),
144 self
.submoduleswidget
= self
.submodulesdock
.widget()
146 # "Commit Message Editor" widget
147 self
.position_label
= QtWidgets
.QLabel()
148 self
.position_label
.setAlignment(Qt
.AlignCenter
)
149 font
= qtutils
.default_monospace_font()
150 font
.setPointSize(int(font
.pointSize() * 0.8))
151 self
.position_label
.setFont(font
)
153 # make the position label fixed size to avoid layout issues
154 fm
= self
.position_label
.fontMetrics()
155 width
= fm
.width('99:999') + defs
.spacing
156 self
.position_label
.setMinimumWidth(width
)
158 editor
= commitmsg
.CommitMessageEditor(context
, self
)
159 self
.commiteditor
= editor
160 self
.commitdock
= create_dock('Commit', N_('Commit'), self
, widget
=editor
)
161 titlebar
= self
.commitdock
.titleBarWidget()
162 titlebar
.add_corner_widget(self
.position_label
)
165 self
.logwidget
= log
.LogWidget(context
)
166 self
.logdock
= create_dock(
167 'Console', N_('Console'), self
, widget
=self
.logwidget
169 qtutils
.hide_dock(self
.logdock
)
171 # "Diff Viewer" widget
172 self
.diffdock
= create_dock(
173 'Diff', N_('Diff'), self
, fn
=lambda dock
: diff
.Viewer(context
, parent
=dock
)
175 self
.diffviewer
= self
.diffdock
.widget()
176 self
.diffviewer
.set_diff_type(self
.model
.diff_type
)
178 self
.diffeditor
= self
.diffviewer
.text
179 titlebar
= self
.diffdock
.titleBarWidget()
180 titlebar
.add_corner_widget(self
.diffviewer
.options
)
183 add_action
= qtutils
.add_action
184 add_action_bool
= qtutils
.add_action_bool
186 self
.commit_amend_action
= add_action_bool(
188 N_('Amend Last Commit'),
189 partial(cmds
.do
, cmds
.AmendMode
, context
),
192 self
.commit_amend_action
.setIcon(icons
.edit())
193 self
.commit_amend_action
.setShortcut(hotkeys
.AMEND
)
194 self
.commit_amend_action
.setShortcutContext(Qt
.WidgetShortcut
)
196 self
.unstage_all_action
= add_action(
197 self
, N_('Unstage All'), cmds
.run(cmds
.UnstageAll
, context
)
199 self
.unstage_all_action
.setIcon(icons
.remove())
201 self
.undo_commit_action
= add_action(
202 self
, N_('Undo Last Commit'), cmds
.run(cmds
.UndoLastCommit
, context
)
204 self
.undo_commit_action
.setIcon(icons
.style_dialog_discard())
206 self
.unstage_selected_action
= add_action(
207 self
, N_('Unstage From Commit'), cmds
.run(cmds
.UnstageSelected
, context
)
209 self
.unstage_selected_action
.setIcon(icons
.remove())
211 self
.show_diffstat_action
= add_action(
212 self
, N_('Diffstat'), self
.statuswidget
.select_header
, hotkeys
.DIFFSTAT
214 self
.show_diffstat_action
.setIcon(icons
.diff())
216 self
.stage_modified_action
= add_action(
218 N_('Stage Changed Files To Commit'),
219 cmds
.run(cmds
.StageModified
, context
),
220 hotkeys
.STAGE_MODIFIED
,
222 self
.stage_modified_action
.setIcon(icons
.add())
224 self
.stage_untracked_action
= add_action(
226 N_('Stage All Untracked'),
227 cmds
.run(cmds
.StageUntracked
, context
),
228 hotkeys
.STAGE_UNTRACKED
,
230 self
.stage_untracked_action
.setIcon(icons
.add())
232 self
.apply_patches_action
= add_action(
233 self
, N_('Apply Patches...'), partial(patch
.apply_patches
, context
)
235 self
.apply_patches_action
.setIcon(icons
.diff())
237 self
.apply_patches_abort_action
= add_action(
239 N_('Abort Applying Patches...'),
240 cmds
.run(cmds
.AbortApplyPatch
, context
),
242 self
.apply_patches_abort_action
.setIcon(icons
.style_dialog_discard())
243 self
.apply_patches_abort_action
.setToolTip(
244 N_('Abort the current "git am" patch session')
247 self
.apply_patches_continue_action
= add_action(
249 N_('Continue Applying Patches'),
250 cmds
.run(cmds
.ApplyPatchesContinue
, context
),
252 self
.apply_patches_continue_action
.setToolTip(
253 N_('Commit the current state and continue applying patches')
255 self
.apply_patches_continue_action
.setIcon(icons
.commit())
257 self
.apply_patches_skip_action
= add_action(
258 self
, N_('Skip Current Patch'), cmds
.run(cmds
.ApplyPatchesContinue
, context
)
260 self
.apply_patches_skip_action
.setToolTip(
261 N_('Skip applying the current patch and continue applying patches')
263 self
.apply_patches_skip_action
.setIcon(icons
.discard())
265 self
.export_patches_action
= add_action(
267 N_('Export Patches...'),
268 partial(guicmds
.export_patches
, context
),
271 self
.export_patches_action
.setIcon(icons
.save())
273 self
.new_repository_action
= add_action(
274 self
, N_('New Repository...'), partial(guicmds
.open_new_repo
, context
)
276 self
.new_repository_action
.setIcon(icons
.new())
278 self
.new_bare_repository_action
= add_action(
279 self
, N_('New Bare Repository...'), partial(guicmds
.new_bare_repo
, context
)
281 self
.new_bare_repository_action
.setIcon(icons
.new())
284 prefs_widget
.preferences
, context
, parent
=self
, model
=prefs_model
286 self
.preferences_action
= add_action(
287 self
, N_('Preferences'), prefs_fn
, QtGui
.QKeySequence
.Preferences
289 self
.preferences_action
.setIcon(icons
.configure())
291 self
.edit_remotes_action
= add_action(
292 self
, N_('Edit Remotes...'), partial(editremotes
.editor
, context
)
294 self
.edit_remotes_action
.setIcon(icons
.edit())
296 self
.rescan_action
= add_action(
299 cmds
.run(cmds
.Refresh
, context
),
300 *hotkeys
.REFRESH_HOTKEYS
302 self
.rescan_action
.setIcon(icons
.sync())
304 self
.find_files_action
= add_action(
307 partial(finder
.finder
, context
),
310 self
.find_files_action
.setIcon(icons
.search())
312 self
.browse_recently_modified_action
= add_action(
314 N_('Recently Modified Files...'),
315 partial(recent
.browse_recent_files
, context
),
316 hotkeys
.EDIT_SECONDARY
,
318 self
.browse_recently_modified_action
.setIcon(icons
.directory())
320 self
.cherry_pick_action
= add_action(
322 N_('Cherry-Pick...'),
323 partial(guicmds
.cherry_pick
, context
),
326 self
.cherry_pick_action
.setIcon(icons
.cherry_pick())
327 self
.cherry_pick_abort_action
= add_action(
328 self
, N_('Abort Cherry-Pick...'), cmds
.run(cmds
.AbortCherryPick
, context
)
330 self
.cherry_pick_abort_action
.setIcon(icons
.style_dialog_discard())
332 self
.load_commitmsg_action
= add_action(
333 self
, N_('Load Commit Message...'), partial(guicmds
.load_commitmsg
, context
)
335 self
.load_commitmsg_action
.setIcon(icons
.file_text())
337 self
.prepare_commitmsg_hook_action
= add_action(
339 N_('Prepare Commit Message'),
340 cmds
.run(cmds
.PrepareCommitMessageHook
, context
),
341 hotkeys
.PREPARE_COMMIT_MESSAGE
,
344 self
.save_tarball_action
= add_action(
345 self
, N_('Save As Tarball/Zip...'), partial(archive
.save_archive
, context
)
347 self
.save_tarball_action
.setIcon(icons
.file_zip())
349 self
.quit_action
= add_action(self
, N_('Quit'), self
.close
, hotkeys
.QUIT
)
351 self
.grep_action
= add_action(
352 self
, N_('Grep'), partial(grep
.grep
, context
), hotkeys
.GREP
354 self
.grep_action
.setIcon(icons
.search())
356 self
.merge_local_action
= add_action(
357 self
, N_('Merge...'), partial(merge
.local_merge
, context
), hotkeys
.MERGE
359 self
.merge_local_action
.setIcon(icons
.merge())
361 self
.merge_abort_action
= add_action(
362 self
, N_('Abort Merge...'), cmds
.run(cmds
.AbortMerge
, context
)
364 self
.merge_abort_action
.setIcon(icons
.style_dialog_discard())
366 self
.update_submodules_action
= add_action(
368 N_('Update All Submodules...'),
369 cmds
.run(cmds
.SubmodulesUpdate
, context
),
371 self
.update_submodules_action
.setIcon(icons
.sync())
373 self
.add_submodule_action
= add_action(
375 N_('Add Submodule...'),
376 partial(submodules
.add_submodule
, context
, parent
=self
),
378 self
.add_submodule_action
.setIcon(icons
.add())
380 self
.fetch_action
= add_action(
381 self
, N_('Fetch...'), partial(remote
.fetch
, context
), hotkeys
.FETCH
383 self
.fetch_action
.setIcon(icons
.download())
385 self
.push_action
= add_action(
386 self
, N_('Push...'), partial(remote
.push
, context
), hotkeys
.PUSH
388 self
.push_action
.setIcon(icons
.push())
390 self
.pull_action
= add_action(
391 self
, N_('Pull...'), partial(remote
.pull
, context
), hotkeys
.PULL
393 self
.pull_action
.setIcon(icons
.pull())
395 self
.open_repo_action
= add_action(
396 self
, N_('Open...'), partial(guicmds
.open_repo
, context
), hotkeys
.OPEN
398 self
.open_repo_action
.setIcon(icons
.folder())
400 self
.open_repo_new_action
= add_action(
402 N_('Open in New Window...'),
403 partial(guicmds
.open_repo_in_new_window
, context
),
405 self
.open_repo_new_action
.setIcon(icons
.folder())
407 self
.stash_action
= add_action(
408 self
, N_('Stash...'), partial(stash
.view
, context
), hotkeys
.STASH
410 self
.stash_action
.setIcon(icons
.commit())
412 self
.reset_soft_action
= add_action(
413 self
, N_('Reset Branch (Soft)'), partial(guicmds
.reset_soft
, context
)
415 self
.reset_soft_action
.setIcon(icons
.style_dialog_reset())
416 self
.reset_soft_action
.setToolTip(cmds
.ResetSoft
.tooltip('<commit>'))
418 self
.reset_mixed_action
= add_action(
420 N_('Reset Branch and Stage (Mixed)'),
421 partial(guicmds
.reset_mixed
, context
),
423 self
.reset_mixed_action
.setIcon(icons
.style_dialog_reset())
424 self
.reset_mixed_action
.setToolTip(cmds
.ResetMixed
.tooltip('<commit>'))
426 self
.reset_keep_action
= add_action(
428 N_('Restore Worktree and Reset All (Keep Unstaged Changes)'),
429 partial(guicmds
.reset_keep
, context
),
431 self
.reset_keep_action
.setIcon(icons
.style_dialog_reset())
432 self
.reset_keep_action
.setToolTip(cmds
.ResetKeep
.tooltip('<commit>'))
434 self
.reset_merge_action
= add_action(
436 N_('Restore Worktree and Reset All (Merge)'),
437 partial(guicmds
.reset_merge
, context
),
439 self
.reset_merge_action
.setIcon(icons
.style_dialog_reset())
440 self
.reset_merge_action
.setToolTip(cmds
.ResetMerge
.tooltip('<commit>'))
442 self
.reset_hard_action
= add_action(
444 N_('Restore Worktree and Reset All (Hard)'),
445 partial(guicmds
.reset_hard
, context
),
447 self
.reset_hard_action
.setIcon(icons
.style_dialog_reset())
448 self
.reset_hard_action
.setToolTip(cmds
.ResetHard
.tooltip('<commit>'))
450 self
.restore_worktree_action
= add_action(
451 self
, N_('Restore Worktree'), partial(guicmds
.restore_worktree
, context
)
453 self
.restore_worktree_action
.setIcon(icons
.edit())
454 self
.restore_worktree_action
.setToolTip(
455 cmds
.RestoreWorktree
.tooltip('<commit>')
458 self
.clone_repo_action
= add_action(
459 self
, N_('Clone...'), partial(clone
.clone
, context
)
461 self
.clone_repo_action
.setIcon(icons
.repo())
463 self
.help_docs_action
= add_action(
466 resources
.show_html_docs
,
467 QtGui
.QKeySequence
.HelpContents
,
470 self
.help_shortcuts_action
= add_action(
471 self
, N_('Keyboard Shortcuts'), about
.show_shortcuts
, hotkeys
.QUESTION
474 self
.visualize_current_action
= add_action(
476 N_('Visualize Current Branch...'),
477 cmds
.run(cmds
.VisualizeCurrent
, context
),
479 self
.visualize_current_action
.setIcon(icons
.visualize())
481 self
.visualize_all_action
= add_action(
482 self
, N_('Visualize All Branches...'), cmds
.run(cmds
.VisualizeAll
, context
)
484 self
.visualize_all_action
.setIcon(icons
.visualize())
486 self
.search_commits_action
= add_action(
487 self
, N_('Search...'), partial(search
.search
, context
)
489 self
.search_commits_action
.setIcon(icons
.search())
491 self
.browse_branch_action
= add_action(
493 N_('Browse Current Branch...'),
494 partial(guicmds
.browse_current
, context
),
496 self
.browse_branch_action
.setIcon(icons
.directory())
498 self
.browse_other_branch_action
= add_action(
499 self
, N_('Browse Other Branch...'), partial(guicmds
.browse_other
, context
)
501 self
.browse_other_branch_action
.setIcon(icons
.directory())
503 self
.load_commitmsg_template_action
= add_action(
505 N_('Get Commit Message Template'),
506 cmds
.run(cmds
.LoadCommitMessageFromTemplate
, context
),
508 self
.load_commitmsg_template_action
.setIcon(icons
.style_dialog_apply())
510 self
.help_about_action
= add_action(
511 self
, N_('About'), partial(about
.about_dialog
, context
)
514 self
.diff_against_commit_action
= add_action(
516 N_('Against Commit... (Diff Mode)'),
517 partial(guicmds
.diff_against_commit
, context
),
519 self
.diff_against_commit_action
.setIcon(icons
.compare())
521 self
.exit_diff_mode_action
= add_action(
522 self
, N_('Exit Diff Mode'), cmds
.run(cmds
.ResetMode
, context
)
524 self
.exit_diff_mode_action
.setIcon(icons
.compare())
526 self
.diff_expression_action
= add_action(
527 self
, N_('Expression...'), partial(guicmds
.diff_expression
, context
)
529 self
.diff_expression_action
.setIcon(icons
.compare())
531 self
.branch_compare_action
= add_action(
532 self
, N_('Branches...'), partial(compare
.compare_branches
, context
)
534 self
.branch_compare_action
.setIcon(icons
.compare())
536 self
.create_tag_action
= add_action(
539 partial(createtag
.create_tag
, context
),
541 self
.create_tag_action
.setIcon(icons
.tag())
543 self
.create_branch_action
= add_action(
546 partial(createbranch
.create_new_branch
, context
),
549 self
.create_branch_action
.setIcon(icons
.branch())
551 self
.delete_branch_action
= add_action(
552 self
, N_('Delete...'), partial(guicmds
.delete_branch
, context
)
554 self
.delete_branch_action
.setIcon(icons
.discard())
556 self
.delete_remote_branch_action
= add_action(
558 N_('Delete Remote Branch...'),
559 partial(guicmds
.delete_remote_branch
, context
),
561 self
.delete_remote_branch_action
.setIcon(icons
.discard())
563 self
.rename_branch_action
= add_action(
564 self
, N_('Rename Branch...'), partial(guicmds
.rename_branch
, context
)
566 self
.rename_branch_action
.setIcon(icons
.edit())
568 self
.checkout_branch_action
= add_action(
571 partial(guicmds
.checkout_branch
, context
),
574 self
.checkout_branch_action
.setIcon(icons
.branch())
576 self
.branch_review_action
= add_action(
577 self
, N_('Review...'), partial(guicmds
.review_branch
, context
)
579 self
.branch_review_action
.setIcon(icons
.compare())
581 self
.browse_action
= add_action(
582 self
, N_('File Browser...'), partial(browse
.worktree_browser
, context
)
584 self
.browse_action
.setIcon(icons
.cola())
586 self
.dag_action
= add_action(self
, N_('DAG...'), self
.git_dag
)
587 self
.dag_action
.setIcon(icons
.cola())
589 self
.rebase_start_action
= add_action(
591 N_('Start Interactive Rebase...'),
592 cmds
.run(cmds
.Rebase
, context
),
593 hotkeys
.REBASE_START_AND_CONTINUE
,
596 self
.rebase_edit_todo_action
= add_action(
597 self
, N_('Edit...'), cmds
.run(cmds
.RebaseEditTodo
, context
)
600 self
.rebase_continue_action
= add_action(
603 cmds
.run(cmds
.RebaseContinue
, context
),
604 hotkeys
.REBASE_START_AND_CONTINUE
,
607 self
.rebase_skip_action
= add_action(
608 self
, N_('Skip Current Patch'), cmds
.run(cmds
.RebaseSkip
, context
)
611 self
.rebase_abort_action
= add_action(
612 self
, N_('Abort'), cmds
.run(cmds
.RebaseAbort
, context
)
615 # For "Start Rebase" only, reverse the first argument to setEnabled()
616 # so that we can operate on it as a group.
617 # We can do this because can_rebase == not is_rebasing
618 self
.rebase_start_action_proxy
= utils
.Proxy(
619 self
.rebase_start_action
,
620 setEnabled
=lambda x
: self
.rebase_start_action
.setEnabled(not x
),
623 self
.rebase_group
= utils
.Group(
624 self
.rebase_start_action_proxy
,
625 self
.rebase_edit_todo_action
,
626 self
.rebase_continue_action
,
627 self
.rebase_skip_action
,
628 self
.rebase_abort_action
,
631 self
.annex_init_action
= qtutils
.add_action(
632 self
, N_('Initialize Git Annex'), cmds
.run(cmds
.AnnexInit
, context
)
635 self
.lfs_init_action
= qtutils
.add_action(
636 self
, N_('Initialize Git LFS'), cmds
.run(cmds
.LFSInstall
, context
)
639 self
.lock_layout_action
= add_action_bool(
640 self
, N_('Lock Layout'), self
.set_lock_layout
, False
643 self
.reset_layout_action
= add_action(
644 self
, N_('Reset Layout'), self
.reset_layout
647 self
.quick_repository_search
= add_action(
650 lambda: guicmds
.open_quick_repo_search(self
.context
, parent
=self
),
651 hotkeys
.OPEN_REPO_SEARCH
,
653 self
.quick_repository_search
.setIcon(icons
.search())
655 self
.terminal_action
= common
.terminal_action(
656 context
, self
, hotkey
=hotkeys
.TERMINAL
659 # Create the application menu
660 self
.menubar
= QtWidgets
.QMenuBar(self
)
661 self
.setMenuBar(self
.menubar
)
664 add_menu
= qtutils
.add_menu
665 self
.file_menu
= add_menu(N_('&File'), self
.menubar
)
666 self
.file_menu
.addAction(self
.quick_repository_search
)
667 # File->Open Recent menu
668 self
.open_recent_menu
= self
.file_menu
.addMenu(N_('Open Recent'))
669 self
.open_recent_menu
.setIcon(icons
.folder())
670 self
.file_menu
.addAction(self
.open_repo_action
)
671 self
.file_menu
.addAction(self
.open_repo_new_action
)
672 self
.file_menu
.addSeparator()
673 self
.file_menu
.addAction(self
.new_repository_action
)
674 self
.file_menu
.addAction(self
.new_bare_repository_action
)
675 self
.file_menu
.addAction(self
.clone_repo_action
)
676 self
.file_menu
.addSeparator()
677 self
.file_menu
.addAction(self
.rescan_action
)
678 self
.file_menu
.addAction(self
.find_files_action
)
679 self
.file_menu
.addAction(self
.edit_remotes_action
)
680 self
.file_menu
.addAction(self
.browse_recently_modified_action
)
681 self
.file_menu
.addSeparator()
682 self
.file_menu
.addAction(self
.save_tarball_action
)
684 self
.patches_menu
= self
.file_menu
.addMenu(N_('Patches'))
685 self
.patches_menu
.setIcon(icons
.diff())
686 self
.patches_menu
.addAction(self
.export_patches_action
)
687 self
.patches_menu
.addAction(self
.apply_patches_action
)
688 self
.patches_menu
.addAction(self
.apply_patches_continue_action
)
689 self
.patches_menu
.addAction(self
.apply_patches_skip_action
)
690 self
.patches_menu
.addAction(self
.apply_patches_abort_action
)
692 # Git Annex / Git LFS
693 annex
= core
.find_executable('git-annex')
694 lfs
= core
.find_executable('git-lfs')
696 self
.file_menu
.addSeparator()
698 self
.file_menu
.addAction(self
.annex_init_action
)
700 self
.file_menu
.addAction(self
.lfs_init_action
)
702 self
.file_menu
.addSeparator()
703 self
.file_menu
.addAction(self
.preferences_action
)
704 self
.file_menu
.addAction(self
.quit_action
)
707 self
.edit_proxy
= edit_proxy
= FocusProxy(
708 editor
, editor
.summary
, editor
.description
716 bookmarkswidget
.tree
,
719 select_widgets
= copy_widgets
+ (self
.statuswidget
.tree
,)
720 edit_proxy
.override('copy', copy_widgets
)
721 edit_proxy
.override('selectAll', select_widgets
)
723 edit_menu
= self
.edit_menu
= add_menu(N_('&Edit'), self
.menubar
)
724 undo
= add_action(edit_menu
, N_('Undo'), edit_proxy
.undo
, hotkeys
.UNDO
)
725 undo
.setIcon(icons
.undo())
726 redo
= add_action(edit_menu
, N_('Redo'), edit_proxy
.redo
, hotkeys
.REDO
)
727 redo
.setIcon(icons
.redo())
728 edit_menu
.addSeparator()
729 cut
= add_action(edit_menu
, N_('Cut'), edit_proxy
.cut
, hotkeys
.CUT
)
730 cut
.setIcon(icons
.cut())
731 copy
= add_action(edit_menu
, N_('Copy'), edit_proxy
.copy
, hotkeys
.COPY
)
732 copy
.setIcon(icons
.copy())
733 paste
= add_action(edit_menu
, N_('Paste'), edit_proxy
.paste
, hotkeys
.PASTE
)
734 paste
.setIcon(icons
.paste())
735 delete
= add_action(edit_menu
, N_('Delete'), edit_proxy
.delete
, hotkeys
.DELETE
)
736 delete
.setIcon(icons
.delete())
737 edit_menu
.addSeparator()
738 select_all
= add_action(
739 edit_menu
, N_('Select All'), edit_proxy
.selectAll
, hotkeys
.SELECT_ALL
741 select_all
.setIcon(icons
.select_all())
742 edit_menu
.addSeparator()
743 commitmsg
.add_menu_actions(edit_menu
, self
.commiteditor
.menu_actions
)
746 self
.actions_menu
= add_menu(N_('Actions'), self
.menubar
)
747 if self
.terminal_action
is not None:
748 self
.actions_menu
.addAction(self
.terminal_action
)
749 self
.actions_menu
.addAction(self
.fetch_action
)
750 self
.actions_menu
.addAction(self
.push_action
)
751 self
.actions_menu
.addAction(self
.pull_action
)
752 self
.actions_menu
.addAction(self
.stash_action
)
753 self
.actions_menu
.addSeparator()
754 self
.actions_menu
.addAction(self
.create_tag_action
)
755 self
.actions_menu
.addAction(self
.cherry_pick_action
)
756 self
.actions_menu
.addAction(self
.cherry_pick_abort_action
)
757 self
.actions_menu
.addAction(self
.merge_local_action
)
758 self
.actions_menu
.addAction(self
.merge_abort_action
)
759 self
.actions_menu
.addSeparator()
760 self
.actions_menu
.addAction(self
.update_submodules_action
)
761 self
.actions_menu
.addAction(self
.add_submodule_action
)
762 self
.actions_menu
.addSeparator()
763 self
.actions_menu
.addAction(self
.grep_action
)
764 self
.actions_menu
.addAction(self
.search_commits_action
)
767 self
.commit_menu
= add_menu(N_('Commit@@verb'), self
.menubar
)
768 self
.commit_menu
.setTitle(N_('Commit@@verb'))
769 self
.commit_menu
.addAction(self
.commiteditor
.commit_action
)
770 self
.commit_menu
.addAction(self
.commit_amend_action
)
771 self
.commit_menu
.addAction(self
.undo_commit_action
)
772 self
.commit_menu
.addSeparator()
773 self
.commit_menu
.addAction(self
.statuswidget
.tree
.process_selection_action
)
774 self
.commit_menu
.addAction(self
.statuswidget
.tree
.stage_or_unstage_all_action
)
775 self
.commit_menu
.addAction(self
.stage_modified_action
)
776 self
.commit_menu
.addAction(self
.stage_untracked_action
)
777 self
.commit_menu
.addSeparator()
778 self
.commit_menu
.addAction(self
.unstage_all_action
)
779 self
.commit_menu
.addAction(self
.unstage_selected_action
)
780 self
.commit_menu
.addSeparator()
781 self
.commit_menu
.addAction(self
.load_commitmsg_action
)
782 self
.commit_menu
.addAction(self
.load_commitmsg_template_action
)
783 self
.commit_menu
.addAction(self
.prepare_commitmsg_hook_action
)
786 self
.diff_menu
= add_menu(N_('Diff'), self
.menubar
)
787 self
.diff_menu
.addAction(self
.diff_expression_action
)
788 self
.diff_menu
.addAction(self
.branch_compare_action
)
789 self
.diff_menu
.addAction(self
.show_diffstat_action
)
790 self
.diff_menu
.addSeparator()
791 self
.diff_menu
.addAction(self
.diff_against_commit_action
)
792 self
.diff_menu
.addAction(self
.exit_diff_mode_action
)
795 self
.branch_menu
= add_menu(N_('Branch'), self
.menubar
)
796 self
.branch_menu
.addAction(self
.branch_review_action
)
797 self
.branch_menu
.addSeparator()
798 self
.branch_menu
.addAction(self
.create_branch_action
)
799 self
.branch_menu
.addAction(self
.checkout_branch_action
)
800 self
.branch_menu
.addAction(self
.delete_branch_action
)
801 self
.branch_menu
.addAction(self
.delete_remote_branch_action
)
802 self
.branch_menu
.addAction(self
.rename_branch_action
)
803 self
.branch_menu
.addSeparator()
804 self
.branch_menu
.addAction(self
.browse_branch_action
)
805 self
.branch_menu
.addAction(self
.browse_other_branch_action
)
806 self
.branch_menu
.addSeparator()
807 self
.branch_menu
.addAction(self
.visualize_current_action
)
808 self
.branch_menu
.addAction(self
.visualize_all_action
)
811 self
.rebase_menu
= add_menu(N_('Rebase'), self
.menubar
)
812 self
.rebase_menu
.addAction(self
.rebase_start_action
)
813 self
.rebase_menu
.addAction(self
.rebase_edit_todo_action
)
814 self
.rebase_menu
.addSeparator()
815 self
.rebase_menu
.addAction(self
.rebase_continue_action
)
816 self
.rebase_menu
.addAction(self
.rebase_skip_action
)
817 self
.rebase_menu
.addSeparator()
818 self
.rebase_menu
.addAction(self
.rebase_abort_action
)
821 self
.reset_menu
= add_menu(N_('Reset'), self
.menubar
)
822 self
.reset_menu
.addAction(self
.unstage_all_action
)
823 self
.reset_menu
.addAction(self
.undo_commit_action
)
824 self
.reset_menu
.addSeparator()
825 self
.reset_menu
.addAction(self
.reset_soft_action
)
826 self
.reset_menu
.addAction(self
.reset_mixed_action
)
827 self
.reset_menu
.addAction(self
.restore_worktree_action
)
828 self
.reset_menu
.addSeparator()
829 self
.reset_menu
.addAction(self
.reset_keep_action
)
830 self
.reset_menu
.addAction(self
.reset_merge_action
)
831 self
.reset_menu
.addAction(self
.reset_hard_action
)
834 self
.view_menu
= add_menu(N_('View'), self
.menubar
)
835 # pylint: disable=no-member
836 self
.view_menu
.aboutToShow
.connect(lambda: self
.build_view_menu(self
.view_menu
))
837 self
.setup_dockwidget_view_menu()
838 if utils
.is_darwin():
839 # TODO or self.menubar.setNativeMenuBar(False)
840 # Since native OSX menu doesn't show empty entries
841 self
.build_view_menu(self
.view_menu
)
844 self
.help_menu
= add_menu(N_('Help'), self
.menubar
)
845 self
.help_menu
.addAction(self
.help_docs_action
)
846 self
.help_menu
.addAction(self
.help_shortcuts_action
)
847 self
.help_menu
.addAction(self
.help_about_action
)
849 # Arrange dock widgets
850 bottom
= Qt
.BottomDockWidgetArea
851 top
= Qt
.TopDockWidgetArea
853 self
.addDockWidget(top
, self
.statusdock
)
854 self
.addDockWidget(top
, self
.commitdock
)
855 if self
.browser_dockable
:
856 self
.addDockWidget(top
, self
.browserdock
)
857 self
.tabifyDockWidget(self
.browserdock
, self
.commitdock
)
859 self
.addDockWidget(top
, self
.branchdock
)
860 self
.addDockWidget(top
, self
.submodulesdock
)
861 self
.addDockWidget(top
, self
.bookmarksdock
)
862 self
.addDockWidget(top
, self
.recentdock
)
864 self
.tabifyDockWidget(self
.branchdock
, self
.submodulesdock
)
865 self
.tabifyDockWidget(self
.submodulesdock
, self
.bookmarksdock
)
866 self
.tabifyDockWidget(self
.bookmarksdock
, self
.recentdock
)
867 self
.branchdock
.raise_()
869 self
.addDockWidget(bottom
, self
.diffdock
)
870 self
.addDockWidget(bottom
, self
.actionsdock
)
871 self
.addDockWidget(bottom
, self
.logdock
)
872 self
.tabifyDockWidget(self
.actionsdock
, self
.logdock
)
874 # Listen for model notifications
875 self
.model
.updated
.connect(self
.refresh
, type=Qt
.QueuedConnection
)
876 self
.model
.mode_changed
.connect(
877 lambda mode
: self
.refresh(), type=Qt
.QueuedConnection
880 prefs_model
.config_updated
.connect(self
._config
_updated
)
882 # Set a default value
883 self
.show_cursor_position(1, 0)
885 self
.commit_menu
.aboutToShow
.connect(self
.update_menu_actions
)
886 self
.open_recent_menu
.aboutToShow
.connect(self
.build_recent_menu
)
887 self
.commiteditor
.cursor_changed
.connect(self
.show_cursor_position
)
889 self
.diffeditor
.options_changed
.connect(self
.statuswidget
.refresh
)
890 self
.diffeditor
.up
.connect(self
.statuswidget
.move_up
)
891 self
.diffeditor
.down
.connect(self
.statuswidget
.move_down
)
893 self
.commiteditor
.up
.connect(self
.statuswidget
.move_up
)
894 self
.commiteditor
.down
.connect(self
.statuswidget
.move_down
)
896 self
.config_actions_changed
.connect(
897 lambda names_and_shortcuts
: _install_config_actions(
902 type=Qt
.QueuedConnection
,
904 self
.init_state(context
.settings
, self
.set_initial_size
)
906 # Route command output here
907 Interaction
.log_status
= self
.logwidget
.log_status
908 Interaction
.log
= self
.logwidget
.log
909 # Focus the status widget; this must be deferred
910 QtCore
.QTimer
.singleShot(0, self
.initialize
)
912 def initialize(self
):
913 context
= self
.context
914 git_version
= version
.git_version_str(context
)
918 git_version
+ '\n' + N_('git cola version %s') % version
.version()
922 error_msg
= N_('error: unable to execute git')
923 Interaction
.log(error_msg
)
926 self
.statuswidget
.setFocus()
928 title
= N_('error: unable to execute git')
932 details
= git
.win32_git_error_hint()
933 Interaction
.critical(title
, message
=msg
, details
=details
)
934 self
.context
.app
.exit(2)
936 def set_initial_size(self
):
937 # Default size; this is thrown out when save/restore is used
938 width
, height
= qtutils
.desktop_size()
939 self
.resize((width
* 3) // 4, height
)
940 self
.statuswidget
.set_initial_size()
941 self
.commiteditor
.set_initial_size()
943 def set_filter(self
, txt
):
944 self
.statuswidget
.set_filter(txt
)
947 def closeEvent(self
, event
):
948 """Save state in the settings"""
949 commit_msg
= self
.commiteditor
.commit_message(raw
=True)
950 self
.model
.save_commitmsg(msg
=commit_msg
)
951 for browser
in list(self
.context
.browser_windows
):
953 standard
.MainWindow
.closeEvent(self
, event
)
955 def create_view_menu(self
):
956 menu
= qtutils
.create_menu(N_('View'), self
)
957 self
.build_view_menu(menu
)
960 def build_view_menu(self
, menu
):
962 menu
.addAction(self
.browse_action
)
963 menu
.addAction(self
.dag_action
)
966 popup_menu
= self
.createPopupMenu()
967 for menu_action
in popup_menu
.actions():
968 menu_action
.setParent(menu
)
969 menu
.addAction(menu_action
)
971 context
= self
.context
972 menu_action
= menu
.addAction(
973 N_('New Toolbar'), partial(toolbar
.add_toolbar
, context
, self
)
975 menu_action
.setIcon(icons
.add())
989 if self
.browser_dockable
:
990 dockwidgets
.append(self
.browserdock
)
992 for dockwidget
in dockwidgets
:
993 # Associate the action with the shortcut
994 toggleview
= dockwidget
.toggleViewAction()
995 menu
.addAction(toggleview
)
998 menu
.addAction(self
.lock_layout_action
)
999 menu
.addAction(self
.reset_layout_action
)
1003 def contextMenuEvent(self
, event
):
1004 menu
= self
.create_view_menu()
1005 menu
.exec_(event
.globalPos())
1007 def build_recent_menu(self
):
1009 context
= self
.context
1010 settings
= context
.settings
1012 menu
= self
.open_recent_menu
1014 worktree
= context
.git
.worktree()
1016 for entry
in settings
.recent
:
1017 directory
= entry
['path']
1018 if directory
== worktree
:
1019 # Omit the current worktree from the "Open Recent" menu.
1021 name
= entry
['name']
1022 text
= '%s %s %s' % (name
, uchr(0x2192), directory
)
1023 menu
.addAction(text
, cmds
.run(cmd
, context
, directory
))
1026 mode
= property(lambda self
: self
.model
.mode
)
1028 def _config_updated(self
, _source
, config
, value
):
1029 if config
== prefs
.FONTDIFF
:
1031 font
= QtGui
.QFont()
1032 if not font
.fromString(value
):
1034 self
.logwidget
.setFont(font
)
1035 self
.diffeditor
.setFont(font
)
1036 self
.commiteditor
.setFont(font
)
1038 elif config
== prefs
.TABWIDTH
:
1039 # This can be set locally or globally, so we have to use the
1040 # effective value otherwise we'll update when we shouldn't.
1041 # For example, if this value is overridden locally, and the
1042 # global value is tweaked, we should not update.
1043 value
= prefs
.tabwidth(self
.context
)
1044 self
.diffeditor
.set_tabwidth(value
)
1045 self
.commiteditor
.set_tabwidth(value
)
1047 elif config
== prefs
.EXPANDTAB
:
1048 self
.commiteditor
.set_expandtab(value
)
1050 elif config
== prefs
.LINEBREAK
:
1051 # enables automatic line breaks
1052 self
.commiteditor
.set_linebreak(value
)
1054 elif config
== prefs
.SORT_BOOKMARKS
:
1055 self
.bookmarksdock
.widget().reload_bookmarks()
1057 elif config
== prefs
.TEXTWIDTH
:
1058 # Use the effective value for the same reason as tabwidth.
1059 value
= prefs
.textwidth(self
.context
)
1060 self
.commiteditor
.set_textwidth(value
)
1062 elif config
== prefs
.SHOW_PATH
:
1063 # the path in the window title was toggled
1064 self
.refresh_window_title()
1066 def start(self
, context
):
1067 """Do the expensive "get_config_actions()" call in the background"""
1068 # Install .git-config-defined actions
1069 task
= qtutils
.SimpleTask(self
.get_config_actions
)
1070 context
.runtask
.start(task
)
1072 def get_config_actions(self
):
1073 actions
= cfgactions
.get_config_actions(self
.context
)
1074 self
.config_actions_changed
.emit(actions
)
1077 """Update the title with the current branch and directory name."""
1078 curbranch
= self
.model
.currentbranch
1079 is_merging
= self
.model
.is_merging
1080 is_rebasing
= self
.model
.is_rebasing
1081 is_applying_patch
= self
.model
.is_applying_patch
1082 is_cherry_picking
= self
.model
.is_rebasing
1084 curdir
= core
.getcwd()
1085 msg
= N_('Repository: %s') % curdir
1087 msg
+= N_('Branch: %s') % curbranch
1092 'This repository is currently being rebased.\n'
1093 'Resolve conflicts, commit changes, and run:\n'
1094 ' Rebase > Continue'
1096 elif is_applying_patch
:
1099 'This repository has unresolved conflicts after applying a patch.\n'
1100 'Resolve conflicts and commit changes.'
1102 elif is_cherry_picking
:
1105 'This repository is in the middle of a cherry-pick.\n'
1106 'Resolve conflicts and commit changes.'
1111 'This repository is in the middle of a merge.\n'
1112 'Resolve conflicts and commit changes.'
1115 self
.refresh_window_title()
1117 if self
.mode
== self
.model
.mode_amend
:
1118 self
.commit_amend_action
.setChecked(True)
1120 self
.commit_amend_action
.setChecked(False)
1122 self
.commitdock
.setToolTip(msg
)
1124 self
.actionswidget
.set_mode(self
.mode
)
1125 self
.commiteditor
.set_mode(self
.mode
)
1126 self
.statuswidget
.set_mode(self
.mode
)
1128 self
.update_actions()
1130 def refresh_window_title(self
):
1131 """Refresh the window title when state changes"""
1134 project
= self
.model
.project
1135 curbranch
= self
.model
.currentbranch
1136 is_cherry_picking
= self
.model
.is_cherry_picking
1137 is_merging
= self
.model
.is_merging
1138 is_rebasing
= self
.model
.is_rebasing
1139 is_applying_patch
= self
.model
.is_applying_patch
1140 is_diff_mode
= self
.mode
== self
.model
.mode_diff
1141 is_amend_mode
= self
.mode
== self
.model
.mode_amend
1147 alerts
.append(N_('Amending'))
1149 alerts
.append(N_('Diff Mode'))
1150 elif is_cherry_picking
:
1151 alerts
.append(N_('Cherry-picking'))
1153 alerts
.append(N_('Merging'))
1155 alerts
.append(N_('Rebasing'))
1156 elif is_applying_patch
:
1157 alerts
.append(N_('Applying Patch'))
1160 alert_text
= (prefix
+ ' %s ' + suffix
+ ' ') % ', '.join(alerts
)
1164 if self
.model
.cfg
.get(prefs
.SHOW_PATH
, True):
1165 path_text
= self
.git
.worktree()
1169 title
= '%s: %s %s%s' % (project
, curbranch
, alert_text
, path_text
)
1170 self
.setWindowTitle(title
)
1172 def update_actions(self
):
1173 is_rebasing
= self
.model
.is_rebasing
1174 self
.rebase_group
.setEnabled(is_rebasing
)
1176 enabled
= not self
.model
.is_empty_repository()
1177 self
.rename_branch_action
.setEnabled(enabled
)
1178 self
.delete_branch_action
.setEnabled(enabled
)
1180 self
.annex_init_action
.setEnabled(not self
.model
.annex
)
1181 self
.lfs_init_action
.setEnabled(not self
.model
.lfs
)
1182 self
.merge_abort_action
.setEnabled(self
.model
.is_merging
)
1183 self
.cherry_pick_abort_action
.setEnabled(self
.model
.is_cherry_picking
)
1184 self
.apply_patches_continue_action
.setEnabled(self
.model
.is_applying_patch
)
1185 self
.apply_patches_skip_action
.setEnabled(self
.model
.is_applying_patch
)
1186 self
.apply_patches_abort_action
.setEnabled(self
.model
.is_applying_patch
)
1188 diff_mode
= self
.model
.mode
== self
.model
.mode_diff
1189 self
.exit_diff_mode_action
.setEnabled(diff_mode
)
1191 def update_menu_actions(self
):
1192 # Enable the Prepare Commit Message action if the hook exists
1193 hook
= gitcmds
.prepare_commit_message_hook(self
.context
)
1194 enabled
= os
.path
.exists(hook
)
1195 self
.prepare_commitmsg_hook_action
.setEnabled(enabled
)
1197 def export_state(self
):
1198 state
= standard
.MainWindow
.export_state(self
)
1199 show_status_filter
= self
.statuswidget
.filter_widget
.isVisible()
1200 state
['show_status_filter'] = show_status_filter
1201 state
['toolbars'] = self
.toolbar_state
.export_state()
1202 state
['ref_sort'] = self
.model
.ref_sort
1203 self
.diffviewer
.export_state(state
)
1207 def apply_state(self
, state
):
1208 """Imports data for save/restore"""
1209 base_ok
= standard
.MainWindow
.apply_state(self
, state
)
1210 lock_layout
= state
.get('lock_layout', False)
1211 self
.lock_layout_action
.setChecked(lock_layout
)
1213 show_status_filter
= state
.get('show_status_filter', False)
1214 self
.statuswidget
.filter_widget
.setVisible(show_status_filter
)
1216 toolbars
= state
.get('toolbars', [])
1217 self
.toolbar_state
.apply_state(toolbars
)
1219 sort_key
= state
.get('ref_sort', 0)
1220 self
.model
.set_ref_sort(sort_key
)
1222 diff_ok
= self
.diffviewer
.apply_state(state
)
1223 return base_ok
and diff_ok
1225 def setup_dockwidget_view_menu(self
):
1226 # Hotkeys for toggling the dock widgets
1227 if utils
.is_darwin():
1232 (optkey
+ '+0', self
.logdock
),
1233 (optkey
+ '+1', self
.commitdock
),
1234 (optkey
+ '+2', self
.statusdock
),
1235 (optkey
+ '+3', self
.diffdock
),
1236 (optkey
+ '+4', self
.actionsdock
),
1237 (optkey
+ '+5', self
.bookmarksdock
),
1238 (optkey
+ '+6', self
.recentdock
),
1239 (optkey
+ '+7', self
.branchdock
),
1240 (optkey
+ '+8', self
.submodulesdock
),
1242 for shortcut
, dockwidget
in dockwidgets
:
1243 # Associate the action with the shortcut
1244 toggleview
= dockwidget
.toggleViewAction()
1245 toggleview
.setShortcut('Shift+' + shortcut
)
1247 def showdock(show
, dockwidget
=dockwidget
):
1250 dockwidget
.widget().setFocus()
1254 self
.addAction(toggleview
)
1255 qtutils
.connect_action_bool(toggleview
, showdock
)
1257 # Create a new shortcut Shift+<shortcut> that gives focus
1258 toggleview
= QtWidgets
.QAction(self
)
1259 toggleview
.setShortcut(shortcut
)
1261 def focusdock(dockwidget
=dockwidget
):
1262 focus_dock(dockwidget
)
1264 self
.addAction(toggleview
)
1265 qtutils
.connect_action(toggleview
, focusdock
)
1267 # These widgets warrant home-row hotkey status
1270 'Focus Commit Message',
1271 lambda: focus_dock(self
.commitdock
),
1277 'Focus Status Window',
1278 lambda: focus_dock(self
.statusdock
),
1279 hotkeys
.FOCUS_STATUS
,
1284 'Focus Diff Editor',
1285 lambda: focus_dock(self
.diffdock
),
1290 self
.dag
= dag
.git_dag(self
.context
, existing_view
=self
.dag
)
1292 def show_cursor_position(self
, rows
, cols
):
1293 display_content
= '%02d:%02d' % (rows
, cols
)
1300 background-color: yellow;
1304 background-color: #f83;
1308 background-color: red;
1316 cls
= 'second-warning'
1318 cls
= 'first-warning'
1321 div
= '<div class="%s">%s</div>' % (cls
, display_content
)
1322 self
.position_label
.setText(css
+ div
)
1325 class FocusProxy(object):
1326 """Proxy over child widgets and operate on the focused widget"""
1328 def __init__(self
, *widgets
):
1329 self
.widgets
= widgets
1332 def override(self
, name
, widgets
):
1333 self
.overrides
[name
] = widgets
1335 def focus(self
, name
):
1336 """Return the currently focused widget"""
1337 widgets
= self
.overrides
.get(name
, self
.widgets
)
1338 # The parent must be the parent of all the proxied widgets
1340 # The first widget is used as a fallback
1341 fallback
= widgets
[1]
1342 # We ignore the parent when delegating to child widgets
1343 widgets
= widgets
[1:]
1345 focus
= parent
.focusWidget()
1346 if focus
not in widgets
:
1350 def __getattr__(self
, name
):
1351 """Return a callback that calls a common child method"""
1354 focus
= self
.focus(name
)
1355 fn
= getattr(focus
, name
, None)
1362 """Specialized delete() to deal with QLineEdit vs QTextEdit"""
1363 focus
= self
.focus('delete')
1364 if hasattr(focus
, 'del_'):
1366 elif hasattr(focus
, 'textCursor'):
1367 focus
.textCursor().deleteChar()
1370 def show_dock(dockwidget
):
1372 dockwidget
.widget().setFocus()
1375 def focus_dock(dockwidget
):
1376 if get(dockwidget
.toggleViewAction()):
1377 show_dock(dockwidget
)
1379 dockwidget
.toggleViewAction().trigger()
1382 def _install_config_actions(context
, menu
, names_and_shortcuts
):
1383 """Install .gitconfig-defined actions"""
1384 if not names_and_shortcuts
:
1388 for (name
, shortcut
) in names_and_shortcuts
:
1389 sub_menu
, action_name
= build_menus(name
, menu
, cache
)
1390 callback
= cmds
.run(cmds
.RunConfigAction
, context
, name
)
1391 menu_action
= sub_menu
.addAction(action_name
, callback
)
1393 menu_action
.setShortcut(shortcut
)
1396 def build_menus(name
, menu
, cache
):
1397 """Create a chain of QMenu entries parented under a root QMenu
1399 A name of "a/b/c" create a menu chain of menu -> QMenu("a") -> QMenu("b")
1400 and returns a tuple of (QMenu("b"), "c").
1402 :param name: The full entry path, ex: "a/b/c" where "a/b" is the menu chain.
1403 :param menu: The root menu under which to create the menu chain.
1404 :param cache: A dict cache of previously created menus to avoid duplicates.
1407 # NOTE: utils.split() and friends are used instead of os.path.split() because
1408 # slash '/' is the only supported "<menu>/name" separator. Use of os.path.split()
1409 # would introduce differences in behavior across platforms.
1411 # If the menu_path is empty then no parent menus need to be created.
1412 # The action will be added to the root menu.
1413 menu_path
, text
= utils
.split(utils
.normalize_slash(name
))
1417 # When menu_path contains ex: "a/b" we will create two menus: "a" and "b".
1418 # The root menu is the parent of "a" and "a" is the parent of "b".
1419 # The menu returned to the caller is "b".
1421 # Loop over the individual menu basenames alongside the full subpath returned by
1422 # pathset(). The subpath is a cache key for finding previously created menus.
1423 menu_names
= utils
.splitpath(menu_path
) # ['a', 'b']
1424 menu_pathset
= utils
.pathset(menu_path
) # ['a', 'a/b']
1425 for menu_name
, menu_id
in zip(menu_names
, menu_pathset
):
1427 menu
= cache
[menu_id
]
1429 menu
= cache
[menu_id
] = menu
.addMenu(menu_name
)