commitmsg: move the progress bar into the dock title
[git-cola.git] / cola / widgets / main.py
blob9b5e7e02e2352d68f77ef51600525c588cdeb445
1 """Main UI for authoring commits and other Git Cola interactions"""
2 import os
3 from functools import partial
5 from qtpy import QtCore
6 from qtpy import QtGui
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
13 from ..i18n import N_
14 from ..interaction import Interaction
15 from ..models import prefs
16 from ..qtutils import get
17 from .. import cmds
18 from .. import core
19 from .. import guicmds
20 from .. import git
21 from .. import gitcmds
22 from .. import hotkeys
23 from .. import icons
24 from .. import qtutils
25 from .. import resources
26 from .. import utils
27 from .. import version
28 from . import about
29 from . import action
30 from . import archive
31 from . import bookmarks
32 from . import branch
33 from . import submodules
34 from . import browse
35 from . import cfgactions
36 from . import clone
37 from . import commitmsg
38 from . import common
39 from . import compare
40 from . import createbranch
41 from . import createtag
42 from . import dag
43 from . import defs
44 from . import diff
45 from . import finder
46 from . import editremotes
47 from . import grep
48 from . import log
49 from . import merge
50 from . import prefs as prefs_widget
51 from . import recent
52 from . import remote
53 from . import search
54 from . import standard
55 from . import status
56 from . import stash
57 from . import toolbar
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
70 self.dag = None
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
80 cfg = context.cfg
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
90 # "Actions" widget
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(
99 'Status',
100 N_('Status'),
101 self,
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(
108 'Favorites',
109 N_('Favorites'),
110 self,
111 func=lambda dock: bookmarks.bookmark(context, dock),
113 bookmarkswidget = self.bookmarksdock.widget()
114 qtutils.hide_dock(self.bookmarksdock)
116 self.recentdock = create_dock(
117 'Recent',
118 N_('Recent'),
119 self,
120 func=lambda dock: bookmarks.recent(context, dock),
122 recentwidget = self.recentdock.widget()
123 qtutils.hide_dock(self.recentdock)
124 bookmarkswidget.connect_to(recentwidget)
126 # "Branch" widgets
127 self.branchdock = create_dock(
128 'Branches',
129 N_('Branches'),
130 self,
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(
140 'Submodules',
141 N_('Submodules'),
142 self,
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 text_width = qtutils.text_width(font, '99:999')
156 width = text_width + 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_title_widget(self.commiteditor.commit_progress_bar)
164 titlebar.add_corner_widget(self.position_label)
166 # "Console" widget
167 self.logwidget = log.LogWidget(context)
168 self.logdock = create_dock(
169 'Console', N_('Console'), self, widget=self.logwidget
171 qtutils.hide_dock(self.logdock)
173 # "Diff Viewer" widget
174 self.diffdock = create_dock(
175 'Diff',
176 N_('Diff'),
177 self,
178 func=lambda dock: diff.Viewer(context, parent=dock),
180 self.diffviewer = self.diffdock.widget()
181 self.diffviewer.set_diff_type(self.model.diff_type)
182 self.diffviewer.enable_filename_tracking()
183 self.diffeditor = self.diffviewer.text
184 titlebar = self.diffdock.titleBarWidget()
185 titlebar.add_title_widget(self.diffviewer.filename)
186 titlebar.add_corner_widget(self.diffviewer.options)
188 # All Actions
189 add_action = qtutils.add_action
190 add_action_bool = qtutils.add_action_bool
192 self.commit_amend_action = add_action_bool(
193 self,
194 N_('Amend Last Commit'),
195 partial(cmds.do, cmds.AmendMode, context),
196 False,
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'), 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(
223 self,
224 cmds.StageModified.name(),
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(
231 self,
232 cmds.StageUntracked.name(),
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(diff.apply_patches, context)
241 self.apply_patches_action.setIcon(icons.diff())
243 self.apply_patches_abort_action = qtutils.add_action_with_tooltip(
244 self,
245 N_('Abort Applying Patches...'),
246 N_('Abort the current "git am" patch session'),
247 cmds.run(cmds.AbortApplyPatch, context),
249 self.apply_patches_abort_action.setIcon(icons.style_dialog_discard())
251 self.apply_patches_continue_action = qtutils.add_action_with_tooltip(
252 self,
253 N_('Continue Applying Patches'),
254 N_('Commit the current state and continue applying patches'),
255 cmds.run(cmds.ApplyPatchesContinue, context),
257 self.apply_patches_continue_action.setIcon(icons.commit())
259 self.apply_patches_skip_action = qtutils.add_action_with_tooltip(
260 self,
261 N_('Skip Current Patch'),
262 N_('Skip applying the current patch and continue applying patches'),
263 cmds.run(cmds.ApplyPatchesContinue, context),
265 self.apply_patches_skip_action.setIcon(icons.discard())
267 self.export_patches_action = add_action(
268 self,
269 N_('Export Patches...'),
270 partial(guicmds.export_patches, context),
271 hotkeys.EXPORT,
273 self.export_patches_action.setIcon(icons.save())
275 self.new_repository_action = add_action(
276 self, N_('New Repository...'), partial(guicmds.open_new_repo, context)
278 self.new_repository_action.setIcon(icons.new())
280 self.new_bare_repository_action = add_action(
281 self, N_('New Bare Repository...'), partial(guicmds.new_bare_repo, context)
283 self.new_bare_repository_action.setIcon(icons.new())
285 prefs_func = partial(
286 prefs_widget.preferences, context, parent=self, model=prefs_model
288 self.preferences_action = add_action(
289 self, N_('Preferences'), prefs_func, QtGui.QKeySequence.Preferences
291 self.preferences_action.setIcon(icons.configure())
293 self.edit_remotes_action = add_action(
294 self, N_('Edit Remotes...'), partial(editremotes.editor, context)
296 self.edit_remotes_action.setIcon(icons.edit())
298 self.rescan_action = add_action(
299 self,
300 cmds.Refresh.name(),
301 cmds.run(cmds.Refresh, context),
302 *hotkeys.REFRESH_HOTKEYS,
304 self.rescan_action.setIcon(icons.sync())
306 self.find_files_action = add_action(
307 self,
308 N_('Find Files'),
309 partial(finder.finder, context),
310 hotkeys.FINDER,
312 self.find_files_action.setIcon(icons.search())
314 self.browse_recently_modified_action = add_action(
315 self,
316 N_('Recently Modified Files...'),
317 partial(recent.browse_recent_files, context),
318 hotkeys.EDIT_SECONDARY,
320 self.browse_recently_modified_action.setIcon(icons.directory())
322 self.cherry_pick_action = add_action(
323 self,
324 N_('Cherry-Pick...'),
325 partial(guicmds.cherry_pick, context),
326 hotkeys.CHERRY_PICK,
328 self.cherry_pick_action.setIcon(icons.cherry_pick())
329 self.cherry_pick_abort_action = add_action(
330 self, N_('Abort Cherry-Pick...'), cmds.run(cmds.AbortCherryPick, context)
332 self.cherry_pick_abort_action.setIcon(icons.style_dialog_discard())
334 self.load_commitmsg_action = add_action(
335 self, N_('Load Commit Message...'), partial(guicmds.load_commitmsg, context)
337 self.load_commitmsg_action.setIcon(icons.file_text())
339 self.prepare_commitmsg_hook_action = add_action(
340 self,
341 N_('Prepare Commit Message'),
342 cmds.run(cmds.PrepareCommitMessageHook, context),
343 hotkeys.PREPARE_COMMIT_MESSAGE,
346 self.save_tarball_action = add_action(
347 self, N_('Save As Tarball/Zip...'), partial(archive.save_archive, context)
349 self.save_tarball_action.setIcon(icons.file_zip())
351 self.quit_action = add_action(self, N_('Quit'), self.close, hotkeys.QUIT)
353 self.grep_action = add_action(
354 self, N_('Grep'), partial(grep.grep, context), hotkeys.GREP
356 self.grep_action.setIcon(icons.search())
358 self.merge_local_action = add_action(
359 self, N_('Merge...'), partial(merge.local_merge, context), hotkeys.MERGE
361 self.merge_local_action.setIcon(icons.merge())
363 self.merge_abort_action = add_action(
364 self, N_('Abort Merge...'), cmds.run(cmds.AbortMerge, context)
366 self.merge_abort_action.setIcon(icons.style_dialog_discard())
368 self.update_submodules_action = add_action(
369 self,
370 N_('Update All Submodules...'),
371 cmds.run(cmds.SubmodulesUpdate, context),
373 self.update_submodules_action.setIcon(icons.sync())
375 self.add_submodule_action = add_action(
376 self,
377 N_('Add Submodule...'),
378 partial(submodules.add_submodule, context, parent=self),
380 self.add_submodule_action.setIcon(icons.add())
382 self.fetch_action = qtutils.add_action_with_tooltip(
383 self,
384 N_('Fetch...'),
385 N_('Fetch from one or more remotes using "git fetch"'),
386 partial(remote.fetch, context),
387 hotkeys.FETCH,
389 self.fetch_action.setIcon(icons.download())
391 self.push_action = qtutils.add_action_with_tooltip(
392 self,
393 N_('Push...'),
394 N_('Push to one or more remotes using "git push"'),
395 partial(remote.push, context),
396 hotkeys.PUSH,
398 self.push_action.setIcon(icons.push())
400 self.pull_action = qtutils.add_action_with_tooltip(
401 self,
402 N_('Pull...'),
403 N_('Integrate changes using "git pull"'),
404 partial(remote.pull, context),
405 hotkeys.PULL,
407 self.pull_action.setIcon(icons.pull())
409 self.open_repo_action = add_action(
410 self, N_('Open...'), partial(guicmds.open_repo, context), hotkeys.OPEN
412 self.open_repo_action.setIcon(icons.folder())
414 self.open_repo_new_action = add_action(
415 self,
416 N_('Open in New Window...'),
417 partial(guicmds.open_repo_in_new_window, context),
419 self.open_repo_new_action.setIcon(icons.folder())
421 self.stash_action = qtutils.add_action_with_tooltip(
422 self,
423 N_('Stash...'),
424 N_('Temporarily stash away uncommitted changes using "git stash"'),
425 partial(stash.view, context),
426 hotkeys.STASH,
428 self.stash_action.setIcon(icons.commit())
430 self.reset_soft_action = qtutils.add_action_with_tooltip(
431 self,
432 N_('Reset Branch (Soft)'),
433 cmds.ResetSoft.tooltip('<commit>'),
434 partial(guicmds.reset_soft, context),
436 self.reset_soft_action.setIcon(icons.style_dialog_reset())
438 self.reset_mixed_action = qtutils.add_action_with_tooltip(
439 self,
440 N_('Reset Branch and Stage (Mixed)'),
441 cmds.ResetMixed.tooltip('<commit>'),
442 partial(guicmds.reset_mixed, context),
444 self.reset_mixed_action.setIcon(icons.style_dialog_reset())
446 self.reset_keep_action = qtutils.add_action_with_tooltip(
447 self,
448 N_('Restore Worktree and Reset All (Keep Unstaged Changes)'),
449 cmds.ResetKeep.tooltip('<commit>'),
450 partial(guicmds.reset_keep, context),
452 self.reset_keep_action.setIcon(icons.style_dialog_reset())
454 self.reset_merge_action = qtutils.add_action_with_tooltip(
455 self,
456 N_('Restore Worktree and Reset All (Merge)'),
457 cmds.ResetMerge.tooltip('<commit>'),
458 partial(guicmds.reset_merge, context),
460 self.reset_merge_action.setIcon(icons.style_dialog_reset())
462 self.reset_hard_action = qtutils.add_action_with_tooltip(
463 self,
464 N_('Restore Worktree and Reset All (Hard)'),
465 cmds.ResetHard.tooltip('<commit>'),
466 partial(guicmds.reset_hard, context),
468 self.reset_hard_action.setIcon(icons.style_dialog_reset())
470 self.restore_worktree_action = qtutils.add_action_with_tooltip(
471 self,
472 N_('Restore Worktree'),
473 cmds.RestoreWorktree.tooltip('<commit>'),
474 partial(guicmds.restore_worktree, context),
476 self.restore_worktree_action.setIcon(icons.edit())
478 self.clone_repo_action = add_action(
479 self, N_('Clone...'), partial(clone.clone, context)
481 self.clone_repo_action.setIcon(icons.repo())
483 self.help_docs_action = add_action(
484 self,
485 N_('Documentation'),
486 resources.show_html_docs,
487 QtGui.QKeySequence.HelpContents,
490 self.help_shortcuts_action = add_action(
491 self, N_('Keyboard Shortcuts'), about.show_shortcuts, hotkeys.QUESTION
494 self.visualize_current_action = add_action(
495 self,
496 N_('Visualize Current Branch...'),
497 cmds.run(cmds.VisualizeCurrent, context),
499 self.visualize_current_action.setIcon(icons.visualize())
501 self.visualize_all_action = add_action(
502 self, N_('Visualize All Branches...'), cmds.run(cmds.VisualizeAll, context)
504 self.visualize_all_action.setIcon(icons.visualize())
506 self.search_commits_action = add_action(
507 self, N_('Search...'), partial(search.search, context)
509 self.search_commits_action.setIcon(icons.search())
511 self.browse_branch_action = add_action(
512 self,
513 N_('Browse Current Branch...'),
514 partial(guicmds.browse_current, context),
516 self.browse_branch_action.setIcon(icons.directory())
518 self.browse_other_branch_action = add_action(
519 self, N_('Browse Other Branch...'), partial(guicmds.browse_other, context)
521 self.browse_other_branch_action.setIcon(icons.directory())
523 self.load_commitmsg_template_action = add_action(
524 self,
525 N_('Get Commit Message Template'),
526 cmds.run(cmds.LoadCommitMessageFromTemplate, context),
528 self.load_commitmsg_template_action.setIcon(icons.style_dialog_apply())
530 self.help_about_action = add_action(
531 self, N_('About'), partial(about.about_dialog, context)
534 self.diff_against_commit_action = add_action(
535 self,
536 N_('Against Commit... (Diff Mode)'),
537 partial(guicmds.diff_against_commit, context),
539 self.diff_against_commit_action.setIcon(icons.compare())
541 self.exit_diff_mode_action = add_action(
542 self, N_('Exit Diff Mode'), cmds.run(cmds.ResetMode, context)
544 self.exit_diff_mode_action.setIcon(icons.compare())
546 self.diff_expression_action = add_action(
547 self, N_('Expression...'), partial(guicmds.diff_expression, context)
549 self.diff_expression_action.setIcon(icons.compare())
551 self.branch_compare_action = add_action(
552 self, N_('Branches...'), partial(compare.compare_branches, context)
554 self.branch_compare_action.setIcon(icons.compare())
556 self.create_tag_action = add_action(
557 self,
558 N_('Create Tag...'),
559 partial(createtag.create_tag, context),
561 self.create_tag_action.setIcon(icons.tag())
563 self.create_branch_action = add_action(
564 self,
565 N_('Create...'),
566 partial(createbranch.create_new_branch, context),
567 hotkeys.BRANCH,
569 self.create_branch_action.setIcon(icons.branch())
571 self.delete_branch_action = add_action(
572 self, N_('Delete...'), partial(guicmds.delete_branch, context)
574 self.delete_branch_action.setIcon(icons.discard())
576 self.delete_remote_branch_action = add_action(
577 self,
578 N_('Delete Remote Branch...'),
579 partial(guicmds.delete_remote_branch, context),
581 self.delete_remote_branch_action.setIcon(icons.discard())
583 self.rename_branch_action = add_action(
584 self, N_('Rename Branch...'), partial(guicmds.rename_branch, context)
586 self.rename_branch_action.setIcon(icons.edit())
588 self.checkout_branch_action = add_action(
589 self,
590 N_('Checkout...'),
591 partial(guicmds.checkout_branch, context),
592 hotkeys.CHECKOUT,
594 self.checkout_branch_action.setIcon(icons.branch())
596 self.branch_review_action = add_action(
597 self, N_('Review...'), partial(guicmds.review_branch, context)
599 self.branch_review_action.setIcon(icons.compare())
601 self.browse_action = add_action(
602 self, N_('File Browser...'), partial(browse.worktree_browser, context)
604 self.browse_action.setIcon(icons.cola())
606 self.dag_action = add_action(self, N_('DAG...'), self.git_dag)
607 self.dag_action.setIcon(icons.cola())
609 self.rebase_start_action = add_action(
610 self,
611 N_('Start Interactive Rebase...'),
612 cmds.run(cmds.Rebase, context),
613 hotkeys.REBASE_START_AND_CONTINUE,
615 self.rebase_start_action.setIcon(icons.play())
617 self.rebase_edit_todo_action = add_action(
618 self, N_('Edit...'), cmds.run(cmds.RebaseEditTodo, context)
620 self.rebase_edit_todo_action.setIcon(icons.edit())
622 self.rebase_continue_action = add_action(
623 self,
624 N_('Continue'),
625 cmds.run(cmds.RebaseContinue, context),
626 hotkeys.REBASE_START_AND_CONTINUE,
628 self.rebase_continue_action.setIcon(icons.play())
630 self.rebase_skip_action = add_action(
631 self, N_('Skip Current Patch'), cmds.run(cmds.RebaseSkip, context)
633 self.rebase_skip_action.setIcon(icons.delete())
635 self.rebase_abort_action = add_action(
636 self, N_('Abort'), cmds.run(cmds.RebaseAbort, context)
638 self.rebase_abort_action.setIcon(icons.close())
640 # For "Start Rebase" only, reverse the first argument to setEnabled()
641 # so that we can operate on it as a group.
642 # We can do this because can_rebase == not is_rebasing
643 self.rebase_start_action_proxy = utils.Proxy(
644 self.rebase_start_action,
645 setEnabled=lambda x: self.rebase_start_action.setEnabled(not x),
648 self.rebase_group = utils.Group(
649 self.rebase_start_action_proxy,
650 self.rebase_edit_todo_action,
651 self.rebase_continue_action,
652 self.rebase_skip_action,
653 self.rebase_abort_action,
656 self.annex_init_action = qtutils.add_action(
657 self, N_('Initialize Git Annex'), cmds.run(cmds.AnnexInit, context)
660 self.lfs_init_action = qtutils.add_action(
661 self, N_('Initialize Git LFS'), cmds.run(cmds.LFSInstall, context)
664 self.lock_layout_action = add_action_bool(
665 self, N_('Lock Layout'), self.set_lock_layout, False
668 self.reset_layout_action = add_action(
669 self, N_('Reset Layout'), self.reset_layout
672 self.quick_repository_search = add_action(
673 self,
674 N_('Quick Open...'),
675 lambda: guicmds.open_quick_repo_search(self.context, parent=self),
676 hotkeys.OPEN_REPO_SEARCH,
678 self.quick_repository_search.setIcon(icons.search())
680 self.terminal_action = common.terminal_action(
681 context, self, hotkey=hotkeys.TERMINAL
684 # Create the application menu
685 self.menubar = QtWidgets.QMenuBar(self)
686 self.setMenuBar(self.menubar)
688 # File Menu
689 add_menu = qtutils.add_menu
690 self.file_menu = add_menu(N_('&File'), self.menubar)
691 self.file_menu.addAction(self.quick_repository_search)
692 # File->Open Recent menu
693 self.open_recent_menu = self.file_menu.addMenu(N_('Open Recent'))
694 self.open_recent_menu.setIcon(icons.folder())
695 self.file_menu.addAction(self.open_repo_action)
696 self.file_menu.addAction(self.open_repo_new_action)
697 self.file_menu.addSeparator()
698 self.file_menu.addAction(self.new_repository_action)
699 self.file_menu.addAction(self.new_bare_repository_action)
700 self.file_menu.addAction(self.clone_repo_action)
701 self.file_menu.addSeparator()
702 self.file_menu.addAction(self.rescan_action)
703 self.file_menu.addAction(self.find_files_action)
704 self.file_menu.addAction(self.edit_remotes_action)
705 self.file_menu.addAction(self.browse_recently_modified_action)
706 self.file_menu.addSeparator()
707 self.file_menu.addAction(self.save_tarball_action)
709 self.patches_menu = self.file_menu.addMenu(N_('Patches'))
710 self.patches_menu.setIcon(icons.diff())
711 self.patches_menu.addAction(self.export_patches_action)
712 self.patches_menu.addAction(self.apply_patches_action)
713 self.patches_menu.addAction(self.apply_patches_continue_action)
714 self.patches_menu.addAction(self.apply_patches_skip_action)
715 self.patches_menu.addAction(self.apply_patches_abort_action)
717 # Git Annex / Git LFS
718 annex = core.find_executable('git-annex')
719 lfs = core.find_executable('git-lfs')
720 if annex or lfs:
721 self.file_menu.addSeparator()
722 if annex:
723 self.file_menu.addAction(self.annex_init_action)
724 if lfs:
725 self.file_menu.addAction(self.lfs_init_action)
727 self.file_menu.addSeparator()
728 self.file_menu.addAction(self.preferences_action)
729 self.file_menu.addAction(self.quit_action)
731 # Edit Menu
732 self.edit_proxy = edit_proxy = FocusProxy(
733 editor, editor.summary, editor.description
736 copy_widgets = (
737 self,
738 editor.summary,
739 editor.description,
740 self.diffeditor,
741 bookmarkswidget.tree,
742 recentwidget.tree,
744 select_widgets = copy_widgets + (self.statuswidget.tree,)
745 edit_proxy.override('copy', copy_widgets)
746 edit_proxy.override('selectAll', select_widgets)
748 edit_menu = self.edit_menu = add_menu(N_('&Edit'), self.menubar)
749 undo = add_action(edit_menu, N_('Undo'), edit_proxy.undo, hotkeys.UNDO)
750 undo.setIcon(icons.undo())
751 redo = add_action(edit_menu, N_('Redo'), edit_proxy.redo, hotkeys.REDO)
752 redo.setIcon(icons.redo())
753 edit_menu.addSeparator()
754 cut = add_action(edit_menu, N_('Cut'), edit_proxy.cut, hotkeys.CUT)
755 cut.setIcon(icons.cut())
756 copy = add_action(edit_menu, N_('Copy'), edit_proxy.copy, hotkeys.COPY)
757 copy.setIcon(icons.copy())
758 paste = add_action(edit_menu, N_('Paste'), edit_proxy.paste, hotkeys.PASTE)
759 paste.setIcon(icons.paste())
760 delete = add_action(edit_menu, N_('Delete'), edit_proxy.delete, hotkeys.DELETE)
761 delete.setIcon(icons.delete())
762 edit_menu.addSeparator()
763 select_all = add_action(
764 edit_menu, N_('Select All'), edit_proxy.selectAll, hotkeys.SELECT_ALL
766 select_all.setIcon(icons.select_all())
767 edit_menu.addSeparator()
768 qtutils.add_menu_actions(edit_menu, self.commiteditor.menu_actions)
770 # Actions menu
771 self.actions_menu = add_menu(N_('Actions'), self.menubar)
772 if self.terminal_action is not None:
773 self.actions_menu.addAction(self.terminal_action)
774 self.actions_menu.addAction(self.fetch_action)
775 self.actions_menu.addAction(self.push_action)
776 self.actions_menu.addAction(self.pull_action)
777 self.actions_menu.addAction(self.stash_action)
778 self.actions_menu.addSeparator()
779 self.actions_menu.addAction(self.create_tag_action)
780 self.actions_menu.addAction(self.cherry_pick_action)
781 self.actions_menu.addAction(self.cherry_pick_abort_action)
782 self.actions_menu.addAction(self.merge_local_action)
783 self.actions_menu.addAction(self.merge_abort_action)
784 self.actions_menu.addSeparator()
785 self.actions_menu.addAction(self.update_submodules_action)
786 self.actions_menu.addAction(self.add_submodule_action)
787 self.actions_menu.addSeparator()
788 self.actions_menu.addAction(self.grep_action)
789 self.actions_menu.addAction(self.search_commits_action)
791 # Commit Menu
792 self.commit_menu = add_menu(N_('Commit@@verb'), self.menubar)
793 self.commit_menu.setTitle(N_('Commit@@verb'))
794 self.commit_menu.addAction(self.commiteditor.commit_action)
795 self.commit_menu.addAction(self.commit_amend_action)
796 self.commit_menu.addAction(self.undo_commit_action)
797 self.commit_menu.addSeparator()
798 self.commit_menu.addAction(self.statuswidget.tree.process_selection_action)
799 self.commit_menu.addAction(self.statuswidget.tree.stage_or_unstage_all_action)
800 self.commit_menu.addAction(self.stage_modified_action)
801 self.commit_menu.addAction(self.stage_untracked_action)
802 self.commit_menu.addSeparator()
803 self.commit_menu.addAction(self.unstage_all_action)
804 self.commit_menu.addAction(self.unstage_selected_action)
805 self.commit_menu.addSeparator()
806 self.commit_menu.addAction(self.load_commitmsg_action)
807 self.commit_menu.addAction(self.load_commitmsg_template_action)
808 self.commit_menu.addAction(self.prepare_commitmsg_hook_action)
810 # Diff Menu
811 self.diff_menu = add_menu(N_('Diff'), self.menubar)
812 self.diff_menu.addAction(self.diff_expression_action)
813 self.diff_menu.addAction(self.branch_compare_action)
814 self.diff_menu.addAction(self.show_diffstat_action)
815 self.diff_menu.addSeparator()
816 self.diff_menu.addAction(self.diff_against_commit_action)
817 self.diff_menu.addAction(self.exit_diff_mode_action)
819 # Branch Menu
820 self.branch_menu = add_menu(N_('Branch'), self.menubar)
821 self.branch_menu.addAction(self.branch_review_action)
822 self.branch_menu.addSeparator()
823 self.branch_menu.addAction(self.create_branch_action)
824 self.branch_menu.addAction(self.checkout_branch_action)
825 self.branch_menu.addAction(self.delete_branch_action)
826 self.branch_menu.addAction(self.delete_remote_branch_action)
827 self.branch_menu.addAction(self.rename_branch_action)
828 self.branch_menu.addSeparator()
829 self.branch_menu.addAction(self.browse_branch_action)
830 self.branch_menu.addAction(self.browse_other_branch_action)
831 self.branch_menu.addSeparator()
832 self.branch_menu.addAction(self.visualize_current_action)
833 self.branch_menu.addAction(self.visualize_all_action)
835 # Rebase menu
836 self.rebase_menu = add_menu(N_('Rebase'), self.menubar)
837 self.rebase_menu.addAction(self.rebase_start_action)
838 self.rebase_menu.addAction(self.rebase_edit_todo_action)
839 self.rebase_menu.addSeparator()
840 self.rebase_menu.addAction(self.rebase_continue_action)
841 self.rebase_menu.addAction(self.rebase_skip_action)
842 self.rebase_menu.addSeparator()
843 self.rebase_menu.addAction(self.rebase_abort_action)
845 # Reset menu
846 self.reset_menu = add_menu(N_('Reset'), self.menubar)
847 self.reset_menu.addAction(self.unstage_all_action)
848 self.reset_menu.addAction(self.undo_commit_action)
849 self.reset_menu.addSeparator()
850 self.reset_menu.addAction(self.reset_soft_action)
851 self.reset_menu.addAction(self.reset_mixed_action)
852 self.reset_menu.addAction(self.restore_worktree_action)
853 self.reset_menu.addSeparator()
854 self.reset_menu.addAction(self.reset_keep_action)
855 self.reset_menu.addAction(self.reset_merge_action)
856 self.reset_menu.addAction(self.reset_hard_action)
858 # View Menu
859 self.view_menu = add_menu(N_('View'), self.menubar)
860 # pylint: disable=no-member
861 self.view_menu.aboutToShow.connect(lambda: self.build_view_menu(self.view_menu))
862 self.setup_dockwidget_view_menu()
863 if utils.is_darwin():
864 # The native macOS menu doesn't show empty entries.
865 self.build_view_menu(self.view_menu)
867 # Help Menu
868 self.help_menu = add_menu(N_('Help'), self.menubar)
869 self.help_menu.addAction(self.help_docs_action)
870 self.help_menu.addAction(self.help_shortcuts_action)
871 self.help_menu.addAction(self.help_about_action)
873 # Arrange dock widgets
874 bottom = Qt.BottomDockWidgetArea
875 top = Qt.TopDockWidgetArea
877 self.addDockWidget(top, self.statusdock)
878 self.addDockWidget(top, self.commitdock)
879 if self.browser_dockable:
880 self.addDockWidget(top, self.browserdock)
881 self.tabifyDockWidget(self.browserdock, self.commitdock)
883 self.addDockWidget(top, self.branchdock)
884 self.addDockWidget(top, self.submodulesdock)
885 self.addDockWidget(top, self.bookmarksdock)
886 self.addDockWidget(top, self.recentdock)
888 self.tabifyDockWidget(self.branchdock, self.submodulesdock)
889 self.tabifyDockWidget(self.submodulesdock, self.bookmarksdock)
890 self.tabifyDockWidget(self.bookmarksdock, self.recentdock)
891 self.branchdock.raise_()
893 self.addDockWidget(bottom, self.diffdock)
894 self.addDockWidget(bottom, self.actionsdock)
895 self.addDockWidget(bottom, self.logdock)
896 self.tabifyDockWidget(self.actionsdock, self.logdock)
898 # Listen for model notifications
899 self.model.updated.connect(self.refresh, type=Qt.QueuedConnection)
900 self.model.mode_changed.connect(
901 lambda mode: self.refresh(), type=Qt.QueuedConnection
904 prefs_model.config_updated.connect(self._config_updated)
906 # Set a default value
907 self.show_cursor_position(1, 0)
909 self.commit_menu.aboutToShow.connect(self.update_menu_actions)
910 self.open_recent_menu.aboutToShow.connect(self.build_recent_menu)
911 self.commiteditor.cursor_changed.connect(self.show_cursor_position)
913 self.diffeditor.options_changed.connect(self.statuswidget.refresh)
914 self.diffeditor.up.connect(self.statuswidget.move_up)
915 self.diffeditor.down.connect(self.statuswidget.move_down)
917 self.commiteditor.up.connect(self.statuswidget.move_up)
918 self.commiteditor.down.connect(self.statuswidget.move_down)
920 self.config_actions_changed.connect(
921 lambda names_and_shortcuts: _install_config_actions(
922 context,
923 self.actions_menu,
924 names_and_shortcuts,
926 type=Qt.QueuedConnection,
928 self.init_state(context.settings, self.set_initial_size)
930 # Route command output here
931 Interaction.log_status = self.logwidget.log_status
932 Interaction.log = self.logwidget.log
933 # Focus the status widget; this must be deferred
934 QtCore.QTimer.singleShot(0, self.initialize)
936 def initialize(self):
937 context = self.context
938 git_version = version.git_version_str(context)
939 if git_version:
940 ok = True
941 Interaction.log(
942 git_version + '\n' + N_('git cola version %s') % version.version()
944 else:
945 ok = False
946 error_msg = N_('error: unable to execute git')
947 Interaction.log(error_msg)
949 if ok:
950 self.statuswidget.setFocus()
951 else:
952 title = N_('error: unable to execute git')
953 msg = title
954 details = ''
955 if WIN32:
956 details = git.win32_git_error_hint()
957 Interaction.critical(title, message=msg, details=details)
958 self.context.app.exit(2)
960 def set_initial_size(self):
961 # Default size; this is thrown out when save/restore is used
962 width, height = qtutils.desktop_size()
963 self.resize((width * 3) // 4, height)
964 self.statuswidget.set_initial_size()
965 self.commiteditor.set_initial_size()
967 def set_filter(self, txt):
968 self.statuswidget.set_filter(txt)
970 # Qt overrides
971 def closeEvent(self, event):
972 """Save state in the settings"""
973 commit_msg = self.commiteditor.commit_message(raw=True)
974 self.model.save_commitmsg(msg=commit_msg)
975 for browser in list(self.context.browser_windows):
976 browser.close()
977 standard.MainWindow.closeEvent(self, event)
979 def create_view_menu(self):
980 menu = qtutils.create_menu(N_('View'), self)
981 self.build_view_menu(menu)
982 return menu
984 def build_view_menu(self, menu):
985 menu.clear()
986 menu.addAction(self.browse_action)
987 menu.addAction(self.dag_action)
988 menu.addSeparator()
990 popup_menu = self.createPopupMenu()
991 for menu_action in popup_menu.actions():
992 menu_action.setParent(menu)
993 menu.addAction(menu_action)
995 context = self.context
996 menu_action = menu.addAction(
997 N_('New Toolbar'), partial(toolbar.add_toolbar, context, self)
999 menu_action.setIcon(icons.add())
1000 menu.addSeparator()
1002 dockwidgets = [
1003 self.logdock,
1004 self.commitdock,
1005 self.statusdock,
1006 self.diffdock,
1007 self.actionsdock,
1008 self.bookmarksdock,
1009 self.recentdock,
1010 self.branchdock,
1011 self.submodulesdock,
1013 if self.browser_dockable:
1014 dockwidgets.append(self.browserdock)
1016 for dockwidget in dockwidgets:
1017 # Associate the action with the shortcut
1018 toggleview = dockwidget.toggleViewAction()
1019 menu.addAction(toggleview)
1021 menu.addSeparator()
1022 menu.addAction(self.lock_layout_action)
1023 menu.addAction(self.reset_layout_action)
1025 return menu
1027 def contextMenuEvent(self, event):
1028 menu = self.create_view_menu()
1029 menu.exec_(event.globalPos())
1031 def build_recent_menu(self):
1032 cmd = cmds.OpenRepo
1033 context = self.context
1034 settings = context.settings
1035 settings.load()
1036 menu = self.open_recent_menu
1037 menu.clear()
1038 worktree = context.git.worktree()
1040 for entry in settings.recent:
1041 directory = entry['path']
1042 if directory == worktree:
1043 # Omit the current worktree from the "Open Recent" menu.
1044 continue
1045 name = entry['name']
1046 text = f'{name} {uchr(0x2192)} {directory}'
1047 menu.addAction(text, cmds.run(cmd, context, directory))
1049 # Accessors
1050 mode = property(lambda self: self.model.mode)
1052 def _config_updated(self, _source, config, value):
1053 if config == prefs.FONTDIFF:
1054 # The diff font
1055 font = QtGui.QFont()
1056 if not font.fromString(value):
1057 return
1058 self.logwidget.setFont(font)
1059 self.diffeditor.setFont(font)
1060 self.commiteditor.setFont(font)
1062 elif config == prefs.TABWIDTH:
1063 # This can be set locally or globally, so we have to use the
1064 # effective value otherwise we'll update when we shouldn't.
1065 # For example, if this value is overridden locally, and the
1066 # global value is tweaked, we should not update.
1067 value = prefs.tabwidth(self.context)
1068 self.diffeditor.set_tabwidth(value)
1069 self.commiteditor.set_tabwidth(value)
1071 elif config == prefs.EXPANDTAB:
1072 self.commiteditor.set_expandtab(value)
1074 elif config == prefs.LINEBREAK:
1075 # enables automatic line breaks
1076 self.commiteditor.set_linebreak(value)
1078 elif config == prefs.SORT_BOOKMARKS:
1079 self.bookmarksdock.widget().reload_bookmarks()
1081 elif config == prefs.TEXTWIDTH:
1082 # Use the effective value for the same reason as tabwidth.
1083 value = prefs.textwidth(self.context)
1084 self.commiteditor.set_textwidth(value)
1086 elif config == prefs.SHOW_PATH:
1087 # the path in the window title was toggled
1088 self.refresh_window_title()
1090 def start(self, context):
1091 """Do the expensive "get_config_actions()" call in the background"""
1092 # Install .git-config-defined actions
1093 task = qtutils.SimpleTask(self.get_config_actions)
1094 context.runtask.start(task)
1096 def get_config_actions(self):
1097 actions = cfgactions.get_config_actions(self.context)
1098 self.config_actions_changed.emit(actions)
1100 def refresh(self):
1101 """Update the title with the current branch and directory name."""
1102 curbranch = self.model.currentbranch
1103 is_merging = self.model.is_merging
1104 is_rebasing = self.model.is_rebasing
1105 is_applying_patch = self.model.is_applying_patch
1106 is_cherry_picking = self.model.is_rebasing
1108 curdir = core.getcwd()
1109 msg = N_('Repository: %s') % curdir
1110 msg += '\n'
1111 msg += N_('Branch: %s') % curbranch
1113 if is_rebasing:
1114 msg += '\n\n'
1115 msg += N_(
1116 'This repository is currently being rebased.\n'
1117 'Resolve conflicts, commit changes, and run:\n'
1118 ' Rebase > Continue'
1120 elif is_applying_patch:
1121 msg += '\n\n'
1122 msg += N_(
1123 'This repository has unresolved conflicts after applying a patch.\n'
1124 'Resolve conflicts and commit changes.'
1126 elif is_cherry_picking:
1127 msg += '\n\n'
1128 msg += N_(
1129 'This repository is in the middle of a cherry-pick.\n'
1130 'Resolve conflicts and commit changes.'
1132 elif is_merging:
1133 msg += '\n\n'
1134 msg += N_(
1135 'This repository is in the middle of a merge.\n'
1136 'Resolve conflicts and commit changes.'
1139 self.refresh_window_title()
1141 if self.mode == self.model.mode_amend:
1142 self.commit_amend_action.setChecked(True)
1143 else:
1144 self.commit_amend_action.setChecked(False)
1146 self.commitdock.setToolTip(msg)
1148 self.actionswidget.set_mode(self.mode)
1149 self.commiteditor.set_mode(self.mode)
1150 self.statuswidget.set_mode(self.mode)
1152 self.update_actions()
1154 def refresh_window_title(self):
1155 """Refresh the window title when state changes"""
1156 alerts = []
1158 project = self.model.project
1159 curbranch = self.model.currentbranch
1160 is_cherry_picking = self.model.is_cherry_picking
1161 is_merging = self.model.is_merging
1162 is_rebasing = self.model.is_rebasing
1163 is_applying_patch = self.model.is_applying_patch
1164 is_diff_mode = self.model.is_diff_mode()
1165 is_amend_mode = self.mode == self.model.mode_amend
1167 prefix = uchr(0xAB)
1168 suffix = uchr(0xBB)
1170 if is_amend_mode:
1171 alerts.append(N_('Amending'))
1172 elif is_diff_mode:
1173 alerts.append(N_('Diff Mode'))
1174 elif is_cherry_picking:
1175 alerts.append(N_('Cherry-picking'))
1176 elif is_merging:
1177 alerts.append(N_('Merging'))
1178 elif is_rebasing:
1179 alerts.append(N_('Rebasing'))
1180 elif is_applying_patch:
1181 alerts.append(N_('Applying Patch'))
1183 if alerts:
1184 alert_text = (prefix + ' %s ' + suffix + ' ') % ', '.join(alerts)
1185 else:
1186 alert_text = ''
1188 if self.model.cfg.get(prefs.SHOW_PATH, True):
1189 path_text = self.git.worktree()
1190 else:
1191 path_text = ''
1193 title = f'{project}: {curbranch} {alert_text}{path_text}'
1194 self.setWindowTitle(title)
1196 def update_actions(self):
1197 is_rebasing = self.model.is_rebasing
1198 self.rebase_group.setEnabled(is_rebasing)
1200 enabled = not self.model.is_empty_repository()
1201 self.rename_branch_action.setEnabled(enabled)
1202 self.delete_branch_action.setEnabled(enabled)
1204 self.annex_init_action.setEnabled(not self.model.annex)
1205 self.lfs_init_action.setEnabled(not self.model.lfs)
1206 self.merge_abort_action.setEnabled(self.model.is_merging)
1207 self.cherry_pick_abort_action.setEnabled(self.model.is_cherry_picking)
1208 self.apply_patches_continue_action.setEnabled(self.model.is_applying_patch)
1209 self.apply_patches_skip_action.setEnabled(self.model.is_applying_patch)
1210 self.apply_patches_abort_action.setEnabled(self.model.is_applying_patch)
1212 diff_mode = self.model.mode == self.model.mode_diff
1213 self.exit_diff_mode_action.setEnabled(diff_mode)
1215 def update_menu_actions(self):
1216 # Enable the Prepare Commit Message action if the hook exists
1217 hook = gitcmds.prepare_commit_message_hook(self.context)
1218 enabled = os.path.exists(hook)
1219 self.prepare_commitmsg_hook_action.setEnabled(enabled)
1221 def export_state(self):
1222 state = standard.MainWindow.export_state(self)
1223 show_status_filter = self.statuswidget.filter_widget.isVisible()
1224 state['show_status_filter'] = show_status_filter
1225 state['toolbars'] = self.toolbar_state.export_state()
1226 state['ref_sort'] = self.model.ref_sort
1227 self.diffviewer.export_state(state)
1229 return state
1231 def apply_state(self, state):
1232 """Imports data for save/restore"""
1233 base_ok = standard.MainWindow.apply_state(self, state)
1234 lock_layout = state.get('lock_layout', False)
1235 self.lock_layout_action.setChecked(lock_layout)
1237 show_status_filter = state.get('show_status_filter', False)
1238 self.statuswidget.filter_widget.setVisible(show_status_filter)
1240 toolbars = state.get('toolbars', [])
1241 self.toolbar_state.apply_state(toolbars)
1243 sort_key = state.get('ref_sort', 0)
1244 self.model.set_ref_sort(sort_key)
1246 diff_ok = self.diffviewer.apply_state(state)
1247 return base_ok and diff_ok
1249 def setup_dockwidget_view_menu(self):
1250 # Hotkeys for toggling the dock widgets
1251 if utils.is_darwin():
1252 optkey = 'Meta'
1253 else:
1254 optkey = 'Ctrl'
1255 dockwidgets = (
1256 (optkey + '+0', self.logdock),
1257 (optkey + '+1', self.commitdock),
1258 (optkey + '+2', self.statusdock),
1259 (optkey + '+3', self.diffdock),
1260 (optkey + '+4', self.actionsdock),
1261 (optkey + '+5', self.bookmarksdock),
1262 (optkey + '+6', self.recentdock),
1263 (optkey + '+7', self.branchdock),
1264 (optkey + '+8', self.submodulesdock),
1266 for shortcut, dockwidget in dockwidgets:
1267 # Associate the action with the shortcut
1268 toggleview = dockwidget.toggleViewAction()
1269 toggleview.setShortcut('Shift+' + shortcut)
1271 def showdock(show, dockwidget=dockwidget):
1272 if show:
1273 dockwidget.raise_()
1274 dockwidget.widget().setFocus()
1275 else:
1276 self.setFocus()
1278 self.addAction(toggleview)
1279 qtutils.connect_action_bool(toggleview, showdock)
1281 # Create a new shortcut Shift+<shortcut> that gives focus
1282 toggleview = QtWidgets.QAction(self)
1283 toggleview.setShortcut(shortcut)
1285 def focusdock(dockwidget=dockwidget):
1286 focus_dock(dockwidget)
1288 self.addAction(toggleview)
1289 qtutils.connect_action(toggleview, focusdock)
1291 # These widgets warrant home-row hotkey status
1292 qtutils.add_action(
1293 self,
1294 'Focus Commit Message',
1295 lambda: focus_dock(self.commitdock),
1296 hotkeys.FOCUS,
1299 qtutils.add_action(
1300 self,
1301 'Focus Status Window',
1302 lambda: focus_dock(self.statusdock),
1303 hotkeys.FOCUS_STATUS,
1306 qtutils.add_action(
1307 self,
1308 'Focus Diff Editor',
1309 lambda: focus_dock(self.diffdock),
1310 hotkeys.FOCUS_DIFF,
1313 def git_dag(self):
1314 self.dag = dag.git_dag(self.context, existing_view=self.dag)
1316 def show_cursor_position(self, rows, cols):
1317 display_content = '%02d:%02d' % (rows, cols)
1318 css = """
1319 <style>
1320 .good {
1322 .first-warning {
1323 color: black;
1324 background-color: yellow;
1326 .second-warning {
1327 color: black;
1328 background-color: #f83;
1330 .error {
1331 color: white;
1332 background-color: red;
1334 </style>
1337 if cols > 78:
1338 cls = 'error'
1339 elif cols > 72:
1340 cls = 'second-warning'
1341 elif cols > 64:
1342 cls = 'first-warning'
1343 else:
1344 cls = 'good'
1345 div = f'<div class="{cls}">{display_content}</div>'
1346 self.position_label.setText(css + div)
1349 class FocusProxy:
1350 """Proxy over child widgets and operate on the focused widget"""
1352 def __init__(self, *widgets):
1353 self.widgets = widgets
1354 self.overrides = {}
1356 def override(self, name, widgets):
1357 self.overrides[name] = widgets
1359 def focus(self, name):
1360 """Return the currently focused widget"""
1361 widgets = self.overrides.get(name, self.widgets)
1362 # The parent must be the parent of all the proxied widgets
1363 parent = widgets[0]
1364 # The first widget is used as a fallback
1365 fallback = widgets[1]
1366 # We ignore the parent when delegating to child widgets
1367 widgets = widgets[1:]
1369 focus = parent.focusWidget()
1370 if focus not in widgets:
1371 focus = fallback
1372 return focus
1374 def __getattr__(self, name):
1375 """Return a callback that calls a common child method"""
1377 def callback():
1378 focus = self.focus(name)
1379 func = getattr(focus, name, None)
1380 if func:
1381 func()
1383 return callback
1385 def delete(self):
1386 """Specialized delete() to deal with QLineEdit vs QTextEdit"""
1387 focus = self.focus('delete')
1388 if hasattr(focus, 'del_'):
1389 focus.del_()
1390 elif hasattr(focus, 'textCursor'):
1391 focus.textCursor().deleteChar()
1394 def show_dock(dockwidget):
1395 dockwidget.raise_()
1396 dockwidget.widget().setFocus()
1399 def focus_dock(dockwidget):
1400 if get(dockwidget.toggleViewAction()):
1401 show_dock(dockwidget)
1402 else:
1403 dockwidget.toggleViewAction().trigger()
1406 def _install_config_actions(context, menu, names_and_shortcuts):
1407 """Install .gitconfig-defined actions"""
1408 if not names_and_shortcuts:
1409 return
1410 menu.addSeparator()
1411 cache = {}
1412 for name, shortcut in names_and_shortcuts:
1413 sub_menu, action_name = build_menus(name, menu, cache)
1414 callback = cmds.run(cmds.RunConfigAction, context, name)
1415 menu_action = sub_menu.addAction(action_name, callback)
1416 if shortcut:
1417 menu_action.setShortcut(shortcut)
1420 def build_menus(name, menu, cache):
1421 """Create a chain of QMenu entries parented under a root QMenu
1423 A name of "a/b/c" create a menu chain of menu -> QMenu("a") -> QMenu("b")
1424 and returns a tuple of (QMenu("b"), "c").
1426 :param name: The full entry path, ex: "a/b/c" where "a/b" is the menu chain.
1427 :param menu: The root menu under which to create the menu chain.
1428 :param cache: A dict cache of previously created menus to avoid duplicates.
1431 # NOTE: utils.split() and friends are used instead of os.path.split() because
1432 # slash '/' is the only supported "<menu>/name" separator. Use of os.path.split()
1433 # would introduce differences in behavior across platforms.
1435 # If the menu_path is empty then no parent menus need to be created.
1436 # The action will be added to the root menu.
1437 menu_path, text = utils.split(utils.normalize_slash(name))
1439 if not menu_path:
1440 return (menu, text)
1441 # When menu_path contains ex: "a/b" we will create two menus: "a" and "b".
1442 # The root menu is the parent of "a" and "a" is the parent of "b".
1443 # The menu returned to the caller is "b".
1445 # Loop over the individual menu basenames alongside the full subpath returned by
1446 # pathset(). The subpath is a cache key for finding previously created menus.
1447 menu_names = utils.splitpath(menu_path) # ['a', 'b']
1448 menu_pathset = utils.pathset(menu_path) # ['a', 'a/b']
1449 for menu_name, menu_id in zip(menu_names, menu_pathset):
1450 try:
1451 menu = cache[menu_id]
1452 except KeyError:
1453 menu = cache[menu_id] = menu.addMenu(menu_name)
1455 return (menu, text)