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 func
=lambda dock
: status
.StatusWidget(context
, dock
.titleBarWidget(), dock
),
106 self
.statuswidget
= self
.statusdock
.widget()
108 # "Switch Repository" widgets
109 self
.bookmarksdock
= create_dock(
113 func
=lambda dock
: bookmarks
.bookmark(context
, dock
),
115 bookmarkswidget
= self
.bookmarksdock
.widget()
116 qtutils
.hide_dock(self
.bookmarksdock
)
118 self
.recentdock
= create_dock(
122 func
=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(
133 func
=partial(branch
.BranchesWidget
, context
),
135 self
.branchwidget
= self
.branchdock
.widget()
136 titlebar
= self
.branchdock
.titleBarWidget()
137 titlebar
.add_corner_widget(self
.branchwidget
.filter_button
)
138 titlebar
.add_corner_widget(self
.branchwidget
.sort_order_button
)
140 # "Submodule" widgets
141 self
.submodulesdock
= create_dock(
145 func
=partial(submodules
.SubmodulesWidget
, context
),
147 self
.submoduleswidget
= self
.submodulesdock
.widget()
149 # "Commit Message Editor" widget
150 self
.position_label
= QtWidgets
.QLabel()
151 self
.position_label
.setAlignment(Qt
.AlignCenter
)
152 font
= qtutils
.default_monospace_font()
153 font
.setPointSize(int(font
.pointSize() * 0.8))
154 self
.position_label
.setFont(font
)
156 # make the position label fixed size to avoid layout issues
157 metrics
= self
.position_label
.fontMetrics()
158 width
= metrics
.width('99:999') + defs
.spacing
159 self
.position_label
.setMinimumWidth(width
)
161 editor
= commitmsg
.CommitMessageEditor(context
, self
)
162 self
.commiteditor
= editor
163 self
.commitdock
= create_dock('Commit', N_('Commit'), self
, widget
=editor
)
164 titlebar
= self
.commitdock
.titleBarWidget()
165 titlebar
.add_corner_widget(self
.position_label
)
168 self
.logwidget
= log
.LogWidget(context
)
169 self
.logdock
= create_dock(
170 'Console', N_('Console'), self
, widget
=self
.logwidget
172 qtutils
.hide_dock(self
.logdock
)
174 # "Diff Viewer" widget
175 self
.diffdock
= create_dock(
179 func
=lambda dock
: diff
.Viewer(context
, parent
=dock
),
181 self
.diffviewer
= self
.diffdock
.widget()
182 self
.diffviewer
.set_diff_type(self
.model
.diff_type
)
184 self
.diffeditor
= self
.diffviewer
.text
185 titlebar
= self
.diffdock
.titleBarWidget()
186 titlebar
.add_corner_widget(self
.diffviewer
.options
)
189 add_action
= qtutils
.add_action
190 add_action_bool
= qtutils
.add_action_bool
192 self
.commit_amend_action
= add_action_bool(
194 N_('Amend Last Commit'),
195 partial(cmds
.do
, cmds
.AmendMode
, context
),
198 self
.commit_amend_action
.setIcon(icons
.edit())
199 self
.commit_amend_action
.setShortcut(hotkeys
.AMEND
)
200 self
.commit_amend_action
.setShortcutContext(Qt
.WidgetShortcut
)
202 self
.unstage_all_action
= add_action(
203 self
, N_('Unstage All'), cmds
.run(cmds
.UnstageAll
, context
)
205 self
.unstage_all_action
.setIcon(icons
.remove())
207 self
.undo_commit_action
= add_action(
208 self
, N_('Undo Last Commit'), cmds
.run(cmds
.UndoLastCommit
, context
)
210 self
.undo_commit_action
.setIcon(icons
.style_dialog_discard())
212 self
.unstage_selected_action
= add_action(
213 self
, N_('Unstage From Commit'), cmds
.run(cmds
.UnstageSelected
, context
)
215 self
.unstage_selected_action
.setIcon(icons
.remove())
217 self
.show_diffstat_action
= add_action(
218 self
, N_('Diffstat'), self
.statuswidget
.select_header
, hotkeys
.DIFFSTAT
220 self
.show_diffstat_action
.setIcon(icons
.diff())
222 self
.stage_modified_action
= add_action(
224 N_('Stage Changed Files To Commit'),
225 cmds
.run(cmds
.StageModified
, context
),
226 hotkeys
.STAGE_MODIFIED
,
228 self
.stage_modified_action
.setIcon(icons
.add())
230 self
.stage_untracked_action
= add_action(
232 N_('Stage All Untracked'),
233 cmds
.run(cmds
.StageUntracked
, context
),
234 hotkeys
.STAGE_UNTRACKED
,
236 self
.stage_untracked_action
.setIcon(icons
.add())
238 self
.apply_patches_action
= add_action(
239 self
, N_('Apply Patches...'), partial(patch
.apply_patches
, context
)
241 self
.apply_patches_action
.setIcon(icons
.diff())
243 self
.apply_patches_abort_action
= add_action(
245 N_('Abort Applying Patches...'),
246 cmds
.run(cmds
.AbortApplyPatch
, context
),
248 self
.apply_patches_abort_action
.setIcon(icons
.style_dialog_discard())
249 self
.apply_patches_abort_action
.setToolTip(
250 N_('Abort the current "git am" patch session')
253 self
.apply_patches_continue_action
= add_action(
255 N_('Continue Applying Patches'),
256 cmds
.run(cmds
.ApplyPatchesContinue
, context
),
258 self
.apply_patches_continue_action
.setToolTip(
259 N_('Commit the current state and continue applying patches')
261 self
.apply_patches_continue_action
.setIcon(icons
.commit())
263 self
.apply_patches_skip_action
= add_action(
264 self
, N_('Skip Current Patch'), cmds
.run(cmds
.ApplyPatchesContinue
, context
)
266 self
.apply_patches_skip_action
.setToolTip(
267 N_('Skip applying the current patch and continue applying patches')
269 self
.apply_patches_skip_action
.setIcon(icons
.discard())
271 self
.export_patches_action
= add_action(
273 N_('Export Patches...'),
274 partial(guicmds
.export_patches
, context
),
277 self
.export_patches_action
.setIcon(icons
.save())
279 self
.new_repository_action
= add_action(
280 self
, N_('New Repository...'), partial(guicmds
.open_new_repo
, context
)
282 self
.new_repository_action
.setIcon(icons
.new())
284 self
.new_bare_repository_action
= add_action(
285 self
, N_('New Bare Repository...'), partial(guicmds
.new_bare_repo
, context
)
287 self
.new_bare_repository_action
.setIcon(icons
.new())
289 prefs_func
= partial(
290 prefs_widget
.preferences
, context
, parent
=self
, model
=prefs_model
292 self
.preferences_action
= add_action(
293 self
, N_('Preferences'), prefs_func
, QtGui
.QKeySequence
.Preferences
295 self
.preferences_action
.setIcon(icons
.configure())
297 self
.edit_remotes_action
= add_action(
298 self
, N_('Edit Remotes...'), partial(editremotes
.editor
, context
)
300 self
.edit_remotes_action
.setIcon(icons
.edit())
302 self
.rescan_action
= add_action(
305 cmds
.run(cmds
.Refresh
, context
),
306 *hotkeys
.REFRESH_HOTKEYS
308 self
.rescan_action
.setIcon(icons
.sync())
310 self
.find_files_action
= add_action(
313 partial(finder
.finder
, context
),
316 self
.find_files_action
.setIcon(icons
.search())
318 self
.browse_recently_modified_action
= add_action(
320 N_('Recently Modified Files...'),
321 partial(recent
.browse_recent_files
, context
),
322 hotkeys
.EDIT_SECONDARY
,
324 self
.browse_recently_modified_action
.setIcon(icons
.directory())
326 self
.cherry_pick_action
= add_action(
328 N_('Cherry-Pick...'),
329 partial(guicmds
.cherry_pick
, context
),
332 self
.cherry_pick_action
.setIcon(icons
.cherry_pick())
333 self
.cherry_pick_abort_action
= add_action(
334 self
, N_('Abort Cherry-Pick...'), cmds
.run(cmds
.AbortCherryPick
, context
)
336 self
.cherry_pick_abort_action
.setIcon(icons
.style_dialog_discard())
338 self
.load_commitmsg_action
= add_action(
339 self
, N_('Load Commit Message...'), partial(guicmds
.load_commitmsg
, context
)
341 self
.load_commitmsg_action
.setIcon(icons
.file_text())
343 self
.prepare_commitmsg_hook_action
= add_action(
345 N_('Prepare Commit Message'),
346 cmds
.run(cmds
.PrepareCommitMessageHook
, context
),
347 hotkeys
.PREPARE_COMMIT_MESSAGE
,
350 self
.save_tarball_action
= add_action(
351 self
, N_('Save As Tarball/Zip...'), partial(archive
.save_archive
, context
)
353 self
.save_tarball_action
.setIcon(icons
.file_zip())
355 self
.quit_action
= add_action(self
, N_('Quit'), self
.close
, hotkeys
.QUIT
)
357 self
.grep_action
= add_action(
358 self
, N_('Grep'), partial(grep
.grep
, context
), hotkeys
.GREP
360 self
.grep_action
.setIcon(icons
.search())
362 self
.merge_local_action
= add_action(
363 self
, N_('Merge...'), partial(merge
.local_merge
, context
), hotkeys
.MERGE
365 self
.merge_local_action
.setIcon(icons
.merge())
367 self
.merge_abort_action
= add_action(
368 self
, N_('Abort Merge...'), cmds
.run(cmds
.AbortMerge
, context
)
370 self
.merge_abort_action
.setIcon(icons
.style_dialog_discard())
372 self
.update_submodules_action
= add_action(
374 N_('Update All Submodules...'),
375 cmds
.run(cmds
.SubmodulesUpdate
, context
),
377 self
.update_submodules_action
.setIcon(icons
.sync())
379 self
.add_submodule_action
= add_action(
381 N_('Add Submodule...'),
382 partial(submodules
.add_submodule
, context
, parent
=self
),
384 self
.add_submodule_action
.setIcon(icons
.add())
386 self
.fetch_action
= add_action(
387 self
, N_('Fetch...'), partial(remote
.fetch
, context
), hotkeys
.FETCH
389 self
.fetch_action
.setIcon(icons
.download())
391 self
.push_action
= add_action(
392 self
, N_('Push...'), partial(remote
.push
, context
), hotkeys
.PUSH
394 self
.push_action
.setIcon(icons
.push())
396 self
.pull_action
= add_action(
397 self
, N_('Pull...'), partial(remote
.pull
, context
), hotkeys
.PULL
399 self
.pull_action
.setIcon(icons
.pull())
401 self
.open_repo_action
= add_action(
402 self
, N_('Open...'), partial(guicmds
.open_repo
, context
), hotkeys
.OPEN
404 self
.open_repo_action
.setIcon(icons
.folder())
406 self
.open_repo_new_action
= add_action(
408 N_('Open in New Window...'),
409 partial(guicmds
.open_repo_in_new_window
, context
),
411 self
.open_repo_new_action
.setIcon(icons
.folder())
413 self
.stash_action
= add_action(
414 self
, N_('Stash...'), partial(stash
.view
, context
), hotkeys
.STASH
416 self
.stash_action
.setIcon(icons
.commit())
418 self
.reset_soft_action
= add_action(
419 self
, N_('Reset Branch (Soft)'), partial(guicmds
.reset_soft
, context
)
421 self
.reset_soft_action
.setIcon(icons
.style_dialog_reset())
422 self
.reset_soft_action
.setToolTip(cmds
.ResetSoft
.tooltip('<commit>'))
424 self
.reset_mixed_action
= add_action(
426 N_('Reset Branch and Stage (Mixed)'),
427 partial(guicmds
.reset_mixed
, context
),
429 self
.reset_mixed_action
.setIcon(icons
.style_dialog_reset())
430 self
.reset_mixed_action
.setToolTip(cmds
.ResetMixed
.tooltip('<commit>'))
432 self
.reset_keep_action
= add_action(
434 N_('Restore Worktree and Reset All (Keep Unstaged Changes)'),
435 partial(guicmds
.reset_keep
, context
),
437 self
.reset_keep_action
.setIcon(icons
.style_dialog_reset())
438 self
.reset_keep_action
.setToolTip(cmds
.ResetKeep
.tooltip('<commit>'))
440 self
.reset_merge_action
= add_action(
442 N_('Restore Worktree and Reset All (Merge)'),
443 partial(guicmds
.reset_merge
, context
),
445 self
.reset_merge_action
.setIcon(icons
.style_dialog_reset())
446 self
.reset_merge_action
.setToolTip(cmds
.ResetMerge
.tooltip('<commit>'))
448 self
.reset_hard_action
= add_action(
450 N_('Restore Worktree and Reset All (Hard)'),
451 partial(guicmds
.reset_hard
, context
),
453 self
.reset_hard_action
.setIcon(icons
.style_dialog_reset())
454 self
.reset_hard_action
.setToolTip(cmds
.ResetHard
.tooltip('<commit>'))
456 self
.restore_worktree_action
= add_action(
457 self
, N_('Restore Worktree'), partial(guicmds
.restore_worktree
, context
)
459 self
.restore_worktree_action
.setIcon(icons
.edit())
460 self
.restore_worktree_action
.setToolTip(
461 cmds
.RestoreWorktree
.tooltip('<commit>')
464 self
.clone_repo_action
= add_action(
465 self
, N_('Clone...'), partial(clone
.clone
, context
)
467 self
.clone_repo_action
.setIcon(icons
.repo())
469 self
.help_docs_action
= add_action(
472 resources
.show_html_docs
,
473 QtGui
.QKeySequence
.HelpContents
,
476 self
.help_shortcuts_action
= add_action(
477 self
, N_('Keyboard Shortcuts'), about
.show_shortcuts
, hotkeys
.QUESTION
480 self
.visualize_current_action
= add_action(
482 N_('Visualize Current Branch...'),
483 cmds
.run(cmds
.VisualizeCurrent
, context
),
485 self
.visualize_current_action
.setIcon(icons
.visualize())
487 self
.visualize_all_action
= add_action(
488 self
, N_('Visualize All Branches...'), cmds
.run(cmds
.VisualizeAll
, context
)
490 self
.visualize_all_action
.setIcon(icons
.visualize())
492 self
.search_commits_action
= add_action(
493 self
, N_('Search...'), partial(search
.search
, context
)
495 self
.search_commits_action
.setIcon(icons
.search())
497 self
.browse_branch_action
= add_action(
499 N_('Browse Current Branch...'),
500 partial(guicmds
.browse_current
, context
),
502 self
.browse_branch_action
.setIcon(icons
.directory())
504 self
.browse_other_branch_action
= add_action(
505 self
, N_('Browse Other Branch...'), partial(guicmds
.browse_other
, context
)
507 self
.browse_other_branch_action
.setIcon(icons
.directory())
509 self
.load_commitmsg_template_action
= add_action(
511 N_('Get Commit Message Template'),
512 cmds
.run(cmds
.LoadCommitMessageFromTemplate
, context
),
514 self
.load_commitmsg_template_action
.setIcon(icons
.style_dialog_apply())
516 self
.help_about_action
= add_action(
517 self
, N_('About'), partial(about
.about_dialog
, context
)
520 self
.diff_against_commit_action
= add_action(
522 N_('Against Commit... (Diff Mode)'),
523 partial(guicmds
.diff_against_commit
, context
),
525 self
.diff_against_commit_action
.setIcon(icons
.compare())
527 self
.exit_diff_mode_action
= add_action(
528 self
, N_('Exit Diff Mode'), cmds
.run(cmds
.ResetMode
, context
)
530 self
.exit_diff_mode_action
.setIcon(icons
.compare())
532 self
.diff_expression_action
= add_action(
533 self
, N_('Expression...'), partial(guicmds
.diff_expression
, context
)
535 self
.diff_expression_action
.setIcon(icons
.compare())
537 self
.branch_compare_action
= add_action(
538 self
, N_('Branches...'), partial(compare
.compare_branches
, context
)
540 self
.branch_compare_action
.setIcon(icons
.compare())
542 self
.create_tag_action
= add_action(
545 partial(createtag
.create_tag
, context
),
547 self
.create_tag_action
.setIcon(icons
.tag())
549 self
.create_branch_action
= add_action(
552 partial(createbranch
.create_new_branch
, context
),
555 self
.create_branch_action
.setIcon(icons
.branch())
557 self
.delete_branch_action
= add_action(
558 self
, N_('Delete...'), partial(guicmds
.delete_branch
, context
)
560 self
.delete_branch_action
.setIcon(icons
.discard())
562 self
.delete_remote_branch_action
= add_action(
564 N_('Delete Remote Branch...'),
565 partial(guicmds
.delete_remote_branch
, context
),
567 self
.delete_remote_branch_action
.setIcon(icons
.discard())
569 self
.rename_branch_action
= add_action(
570 self
, N_('Rename Branch...'), partial(guicmds
.rename_branch
, context
)
572 self
.rename_branch_action
.setIcon(icons
.edit())
574 self
.checkout_branch_action
= add_action(
577 partial(guicmds
.checkout_branch
, context
),
580 self
.checkout_branch_action
.setIcon(icons
.branch())
582 self
.branch_review_action
= add_action(
583 self
, N_('Review...'), partial(guicmds
.review_branch
, context
)
585 self
.branch_review_action
.setIcon(icons
.compare())
587 self
.browse_action
= add_action(
588 self
, N_('File Browser...'), partial(browse
.worktree_browser
, context
)
590 self
.browse_action
.setIcon(icons
.cola())
592 self
.dag_action
= add_action(self
, N_('DAG...'), self
.git_dag
)
593 self
.dag_action
.setIcon(icons
.cola())
595 self
.rebase_start_action
= add_action(
597 N_('Start Interactive Rebase...'),
598 cmds
.run(cmds
.Rebase
, context
),
599 hotkeys
.REBASE_START_AND_CONTINUE
,
601 self
.rebase_start_action
.setIcon(icons
.play())
603 self
.rebase_edit_todo_action
= add_action(
604 self
, N_('Edit...'), cmds
.run(cmds
.RebaseEditTodo
, context
)
606 self
.rebase_edit_todo_action
.setIcon(icons
.edit())
608 self
.rebase_continue_action
= add_action(
611 cmds
.run(cmds
.RebaseContinue
, context
),
612 hotkeys
.REBASE_START_AND_CONTINUE
,
614 self
.rebase_continue_action
.setIcon(icons
.play())
616 self
.rebase_skip_action
= add_action(
617 self
, N_('Skip Current Patch'), cmds
.run(cmds
.RebaseSkip
, context
)
619 self
.rebase_skip_action
.setIcon(icons
.delete())
621 self
.rebase_abort_action
= add_action(
622 self
, N_('Abort'), cmds
.run(cmds
.RebaseAbort
, context
)
624 self
.rebase_abort_action
.setIcon(icons
.close())
626 # For "Start Rebase" only, reverse the first argument to setEnabled()
627 # so that we can operate on it as a group.
628 # We can do this because can_rebase == not is_rebasing
629 self
.rebase_start_action_proxy
= utils
.Proxy(
630 self
.rebase_start_action
,
631 setEnabled
=lambda x
: self
.rebase_start_action
.setEnabled(not x
),
634 self
.rebase_group
= utils
.Group(
635 self
.rebase_start_action_proxy
,
636 self
.rebase_edit_todo_action
,
637 self
.rebase_continue_action
,
638 self
.rebase_skip_action
,
639 self
.rebase_abort_action
,
642 self
.annex_init_action
= qtutils
.add_action(
643 self
, N_('Initialize Git Annex'), cmds
.run(cmds
.AnnexInit
, context
)
646 self
.lfs_init_action
= qtutils
.add_action(
647 self
, N_('Initialize Git LFS'), cmds
.run(cmds
.LFSInstall
, context
)
650 self
.lock_layout_action
= add_action_bool(
651 self
, N_('Lock Layout'), self
.set_lock_layout
, False
654 self
.reset_layout_action
= add_action(
655 self
, N_('Reset Layout'), self
.reset_layout
658 self
.quick_repository_search
= add_action(
661 lambda: guicmds
.open_quick_repo_search(self
.context
, parent
=self
),
662 hotkeys
.OPEN_REPO_SEARCH
,
664 self
.quick_repository_search
.setIcon(icons
.search())
666 self
.terminal_action
= common
.terminal_action(
667 context
, self
, hotkey
=hotkeys
.TERMINAL
670 # Create the application menu
671 self
.menubar
= QtWidgets
.QMenuBar(self
)
672 self
.setMenuBar(self
.menubar
)
675 add_menu
= qtutils
.add_menu
676 self
.file_menu
= add_menu(N_('&File'), self
.menubar
)
677 self
.file_menu
.addAction(self
.quick_repository_search
)
678 # File->Open Recent menu
679 self
.open_recent_menu
= self
.file_menu
.addMenu(N_('Open Recent'))
680 self
.open_recent_menu
.setIcon(icons
.folder())
681 self
.file_menu
.addAction(self
.open_repo_action
)
682 self
.file_menu
.addAction(self
.open_repo_new_action
)
683 self
.file_menu
.addSeparator()
684 self
.file_menu
.addAction(self
.new_repository_action
)
685 self
.file_menu
.addAction(self
.new_bare_repository_action
)
686 self
.file_menu
.addAction(self
.clone_repo_action
)
687 self
.file_menu
.addSeparator()
688 self
.file_menu
.addAction(self
.rescan_action
)
689 self
.file_menu
.addAction(self
.find_files_action
)
690 self
.file_menu
.addAction(self
.edit_remotes_action
)
691 self
.file_menu
.addAction(self
.browse_recently_modified_action
)
692 self
.file_menu
.addSeparator()
693 self
.file_menu
.addAction(self
.save_tarball_action
)
695 self
.patches_menu
= self
.file_menu
.addMenu(N_('Patches'))
696 self
.patches_menu
.setIcon(icons
.diff())
697 self
.patches_menu
.addAction(self
.export_patches_action
)
698 self
.patches_menu
.addAction(self
.apply_patches_action
)
699 self
.patches_menu
.addAction(self
.apply_patches_continue_action
)
700 self
.patches_menu
.addAction(self
.apply_patches_skip_action
)
701 self
.patches_menu
.addAction(self
.apply_patches_abort_action
)
703 # Git Annex / Git LFS
704 annex
= core
.find_executable('git-annex')
705 lfs
= core
.find_executable('git-lfs')
707 self
.file_menu
.addSeparator()
709 self
.file_menu
.addAction(self
.annex_init_action
)
711 self
.file_menu
.addAction(self
.lfs_init_action
)
713 self
.file_menu
.addSeparator()
714 self
.file_menu
.addAction(self
.preferences_action
)
715 self
.file_menu
.addAction(self
.quit_action
)
718 self
.edit_proxy
= edit_proxy
= FocusProxy(
719 editor
, editor
.summary
, editor
.description
727 bookmarkswidget
.tree
,
730 select_widgets
= copy_widgets
+ (self
.statuswidget
.tree
,)
731 edit_proxy
.override('copy', copy_widgets
)
732 edit_proxy
.override('selectAll', select_widgets
)
734 edit_menu
= self
.edit_menu
= add_menu(N_('&Edit'), self
.menubar
)
735 undo
= add_action(edit_menu
, N_('Undo'), edit_proxy
.undo
, hotkeys
.UNDO
)
736 undo
.setIcon(icons
.undo())
737 redo
= add_action(edit_menu
, N_('Redo'), edit_proxy
.redo
, hotkeys
.REDO
)
738 redo
.setIcon(icons
.redo())
739 edit_menu
.addSeparator()
740 cut
= add_action(edit_menu
, N_('Cut'), edit_proxy
.cut
, hotkeys
.CUT
)
741 cut
.setIcon(icons
.cut())
742 copy
= add_action(edit_menu
, N_('Copy'), edit_proxy
.copy
, hotkeys
.COPY
)
743 copy
.setIcon(icons
.copy())
744 paste
= add_action(edit_menu
, N_('Paste'), edit_proxy
.paste
, hotkeys
.PASTE
)
745 paste
.setIcon(icons
.paste())
746 delete
= add_action(edit_menu
, N_('Delete'), edit_proxy
.delete
, hotkeys
.DELETE
)
747 delete
.setIcon(icons
.delete())
748 edit_menu
.addSeparator()
749 select_all
= add_action(
750 edit_menu
, N_('Select All'), edit_proxy
.selectAll
, hotkeys
.SELECT_ALL
752 select_all
.setIcon(icons
.select_all())
753 edit_menu
.addSeparator()
754 qtutils
.add_menu_actions(edit_menu
, self
.commiteditor
.menu_actions
)
757 self
.actions_menu
= add_menu(N_('Actions'), self
.menubar
)
758 if self
.terminal_action
is not None:
759 self
.actions_menu
.addAction(self
.terminal_action
)
760 self
.actions_menu
.addAction(self
.fetch_action
)
761 self
.actions_menu
.addAction(self
.push_action
)
762 self
.actions_menu
.addAction(self
.pull_action
)
763 self
.actions_menu
.addAction(self
.stash_action
)
764 self
.actions_menu
.addSeparator()
765 self
.actions_menu
.addAction(self
.create_tag_action
)
766 self
.actions_menu
.addAction(self
.cherry_pick_action
)
767 self
.actions_menu
.addAction(self
.cherry_pick_abort_action
)
768 self
.actions_menu
.addAction(self
.merge_local_action
)
769 self
.actions_menu
.addAction(self
.merge_abort_action
)
770 self
.actions_menu
.addSeparator()
771 self
.actions_menu
.addAction(self
.update_submodules_action
)
772 self
.actions_menu
.addAction(self
.add_submodule_action
)
773 self
.actions_menu
.addSeparator()
774 self
.actions_menu
.addAction(self
.grep_action
)
775 self
.actions_menu
.addAction(self
.search_commits_action
)
778 self
.commit_menu
= add_menu(N_('Commit@@verb'), self
.menubar
)
779 self
.commit_menu
.setTitle(N_('Commit@@verb'))
780 self
.commit_menu
.addAction(self
.commiteditor
.commit_action
)
781 self
.commit_menu
.addAction(self
.commit_amend_action
)
782 self
.commit_menu
.addAction(self
.undo_commit_action
)
783 self
.commit_menu
.addSeparator()
784 self
.commit_menu
.addAction(self
.statuswidget
.tree
.process_selection_action
)
785 self
.commit_menu
.addAction(self
.statuswidget
.tree
.stage_or_unstage_all_action
)
786 self
.commit_menu
.addAction(self
.stage_modified_action
)
787 self
.commit_menu
.addAction(self
.stage_untracked_action
)
788 self
.commit_menu
.addSeparator()
789 self
.commit_menu
.addAction(self
.unstage_all_action
)
790 self
.commit_menu
.addAction(self
.unstage_selected_action
)
791 self
.commit_menu
.addSeparator()
792 self
.commit_menu
.addAction(self
.load_commitmsg_action
)
793 self
.commit_menu
.addAction(self
.load_commitmsg_template_action
)
794 self
.commit_menu
.addAction(self
.prepare_commitmsg_hook_action
)
797 self
.diff_menu
= add_menu(N_('Diff'), self
.menubar
)
798 self
.diff_menu
.addAction(self
.diff_expression_action
)
799 self
.diff_menu
.addAction(self
.branch_compare_action
)
800 self
.diff_menu
.addAction(self
.show_diffstat_action
)
801 self
.diff_menu
.addSeparator()
802 self
.diff_menu
.addAction(self
.diff_against_commit_action
)
803 self
.diff_menu
.addAction(self
.exit_diff_mode_action
)
806 self
.branch_menu
= add_menu(N_('Branch'), self
.menubar
)
807 self
.branch_menu
.addAction(self
.branch_review_action
)
808 self
.branch_menu
.addSeparator()
809 self
.branch_menu
.addAction(self
.create_branch_action
)
810 self
.branch_menu
.addAction(self
.checkout_branch_action
)
811 self
.branch_menu
.addAction(self
.delete_branch_action
)
812 self
.branch_menu
.addAction(self
.delete_remote_branch_action
)
813 self
.branch_menu
.addAction(self
.rename_branch_action
)
814 self
.branch_menu
.addSeparator()
815 self
.branch_menu
.addAction(self
.browse_branch_action
)
816 self
.branch_menu
.addAction(self
.browse_other_branch_action
)
817 self
.branch_menu
.addSeparator()
818 self
.branch_menu
.addAction(self
.visualize_current_action
)
819 self
.branch_menu
.addAction(self
.visualize_all_action
)
822 self
.rebase_menu
= add_menu(N_('Rebase'), self
.menubar
)
823 self
.rebase_menu
.addAction(self
.rebase_start_action
)
824 self
.rebase_menu
.addAction(self
.rebase_edit_todo_action
)
825 self
.rebase_menu
.addSeparator()
826 self
.rebase_menu
.addAction(self
.rebase_continue_action
)
827 self
.rebase_menu
.addAction(self
.rebase_skip_action
)
828 self
.rebase_menu
.addSeparator()
829 self
.rebase_menu
.addAction(self
.rebase_abort_action
)
832 self
.reset_menu
= add_menu(N_('Reset'), self
.menubar
)
833 self
.reset_menu
.addAction(self
.unstage_all_action
)
834 self
.reset_menu
.addAction(self
.undo_commit_action
)
835 self
.reset_menu
.addSeparator()
836 self
.reset_menu
.addAction(self
.reset_soft_action
)
837 self
.reset_menu
.addAction(self
.reset_mixed_action
)
838 self
.reset_menu
.addAction(self
.restore_worktree_action
)
839 self
.reset_menu
.addSeparator()
840 self
.reset_menu
.addAction(self
.reset_keep_action
)
841 self
.reset_menu
.addAction(self
.reset_merge_action
)
842 self
.reset_menu
.addAction(self
.reset_hard_action
)
845 self
.view_menu
= add_menu(N_('View'), self
.menubar
)
846 # pylint: disable=no-member
847 self
.view_menu
.aboutToShow
.connect(lambda: self
.build_view_menu(self
.view_menu
))
848 self
.setup_dockwidget_view_menu()
849 if utils
.is_darwin():
850 # The native macOS menu doesn't show empty entries.
851 self
.build_view_menu(self
.view_menu
)
854 self
.help_menu
= add_menu(N_('Help'), self
.menubar
)
855 self
.help_menu
.addAction(self
.help_docs_action
)
856 self
.help_menu
.addAction(self
.help_shortcuts_action
)
857 self
.help_menu
.addAction(self
.help_about_action
)
859 # Arrange dock widgets
860 bottom
= Qt
.BottomDockWidgetArea
861 top
= Qt
.TopDockWidgetArea
863 self
.addDockWidget(top
, self
.statusdock
)
864 self
.addDockWidget(top
, self
.commitdock
)
865 if self
.browser_dockable
:
866 self
.addDockWidget(top
, self
.browserdock
)
867 self
.tabifyDockWidget(self
.browserdock
, self
.commitdock
)
869 self
.addDockWidget(top
, self
.branchdock
)
870 self
.addDockWidget(top
, self
.submodulesdock
)
871 self
.addDockWidget(top
, self
.bookmarksdock
)
872 self
.addDockWidget(top
, self
.recentdock
)
874 self
.tabifyDockWidget(self
.branchdock
, self
.submodulesdock
)
875 self
.tabifyDockWidget(self
.submodulesdock
, self
.bookmarksdock
)
876 self
.tabifyDockWidget(self
.bookmarksdock
, self
.recentdock
)
877 self
.branchdock
.raise_()
879 self
.addDockWidget(bottom
, self
.diffdock
)
880 self
.addDockWidget(bottom
, self
.actionsdock
)
881 self
.addDockWidget(bottom
, self
.logdock
)
882 self
.tabifyDockWidget(self
.actionsdock
, self
.logdock
)
884 # Listen for model notifications
885 self
.model
.updated
.connect(self
.refresh
, type=Qt
.QueuedConnection
)
886 self
.model
.mode_changed
.connect(
887 lambda mode
: self
.refresh(), type=Qt
.QueuedConnection
890 prefs_model
.config_updated
.connect(self
._config
_updated
)
892 # Set a default value
893 self
.show_cursor_position(1, 0)
895 self
.commit_menu
.aboutToShow
.connect(self
.update_menu_actions
)
896 self
.open_recent_menu
.aboutToShow
.connect(self
.build_recent_menu
)
897 self
.commiteditor
.cursor_changed
.connect(self
.show_cursor_position
)
899 self
.diffeditor
.options_changed
.connect(self
.statuswidget
.refresh
)
900 self
.diffeditor
.up
.connect(self
.statuswidget
.move_up
)
901 self
.diffeditor
.down
.connect(self
.statuswidget
.move_down
)
903 self
.commiteditor
.up
.connect(self
.statuswidget
.move_up
)
904 self
.commiteditor
.down
.connect(self
.statuswidget
.move_down
)
906 self
.config_actions_changed
.connect(
907 lambda names_and_shortcuts
: _install_config_actions(
912 type=Qt
.QueuedConnection
,
914 self
.init_state(context
.settings
, self
.set_initial_size
)
916 # Route command output here
917 Interaction
.log_status
= self
.logwidget
.log_status
918 Interaction
.log
= self
.logwidget
.log
919 # Focus the status widget; this must be deferred
920 QtCore
.QTimer
.singleShot(0, self
.initialize
)
922 def initialize(self
):
923 context
= self
.context
924 git_version
= version
.git_version_str(context
)
928 git_version
+ '\n' + N_('git cola version %s') % version
.version()
932 error_msg
= N_('error: unable to execute git')
933 Interaction
.log(error_msg
)
936 self
.statuswidget
.setFocus()
938 title
= N_('error: unable to execute git')
942 details
= git
.win32_git_error_hint()
943 Interaction
.critical(title
, message
=msg
, details
=details
)
944 self
.context
.app
.exit(2)
946 def set_initial_size(self
):
947 # Default size; this is thrown out when save/restore is used
948 width
, height
= qtutils
.desktop_size()
949 self
.resize((width
* 3) // 4, height
)
950 self
.statuswidget
.set_initial_size()
951 self
.commiteditor
.set_initial_size()
953 def set_filter(self
, txt
):
954 self
.statuswidget
.set_filter(txt
)
957 def closeEvent(self
, event
):
958 """Save state in the settings"""
959 commit_msg
= self
.commiteditor
.commit_message(raw
=True)
960 self
.model
.save_commitmsg(msg
=commit_msg
)
961 for browser
in list(self
.context
.browser_windows
):
963 standard
.MainWindow
.closeEvent(self
, event
)
965 def create_view_menu(self
):
966 menu
= qtutils
.create_menu(N_('View'), self
)
967 self
.build_view_menu(menu
)
970 def build_view_menu(self
, menu
):
972 menu
.addAction(self
.browse_action
)
973 menu
.addAction(self
.dag_action
)
976 popup_menu
= self
.createPopupMenu()
977 for menu_action
in popup_menu
.actions():
978 menu_action
.setParent(menu
)
979 menu
.addAction(menu_action
)
981 context
= self
.context
982 menu_action
= menu
.addAction(
983 N_('New Toolbar'), partial(toolbar
.add_toolbar
, context
, self
)
985 menu_action
.setIcon(icons
.add())
999 if self
.browser_dockable
:
1000 dockwidgets
.append(self
.browserdock
)
1002 for dockwidget
in dockwidgets
:
1003 # Associate the action with the shortcut
1004 toggleview
= dockwidget
.toggleViewAction()
1005 menu
.addAction(toggleview
)
1008 menu
.addAction(self
.lock_layout_action
)
1009 menu
.addAction(self
.reset_layout_action
)
1013 def contextMenuEvent(self
, event
):
1014 menu
= self
.create_view_menu()
1015 menu
.exec_(event
.globalPos())
1017 def build_recent_menu(self
):
1019 context
= self
.context
1020 settings
= context
.settings
1022 menu
= self
.open_recent_menu
1024 worktree
= context
.git
.worktree()
1026 for entry
in settings
.recent
:
1027 directory
= entry
['path']
1028 if directory
== worktree
:
1029 # Omit the current worktree from the "Open Recent" menu.
1031 name
= entry
['name']
1032 text
= '%s %s %s' % (name
, uchr(0x2192), directory
)
1033 menu
.addAction(text
, cmds
.run(cmd
, context
, directory
))
1036 mode
= property(lambda self
: self
.model
.mode
)
1038 def _config_updated(self
, _source
, config
, value
):
1039 if config
== prefs
.FONTDIFF
:
1041 font
= QtGui
.QFont()
1042 if not font
.fromString(value
):
1044 self
.logwidget
.setFont(font
)
1045 self
.diffeditor
.setFont(font
)
1046 self
.commiteditor
.setFont(font
)
1048 elif config
== prefs
.TABWIDTH
:
1049 # This can be set locally or globally, so we have to use the
1050 # effective value otherwise we'll update when we shouldn't.
1051 # For example, if this value is overridden locally, and the
1052 # global value is tweaked, we should not update.
1053 value
= prefs
.tabwidth(self
.context
)
1054 self
.diffeditor
.set_tabwidth(value
)
1055 self
.commiteditor
.set_tabwidth(value
)
1057 elif config
== prefs
.EXPANDTAB
:
1058 self
.commiteditor
.set_expandtab(value
)
1060 elif config
== prefs
.LINEBREAK
:
1061 # enables automatic line breaks
1062 self
.commiteditor
.set_linebreak(value
)
1064 elif config
== prefs
.SORT_BOOKMARKS
:
1065 self
.bookmarksdock
.widget().reload_bookmarks()
1067 elif config
== prefs
.TEXTWIDTH
:
1068 # Use the effective value for the same reason as tabwidth.
1069 value
= prefs
.textwidth(self
.context
)
1070 self
.commiteditor
.set_textwidth(value
)
1072 elif config
== prefs
.SHOW_PATH
:
1073 # the path in the window title was toggled
1074 self
.refresh_window_title()
1076 def start(self
, context
):
1077 """Do the expensive "get_config_actions()" call in the background"""
1078 # Install .git-config-defined actions
1079 task
= qtutils
.SimpleTask(self
.get_config_actions
)
1080 context
.runtask
.start(task
)
1082 def get_config_actions(self
):
1083 actions
= cfgactions
.get_config_actions(self
.context
)
1084 self
.config_actions_changed
.emit(actions
)
1087 """Update the title with the current branch and directory name."""
1088 curbranch
= self
.model
.currentbranch
1089 is_merging
= self
.model
.is_merging
1090 is_rebasing
= self
.model
.is_rebasing
1091 is_applying_patch
= self
.model
.is_applying_patch
1092 is_cherry_picking
= self
.model
.is_rebasing
1094 curdir
= core
.getcwd()
1095 msg
= N_('Repository: %s') % curdir
1097 msg
+= N_('Branch: %s') % curbranch
1102 'This repository is currently being rebased.\n'
1103 'Resolve conflicts, commit changes, and run:\n'
1104 ' Rebase > Continue'
1106 elif is_applying_patch
:
1109 'This repository has unresolved conflicts after applying a patch.\n'
1110 'Resolve conflicts and commit changes.'
1112 elif is_cherry_picking
:
1115 'This repository is in the middle of a cherry-pick.\n'
1116 'Resolve conflicts and commit changes.'
1121 'This repository is in the middle of a merge.\n'
1122 'Resolve conflicts and commit changes.'
1125 self
.refresh_window_title()
1127 if self
.mode
== self
.model
.mode_amend
:
1128 self
.commit_amend_action
.setChecked(True)
1130 self
.commit_amend_action
.setChecked(False)
1132 self
.commitdock
.setToolTip(msg
)
1134 self
.actionswidget
.set_mode(self
.mode
)
1135 self
.commiteditor
.set_mode(self
.mode
)
1136 self
.statuswidget
.set_mode(self
.mode
)
1138 self
.update_actions()
1140 def refresh_window_title(self
):
1141 """Refresh the window title when state changes"""
1144 project
= self
.model
.project
1145 curbranch
= self
.model
.currentbranch
1146 is_cherry_picking
= self
.model
.is_cherry_picking
1147 is_merging
= self
.model
.is_merging
1148 is_rebasing
= self
.model
.is_rebasing
1149 is_applying_patch
= self
.model
.is_applying_patch
1150 is_diff_mode
= self
.model
.is_diff_mode()
1151 is_amend_mode
= self
.mode
== self
.model
.mode_amend
1157 alerts
.append(N_('Amending'))
1159 alerts
.append(N_('Diff Mode'))
1160 elif is_cherry_picking
:
1161 alerts
.append(N_('Cherry-picking'))
1163 alerts
.append(N_('Merging'))
1165 alerts
.append(N_('Rebasing'))
1166 elif is_applying_patch
:
1167 alerts
.append(N_('Applying Patch'))
1170 alert_text
= (prefix
+ ' %s ' + suffix
+ ' ') % ', '.join(alerts
)
1174 if self
.model
.cfg
.get(prefs
.SHOW_PATH
, True):
1175 path_text
= self
.git
.worktree()
1179 title
= '%s: %s %s%s' % (project
, curbranch
, alert_text
, path_text
)
1180 self
.setWindowTitle(title
)
1182 def update_actions(self
):
1183 is_rebasing
= self
.model
.is_rebasing
1184 self
.rebase_group
.setEnabled(is_rebasing
)
1186 enabled
= not self
.model
.is_empty_repository()
1187 self
.rename_branch_action
.setEnabled(enabled
)
1188 self
.delete_branch_action
.setEnabled(enabled
)
1190 self
.annex_init_action
.setEnabled(not self
.model
.annex
)
1191 self
.lfs_init_action
.setEnabled(not self
.model
.lfs
)
1192 self
.merge_abort_action
.setEnabled(self
.model
.is_merging
)
1193 self
.cherry_pick_abort_action
.setEnabled(self
.model
.is_cherry_picking
)
1194 self
.apply_patches_continue_action
.setEnabled(self
.model
.is_applying_patch
)
1195 self
.apply_patches_skip_action
.setEnabled(self
.model
.is_applying_patch
)
1196 self
.apply_patches_abort_action
.setEnabled(self
.model
.is_applying_patch
)
1198 diff_mode
= self
.model
.mode
== self
.model
.mode_diff
1199 self
.exit_diff_mode_action
.setEnabled(diff_mode
)
1201 def update_menu_actions(self
):
1202 # Enable the Prepare Commit Message action if the hook exists
1203 hook
= gitcmds
.prepare_commit_message_hook(self
.context
)
1204 enabled
= os
.path
.exists(hook
)
1205 self
.prepare_commitmsg_hook_action
.setEnabled(enabled
)
1207 def export_state(self
):
1208 state
= standard
.MainWindow
.export_state(self
)
1209 show_status_filter
= self
.statuswidget
.filter_widget
.isVisible()
1210 state
['show_status_filter'] = show_status_filter
1211 state
['toolbars'] = self
.toolbar_state
.export_state()
1212 state
['ref_sort'] = self
.model
.ref_sort
1213 self
.diffviewer
.export_state(state
)
1217 def apply_state(self
, state
):
1218 """Imports data for save/restore"""
1219 base_ok
= standard
.MainWindow
.apply_state(self
, state
)
1220 lock_layout
= state
.get('lock_layout', False)
1221 self
.lock_layout_action
.setChecked(lock_layout
)
1223 show_status_filter
= state
.get('show_status_filter', False)
1224 self
.statuswidget
.filter_widget
.setVisible(show_status_filter
)
1226 toolbars
= state
.get('toolbars', [])
1227 self
.toolbar_state
.apply_state(toolbars
)
1229 sort_key
= state
.get('ref_sort', 0)
1230 self
.model
.set_ref_sort(sort_key
)
1232 diff_ok
= self
.diffviewer
.apply_state(state
)
1233 return base_ok
and diff_ok
1235 def setup_dockwidget_view_menu(self
):
1236 # Hotkeys for toggling the dock widgets
1237 if utils
.is_darwin():
1242 (optkey
+ '+0', self
.logdock
),
1243 (optkey
+ '+1', self
.commitdock
),
1244 (optkey
+ '+2', self
.statusdock
),
1245 (optkey
+ '+3', self
.diffdock
),
1246 (optkey
+ '+4', self
.actionsdock
),
1247 (optkey
+ '+5', self
.bookmarksdock
),
1248 (optkey
+ '+6', self
.recentdock
),
1249 (optkey
+ '+7', self
.branchdock
),
1250 (optkey
+ '+8', self
.submodulesdock
),
1252 for shortcut
, dockwidget
in dockwidgets
:
1253 # Associate the action with the shortcut
1254 toggleview
= dockwidget
.toggleViewAction()
1255 toggleview
.setShortcut('Shift+' + shortcut
)
1257 def showdock(show
, dockwidget
=dockwidget
):
1260 dockwidget
.widget().setFocus()
1264 self
.addAction(toggleview
)
1265 qtutils
.connect_action_bool(toggleview
, showdock
)
1267 # Create a new shortcut Shift+<shortcut> that gives focus
1268 toggleview
= QtWidgets
.QAction(self
)
1269 toggleview
.setShortcut(shortcut
)
1271 def focusdock(dockwidget
=dockwidget
):
1272 focus_dock(dockwidget
)
1274 self
.addAction(toggleview
)
1275 qtutils
.connect_action(toggleview
, focusdock
)
1277 # These widgets warrant home-row hotkey status
1280 'Focus Commit Message',
1281 lambda: focus_dock(self
.commitdock
),
1287 'Focus Status Window',
1288 lambda: focus_dock(self
.statusdock
),
1289 hotkeys
.FOCUS_STATUS
,
1294 'Focus Diff Editor',
1295 lambda: focus_dock(self
.diffdock
),
1300 self
.dag
= dag
.git_dag(self
.context
, existing_view
=self
.dag
)
1302 def show_cursor_position(self
, rows
, cols
):
1303 display_content
= '%02d:%02d' % (rows
, cols
)
1310 background-color: yellow;
1314 background-color: #f83;
1318 background-color: red;
1326 cls
= 'second-warning'
1328 cls
= 'first-warning'
1331 div
= '<div class="%s">%s</div>' % (cls
, display_content
)
1332 self
.position_label
.setText(css
+ div
)
1335 class FocusProxy(object):
1336 """Proxy over child widgets and operate on the focused widget"""
1338 def __init__(self
, *widgets
):
1339 self
.widgets
= widgets
1342 def override(self
, name
, widgets
):
1343 self
.overrides
[name
] = widgets
1345 def focus(self
, name
):
1346 """Return the currently focused widget"""
1347 widgets
= self
.overrides
.get(name
, self
.widgets
)
1348 # The parent must be the parent of all the proxied widgets
1350 # The first widget is used as a fallback
1351 fallback
= widgets
[1]
1352 # We ignore the parent when delegating to child widgets
1353 widgets
= widgets
[1:]
1355 focus
= parent
.focusWidget()
1356 if focus
not in widgets
:
1360 def __getattr__(self
, name
):
1361 """Return a callback that calls a common child method"""
1364 focus
= self
.focus(name
)
1365 func
= getattr(focus
, name
, None)
1372 """Specialized delete() to deal with QLineEdit vs QTextEdit"""
1373 focus
= self
.focus('delete')
1374 if hasattr(focus
, 'del_'):
1376 elif hasattr(focus
, 'textCursor'):
1377 focus
.textCursor().deleteChar()
1380 def show_dock(dockwidget
):
1382 dockwidget
.widget().setFocus()
1385 def focus_dock(dockwidget
):
1386 if get(dockwidget
.toggleViewAction()):
1387 show_dock(dockwidget
)
1389 dockwidget
.toggleViewAction().trigger()
1392 def _install_config_actions(context
, menu
, names_and_shortcuts
):
1393 """Install .gitconfig-defined actions"""
1394 if not names_and_shortcuts
:
1398 for name
, shortcut
in names_and_shortcuts
:
1399 sub_menu
, action_name
= build_menus(name
, menu
, cache
)
1400 callback
= cmds
.run(cmds
.RunConfigAction
, context
, name
)
1401 menu_action
= sub_menu
.addAction(action_name
, callback
)
1403 menu_action
.setShortcut(shortcut
)
1406 def build_menus(name
, menu
, cache
):
1407 """Create a chain of QMenu entries parented under a root QMenu
1409 A name of "a/b/c" create a menu chain of menu -> QMenu("a") -> QMenu("b")
1410 and returns a tuple of (QMenu("b"), "c").
1412 :param name: The full entry path, ex: "a/b/c" where "a/b" is the menu chain.
1413 :param menu: The root menu under which to create the menu chain.
1414 :param cache: A dict cache of previously created menus to avoid duplicates.
1417 # NOTE: utils.split() and friends are used instead of os.path.split() because
1418 # slash '/' is the only supported "<menu>/name" separator. Use of os.path.split()
1419 # would introduce differences in behavior across platforms.
1421 # If the menu_path is empty then no parent menus need to be created.
1422 # The action will be added to the root menu.
1423 menu_path
, text
= utils
.split(utils
.normalize_slash(name
))
1427 # When menu_path contains ex: "a/b" we will create two menus: "a" and "b".
1428 # The root menu is the parent of "a" and "a" is the parent of "b".
1429 # The menu returned to the caller is "b".
1431 # Loop over the individual menu basenames alongside the full subpath returned by
1432 # pathset(). The subpath is a cache key for finding previously created menus.
1433 menu_names
= utils
.splitpath(menu_path
) # ['a', 'b']
1434 menu_pathset
= utils
.pathset(menu_path
) # ['a', 'a/b']
1435 for menu_name
, menu_id
in zip(menu_names
, menu_pathset
):
1437 menu
= cache
[menu_id
]
1439 menu
= cache
[menu_id
] = menu
.addMenu(menu_name
)