tree-wide: remove pylint cruft
[git-cola.git] / cola / widgets / main.py
blob9f2829ab7aa72cfaa9fbcc6aa42c7085ad581776
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 standard.MainWindow.__init__(self, parent)
65 self.setAttribute(Qt.WA_DeleteOnClose)
67 self.context = context
68 self.git = context.git
69 self.dag = None
70 self.model = context.model
71 self.prefs_model = prefs_model = prefs.PreferencesModel(context)
72 self.toolbar_state = toolbar.ToolBarState(context, self)
74 # The widget version is used by import/export_state().
75 # Change this whenever dockwidgets are removed.
76 self.widget_version = 2
78 create_dock = qtutils.create_dock
79 cfg = context.cfg
80 self.browser_dockable = cfg.get('cola.browserdockable')
81 if self.browser_dockable:
82 browser = browse.worktree_browser(
83 context, parent=self, show=False, update=False
85 self.browserdock = create_dock(
86 'Browser', N_('Browser'), self, widget=browser
89 # "Actions" widget
90 self.actionswidget = action.ActionButtons(context, self)
91 self.actionsdock = create_dock(
92 'Actions', N_('Actions'), self, widget=self.actionswidget
94 qtutils.hide_dock(self.actionsdock)
96 # "Repository Status" widget
97 self.statusdock = create_dock(
98 'Status',
99 N_('Status'),
100 self,
101 func=lambda dock: status.StatusWidget(context, dock.titleBarWidget(), dock),
103 self.statuswidget = self.statusdock.widget()
105 # "Switch Repository" widgets
106 self.bookmarksdock = create_dock(
107 'Favorites',
108 N_('Favorites'),
109 self,
110 func=lambda dock: bookmarks.bookmark(context, dock),
112 bookmarkswidget = self.bookmarksdock.widget()
113 qtutils.hide_dock(self.bookmarksdock)
115 self.recentdock = create_dock(
116 'Recent',
117 N_('Recent'),
118 self,
119 func=lambda dock: bookmarks.recent(context, dock),
121 recentwidget = self.recentdock.widget()
122 qtutils.hide_dock(self.recentdock)
123 bookmarkswidget.connect_to(recentwidget)
125 # "Branch" widgets
126 self.branchdock = create_dock(
127 'Branches',
128 N_('Branches'),
129 self,
130 func=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(
139 'Submodules',
140 N_('Submodules'),
141 self,
142 func=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 text_width = qtutils.text_width(font, '99:999')
155 width = text_width + 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_title_widget(self.commiteditor.commit_progress_bar)
163 titlebar.add_corner_widget(self.position_label)
165 # "Console" widget
166 self.logwidget = log.LogWidget(context)
167 self.logdock = create_dock(
168 'Console', N_('Console'), self, widget=self.logwidget
170 qtutils.hide_dock(self.logdock)
172 # "Diff Viewer" widget
173 self.diffdock = create_dock(
174 'Diff',
175 N_('Diff'),
176 self,
177 func=lambda dock: diff.Viewer(context, parent=dock),
179 self.diffviewer = self.diffdock.widget()
180 self.diffviewer.set_diff_type(self.model.diff_type)
181 self.diffviewer.enable_filename_tracking()
182 self.diffeditor = self.diffviewer.text
183 titlebar = self.diffdock.titleBarWidget()
184 titlebar.add_title_widget(self.diffviewer.filename)
185 titlebar.add_corner_widget(self.diffviewer.options)
187 # All Actions
188 add_action = qtutils.add_action
189 add_action_bool = qtutils.add_action_bool
191 self.commit_amend_action = add_action_bool(
192 self,
193 N_('Amend Last Commit'),
194 partial(cmds.do, cmds.AmendMode, context),
195 False,
197 self.commit_amend_action.setIcon(icons.edit())
198 self.commit_amend_action.setShortcut(hotkeys.AMEND)
199 self.commit_amend_action.setShortcutContext(Qt.WidgetShortcut)
201 self.unstage_all_action = add_action(
202 self, N_('Unstage All'), cmds.run(cmds.UnstageAll, context)
204 self.unstage_all_action.setIcon(icons.remove())
206 self.undo_commit_action = add_action(
207 self, N_('Undo Last Commit'), cmds.run(cmds.UndoLastCommit, context)
209 self.undo_commit_action.setIcon(icons.style_dialog_discard())
211 self.unstage_selected_action = add_action(
212 self, N_('Unstage'), cmds.run(cmds.UnstageSelected, context)
214 self.unstage_selected_action.setIcon(icons.remove())
216 self.show_diffstat_action = add_action(
217 self, N_('Diffstat'), self.statuswidget.select_header, hotkeys.DIFFSTAT
219 self.show_diffstat_action.setIcon(icons.diff())
221 self.stage_modified_action = add_action(
222 self,
223 cmds.StageModified.name(),
224 cmds.run(cmds.StageModified, context),
225 hotkeys.STAGE_MODIFIED,
227 self.stage_modified_action.setIcon(icons.add())
229 self.stage_untracked_action = add_action(
230 self,
231 cmds.StageUntracked.name(),
232 cmds.run(cmds.StageUntracked, context),
233 hotkeys.STAGE_UNTRACKED,
235 self.stage_untracked_action.setIcon(icons.add())
237 self.apply_patches_action = add_action(
238 self, N_('Apply Patches...'), partial(diff.apply_patches, context)
240 self.apply_patches_action.setIcon(icons.diff())
242 self.apply_patches_abort_action = qtutils.add_action_with_tooltip(
243 self,
244 N_('Abort Applying Patches...'),
245 N_('Abort the current "git am" patch session'),
246 cmds.run(cmds.AbortApplyPatch, context),
248 self.apply_patches_abort_action.setIcon(icons.style_dialog_discard())
250 self.apply_patches_continue_action = qtutils.add_action_with_tooltip(
251 self,
252 N_('Continue Applying Patches'),
253 N_('Commit the current state and continue applying patches'),
254 cmds.run(cmds.ApplyPatchesContinue, context),
256 self.apply_patches_continue_action.setIcon(icons.commit())
258 self.apply_patches_skip_action = qtutils.add_action_with_tooltip(
259 self,
260 N_('Skip Current Patch'),
261 N_('Skip applying the current patch and continue applying patches'),
262 cmds.run(cmds.ApplyPatchesContinue, context),
264 self.apply_patches_skip_action.setIcon(icons.discard())
266 self.export_patches_action = add_action(
267 self,
268 N_('Export Patches...'),
269 partial(guicmds.export_patches, context),
270 hotkeys.EXPORT,
272 self.export_patches_action.setIcon(icons.save())
274 self.new_repository_action = add_action(
275 self, N_('New Repository...'), partial(guicmds.open_new_repo, context)
277 self.new_repository_action.setIcon(icons.new())
279 self.new_bare_repository_action = add_action(
280 self, N_('New Bare Repository...'), partial(guicmds.new_bare_repo, context)
282 self.new_bare_repository_action.setIcon(icons.new())
284 prefs_func = partial(
285 prefs_widget.preferences, context, parent=self, model=prefs_model
287 self.preferences_action = add_action(
288 self, N_('Preferences'), prefs_func, QtGui.QKeySequence.Preferences
290 self.preferences_action.setIcon(icons.configure())
292 self.edit_remotes_action = add_action(
293 self, N_('Edit Remotes...'), partial(editremotes.editor, context)
295 self.edit_remotes_action.setIcon(icons.edit())
297 self.rescan_action = add_action(
298 self,
299 cmds.Refresh.name(),
300 cmds.run(cmds.Refresh, context),
301 *hotkeys.REFRESH_HOTKEYS,
303 self.rescan_action.setIcon(icons.sync())
305 self.find_files_action = add_action(
306 self,
307 N_('Find Files'),
308 partial(finder.finder, context),
309 hotkeys.FINDER,
311 self.find_files_action.setIcon(icons.search())
313 self.browse_recently_modified_action = add_action(
314 self,
315 N_('Recently Modified Files...'),
316 partial(recent.browse_recent_files, context),
317 hotkeys.EDIT_SECONDARY,
319 self.browse_recently_modified_action.setIcon(icons.directory())
321 self.cherry_pick_action = add_action(
322 self,
323 N_('Cherry-Pick...'),
324 partial(guicmds.cherry_pick, context),
325 hotkeys.CHERRY_PICK,
327 self.cherry_pick_action.setIcon(icons.cherry_pick())
328 self.cherry_pick_abort_action = add_action(
329 self, N_('Abort Cherry-Pick...'), cmds.run(cmds.AbortCherryPick, context)
331 self.cherry_pick_abort_action.setIcon(icons.style_dialog_discard())
333 self.load_commitmsg_action = add_action(
334 self, N_('Load Commit Message...'), partial(guicmds.load_commitmsg, context)
336 self.load_commitmsg_action.setIcon(icons.file_text())
338 self.prepare_commitmsg_hook_action = add_action(
339 self,
340 N_('Prepare Commit Message'),
341 cmds.run(cmds.PrepareCommitMessageHook, context),
342 hotkeys.PREPARE_COMMIT_MESSAGE,
345 self.save_tarball_action = add_action(
346 self, N_('Save As Tarball/Zip...'), partial(archive.save_archive, context)
348 self.save_tarball_action.setIcon(icons.file_zip())
350 self.quit_action = add_action(self, N_('Quit'), self.close, hotkeys.QUIT)
352 self.grep_action = add_action(
353 self, N_('Grep'), partial(grep.grep, context), hotkeys.GREP
355 self.grep_action.setIcon(icons.search())
357 self.merge_local_action = add_action(
358 self, N_('Merge...'), partial(merge.local_merge, context), hotkeys.MERGE
360 self.merge_local_action.setIcon(icons.merge())
362 self.merge_abort_action = add_action(
363 self, N_('Abort Merge...'), cmds.run(cmds.AbortMerge, context)
365 self.merge_abort_action.setIcon(icons.style_dialog_discard())
367 self.update_submodules_action = add_action(
368 self,
369 N_('Update All Submodules...'),
370 cmds.run(cmds.SubmodulesUpdate, context),
372 self.update_submodules_action.setIcon(icons.sync())
374 self.add_submodule_action = add_action(
375 self,
376 N_('Add Submodule...'),
377 partial(submodules.add_submodule, context, parent=self),
379 self.add_submodule_action.setIcon(icons.add())
381 self.fetch_action = qtutils.add_action_with_tooltip(
382 self,
383 N_('Fetch...'),
384 N_('Fetch from one or more remotes using "git fetch"'),
385 partial(remote.fetch, context),
386 hotkeys.FETCH,
388 self.fetch_action.setIcon(icons.download())
390 self.push_action = qtutils.add_action_with_tooltip(
391 self,
392 N_('Push...'),
393 N_('Push to one or more remotes using "git push"'),
394 partial(remote.push, context),
395 hotkeys.PUSH,
397 self.push_action.setIcon(icons.push())
399 self.pull_action = qtutils.add_action_with_tooltip(
400 self,
401 N_('Pull...'),
402 N_('Integrate changes using "git pull"'),
403 partial(remote.pull, context),
404 hotkeys.PULL,
406 self.pull_action.setIcon(icons.pull())
408 self.open_repo_action = add_action(
409 self, N_('Open...'), partial(guicmds.open_repo, context), hotkeys.OPEN
411 self.open_repo_action.setIcon(icons.folder())
413 self.open_repo_new_action = add_action(
414 self,
415 N_('Open in New Window...'),
416 partial(guicmds.open_repo_in_new_window, context),
418 self.open_repo_new_action.setIcon(icons.folder())
420 self.stash_action = qtutils.add_action_with_tooltip(
421 self,
422 N_('Stash...'),
423 N_('Temporarily stash away uncommitted changes using "git stash"'),
424 partial(stash.view, context),
425 hotkeys.STASH,
427 self.stash_action.setIcon(icons.commit())
429 self.reset_soft_action = qtutils.add_action_with_tooltip(
430 self,
431 N_('Reset Branch (Soft)'),
432 cmds.ResetSoft.tooltip('<commit>'),
433 partial(guicmds.reset_soft, context),
435 self.reset_soft_action.setIcon(icons.style_dialog_reset())
437 self.reset_mixed_action = qtutils.add_action_with_tooltip(
438 self,
439 N_('Reset Branch and Stage (Mixed)'),
440 cmds.ResetMixed.tooltip('<commit>'),
441 partial(guicmds.reset_mixed, context),
443 self.reset_mixed_action.setIcon(icons.style_dialog_reset())
445 self.reset_keep_action = qtutils.add_action_with_tooltip(
446 self,
447 N_('Restore Worktree and Reset All (Keep Unstaged Changes)'),
448 cmds.ResetKeep.tooltip('<commit>'),
449 partial(guicmds.reset_keep, context),
451 self.reset_keep_action.setIcon(icons.style_dialog_reset())
453 self.reset_merge_action = qtutils.add_action_with_tooltip(
454 self,
455 N_('Restore Worktree and Reset All (Merge)'),
456 cmds.ResetMerge.tooltip('<commit>'),
457 partial(guicmds.reset_merge, context),
459 self.reset_merge_action.setIcon(icons.style_dialog_reset())
461 self.reset_hard_action = qtutils.add_action_with_tooltip(
462 self,
463 N_('Restore Worktree and Reset All (Hard)'),
464 cmds.ResetHard.tooltip('<commit>'),
465 partial(guicmds.reset_hard, context),
467 self.reset_hard_action.setIcon(icons.style_dialog_reset())
469 self.restore_worktree_action = qtutils.add_action_with_tooltip(
470 self,
471 N_('Restore Worktree'),
472 cmds.RestoreWorktree.tooltip('<commit>'),
473 partial(guicmds.restore_worktree, context),
475 self.restore_worktree_action.setIcon(icons.edit())
477 self.clone_repo_action = add_action(
478 self, N_('Clone...'), partial(clone.clone, context)
480 self.clone_repo_action.setIcon(icons.repo())
482 self.help_docs_action = add_action(
483 self,
484 N_('Documentation'),
485 resources.show_html_docs,
486 QtGui.QKeySequence.HelpContents,
489 self.help_shortcuts_action = add_action(
490 self, N_('Keyboard Shortcuts'), about.show_shortcuts, hotkeys.QUESTION
493 self.visualize_current_action = add_action(
494 self,
495 N_('Visualize Current Branch...'),
496 cmds.run(cmds.VisualizeCurrent, context),
498 self.visualize_current_action.setIcon(icons.visualize())
500 self.visualize_all_action = add_action(
501 self, N_('Visualize All Branches...'), cmds.run(cmds.VisualizeAll, context)
503 self.visualize_all_action.setIcon(icons.visualize())
505 self.search_commits_action = add_action(
506 self, N_('Search...'), partial(search.search, context)
508 self.search_commits_action.setIcon(icons.search())
510 self.browse_branch_action = add_action(
511 self,
512 N_('Browse Current Branch...'),
513 partial(guicmds.browse_current, context),
515 self.browse_branch_action.setIcon(icons.directory())
517 self.browse_other_branch_action = add_action(
518 self, N_('Browse Other Branch...'), partial(guicmds.browse_other, context)
520 self.browse_other_branch_action.setIcon(icons.directory())
522 self.load_commitmsg_template_action = add_action(
523 self,
524 N_('Get Commit Message Template'),
525 cmds.run(cmds.LoadCommitMessageFromTemplate, context),
527 self.load_commitmsg_template_action.setIcon(icons.style_dialog_apply())
529 self.help_about_action = add_action(
530 self, N_('About'), partial(about.about_dialog, context)
533 self.diff_against_commit_action = add_action(
534 self,
535 N_('Against Commit... (Diff Mode)'),
536 partial(guicmds.diff_against_commit, context),
538 self.diff_against_commit_action.setIcon(icons.compare())
540 self.exit_diff_mode_action = add_action(
541 self, N_('Exit Diff Mode'), cmds.run(cmds.ResetMode, context)
543 self.exit_diff_mode_action.setIcon(icons.compare())
545 self.diff_expression_action = add_action(
546 self, N_('Expression...'), partial(guicmds.diff_expression, context)
548 self.diff_expression_action.setIcon(icons.compare())
550 self.branch_compare_action = add_action(
551 self, N_('Branches...'), partial(compare.compare_branches, context)
553 self.branch_compare_action.setIcon(icons.compare())
555 self.create_tag_action = add_action(
556 self,
557 N_('Create Tag...'),
558 partial(createtag.create_tag, context),
560 self.create_tag_action.setIcon(icons.tag())
562 self.create_branch_action = add_action(
563 self,
564 N_('Create...'),
565 partial(createbranch.create_new_branch, context),
566 hotkeys.BRANCH,
568 self.create_branch_action.setIcon(icons.branch())
570 self.delete_branch_action = add_action(
571 self, N_('Delete...'), partial(guicmds.delete_branch, context)
573 self.delete_branch_action.setIcon(icons.discard())
575 self.delete_remote_branch_action = add_action(
576 self,
577 N_('Delete Remote Branch...'),
578 partial(guicmds.delete_remote_branch, context),
580 self.delete_remote_branch_action.setIcon(icons.discard())
582 self.rename_branch_action = add_action(
583 self, N_('Rename Branch...'), partial(guicmds.rename_branch, context)
585 self.rename_branch_action.setIcon(icons.edit())
587 self.checkout_branch_action = add_action(
588 self,
589 N_('Checkout...'),
590 partial(guicmds.checkout_branch, context),
591 hotkeys.CHECKOUT,
593 self.checkout_branch_action.setIcon(icons.branch())
595 self.branch_review_action = add_action(
596 self, N_('Review...'), partial(guicmds.review_branch, context)
598 self.branch_review_action.setIcon(icons.compare())
600 self.browse_action = add_action(
601 self, N_('File Browser...'), partial(browse.worktree_browser, context)
603 self.browse_action.setIcon(icons.cola())
605 self.dag_action = add_action(self, N_('DAG...'), self.git_dag)
606 self.dag_action.setIcon(icons.cola())
608 self.rebase_start_action = add_action(
609 self,
610 N_('Start Interactive Rebase...'),
611 cmds.run(cmds.Rebase, context),
612 hotkeys.REBASE_START_AND_CONTINUE,
614 self.rebase_start_action.setIcon(icons.play())
616 self.rebase_edit_todo_action = add_action(
617 self, N_('Edit...'), cmds.run(cmds.RebaseEditTodo, context)
619 self.rebase_edit_todo_action.setIcon(icons.edit())
621 self.rebase_continue_action = add_action(
622 self,
623 N_('Continue'),
624 cmds.run(cmds.RebaseContinue, context),
625 hotkeys.REBASE_START_AND_CONTINUE,
627 self.rebase_continue_action.setIcon(icons.play())
629 self.rebase_skip_action = add_action(
630 self, N_('Skip Current Patch'), cmds.run(cmds.RebaseSkip, context)
632 self.rebase_skip_action.setIcon(icons.delete())
634 self.rebase_abort_action = add_action(
635 self, N_('Abort'), cmds.run(cmds.RebaseAbort, context)
637 self.rebase_abort_action.setIcon(icons.close())
639 # For "Start Rebase" only, reverse the first argument to setEnabled()
640 # so that we can operate on it as a group.
641 # We can do this because can_rebase == not is_rebasing
642 self.rebase_start_action_proxy = utils.Proxy(
643 self.rebase_start_action,
644 setEnabled=lambda x: self.rebase_start_action.setEnabled(not x),
647 self.rebase_group = utils.Group(
648 self.rebase_start_action_proxy,
649 self.rebase_edit_todo_action,
650 self.rebase_continue_action,
651 self.rebase_skip_action,
652 self.rebase_abort_action,
655 self.annex_init_action = qtutils.add_action(
656 self, N_('Initialize Git Annex'), cmds.run(cmds.AnnexInit, context)
659 self.lfs_init_action = qtutils.add_action(
660 self, N_('Initialize Git LFS'), cmds.run(cmds.LFSInstall, context)
663 self.lock_layout_action = add_action_bool(
664 self, N_('Lock Layout'), self.set_lock_layout, False
667 self.reset_layout_action = add_action(
668 self, N_('Reset Layout'), self.reset_layout
671 self.quick_repository_search = add_action(
672 self,
673 N_('Quick Open...'),
674 lambda: guicmds.open_quick_repo_search(self.context, parent=self),
675 hotkeys.OPEN_REPO_SEARCH,
677 self.quick_repository_search.setIcon(icons.search())
679 self.terminal_action = common.terminal_action(
680 context, self, hotkey=hotkeys.TERMINAL
683 # Create the application menu
684 self.menubar = QtWidgets.QMenuBar(self)
685 self.setMenuBar(self.menubar)
687 # File Menu
688 add_menu = qtutils.add_menu
689 self.file_menu = add_menu(N_('&File'), self.menubar)
690 self.file_menu.addAction(self.quick_repository_search)
691 # File->Open Recent menu
692 self.open_recent_menu = self.file_menu.addMenu(N_('Open Recent'))
693 self.open_recent_menu.setIcon(icons.folder())
694 self.file_menu.addAction(self.open_repo_action)
695 self.file_menu.addAction(self.open_repo_new_action)
696 self.file_menu.addSeparator()
697 self.file_menu.addAction(self.new_repository_action)
698 self.file_menu.addAction(self.new_bare_repository_action)
699 self.file_menu.addAction(self.clone_repo_action)
700 self.file_menu.addSeparator()
701 self.file_menu.addAction(self.rescan_action)
702 self.file_menu.addAction(self.find_files_action)
703 self.file_menu.addAction(self.edit_remotes_action)
704 self.file_menu.addAction(self.browse_recently_modified_action)
705 self.file_menu.addSeparator()
706 self.file_menu.addAction(self.save_tarball_action)
708 self.patches_menu = self.file_menu.addMenu(N_('Patches'))
709 self.patches_menu.setIcon(icons.diff())
710 self.patches_menu.addAction(self.export_patches_action)
711 self.patches_menu.addAction(self.apply_patches_action)
712 self.patches_menu.addAction(self.apply_patches_continue_action)
713 self.patches_menu.addAction(self.apply_patches_skip_action)
714 self.patches_menu.addAction(self.apply_patches_abort_action)
716 # Git Annex / Git LFS
717 annex = core.find_executable('git-annex')
718 lfs = core.find_executable('git-lfs')
719 if annex or lfs:
720 self.file_menu.addSeparator()
721 if annex:
722 self.file_menu.addAction(self.annex_init_action)
723 if lfs:
724 self.file_menu.addAction(self.lfs_init_action)
726 self.file_menu.addSeparator()
727 self.file_menu.addAction(self.preferences_action)
728 self.file_menu.addAction(self.quit_action)
730 # Edit Menu
731 self.edit_proxy = edit_proxy = FocusProxy(
732 editor, editor.summary, editor.description
735 copy_widgets = (
736 self,
737 editor.summary,
738 editor.description,
739 self.diffeditor,
740 bookmarkswidget.tree,
741 recentwidget.tree,
743 select_widgets = copy_widgets + (self.statuswidget.tree,)
744 edit_proxy.override('copy', copy_widgets)
745 edit_proxy.override('selectAll', select_widgets)
747 edit_menu = self.edit_menu = add_menu(N_('&Edit'), self.menubar)
748 undo = add_action(edit_menu, N_('Undo'), edit_proxy.undo, hotkeys.UNDO)
749 undo.setIcon(icons.undo())
750 redo = add_action(edit_menu, N_('Redo'), edit_proxy.redo, hotkeys.REDO)
751 redo.setIcon(icons.redo())
752 edit_menu.addSeparator()
753 cut = add_action(edit_menu, N_('Cut'), edit_proxy.cut, hotkeys.CUT)
754 cut.setIcon(icons.cut())
755 copy = add_action(edit_menu, N_('Copy'), edit_proxy.copy, hotkeys.COPY)
756 copy.setIcon(icons.copy())
757 paste = add_action(edit_menu, N_('Paste'), edit_proxy.paste, hotkeys.PASTE)
758 paste.setIcon(icons.paste())
759 delete = add_action(edit_menu, N_('Delete'), edit_proxy.delete, hotkeys.DELETE)
760 delete.setIcon(icons.delete())
761 edit_menu.addSeparator()
762 select_all = add_action(
763 edit_menu, N_('Select All'), edit_proxy.selectAll, hotkeys.SELECT_ALL
765 select_all.setIcon(icons.select_all())
766 edit_menu.addSeparator()
767 qtutils.add_menu_actions(edit_menu, self.commiteditor.menu_actions)
769 # Actions menu
770 self.actions_menu = add_menu(N_('Actions'), self.menubar)
771 if self.terminal_action is not None:
772 self.actions_menu.addAction(self.terminal_action)
773 self.actions_menu.addAction(self.fetch_action)
774 self.actions_menu.addAction(self.push_action)
775 self.actions_menu.addAction(self.pull_action)
776 self.actions_menu.addAction(self.stash_action)
777 self.actions_menu.addSeparator()
778 self.actions_menu.addAction(self.create_tag_action)
779 self.actions_menu.addAction(self.cherry_pick_action)
780 self.actions_menu.addAction(self.cherry_pick_abort_action)
781 self.actions_menu.addAction(self.merge_local_action)
782 self.actions_menu.addAction(self.merge_abort_action)
783 self.actions_menu.addSeparator()
784 self.actions_menu.addAction(self.update_submodules_action)
785 self.actions_menu.addAction(self.add_submodule_action)
786 self.actions_menu.addSeparator()
787 self.actions_menu.addAction(self.grep_action)
788 self.actions_menu.addAction(self.search_commits_action)
790 # Commit Menu
791 self.commit_menu = add_menu(N_('Commit@@verb'), self.menubar)
792 self.commit_menu.setTitle(N_('Commit@@verb'))
793 self.commit_menu.addAction(self.commiteditor.commit_action)
794 self.commit_menu.addAction(self.commit_amend_action)
795 self.commit_menu.addAction(self.undo_commit_action)
796 self.commit_menu.addSeparator()
797 self.commit_menu.addAction(self.statuswidget.tree.process_selection_action)
798 self.commit_menu.addAction(self.statuswidget.tree.stage_or_unstage_all_action)
799 self.commit_menu.addAction(self.stage_modified_action)
800 self.commit_menu.addAction(self.stage_untracked_action)
801 self.commit_menu.addSeparator()
802 self.commit_menu.addAction(self.unstage_all_action)
803 self.commit_menu.addAction(self.unstage_selected_action)
804 self.commit_menu.addSeparator()
805 self.commit_menu.addAction(self.load_commitmsg_action)
806 self.commit_menu.addAction(self.load_commitmsg_template_action)
807 self.commit_menu.addAction(self.prepare_commitmsg_hook_action)
809 # Diff Menu
810 self.diff_menu = add_menu(N_('Diff'), self.menubar)
811 self.diff_menu.addAction(self.diff_expression_action)
812 self.diff_menu.addAction(self.branch_compare_action)
813 self.diff_menu.addAction(self.show_diffstat_action)
814 self.diff_menu.addSeparator()
815 self.diff_menu.addAction(self.diff_against_commit_action)
816 self.diff_menu.addAction(self.exit_diff_mode_action)
818 # Branch Menu
819 self.branch_menu = add_menu(N_('Branch'), self.menubar)
820 self.branch_menu.addAction(self.branch_review_action)
821 self.branch_menu.addSeparator()
822 self.branch_menu.addAction(self.create_branch_action)
823 self.branch_menu.addAction(self.checkout_branch_action)
824 self.branch_menu.addAction(self.delete_branch_action)
825 self.branch_menu.addAction(self.delete_remote_branch_action)
826 self.branch_menu.addAction(self.rename_branch_action)
827 self.branch_menu.addSeparator()
828 self.branch_menu.addAction(self.browse_branch_action)
829 self.branch_menu.addAction(self.browse_other_branch_action)
830 self.branch_menu.addSeparator()
831 self.branch_menu.addAction(self.visualize_current_action)
832 self.branch_menu.addAction(self.visualize_all_action)
834 # Rebase menu
835 self.rebase_menu = add_menu(N_('Rebase'), self.menubar)
836 self.rebase_menu.addAction(self.rebase_start_action)
837 self.rebase_menu.addAction(self.rebase_edit_todo_action)
838 self.rebase_menu.addSeparator()
839 self.rebase_menu.addAction(self.rebase_continue_action)
840 self.rebase_menu.addAction(self.rebase_skip_action)
841 self.rebase_menu.addSeparator()
842 self.rebase_menu.addAction(self.rebase_abort_action)
844 # Reset menu
845 self.reset_menu = add_menu(N_('Reset'), self.menubar)
846 self.reset_menu.addAction(self.unstage_all_action)
847 self.reset_menu.addAction(self.undo_commit_action)
848 self.reset_menu.addSeparator()
849 self.reset_menu.addAction(self.reset_soft_action)
850 self.reset_menu.addAction(self.reset_mixed_action)
851 self.reset_menu.addAction(self.restore_worktree_action)
852 self.reset_menu.addSeparator()
853 self.reset_menu.addAction(self.reset_keep_action)
854 self.reset_menu.addAction(self.reset_merge_action)
855 self.reset_menu.addAction(self.reset_hard_action)
857 # View Menu
858 self.view_menu = add_menu(N_('View'), self.menubar)
859 self.view_menu.aboutToShow.connect(lambda: self.build_view_menu(self.view_menu))
860 self.setup_dockwidget_view_menu()
861 if utils.is_darwin():
862 # The native macOS menu doesn't show empty entries.
863 self.build_view_menu(self.view_menu)
865 # Help Menu
866 self.help_menu = add_menu(N_('Help'), self.menubar)
867 self.help_menu.addAction(self.help_docs_action)
868 self.help_menu.addAction(self.help_shortcuts_action)
869 self.help_menu.addAction(self.help_about_action)
871 # Arrange dock widgets
872 bottom = Qt.BottomDockWidgetArea
873 top = Qt.TopDockWidgetArea
875 self.addDockWidget(top, self.statusdock)
876 self.addDockWidget(top, self.commitdock)
877 if self.browser_dockable:
878 self.addDockWidget(top, self.browserdock)
879 self.tabifyDockWidget(self.browserdock, self.commitdock)
881 self.addDockWidget(top, self.branchdock)
882 self.addDockWidget(top, self.submodulesdock)
883 self.addDockWidget(top, self.bookmarksdock)
884 self.addDockWidget(top, self.recentdock)
886 self.tabifyDockWidget(self.branchdock, self.submodulesdock)
887 self.tabifyDockWidget(self.submodulesdock, self.bookmarksdock)
888 self.tabifyDockWidget(self.bookmarksdock, self.recentdock)
889 self.branchdock.raise_()
891 self.addDockWidget(bottom, self.diffdock)
892 self.addDockWidget(bottom, self.actionsdock)
893 self.addDockWidget(bottom, self.logdock)
894 self.tabifyDockWidget(self.actionsdock, self.logdock)
896 # Listen for model notifications
897 self.model.updated.connect(self.refresh, type=Qt.QueuedConnection)
898 self.model.mode_changed.connect(
899 lambda mode: self.refresh(), type=Qt.QueuedConnection
902 prefs_model.config_updated.connect(self._config_updated)
904 # Set a default value
905 self.show_cursor_position(1, 0)
907 self.commit_menu.aboutToShow.connect(self.update_menu_actions)
908 self.open_recent_menu.aboutToShow.connect(self.build_recent_menu)
909 self.commiteditor.cursor_changed.connect(self.show_cursor_position)
911 self.diffeditor.options_changed.connect(self.statuswidget.refresh)
912 self.diffeditor.up.connect(self.statuswidget.move_up)
913 self.diffeditor.down.connect(self.statuswidget.move_down)
915 self.commiteditor.up.connect(self.statuswidget.move_up)
916 self.commiteditor.down.connect(self.statuswidget.move_down)
918 self.config_actions_changed.connect(
919 lambda names_and_shortcuts: _install_config_actions(
920 context,
921 self.actions_menu,
922 names_and_shortcuts,
924 type=Qt.QueuedConnection,
926 self.init_state(context.settings, self.set_initial_size)
928 # Route command output here
929 Interaction.log_status = self.logwidget.log_status
930 Interaction.log = self.logwidget.log
931 # Focus the status widget; this must be deferred
932 QtCore.QTimer.singleShot(0, self.initialize)
934 def initialize(self):
935 context = self.context
936 git_version = version.git_version_str(context)
937 if git_version:
938 ok = True
939 Interaction.log(
940 git_version + '\n' + N_('git cola version %s') % version.version()
942 else:
943 ok = False
944 error_msg = N_('error: unable to execute git')
945 Interaction.log(error_msg)
947 if ok:
948 self.statuswidget.setFocus()
949 else:
950 title = N_('error: unable to execute git')
951 msg = title
952 details = ''
953 if WIN32:
954 details = git.win32_git_error_hint()
955 Interaction.critical(title, message=msg, details=details)
956 self.context.app.exit(2)
958 def set_initial_size(self):
959 # Default size; this is thrown out when save/restore is used
960 width, height = qtutils.desktop_size()
961 self.resize((width * 3) // 4, height)
962 self.statuswidget.set_initial_size()
963 self.commiteditor.set_initial_size()
965 def set_filter(self, txt):
966 self.statuswidget.set_filter(txt)
968 # Qt overrides
969 def closeEvent(self, event):
970 """Save state in the settings"""
971 commit_msg = self.commiteditor.commit_message(raw=True)
972 self.model.save_commitmsg(msg=commit_msg)
973 for browser in list(self.context.browser_windows):
974 browser.close()
975 standard.MainWindow.closeEvent(self, event)
977 def create_view_menu(self):
978 menu = qtutils.create_menu(N_('View'), self)
979 self.build_view_menu(menu)
980 return menu
982 def build_view_menu(self, menu):
983 menu.clear()
984 menu.addAction(self.browse_action)
985 menu.addAction(self.dag_action)
986 menu.addSeparator()
988 popup_menu = self.createPopupMenu()
989 for menu_action in popup_menu.actions():
990 menu_action.setParent(menu)
991 menu.addAction(menu_action)
993 context = self.context
994 menu_action = menu.addAction(
995 N_('New Toolbar'), partial(toolbar.add_toolbar, context, self)
997 menu_action.setIcon(icons.add())
998 menu.addSeparator()
1000 dockwidgets = [
1001 self.logdock,
1002 self.commitdock,
1003 self.statusdock,
1004 self.diffdock,
1005 self.actionsdock,
1006 self.bookmarksdock,
1007 self.recentdock,
1008 self.branchdock,
1009 self.submodulesdock,
1011 if self.browser_dockable:
1012 dockwidgets.append(self.browserdock)
1014 for dockwidget in dockwidgets:
1015 # Associate the action with the shortcut
1016 toggleview = dockwidget.toggleViewAction()
1017 menu.addAction(toggleview)
1019 menu.addSeparator()
1020 menu.addAction(self.lock_layout_action)
1021 menu.addAction(self.reset_layout_action)
1023 return menu
1025 def contextMenuEvent(self, event):
1026 menu = self.create_view_menu()
1027 menu.exec_(event.globalPos())
1029 def build_recent_menu(self):
1030 cmd = cmds.OpenRepo
1031 context = self.context
1032 settings = context.settings
1033 settings.load()
1034 menu = self.open_recent_menu
1035 menu.clear()
1036 worktree = context.git.worktree()
1038 for entry in settings.recent:
1039 directory = entry['path']
1040 if directory == worktree:
1041 # Omit the current worktree from the "Open Recent" menu.
1042 continue
1043 name = entry['name']
1044 text = f'{name} {uchr(0x2192)} {directory}'
1045 menu.addAction(text, cmds.run(cmd, context, directory))
1047 # Accessors
1048 mode = property(lambda self: self.model.mode)
1050 def _config_updated(self, _source, config, value):
1051 if config == prefs.FONTDIFF:
1052 # The diff font
1053 font = QtGui.QFont()
1054 if not font.fromString(value):
1055 return
1056 self.logwidget.setFont(font)
1057 self.diffeditor.setFont(font)
1058 self.commiteditor.setFont(font)
1060 elif config == prefs.TABWIDTH:
1061 # This can be set locally or globally, so we have to use the
1062 # effective value otherwise we'll update when we shouldn't.
1063 # For example, if this value is overridden locally, and the
1064 # global value is tweaked, we should not update.
1065 value = prefs.tabwidth(self.context)
1066 self.diffeditor.set_tabwidth(value)
1067 self.commiteditor.set_tabwidth(value)
1069 elif config == prefs.EXPANDTAB:
1070 self.commiteditor.set_expandtab(value)
1072 elif config == prefs.LINEBREAK:
1073 # enables automatic line breaks
1074 self.commiteditor.set_linebreak(value)
1076 elif config == prefs.SORT_BOOKMARKS:
1077 self.bookmarksdock.widget().reload_bookmarks()
1079 elif config == prefs.TEXTWIDTH:
1080 # Use the effective value for the same reason as tabwidth.
1081 value = prefs.textwidth(self.context)
1082 self.commiteditor.set_textwidth(value)
1084 elif config == prefs.SHOW_PATH:
1085 # the path in the window title was toggled
1086 self.refresh_window_title()
1088 def start(self, context):
1089 """Do the expensive "get_config_actions()" call in the background"""
1090 # Install .git-config-defined actions
1091 task = qtutils.SimpleTask(self.get_config_actions)
1092 context.runtask.start(task)
1094 def get_config_actions(self):
1095 actions = cfgactions.get_config_actions(self.context)
1096 self.config_actions_changed.emit(actions)
1098 def refresh(self):
1099 """Update the title with the current branch and directory name."""
1100 curbranch = self.model.currentbranch
1101 is_merging = self.model.is_merging
1102 is_rebasing = self.model.is_rebasing
1103 is_applying_patch = self.model.is_applying_patch
1104 is_cherry_picking = self.model.is_rebasing
1106 curdir = core.getcwd()
1107 msg = N_('Repository: %s') % curdir
1108 msg += '\n'
1109 msg += N_('Branch: %s') % curbranch
1111 if is_rebasing:
1112 msg += '\n\n'
1113 msg += N_(
1114 'This repository is currently being rebased.\n'
1115 'Resolve conflicts, commit changes, and run:\n'
1116 ' Rebase > Continue'
1118 elif is_applying_patch:
1119 msg += '\n\n'
1120 msg += N_(
1121 'This repository has unresolved conflicts after applying a patch.\n'
1122 'Resolve conflicts and commit changes.'
1124 elif is_cherry_picking:
1125 msg += '\n\n'
1126 msg += N_(
1127 'This repository is in the middle of a cherry-pick.\n'
1128 'Resolve conflicts and commit changes.'
1130 elif is_merging:
1131 msg += '\n\n'
1132 msg += N_(
1133 'This repository is in the middle of a merge.\n'
1134 'Resolve conflicts and commit changes.'
1137 self.refresh_window_title()
1139 if self.mode == self.model.mode_amend:
1140 self.commit_amend_action.setChecked(True)
1141 else:
1142 self.commit_amend_action.setChecked(False)
1144 self.commitdock.setToolTip(msg)
1146 self.actionswidget.set_mode(self.mode)
1147 self.commiteditor.set_mode(self.mode)
1148 self.statuswidget.set_mode(self.mode)
1150 self.update_actions()
1152 def refresh_window_title(self):
1153 """Refresh the window title when state changes"""
1154 alerts = []
1156 project = self.model.project
1157 curbranch = self.model.currentbranch
1158 is_cherry_picking = self.model.is_cherry_picking
1159 is_merging = self.model.is_merging
1160 is_rebasing = self.model.is_rebasing
1161 is_applying_patch = self.model.is_applying_patch
1162 is_diff_mode = self.model.is_diff_mode()
1163 is_amend_mode = self.mode == self.model.mode_amend
1165 prefix = uchr(0xAB)
1166 suffix = uchr(0xBB)
1168 if is_amend_mode:
1169 alerts.append(N_('Amending'))
1170 elif is_diff_mode:
1171 alerts.append(N_('Diff Mode'))
1172 elif is_cherry_picking:
1173 alerts.append(N_('Cherry-picking'))
1174 elif is_merging:
1175 alerts.append(N_('Merging'))
1176 elif is_rebasing:
1177 alerts.append(N_('Rebasing'))
1178 elif is_applying_patch:
1179 alerts.append(N_('Applying Patch'))
1181 if alerts:
1182 alert_text = (prefix + ' %s ' + suffix + ' ') % ', '.join(alerts)
1183 else:
1184 alert_text = ''
1186 if self.model.cfg.get(prefs.SHOW_PATH, True):
1187 path_text = self.git.worktree()
1188 else:
1189 path_text = ''
1191 title = f'{project}: {curbranch} {alert_text}{path_text}'
1192 self.setWindowTitle(title)
1194 def update_actions(self):
1195 is_rebasing = self.model.is_rebasing
1196 self.rebase_group.setEnabled(is_rebasing)
1198 enabled = not self.model.is_empty_repository()
1199 self.rename_branch_action.setEnabled(enabled)
1200 self.delete_branch_action.setEnabled(enabled)
1202 self.annex_init_action.setEnabled(not self.model.annex)
1203 self.lfs_init_action.setEnabled(not self.model.lfs)
1204 self.merge_abort_action.setEnabled(self.model.is_merging)
1205 self.cherry_pick_abort_action.setEnabled(self.model.is_cherry_picking)
1206 self.apply_patches_continue_action.setEnabled(self.model.is_applying_patch)
1207 self.apply_patches_skip_action.setEnabled(self.model.is_applying_patch)
1208 self.apply_patches_abort_action.setEnabled(self.model.is_applying_patch)
1210 diff_mode = self.model.mode == self.model.mode_diff
1211 self.exit_diff_mode_action.setEnabled(diff_mode)
1213 def update_menu_actions(self):
1214 # Enable the Prepare Commit Message action if the hook exists
1215 hook = gitcmds.prepare_commit_message_hook(self.context)
1216 enabled = os.path.exists(hook)
1217 self.prepare_commitmsg_hook_action.setEnabled(enabled)
1219 def export_state(self):
1220 state = standard.MainWindow.export_state(self)
1221 show_status_filter = self.statuswidget.filter_widget.isVisible()
1222 state['show_status_filter'] = show_status_filter
1223 state['toolbars'] = self.toolbar_state.export_state()
1224 state['ref_sort'] = self.model.ref_sort
1225 self.diffviewer.export_state(state)
1227 return state
1229 def apply_state(self, state):
1230 """Imports data for save/restore"""
1231 base_ok = standard.MainWindow.apply_state(self, state)
1232 lock_layout = state.get('lock_layout', False)
1233 self.lock_layout_action.setChecked(lock_layout)
1235 show_status_filter = state.get('show_status_filter', False)
1236 self.statuswidget.filter_widget.setVisible(show_status_filter)
1238 toolbars = state.get('toolbars', [])
1239 self.toolbar_state.apply_state(toolbars)
1241 sort_key = state.get('ref_sort', 0)
1242 self.model.set_ref_sort(sort_key)
1244 diff_ok = self.diffviewer.apply_state(state)
1245 return base_ok and diff_ok
1247 def setup_dockwidget_view_menu(self):
1248 # Hotkeys for toggling the dock widgets
1249 if utils.is_darwin():
1250 optkey = 'Meta'
1251 else:
1252 optkey = 'Ctrl'
1253 dockwidgets = (
1254 (optkey + '+0', self.logdock),
1255 (optkey + '+1', self.commitdock),
1256 (optkey + '+2', self.statusdock),
1257 (optkey + '+3', self.diffdock),
1258 (optkey + '+4', self.actionsdock),
1259 (optkey + '+5', self.bookmarksdock),
1260 (optkey + '+6', self.recentdock),
1261 (optkey + '+7', self.branchdock),
1262 (optkey + '+8', self.submodulesdock),
1264 for shortcut, dockwidget in dockwidgets:
1265 # Associate the action with the shortcut
1266 toggleview = dockwidget.toggleViewAction()
1267 toggleview.setShortcut('Shift+' + shortcut)
1269 def showdock(show, dockwidget=dockwidget):
1270 if show:
1271 dockwidget.raise_()
1272 dockwidget.widget().setFocus()
1273 else:
1274 self.setFocus()
1276 self.addAction(toggleview)
1277 qtutils.connect_action_bool(toggleview, showdock)
1279 # Create a new shortcut Shift+<shortcut> that gives focus
1280 toggleview = QtWidgets.QAction(self)
1281 toggleview.setShortcut(shortcut)
1283 def focusdock(dockwidget=dockwidget):
1284 focus_dock(dockwidget)
1286 self.addAction(toggleview)
1287 qtutils.connect_action(toggleview, focusdock)
1289 # These widgets warrant home-row hotkey status
1290 qtutils.add_action(
1291 self,
1292 'Focus Commit Message',
1293 lambda: focus_dock(self.commitdock),
1294 hotkeys.FOCUS,
1297 qtutils.add_action(
1298 self,
1299 'Focus Status Window',
1300 lambda: focus_dock(self.statusdock),
1301 hotkeys.FOCUS_STATUS,
1304 qtutils.add_action(
1305 self,
1306 'Focus Diff Editor',
1307 lambda: focus_dock(self.diffdock),
1308 hotkeys.FOCUS_DIFF,
1311 def git_dag(self):
1312 self.dag = dag.git_dag(self.context, existing_view=self.dag)
1314 def show_cursor_position(self, rows, cols):
1315 display_content = '%02d:%02d' % (rows, cols)
1316 css = """
1317 <style>
1318 .good {
1320 .first-warning {
1321 color: black;
1322 background-color: yellow;
1324 .second-warning {
1325 color: black;
1326 background-color: #f83;
1328 .error {
1329 color: white;
1330 background-color: red;
1332 </style>
1335 if cols > 78:
1336 cls = 'error'
1337 elif cols > 72:
1338 cls = 'second-warning'
1339 elif cols > 64:
1340 cls = 'first-warning'
1341 else:
1342 cls = 'good'
1343 div = f'<div class="{cls}">{display_content}</div>'
1344 self.position_label.setText(css + div)
1347 class FocusProxy:
1348 """Proxy over child widgets and operate on the focused widget"""
1350 def __init__(self, *widgets):
1351 self.widgets = widgets
1352 self.overrides = {}
1354 def override(self, name, widgets):
1355 self.overrides[name] = widgets
1357 def focus(self, name):
1358 """Return the currently focused widget"""
1359 widgets = self.overrides.get(name, self.widgets)
1360 # The parent must be the parent of all the proxied widgets
1361 parent = widgets[0]
1362 # The first widget is used as a fallback
1363 fallback = widgets[1]
1364 # We ignore the parent when delegating to child widgets
1365 widgets = widgets[1:]
1367 focus = parent.focusWidget()
1368 if focus not in widgets:
1369 focus = fallback
1370 return focus
1372 def __getattr__(self, name):
1373 """Return a callback that calls a common child method"""
1375 def callback():
1376 focus = self.focus(name)
1377 func = getattr(focus, name, None)
1378 if func:
1379 func()
1381 return callback
1383 def delete(self):
1384 """Specialized delete() to deal with QLineEdit vs. QTextEdit"""
1385 focus = self.focus('delete')
1386 if hasattr(focus, 'del_'):
1387 focus.del_()
1388 elif hasattr(focus, 'textCursor'):
1389 focus.textCursor().deleteChar()
1392 def show_dock(dockwidget):
1393 dockwidget.raise_()
1394 dockwidget.widget().setFocus()
1397 def focus_dock(dockwidget):
1398 if get(dockwidget.toggleViewAction()):
1399 show_dock(dockwidget)
1400 else:
1401 dockwidget.toggleViewAction().trigger()
1404 def _install_config_actions(context, menu, names_and_shortcuts):
1405 """Install .gitconfig-defined actions"""
1406 if not names_and_shortcuts:
1407 return
1408 menu.addSeparator()
1409 cache = {}
1410 for name, shortcut in names_and_shortcuts:
1411 sub_menu, action_name = build_menus(name, menu, cache)
1412 callback = cmds.run(cmds.RunConfigAction, context, name)
1413 menu_action = sub_menu.addAction(action_name, callback)
1414 if shortcut:
1415 menu_action.setShortcut(shortcut)
1418 def build_menus(name, menu, cache):
1419 """Create a chain of QMenu entries parented under a root QMenu
1421 A name of "a/b/c" create a menu chain of menu -> QMenu("a") -> QMenu("b")
1422 and returns a tuple of (QMenu("b"), "c").
1424 :param name: The full entry path, ex: "a/b/c" where "a/b" is the menu chain.
1425 :param menu: The root menu under which to create the menu chain.
1426 :param cache: A dict cache of previously created menus to avoid duplicates.
1429 # NOTE: utils.split() and friends are used instead of os.path.split() because
1430 # slash '/' is the only supported "<menu>/name" separator. Use of os.path.split()
1431 # would introduce differences in behavior across platforms.
1433 # If the menu_path is empty then no parent menus need to be created.
1434 # The action will be added to the root menu.
1435 menu_path, text = utils.split(utils.normalize_slash(name))
1437 if not menu_path:
1438 return (menu, text)
1439 # When menu_path contains ex: "a/b" we will create two menus: "a" and "b".
1440 # The root menu is the parent of "a" and "a" is the parent of "b".
1441 # The menu returned to the caller is "b".
1443 # Loop over the individual menu basenames alongside the full subpath returned by
1444 # pathset(). The subpath is a cache key for finding previously created menus.
1445 menu_names = utils.splitpath(menu_path) # ['a', 'b']
1446 menu_pathset = utils.pathset(menu_path) # ['a', 'a/b']
1447 for menu_name, menu_id in zip(menu_names, menu_pathset):
1448 try:
1449 menu = cache[menu_id]
1450 except KeyError:
1451 menu = cache[menu_id] = menu.addMenu(menu_name)
1453 return (menu, text)