1 """Main UI for authoring commits and other Git Cola interactions"""
3 from functools
import partial
5 from qtpy
import QtCore
7 from qtpy
import QtWidgets
8 from qtpy
.QtCore
import Qt
9 from qtpy
.QtCore
import Signal
11 from ..compat
import uchr
12 from ..compat
import WIN32
14 from ..interaction
import Interaction
15 from ..models
import prefs
16 from ..qtutils
import get
19 from .. import guicmds
21 from .. import gitcmds
22 from .. import hotkeys
24 from .. import qtutils
25 from .. import resources
27 from .. import version
31 from . import bookmarks
33 from . import submodules
35 from . import cfgactions
37 from . import commitmsg
40 from . import createbranch
41 from . import createtag
46 from . import editremotes
50 from . import prefs
as prefs_widget
54 from . import standard
60 class MainView(standard
.MainWindow
):
61 config_actions_changed
= Signal(object)
63 def __init__(self
, context
, parent
=None):
64 standard
.MainWindow
.__init
__(self
, parent
)
65 self
.setAttribute(Qt
.WA_DeleteOnClose
)
67 self
.context
= context
68 self
.git
= context
.git
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
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
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(
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(
110 func
=lambda dock
: bookmarks
.bookmark(context
, dock
),
112 bookmarkswidget
= self
.bookmarksdock
.widget()
113 qtutils
.hide_dock(self
.bookmarksdock
)
115 self
.recentdock
= create_dock(
119 func
=lambda dock
: bookmarks
.recent(context
, dock
),
121 recentwidget
= self
.recentdock
.widget()
122 qtutils
.hide_dock(self
.recentdock
)
123 bookmarkswidget
.connect_to(recentwidget
)
126 self
.branchdock
= create_dock(
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(
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
)
166 self
.logwidget
= log
.LogWidget(context
)
167 self
.logdock
= create_dock(
168 'Console', N_('Console'), self
, widget
=self
.logwidget
170 qtutils
.hide_dock(self
.logdock
)
172 # "Diff Viewer" widget
173 self
.diffdock
= create_dock(
177 func
=lambda dock
: diff
.Viewer(context
, parent
=dock
),
179 self
.diffviewer
= self
.diffdock
.widget()
180 self
.diffviewer
.set_diff_type(self
.model
.diff_type
)
181 self
.diffviewer
.enable_filename_tracking()
182 self
.diffeditor
= self
.diffviewer
.text
183 titlebar
= self
.diffdock
.titleBarWidget()
184 titlebar
.add_title_widget(self
.diffviewer
.filename
)
185 titlebar
.add_corner_widget(self
.diffviewer
.options
)
188 add_action
= qtutils
.add_action
189 add_action_bool
= qtutils
.add_action_bool
191 self
.commit_amend_action
= add_action_bool(
193 N_('Amend Last Commit'),
194 partial(cmds
.do
, cmds
.AmendMode
, context
),
197 self
.commit_amend_action
.setIcon(icons
.edit())
198 self
.commit_amend_action
.setShortcut(hotkeys
.AMEND
)
199 self
.commit_amend_action
.setShortcutContext(Qt
.WidgetShortcut
)
201 self
.unstage_all_action
= add_action(
202 self
, N_('Unstage All'), cmds
.run(cmds
.UnstageAll
, context
)
204 self
.unstage_all_action
.setIcon(icons
.remove())
206 self
.undo_commit_action
= add_action(
207 self
, N_('Undo Last Commit'), cmds
.run(cmds
.UndoLastCommit
, context
)
209 self
.undo_commit_action
.setIcon(icons
.style_dialog_discard())
211 self
.unstage_selected_action
= add_action(
212 self
, N_('Unstage'), cmds
.run(cmds
.UnstageSelected
, context
)
214 self
.unstage_selected_action
.setIcon(icons
.remove())
216 self
.show_diffstat_action
= add_action(
217 self
, N_('Diffstat'), self
.statuswidget
.select_header
, hotkeys
.DIFFSTAT
219 self
.show_diffstat_action
.setIcon(icons
.diff())
221 self
.stage_modified_action
= add_action(
223 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(
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(
244 N_('Abort Applying Patches...'),
245 N_('Abort the current "git am" patch session'),
246 cmds
.run(cmds
.AbortApplyPatch
, context
),
248 self
.apply_patches_abort_action
.setIcon(icons
.style_dialog_discard())
250 self
.apply_patches_continue_action
= qtutils
.add_action_with_tooltip(
252 N_('Continue Applying Patches'),
253 N_('Commit the current state and continue applying patches'),
254 cmds
.run(cmds
.ApplyPatchesContinue
, context
),
256 self
.apply_patches_continue_action
.setIcon(icons
.commit())
258 self
.apply_patches_skip_action
= qtutils
.add_action_with_tooltip(
260 N_('Skip Current Patch'),
261 N_('Skip applying the current patch and continue applying patches'),
262 cmds
.run(cmds
.ApplyPatchesContinue
, context
),
264 self
.apply_patches_skip_action
.setIcon(icons
.discard())
266 self
.export_patches_action
= add_action(
268 N_('Export Patches...'),
269 partial(guicmds
.export_patches
, context
),
272 self
.export_patches_action
.setIcon(icons
.save())
274 self
.new_repository_action
= add_action(
275 self
, N_('New Repository...'), partial(guicmds
.open_new_repo
, context
)
277 self
.new_repository_action
.setIcon(icons
.new())
279 self
.new_bare_repository_action
= add_action(
280 self
, N_('New Bare Repository...'), partial(guicmds
.new_bare_repo
, context
)
282 self
.new_bare_repository_action
.setIcon(icons
.new())
284 prefs_func
= partial(
285 prefs_widget
.preferences
, context
, parent
=self
, model
=prefs_model
287 self
.preferences_action
= add_action(
288 self
, N_('Preferences'), prefs_func
, QtGui
.QKeySequence
.Preferences
290 self
.preferences_action
.setIcon(icons
.configure())
292 self
.edit_remotes_action
= add_action(
293 self
, N_('Edit Remotes...'), partial(editremotes
.editor
, context
)
295 self
.edit_remotes_action
.setIcon(icons
.edit())
297 self
.rescan_action
= add_action(
300 cmds
.run(cmds
.Refresh
, context
),
301 *hotkeys
.REFRESH_HOTKEYS
,
303 self
.rescan_action
.setIcon(icons
.sync())
305 self
.find_files_action
= add_action(
308 partial(finder
.finder
, context
),
311 self
.find_files_action
.setIcon(icons
.search())
313 self
.browse_recently_modified_action
= add_action(
315 N_('Recently Modified Files...'),
316 partial(recent
.browse_recent_files
, context
),
317 hotkeys
.EDIT_SECONDARY
,
319 self
.browse_recently_modified_action
.setIcon(icons
.directory())
321 self
.cherry_pick_action
= add_action(
323 N_('Cherry-Pick...'),
324 partial(guicmds
.cherry_pick
, context
),
327 self
.cherry_pick_action
.setIcon(icons
.cherry_pick())
328 self
.cherry_pick_abort_action
= add_action(
329 self
, N_('Abort Cherry-Pick...'), cmds
.run(cmds
.AbortCherryPick
, context
)
331 self
.cherry_pick_abort_action
.setIcon(icons
.style_dialog_discard())
333 self
.load_commitmsg_action
= add_action(
334 self
, N_('Load Commit Message...'), partial(guicmds
.load_commitmsg
, context
)
336 self
.load_commitmsg_action
.setIcon(icons
.file_text())
338 self
.prepare_commitmsg_hook_action
= add_action(
340 N_('Prepare Commit Message'),
341 cmds
.run(cmds
.PrepareCommitMessageHook
, context
),
342 hotkeys
.PREPARE_COMMIT_MESSAGE
,
345 self
.save_tarball_action
= add_action(
346 self
, N_('Save As Tarball/Zip...'), partial(archive
.save_archive
, context
)
348 self
.save_tarball_action
.setIcon(icons
.file_zip())
350 self
.quit_action
= add_action(self
, N_('Quit'), self
.close
, hotkeys
.QUIT
)
352 self
.grep_action
= add_action(
353 self
, N_('Grep'), partial(grep
.grep
, context
), hotkeys
.GREP
355 self
.grep_action
.setIcon(icons
.search())
357 self
.merge_local_action
= add_action(
358 self
, N_('Merge...'), partial(merge
.local_merge
, context
), hotkeys
.MERGE
360 self
.merge_local_action
.setIcon(icons
.merge())
362 self
.merge_abort_action
= add_action(
363 self
, N_('Abort Merge...'), cmds
.run(cmds
.AbortMerge
, context
)
365 self
.merge_abort_action
.setIcon(icons
.style_dialog_discard())
367 self
.update_submodules_action
= add_action(
369 N_('Update All Submodules...'),
370 cmds
.run(cmds
.SubmodulesUpdate
, context
),
372 self
.update_submodules_action
.setIcon(icons
.sync())
374 self
.add_submodule_action
= add_action(
376 N_('Add Submodule...'),
377 partial(submodules
.add_submodule
, context
, parent
=self
),
379 self
.add_submodule_action
.setIcon(icons
.add())
381 self
.fetch_action
= qtutils
.add_action_with_tooltip(
384 N_('Fetch from one or more remotes using "git fetch"'),
385 partial(remote
.fetch
, context
),
388 self
.fetch_action
.setIcon(icons
.download())
390 self
.push_action
= qtutils
.add_action_with_tooltip(
393 N_('Push to one or more remotes using "git push"'),
394 partial(remote
.push
, context
),
397 self
.push_action
.setIcon(icons
.push())
399 self
.pull_action
= qtutils
.add_action_with_tooltip(
402 N_('Integrate changes using "git pull"'),
403 partial(remote
.pull
, context
),
406 self
.pull_action
.setIcon(icons
.pull())
408 self
.open_repo_action
= add_action(
409 self
, N_('Open...'), partial(guicmds
.open_repo
, context
), hotkeys
.OPEN
411 self
.open_repo_action
.setIcon(icons
.folder())
413 self
.open_repo_new_action
= add_action(
415 N_('Open in New Window...'),
416 partial(guicmds
.open_repo_in_new_window
, context
),
418 self
.open_repo_new_action
.setIcon(icons
.folder())
420 self
.stash_action
= qtutils
.add_action_with_tooltip(
423 N_('Temporarily stash away uncommitted changes using "git stash"'),
424 partial(stash
.view
, context
),
427 self
.stash_action
.setIcon(icons
.commit())
429 self
.reset_soft_action
= qtutils
.add_action_with_tooltip(
431 N_('Reset Branch (Soft)'),
432 cmds
.ResetSoft
.tooltip('<commit>'),
433 partial(guicmds
.reset_soft
, context
),
435 self
.reset_soft_action
.setIcon(icons
.style_dialog_reset())
437 self
.reset_mixed_action
= qtutils
.add_action_with_tooltip(
439 N_('Reset Branch and Stage (Mixed)'),
440 cmds
.ResetMixed
.tooltip('<commit>'),
441 partial(guicmds
.reset_mixed
, context
),
443 self
.reset_mixed_action
.setIcon(icons
.style_dialog_reset())
445 self
.reset_keep_action
= qtutils
.add_action_with_tooltip(
447 N_('Restore Worktree and Reset All (Keep Unstaged Changes)'),
448 cmds
.ResetKeep
.tooltip('<commit>'),
449 partial(guicmds
.reset_keep
, context
),
451 self
.reset_keep_action
.setIcon(icons
.style_dialog_reset())
453 self
.reset_merge_action
= qtutils
.add_action_with_tooltip(
455 N_('Restore Worktree and Reset All (Merge)'),
456 cmds
.ResetMerge
.tooltip('<commit>'),
457 partial(guicmds
.reset_merge
, context
),
459 self
.reset_merge_action
.setIcon(icons
.style_dialog_reset())
461 self
.reset_hard_action
= qtutils
.add_action_with_tooltip(
463 N_('Restore Worktree and Reset All (Hard)'),
464 cmds
.ResetHard
.tooltip('<commit>'),
465 partial(guicmds
.reset_hard
, context
),
467 self
.reset_hard_action
.setIcon(icons
.style_dialog_reset())
469 self
.restore_worktree_action
= qtutils
.add_action_with_tooltip(
471 N_('Restore Worktree'),
472 cmds
.RestoreWorktree
.tooltip('<commit>'),
473 partial(guicmds
.restore_worktree
, context
),
475 self
.restore_worktree_action
.setIcon(icons
.edit())
477 self
.clone_repo_action
= add_action(
478 self
, N_('Clone...'), partial(clone
.clone
, context
)
480 self
.clone_repo_action
.setIcon(icons
.repo())
482 self
.help_docs_action
= add_action(
485 resources
.show_html_docs
,
486 QtGui
.QKeySequence
.HelpContents
,
489 self
.help_shortcuts_action
= add_action(
490 self
, N_('Keyboard Shortcuts'), about
.show_shortcuts
, hotkeys
.QUESTION
493 self
.visualize_current_action
= add_action(
495 N_('Visualize Current Branch...'),
496 cmds
.run(cmds
.VisualizeCurrent
, context
),
498 self
.visualize_current_action
.setIcon(icons
.visualize())
500 self
.visualize_all_action
= add_action(
501 self
, N_('Visualize All Branches...'), cmds
.run(cmds
.VisualizeAll
, context
)
503 self
.visualize_all_action
.setIcon(icons
.visualize())
505 self
.search_commits_action
= add_action(
506 self
, N_('Search...'), partial(search
.search
, context
)
508 self
.search_commits_action
.setIcon(icons
.search())
510 self
.browse_branch_action
= add_action(
512 N_('Browse Current Branch...'),
513 partial(guicmds
.browse_current
, context
),
515 self
.browse_branch_action
.setIcon(icons
.directory())
517 self
.browse_other_branch_action
= add_action(
518 self
, N_('Browse Other Branch...'), partial(guicmds
.browse_other
, context
)
520 self
.browse_other_branch_action
.setIcon(icons
.directory())
522 self
.load_commitmsg_template_action
= add_action(
524 N_('Get Commit Message Template'),
525 cmds
.run(cmds
.LoadCommitMessageFromTemplate
, context
),
527 self
.load_commitmsg_template_action
.setIcon(icons
.style_dialog_apply())
529 self
.help_about_action
= add_action(
530 self
, N_('About'), partial(about
.about_dialog
, context
)
533 self
.diff_against_commit_action
= add_action(
535 N_('Against Commit... (Diff Mode)'),
536 partial(guicmds
.diff_against_commit
, context
),
538 self
.diff_against_commit_action
.setIcon(icons
.compare())
540 self
.exit_diff_mode_action
= add_action(
541 self
, N_('Exit Diff Mode'), cmds
.run(cmds
.ResetMode
, context
)
543 self
.exit_diff_mode_action
.setIcon(icons
.compare())
545 self
.diff_expression_action
= add_action(
546 self
, N_('Expression...'), partial(guicmds
.diff_expression
, context
)
548 self
.diff_expression_action
.setIcon(icons
.compare())
550 self
.branch_compare_action
= add_action(
551 self
, N_('Branches...'), partial(compare
.compare_branches
, context
)
553 self
.branch_compare_action
.setIcon(icons
.compare())
555 self
.create_tag_action
= add_action(
558 partial(createtag
.create_tag
, context
),
560 self
.create_tag_action
.setIcon(icons
.tag())
562 self
.create_branch_action
= add_action(
565 partial(createbranch
.create_new_branch
, context
),
568 self
.create_branch_action
.setIcon(icons
.branch())
570 self
.delete_branch_action
= add_action(
571 self
, N_('Delete...'), partial(guicmds
.delete_branch
, context
)
573 self
.delete_branch_action
.setIcon(icons
.discard())
575 self
.delete_remote_branch_action
= add_action(
577 N_('Delete Remote Branch...'),
578 partial(guicmds
.delete_remote_branch
, context
),
580 self
.delete_remote_branch_action
.setIcon(icons
.discard())
582 self
.rename_branch_action
= add_action(
583 self
, N_('Rename Branch...'), partial(guicmds
.rename_branch
, context
)
585 self
.rename_branch_action
.setIcon(icons
.edit())
587 self
.checkout_branch_action
= add_action(
590 partial(guicmds
.checkout_branch
, context
),
593 self
.checkout_branch_action
.setIcon(icons
.branch())
595 self
.branch_review_action
= add_action(
596 self
, N_('Review...'), partial(guicmds
.review_branch
, context
)
598 self
.branch_review_action
.setIcon(icons
.compare())
600 self
.browse_action
= add_action(
601 self
, N_('File Browser...'), partial(browse
.worktree_browser
, context
)
603 self
.browse_action
.setIcon(icons
.cola())
605 self
.dag_action
= add_action(self
, N_('DAG...'), self
.git_dag
)
606 self
.dag_action
.setIcon(icons
.cola())
608 self
.rebase_start_action
= add_action(
610 N_('Start Interactive Rebase...'),
611 cmds
.run(cmds
.Rebase
, context
),
612 hotkeys
.REBASE_START_AND_CONTINUE
,
614 self
.rebase_start_action
.setIcon(icons
.play())
616 self
.rebase_edit_todo_action
= add_action(
617 self
, N_('Edit...'), cmds
.run(cmds
.RebaseEditTodo
, context
)
619 self
.rebase_edit_todo_action
.setIcon(icons
.edit())
621 self
.rebase_continue_action
= add_action(
624 cmds
.run(cmds
.RebaseContinue
, context
),
625 hotkeys
.REBASE_START_AND_CONTINUE
,
627 self
.rebase_continue_action
.setIcon(icons
.play())
629 self
.rebase_skip_action
= add_action(
630 self
, N_('Skip Current Patch'), cmds
.run(cmds
.RebaseSkip
, context
)
632 self
.rebase_skip_action
.setIcon(icons
.delete())
634 self
.rebase_abort_action
= add_action(
635 self
, N_('Abort'), cmds
.run(cmds
.RebaseAbort
, context
)
637 self
.rebase_abort_action
.setIcon(icons
.close())
639 # For "Start Rebase" only, reverse the first argument to setEnabled()
640 # so that we can operate on it as a group.
641 # We can do this because can_rebase == not is_rebasing
642 self
.rebase_start_action_proxy
= utils
.Proxy(
643 self
.rebase_start_action
,
644 setEnabled
=lambda x
: self
.rebase_start_action
.setEnabled(not x
),
647 self
.rebase_group
= utils
.Group(
648 self
.rebase_start_action_proxy
,
649 self
.rebase_edit_todo_action
,
650 self
.rebase_continue_action
,
651 self
.rebase_skip_action
,
652 self
.rebase_abort_action
,
655 self
.annex_init_action
= qtutils
.add_action(
656 self
, N_('Initialize Git Annex'), cmds
.run(cmds
.AnnexInit
, context
)
659 self
.lfs_init_action
= qtutils
.add_action(
660 self
, N_('Initialize Git LFS'), cmds
.run(cmds
.LFSInstall
, context
)
663 self
.lock_layout_action
= add_action_bool(
664 self
, N_('Lock Layout'), self
.set_lock_layout
, False
667 self
.reset_layout_action
= add_action(
668 self
, N_('Reset Layout'), self
.reset_layout
671 self
.quick_repository_search
= add_action(
674 lambda: guicmds
.open_quick_repo_search(self
.context
, parent
=self
),
675 hotkeys
.OPEN_REPO_SEARCH
,
677 self
.quick_repository_search
.setIcon(icons
.search())
679 self
.terminal_action
= common
.terminal_action(
680 context
, self
, hotkey
=hotkeys
.TERMINAL
683 # Create the application menu
684 self
.menubar
= QtWidgets
.QMenuBar(self
)
685 self
.setMenuBar(self
.menubar
)
688 add_menu
= qtutils
.add_menu
689 self
.file_menu
= add_menu(N_('&File'), self
.menubar
)
690 self
.file_menu
.addAction(self
.quick_repository_search
)
691 # File->Open Recent menu
692 self
.open_recent_menu
= self
.file_menu
.addMenu(N_('Open Recent'))
693 self
.open_recent_menu
.setIcon(icons
.folder())
694 self
.file_menu
.addAction(self
.open_repo_action
)
695 self
.file_menu
.addAction(self
.open_repo_new_action
)
696 self
.file_menu
.addSeparator()
697 self
.file_menu
.addAction(self
.new_repository_action
)
698 self
.file_menu
.addAction(self
.new_bare_repository_action
)
699 self
.file_menu
.addAction(self
.clone_repo_action
)
700 self
.file_menu
.addSeparator()
701 self
.file_menu
.addAction(self
.rescan_action
)
702 self
.file_menu
.addAction(self
.find_files_action
)
703 self
.file_menu
.addAction(self
.edit_remotes_action
)
704 self
.file_menu
.addAction(self
.browse_recently_modified_action
)
705 self
.file_menu
.addSeparator()
706 self
.file_menu
.addAction(self
.save_tarball_action
)
708 self
.patches_menu
= self
.file_menu
.addMenu(N_('Patches'))
709 self
.patches_menu
.setIcon(icons
.diff())
710 self
.patches_menu
.addAction(self
.export_patches_action
)
711 self
.patches_menu
.addAction(self
.apply_patches_action
)
712 self
.patches_menu
.addAction(self
.apply_patches_continue_action
)
713 self
.patches_menu
.addAction(self
.apply_patches_skip_action
)
714 self
.patches_menu
.addAction(self
.apply_patches_abort_action
)
716 # Git Annex / Git LFS
717 annex
= core
.find_executable('git-annex')
718 lfs
= core
.find_executable('git-lfs')
720 self
.file_menu
.addSeparator()
722 self
.file_menu
.addAction(self
.annex_init_action
)
724 self
.file_menu
.addAction(self
.lfs_init_action
)
726 self
.file_menu
.addSeparator()
727 self
.file_menu
.addAction(self
.preferences_action
)
728 self
.file_menu
.addAction(self
.quit_action
)
731 self
.edit_proxy
= edit_proxy
= FocusProxy(
732 editor
, editor
.summary
, editor
.description
740 bookmarkswidget
.tree
,
743 select_widgets
= copy_widgets
+ (self
.statuswidget
.tree
,)
744 edit_proxy
.override('copy', copy_widgets
)
745 edit_proxy
.override('selectAll', select_widgets
)
747 edit_menu
= self
.edit_menu
= add_menu(N_('&Edit'), self
.menubar
)
748 undo
= add_action(edit_menu
, N_('Undo'), edit_proxy
.undo
, hotkeys
.UNDO
)
749 undo
.setIcon(icons
.undo())
750 redo
= add_action(edit_menu
, N_('Redo'), edit_proxy
.redo
, hotkeys
.REDO
)
751 redo
.setIcon(icons
.redo())
752 edit_menu
.addSeparator()
753 cut
= add_action(edit_menu
, N_('Cut'), edit_proxy
.cut
, hotkeys
.CUT
)
754 cut
.setIcon(icons
.cut())
755 copy
= add_action(edit_menu
, N_('Copy'), edit_proxy
.copy
, hotkeys
.COPY
)
756 copy
.setIcon(icons
.copy())
757 paste
= add_action(edit_menu
, N_('Paste'), edit_proxy
.paste
, hotkeys
.PASTE
)
758 paste
.setIcon(icons
.paste())
759 delete
= add_action(edit_menu
, N_('Delete'), edit_proxy
.delete
, hotkeys
.DELETE
)
760 delete
.setIcon(icons
.delete())
761 edit_menu
.addSeparator()
762 select_all
= add_action(
763 edit_menu
, N_('Select All'), edit_proxy
.selectAll
, hotkeys
.SELECT_ALL
765 select_all
.setIcon(icons
.select_all())
766 edit_menu
.addSeparator()
767 qtutils
.add_menu_actions(edit_menu
, self
.commiteditor
.menu_actions
)
770 self
.actions_menu
= add_menu(N_('Actions'), self
.menubar
)
771 if self
.terminal_action
is not None:
772 self
.actions_menu
.addAction(self
.terminal_action
)
773 self
.actions_menu
.addAction(self
.fetch_action
)
774 self
.actions_menu
.addAction(self
.push_action
)
775 self
.actions_menu
.addAction(self
.pull_action
)
776 self
.actions_menu
.addAction(self
.stash_action
)
777 self
.actions_menu
.addSeparator()
778 self
.actions_menu
.addAction(self
.create_tag_action
)
779 self
.actions_menu
.addAction(self
.cherry_pick_action
)
780 self
.actions_menu
.addAction(self
.cherry_pick_abort_action
)
781 self
.actions_menu
.addAction(self
.merge_local_action
)
782 self
.actions_menu
.addAction(self
.merge_abort_action
)
783 self
.actions_menu
.addSeparator()
784 self
.actions_menu
.addAction(self
.update_submodules_action
)
785 self
.actions_menu
.addAction(self
.add_submodule_action
)
786 self
.actions_menu
.addSeparator()
787 self
.actions_menu
.addAction(self
.grep_action
)
788 self
.actions_menu
.addAction(self
.search_commits_action
)
791 self
.commit_menu
= add_menu(N_('Commit@@verb'), self
.menubar
)
792 self
.commit_menu
.setTitle(N_('Commit@@verb'))
793 self
.commit_menu
.addAction(self
.commiteditor
.commit_action
)
794 self
.commit_menu
.addAction(self
.commit_amend_action
)
795 self
.commit_menu
.addAction(self
.undo_commit_action
)
796 self
.commit_menu
.addSeparator()
797 self
.commit_menu
.addAction(self
.statuswidget
.tree
.process_selection_action
)
798 self
.commit_menu
.addAction(self
.statuswidget
.tree
.stage_or_unstage_all_action
)
799 self
.commit_menu
.addAction(self
.stage_modified_action
)
800 self
.commit_menu
.addAction(self
.stage_untracked_action
)
801 self
.commit_menu
.addSeparator()
802 self
.commit_menu
.addAction(self
.unstage_all_action
)
803 self
.commit_menu
.addAction(self
.unstage_selected_action
)
804 self
.commit_menu
.addSeparator()
805 self
.commit_menu
.addAction(self
.load_commitmsg_action
)
806 self
.commit_menu
.addAction(self
.load_commitmsg_template_action
)
807 self
.commit_menu
.addAction(self
.prepare_commitmsg_hook_action
)
810 self
.diff_menu
= add_menu(N_('Diff'), self
.menubar
)
811 self
.diff_menu
.addAction(self
.diff_expression_action
)
812 self
.diff_menu
.addAction(self
.branch_compare_action
)
813 self
.diff_menu
.addAction(self
.show_diffstat_action
)
814 self
.diff_menu
.addSeparator()
815 self
.diff_menu
.addAction(self
.diff_against_commit_action
)
816 self
.diff_menu
.addAction(self
.exit_diff_mode_action
)
819 self
.branch_menu
= add_menu(N_('Branch'), self
.menubar
)
820 self
.branch_menu
.addAction(self
.branch_review_action
)
821 self
.branch_menu
.addSeparator()
822 self
.branch_menu
.addAction(self
.create_branch_action
)
823 self
.branch_menu
.addAction(self
.checkout_branch_action
)
824 self
.branch_menu
.addAction(self
.delete_branch_action
)
825 self
.branch_menu
.addAction(self
.delete_remote_branch_action
)
826 self
.branch_menu
.addAction(self
.rename_branch_action
)
827 self
.branch_menu
.addSeparator()
828 self
.branch_menu
.addAction(self
.browse_branch_action
)
829 self
.branch_menu
.addAction(self
.browse_other_branch_action
)
830 self
.branch_menu
.addSeparator()
831 self
.branch_menu
.addAction(self
.visualize_current_action
)
832 self
.branch_menu
.addAction(self
.visualize_all_action
)
835 self
.rebase_menu
= add_menu(N_('Rebase'), self
.menubar
)
836 self
.rebase_menu
.addAction(self
.rebase_start_action
)
837 self
.rebase_menu
.addAction(self
.rebase_edit_todo_action
)
838 self
.rebase_menu
.addSeparator()
839 self
.rebase_menu
.addAction(self
.rebase_continue_action
)
840 self
.rebase_menu
.addAction(self
.rebase_skip_action
)
841 self
.rebase_menu
.addSeparator()
842 self
.rebase_menu
.addAction(self
.rebase_abort_action
)
845 self
.reset_menu
= add_menu(N_('Reset'), self
.menubar
)
846 self
.reset_menu
.addAction(self
.unstage_all_action
)
847 self
.reset_menu
.addAction(self
.undo_commit_action
)
848 self
.reset_menu
.addSeparator()
849 self
.reset_menu
.addAction(self
.reset_soft_action
)
850 self
.reset_menu
.addAction(self
.reset_mixed_action
)
851 self
.reset_menu
.addAction(self
.restore_worktree_action
)
852 self
.reset_menu
.addSeparator()
853 self
.reset_menu
.addAction(self
.reset_keep_action
)
854 self
.reset_menu
.addAction(self
.reset_merge_action
)
855 self
.reset_menu
.addAction(self
.reset_hard_action
)
858 self
.view_menu
= add_menu(N_('View'), self
.menubar
)
859 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
)
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(
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
)
940 git_version
+ '\n' + N_('git cola version %s') % version
.version()
944 error_msg
= N_('error: unable to execute git')
945 Interaction
.log(error_msg
)
948 self
.statuswidget
.setFocus()
950 title
= N_('error: unable to execute git')
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
)
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
):
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
)
982 def build_view_menu(self
, menu
):
984 menu
.addAction(self
.browse_action
)
985 menu
.addAction(self
.dag_action
)
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())
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
)
1020 menu
.addAction(self
.lock_layout_action
)
1021 menu
.addAction(self
.reset_layout_action
)
1025 def contextMenuEvent(self
, event
):
1026 menu
= self
.create_view_menu()
1027 menu
.exec_(event
.globalPos())
1029 def build_recent_menu(self
):
1031 context
= self
.context
1032 settings
= context
.settings
1034 menu
= self
.open_recent_menu
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.
1043 name
= entry
['name']
1044 text
= f
'{name} {uchr(0x2192)} {directory}'
1045 menu
.addAction(text
, cmds
.run(cmd
, context
, directory
))
1048 mode
= property(lambda self
: self
.model
.mode
)
1050 def _config_updated(self
, _source
, config
, value
):
1051 if config
== prefs
.FONTDIFF
:
1053 font
= QtGui
.QFont()
1054 if not font
.fromString(value
):
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
)
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
1109 msg
+= N_('Branch: %s') % curbranch
1114 'This repository is currently being rebased.\n'
1115 'Resolve conflicts, commit changes, and run:\n'
1116 ' Rebase > Continue'
1118 elif is_applying_patch
:
1121 'This repository has unresolved conflicts after applying a patch.\n'
1122 'Resolve conflicts and commit changes.'
1124 elif is_cherry_picking
:
1127 'This repository is in the middle of a cherry-pick.\n'
1128 'Resolve conflicts and commit changes.'
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)
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"""
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
1169 alerts
.append(N_('Amending'))
1171 alerts
.append(N_('Diff Mode'))
1172 elif is_cherry_picking
:
1173 alerts
.append(N_('Cherry-picking'))
1175 alerts
.append(N_('Merging'))
1177 alerts
.append(N_('Rebasing'))
1178 elif is_applying_patch
:
1179 alerts
.append(N_('Applying Patch'))
1182 alert_text
= (prefix
+ ' %s ' + suffix
+ ' ') % ', '.join(alerts
)
1186 if self
.model
.cfg
.get(prefs
.SHOW_PATH
, True):
1187 path_text
= self
.git
.worktree()
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
)
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():
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
):
1272 dockwidget
.widget().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
1292 'Focus Commit Message',
1293 lambda: focus_dock(self
.commitdock
),
1299 'Focus Status Window',
1300 lambda: focus_dock(self
.statusdock
),
1301 hotkeys
.FOCUS_STATUS
,
1306 'Focus Diff Editor',
1307 lambda: focus_dock(self
.diffdock
),
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
)
1322 background-color: yellow;
1326 background-color: #f83;
1330 background-color: red;
1338 cls
= 'second-warning'
1340 cls
= 'first-warning'
1343 div
= f
'<div class="{cls}">{display_content}</div>'
1344 self
.position_label
.setText(css
+ div
)
1348 """Proxy over child widgets and operate on the focused widget"""
1350 def __init__(self
, *widgets
):
1351 self
.widgets
= widgets
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
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
:
1372 def __getattr__(self
, name
):
1373 """Return a callback that calls a common child method"""
1376 focus
= self
.focus(name
)
1377 func
= getattr(focus
, name
, None)
1384 """Specialized delete() to deal with QLineEdit vs. QTextEdit"""
1385 focus
= self
.focus('delete')
1386 if hasattr(focus
, 'del_'):
1388 elif hasattr(focus
, 'textCursor'):
1389 focus
.textCursor().deleteChar()
1392 def show_dock(dockwidget
):
1394 dockwidget
.widget().setFocus()
1397 def focus_dock(dockwidget
):
1398 if get(dockwidget
.toggleViewAction()):
1399 show_dock(dockwidget
)
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
:
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
)
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
))
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
):
1449 menu
= cache
[menu_id
]
1451 menu
= cache
[menu_id
] = menu
.addMenu(menu_name
)