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 # 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
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
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
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(
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(
111 func
=lambda dock
: bookmarks
.bookmark(context
, dock
),
113 bookmarkswidget
= self
.bookmarksdock
.widget()
114 qtutils
.hide_dock(self
.bookmarksdock
)
116 self
.recentdock
= create_dock(
120 func
=lambda dock
: bookmarks
.recent(context
, dock
),
122 recentwidget
= self
.recentdock
.widget()
123 qtutils
.hide_dock(self
.recentdock
)
124 bookmarkswidget
.connect_to(recentwidget
)
127 self
.branchdock
= create_dock(
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(
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
)
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(
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
)
189 add_action
= qtutils
.add_action
190 add_action_bool
= qtutils
.add_action_bool
192 self
.commit_amend_action
= add_action_bool(
194 N_('Amend Last Commit'),
195 partial(cmds
.do
, cmds
.AmendMode
, context
),
198 self
.commit_amend_action
.setIcon(icons
.edit())
199 self
.commit_amend_action
.setShortcut(hotkeys
.AMEND
)
200 self
.commit_amend_action
.setShortcutContext(Qt
.WidgetShortcut
)
202 self
.unstage_all_action
= add_action(
203 self
, N_('Unstage All'), cmds
.run(cmds
.UnstageAll
, context
)
205 self
.unstage_all_action
.setIcon(icons
.remove())
207 self
.undo_commit_action
= add_action(
208 self
, N_('Undo Last Commit'), cmds
.run(cmds
.UndoLastCommit
, context
)
210 self
.undo_commit_action
.setIcon(icons
.style_dialog_discard())
212 self
.unstage_selected_action
= add_action(
213 self
, N_('Unstage'), cmds
.run(cmds
.UnstageSelected
, context
)
215 self
.unstage_selected_action
.setIcon(icons
.remove())
217 self
.show_diffstat_action
= add_action(
218 self
, N_('Diffstat'), self
.statuswidget
.select_header
, hotkeys
.DIFFSTAT
220 self
.show_diffstat_action
.setIcon(icons
.diff())
222 self
.stage_modified_action
= add_action(
224 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(
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(
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(
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(
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(
269 N_('Export Patches...'),
270 partial(guicmds
.export_patches
, context
),
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(
301 cmds
.run(cmds
.Refresh
, context
),
302 *hotkeys
.REFRESH_HOTKEYS
,
304 self
.rescan_action
.setIcon(icons
.sync())
306 self
.find_files_action
= add_action(
309 partial(finder
.finder
, context
),
312 self
.find_files_action
.setIcon(icons
.search())
314 self
.browse_recently_modified_action
= add_action(
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(
324 N_('Cherry-Pick...'),
325 partial(guicmds
.cherry_pick
, context
),
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(
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(
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(
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(
385 N_('Fetch from one or more remotes using "git fetch"'),
386 partial(remote
.fetch
, context
),
389 self
.fetch_action
.setIcon(icons
.download())
391 self
.push_action
= qtutils
.add_action_with_tooltip(
394 N_('Push to one or more remotes using "git push"'),
395 partial(remote
.push
, context
),
398 self
.push_action
.setIcon(icons
.push())
400 self
.pull_action
= qtutils
.add_action_with_tooltip(
403 N_('Integrate changes using "git pull"'),
404 partial(remote
.pull
, context
),
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(
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(
424 N_('Temporarily stash away uncommitted changes using "git stash"'),
425 partial(stash
.view
, context
),
428 self
.stash_action
.setIcon(icons
.commit())
430 self
.reset_soft_action
= qtutils
.add_action_with_tooltip(
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(
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(
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(
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(
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(
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(
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(
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(
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(
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(
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(
559 partial(createtag
.create_tag
, context
),
561 self
.create_tag_action
.setIcon(icons
.tag())
563 self
.create_branch_action
= add_action(
566 partial(createbranch
.create_new_branch
, context
),
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(
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(
591 partial(guicmds
.checkout_branch
, context
),
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(
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(
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(
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
)
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')
721 self
.file_menu
.addSeparator()
723 self
.file_menu
.addAction(self
.annex_init_action
)
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
)
732 self
.edit_proxy
= edit_proxy
= FocusProxy(
733 editor
, editor
.summary
, editor
.description
741 bookmarkswidget
.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
)
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
)
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
)
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
)
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
)
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
)
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
)
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
)
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(
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
)
942 git_version
+ '\n' + N_('git cola version %s') % version
.version()
946 error_msg
= N_('error: unable to execute git')
947 Interaction
.log(error_msg
)
950 self
.statuswidget
.setFocus()
952 title
= N_('error: unable to execute git')
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
)
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
):
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
)
984 def build_view_menu(self
, menu
):
986 menu
.addAction(self
.browse_action
)
987 menu
.addAction(self
.dag_action
)
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())
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
)
1022 menu
.addAction(self
.lock_layout_action
)
1023 menu
.addAction(self
.reset_layout_action
)
1027 def contextMenuEvent(self
, event
):
1028 menu
= self
.create_view_menu()
1029 menu
.exec_(event
.globalPos())
1031 def build_recent_menu(self
):
1033 context
= self
.context
1034 settings
= context
.settings
1036 menu
= self
.open_recent_menu
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.
1045 name
= entry
['name']
1046 text
= f
'{name} {uchr(0x2192)} {directory}'
1047 menu
.addAction(text
, cmds
.run(cmd
, context
, directory
))
1050 mode
= property(lambda self
: self
.model
.mode
)
1052 def _config_updated(self
, _source
, config
, value
):
1053 if config
== prefs
.FONTDIFF
:
1055 font
= QtGui
.QFont()
1056 if not font
.fromString(value
):
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
)
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
1111 msg
+= N_('Branch: %s') % curbranch
1116 'This repository is currently being rebased.\n'
1117 'Resolve conflicts, commit changes, and run:\n'
1118 ' Rebase > Continue'
1120 elif is_applying_patch
:
1123 'This repository has unresolved conflicts after applying a patch.\n'
1124 'Resolve conflicts and commit changes.'
1126 elif is_cherry_picking
:
1129 'This repository is in the middle of a cherry-pick.\n'
1130 'Resolve conflicts and commit changes.'
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)
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"""
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
1171 alerts
.append(N_('Amending'))
1173 alerts
.append(N_('Diff Mode'))
1174 elif is_cherry_picking
:
1175 alerts
.append(N_('Cherry-picking'))
1177 alerts
.append(N_('Merging'))
1179 alerts
.append(N_('Rebasing'))
1180 elif is_applying_patch
:
1181 alerts
.append(N_('Applying Patch'))
1184 alert_text
= (prefix
+ ' %s ' + suffix
+ ' ') % ', '.join(alerts
)
1188 if self
.model
.cfg
.get(prefs
.SHOW_PATH
, True):
1189 path_text
= self
.git
.worktree()
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
)
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():
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
):
1274 dockwidget
.widget().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
1294 'Focus Commit Message',
1295 lambda: focus_dock(self
.commitdock
),
1301 'Focus Status Window',
1302 lambda: focus_dock(self
.statusdock
),
1303 hotkeys
.FOCUS_STATUS
,
1308 'Focus Diff Editor',
1309 lambda: focus_dock(self
.diffdock
),
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
)
1324 background-color: yellow;
1328 background-color: #f83;
1332 background-color: red;
1340 cls
= 'second-warning'
1342 cls
= 'first-warning'
1345 div
= f
'<div class="{cls}">{display_content}</div>'
1346 self
.position_label
.setText(css
+ div
)
1350 """Proxy over child widgets and operate on the focused widget"""
1352 def __init__(self
, *widgets
):
1353 self
.widgets
= widgets
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
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
:
1374 def __getattr__(self
, name
):
1375 """Return a callback that calls a common child method"""
1378 focus
= self
.focus(name
)
1379 func
= getattr(focus
, name
, None)
1386 """Specialized delete() to deal with QLineEdit vs QTextEdit"""
1387 focus
= self
.focus('delete')
1388 if hasattr(focus
, 'del_'):
1390 elif hasattr(focus
, 'textCursor'):
1391 focus
.textCursor().deleteChar()
1394 def show_dock(dockwidget
):
1396 dockwidget
.widget().setFocus()
1399 def focus_dock(dockwidget
):
1400 if get(dockwidget
.toggleViewAction()):
1401 show_dock(dockwidget
)
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
:
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
)
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
))
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
):
1451 menu
= cache
[menu_id
]
1453 menu
= cache
[menu_id
] = menu
.addMenu(menu_name
)