1 """This view provides the main git-cola user interface.
3 from __future__
import division
, absolute_import
, unicode_literals
5 from functools
import partial
7 from qtpy
import QtCore
9 from qtpy
import QtWidgets
10 from qtpy
.QtCore
import Qt
11 from qtpy
.QtCore
import Signal
13 from ..compat
import uchr
14 from ..compat
import WIN32
16 from ..interaction
import Interaction
17 from ..models
import prefs
18 from ..qtutils
import get
19 from ..settings
import Settings
22 from .. import guicmds
24 from .. import gitcmds
25 from .. import hotkeys
27 from .. import qtutils
28 from .. import resources
30 from .. import version
34 from . import bookmarks
36 from . import submodules
38 from . import cfgactions
40 from . import commitmsg
42 from . import createbranch
43 from . import createtag
48 from . import editremotes
53 from . import prefs
as prefs_widget
57 from . import standard
63 class MainView(standard
.MainWindow
):
64 config_actions_changed
= Signal(object)
67 def __init__(self
, context
, parent
=None, settings
=None):
68 standard
.MainWindow
.__init
__(self
, parent
)
69 self
.setAttribute(Qt
.WA_DeleteOnClose
)
71 self
.context
= context
72 self
.git
= context
.git
74 self
.model
= model
= context
.model
75 self
.settings
= settings
76 self
.prefs_model
= prefs_model
= prefs
.PreferencesModel(context
)
77 self
.toolbar_state
= toolbar
.ToolBarState(context
, self
)
79 # The widget version is used by import/export_state().
80 # Change this whenever dockwidgets are removed.
81 self
.widget_version
= 2
83 create_dock
= qtutils
.create_dock
85 self
.browser_dockable
= cfg
.get('cola.browserdockable')
86 if self
.browser_dockable
:
87 browser
= browse
.worktree_browser(
88 context
, parent
=self
, show
=False, update
=False
90 self
.browserdock
= create_dock(N_('Browser'), self
, widget
=browser
)
93 self
.actionsdock
= create_dock(
94 N_('Actions'), self
, widget
=action
.ActionButtons(context
, self
)
96 qtutils
.hide_dock(self
.actionsdock
)
98 # "Repository Status" widget
99 self
.statusdock
= create_dock(
102 fn
=lambda dock
: status
.StatusWidget(context
, dock
.titleBarWidget(), dock
),
104 self
.statuswidget
= self
.statusdock
.widget()
106 # "Switch Repository" widgets
107 self
.bookmarksdock
= create_dock(
108 N_('Favorites'), self
, fn
=lambda dock
: bookmarks
.bookmark(context
, dock
)
110 bookmarkswidget
= self
.bookmarksdock
.widget()
111 qtutils
.hide_dock(self
.bookmarksdock
)
113 self
.recentdock
= create_dock(
114 N_('Recent'), self
, fn
=lambda dock
: bookmarks
.recent(context
, dock
)
116 recentwidget
= self
.recentdock
.widget()
117 qtutils
.hide_dock(self
.recentdock
)
118 bookmarkswidget
.connect_to(recentwidget
)
121 self
.branchdock
= create_dock(
122 N_('Branches'), self
, fn
=partial(branch
.BranchesWidget
, context
)
124 self
.branchwidget
= self
.branchdock
.widget()
125 titlebar
= self
.branchdock
.titleBarWidget()
126 titlebar
.add_corner_widget(self
.branchwidget
.filter_button
)
127 titlebar
.add_corner_widget(self
.branchwidget
.sort_order_button
)
129 # "Submodule" widgets
130 self
.submodulesdock
= create_dock(
131 N_('Submodules'), self
, fn
=partial(submodules
.SubmodulesWidget
, context
)
133 self
.submoduleswidget
= self
.submodulesdock
.widget()
135 # "Commit Message Editor" widget
136 self
.position_label
= QtWidgets
.QLabel()
137 self
.position_label
.setAlignment(Qt
.AlignCenter
)
138 font
= qtutils
.default_monospace_font()
139 font
.setPointSize(int(font
.pointSize() * 0.8))
140 self
.position_label
.setFont(font
)
142 # make the position label fixed size to avoid layout issues
143 fm
= self
.position_label
.fontMetrics()
144 width
= fm
.width('99:999') + defs
.spacing
145 self
.position_label
.setMinimumWidth(width
)
147 editor
= commitmsg
.CommitMessageEditor(context
, self
)
148 self
.commiteditor
= editor
149 self
.commitdock
= create_dock(N_('Commit'), self
, widget
=editor
)
150 titlebar
= self
.commitdock
.titleBarWidget()
151 titlebar
.add_corner_widget(self
.position_label
)
154 self
.logwidget
= log
.LogWidget(context
)
155 self
.logdock
= create_dock(N_('Console'), self
, widget
=self
.logwidget
)
156 qtutils
.hide_dock(self
.logdock
)
158 # "Diff Viewer" widget
159 self
.diffdock
= create_dock(
160 N_('Diff'), self
, fn
=lambda dock
: diff
.Viewer(context
, parent
=dock
)
162 self
.diffviewer
= self
.diffdock
.widget()
163 self
.diffviewer
.set_diff_type(self
.model
.diff_type
)
165 self
.diffeditor
= self
.diffviewer
.text
166 titlebar
= self
.diffdock
.titleBarWidget()
167 titlebar
.add_corner_widget(self
.diffviewer
.options
)
170 add_action
= qtutils
.add_action
171 add_action_bool
= qtutils
.add_action_bool
173 self
.commit_amend_action
= add_action_bool(
175 N_('Amend Last Commit'),
176 partial(cmds
.do
, cmds
.AmendMode
, context
),
179 self
.commit_amend_action
.setShortcut(hotkeys
.AMEND
)
180 self
.commit_amend_action
.setShortcutContext(Qt
.WidgetShortcut
)
182 self
.unstage_all_action
= add_action(
183 self
, N_('Unstage All'), cmds
.run(cmds
.UnstageAll
, context
)
185 self
.unstage_all_action
.setIcon(icons
.remove())
187 self
.unstage_selected_action
= add_action(
188 self
, N_('Unstage From Commit'), cmds
.run(cmds
.UnstageSelected
, context
)
190 self
.unstage_selected_action
.setIcon(icons
.remove())
192 self
.show_diffstat_action
= add_action(
193 self
, N_('Diffstat'), self
.statuswidget
.select_header
, hotkeys
.DIFFSTAT
196 self
.stage_modified_action
= add_action(
198 N_('Stage Changed Files To Commit'),
199 cmds
.run(cmds
.StageModified
, context
),
200 hotkeys
.STAGE_MODIFIED
,
202 self
.stage_modified_action
.setIcon(icons
.add())
204 self
.stage_untracked_action
= add_action(
206 N_('Stage All Untracked'),
207 cmds
.run(cmds
.StageUntracked
, context
),
208 hotkeys
.STAGE_UNTRACKED
,
210 self
.stage_untracked_action
.setIcon(icons
.add())
212 self
.apply_patches_action
= add_action(
213 self
, N_('Apply Patches...'), partial(patch
.apply_patches
, context
)
216 self
.export_patches_action
= add_action(
218 N_('Export Patches...'),
219 partial(guicmds
.export_patches
, context
),
223 self
.new_repository_action
= add_action(
224 self
, N_('New Repository...'), partial(guicmds
.open_new_repo
, context
)
226 self
.new_repository_action
.setIcon(icons
.new())
228 self
.new_bare_repository_action
= add_action(
229 self
, N_('New Bare Repository...'), partial(guicmds
.new_bare_repo
, context
)
231 self
.new_bare_repository_action
.setIcon(icons
.new())
234 prefs_widget
.preferences
, context
, parent
=self
, model
=prefs_model
236 self
.preferences_action
= add_action(
237 self
, N_('Preferences'), prefs_fn
, QtGui
.QKeySequence
.Preferences
240 self
.edit_remotes_action
= add_action(
241 self
, N_('Edit Remotes...'), partial(editremotes
.editor
, context
)
244 self
.rescan_action
= add_action(
247 cmds
.run(cmds
.Refresh
, context
),
248 *hotkeys
.REFRESH_HOTKEYS
250 self
.rescan_action
.setIcon(icons
.sync())
252 self
.find_files_action
= add_action(
255 partial(finder
.finder
, context
),
257 hotkeys
.FINDER_SECONDARY
,
259 self
.find_files_action
.setIcon(icons
.zoom_in())
261 self
.browse_recently_modified_action
= add_action(
263 N_('Recently Modified Files...'),
264 partial(recent
.browse_recent_files
, context
),
265 hotkeys
.EDIT_SECONDARY
,
268 self
.cherry_pick_action
= add_action(
270 N_('Cherry-Pick...'),
271 partial(guicmds
.cherry_pick
, context
),
275 self
.load_commitmsg_action
= add_action(
276 self
, N_('Load Commit Message...'), partial(guicmds
.load_commitmsg
, context
)
279 self
.prepare_commitmsg_hook_action
= add_action(
281 N_('Prepare Commit Message'),
282 cmds
.run(cmds
.PrepareCommitMessageHook
, context
),
283 hotkeys
.PREPARE_COMMIT_MESSAGE
,
286 self
.save_tarball_action
= add_action(
287 self
, N_('Save As Tarball/Zip...'), partial(archive
.save_archive
, context
)
290 self
.quit_action
= add_action(self
, N_('Quit'), self
.close
, hotkeys
.QUIT
)
292 self
.grep_action
= add_action(
293 self
, N_('Grep'), partial(grep
.grep
, context
), hotkeys
.GREP
296 self
.merge_local_action
= add_action(
297 self
, N_('Merge...'), partial(merge
.local_merge
, context
), hotkeys
.MERGE
300 self
.merge_abort_action
= add_action(
301 self
, N_('Abort Merge...'), cmds
.run(cmds
.AbortMerge
, context
)
304 self
.update_submodules_action
= add_action(
306 N_('Update All Submodules...'),
307 cmds
.run(cmds
.SubmodulesUpdate
, context
),
310 self
.add_submodule_action
= add_action(
311 self
, N_('Add Submodule...'),
312 partial(submodules
.add_submodule
, context
, parent
=self
)
315 self
.fetch_action
= add_action(
316 self
, N_('Fetch...'), partial(remote
.fetch
, context
), hotkeys
.FETCH
318 self
.push_action
= add_action(
319 self
, N_('Push...'), partial(remote
.push
, context
), hotkeys
.PUSH
321 self
.pull_action
= add_action(
322 self
, N_('Pull...'), partial(remote
.pull
, context
), hotkeys
.PULL
325 self
.open_repo_action
= add_action(
326 self
, N_('Open...'), partial(guicmds
.open_repo
, context
), hotkeys
.OPEN
328 self
.open_repo_action
.setIcon(icons
.folder())
330 self
.open_repo_new_action
= add_action(
332 N_('Open in New Window...'),
333 partial(guicmds
.open_repo_in_new_window
, context
),
335 self
.open_repo_new_action
.setIcon(icons
.folder())
337 self
.stash_action
= add_action(
338 self
, N_('Stash...'), partial(stash
.view
, context
), hotkeys
.STASH
341 self
.reset_branch_head_action
= add_action(
342 self
, N_('Reset Branch Head'), partial(guicmds
.reset_branch_head
, context
)
345 self
.reset_worktree_action
= add_action(
346 self
, N_('Reset Worktree'), partial(guicmds
.reset_worktree
, context
)
349 self
.clone_repo_action
= add_action(
350 self
, N_('Clone...'), partial(clone
.clone
, context
, settings
=settings
)
352 self
.clone_repo_action
.setIcon(icons
.repo())
354 self
.help_docs_action
= add_action(
357 resources
.show_html_docs
,
358 QtGui
.QKeySequence
.HelpContents
,
361 self
.help_shortcuts_action
= add_action(
362 self
, N_('Keyboard Shortcuts'), about
.show_shortcuts
, hotkeys
.QUESTION
365 self
.visualize_current_action
= add_action(
367 N_('Visualize Current Branch...'),
368 cmds
.run(cmds
.VisualizeCurrent
, context
),
370 self
.visualize_all_action
= add_action(
371 self
, N_('Visualize All Branches...'), cmds
.run(cmds
.VisualizeAll
, context
)
373 self
.search_commits_action
= add_action(
374 self
, N_('Search...'), partial(search
.search
, context
)
377 self
.browse_branch_action
= add_action(
379 N_('Browse Current Branch...'),
380 partial(guicmds
.browse_current
, context
),
382 self
.browse_other_branch_action
= add_action(
383 self
, N_('Browse Other Branch...'), partial(guicmds
.browse_other
, context
)
385 self
.load_commitmsg_template_action
= add_action(
387 N_('Get Commit Message Template'),
388 cmds
.run(cmds
.LoadCommitMessageFromTemplate
, context
),
390 self
.help_about_action
= add_action(
391 self
, N_('About'), partial(about
.about_dialog
, context
)
394 self
.diff_expression_action
= add_action(
395 self
, N_('Expression...'), partial(guicmds
.diff_expression
, context
)
397 self
.branch_compare_action
= add_action(
398 self
, N_('Branches...'), partial(compare
.compare_branches
, context
)
401 self
.create_tag_action
= add_action(
404 partial(createtag
.create_tag
, context
, settings
=settings
),
407 self
.create_branch_action
= add_action(
410 partial(createbranch
.create_new_branch
, context
, settings
=settings
),
413 self
.create_branch_action
.setIcon(icons
.branch())
415 self
.delete_branch_action
= add_action(
416 self
, N_('Delete...'), partial(guicmds
.delete_branch
, context
)
419 self
.delete_remote_branch_action
= add_action(
421 N_('Delete Remote Branch...'),
422 partial(guicmds
.delete_remote_branch
, context
),
425 self
.rename_branch_action
= add_action(
426 self
, N_('Rename Branch...'), partial(guicmds
.rename_branch
, context
)
429 self
.checkout_branch_action
= add_action(
432 partial(guicmds
.checkout_branch
, context
),
435 self
.branch_review_action
= add_action(
436 self
, N_('Review...'), partial(guicmds
.review_branch
, context
)
439 self
.browse_action
= add_action(
440 self
, N_('File Browser...'), partial(browse
.worktree_browser
, context
)
442 self
.browse_action
.setIcon(icons
.cola())
444 self
.dag_action
= add_action(self
, N_('DAG...'), self
.git_dag
)
445 self
.dag_action
.setIcon(icons
.cola())
447 self
.rebase_start_action
= add_action(
449 N_('Start Interactive Rebase...'),
450 cmds
.run(cmds
.Rebase
, context
),
451 hotkeys
.REBASE_START_AND_CONTINUE
,
454 self
.rebase_edit_todo_action
= add_action(
455 self
, N_('Edit...'), cmds
.run(cmds
.RebaseEditTodo
, context
)
458 self
.rebase_continue_action
= add_action(
461 cmds
.run(cmds
.RebaseContinue
, context
),
462 hotkeys
.REBASE_START_AND_CONTINUE
,
465 self
.rebase_skip_action
= add_action(
466 self
, N_('Skip Current Patch'), cmds
.run(cmds
.RebaseSkip
, context
)
469 self
.rebase_abort_action
= add_action(
470 self
, N_('Abort'), cmds
.run(cmds
.RebaseAbort
, context
)
473 # For "Start Rebase" only, reverse the first argument to setEnabled()
474 # so that we can operate on it as a group.
475 # We can do this because can_rebase == not is_rebasing
476 self
.rebase_start_action_proxy
= utils
.Proxy(
477 self
.rebase_start_action
,
478 setEnabled
=lambda x
: self
.rebase_start_action
.setEnabled(not x
),
481 self
.rebase_group
= utils
.Group(
482 self
.rebase_start_action_proxy
,
483 self
.rebase_edit_todo_action
,
484 self
.rebase_continue_action
,
485 self
.rebase_skip_action
,
486 self
.rebase_abort_action
,
489 self
.annex_init_action
= qtutils
.add_action(
490 self
, N_('Initialize Git Annex'), cmds
.run(cmds
.AnnexInit
, context
)
493 self
.lfs_init_action
= qtutils
.add_action(
494 self
, N_('Initialize Git LFS'), cmds
.run(cmds
.LFSInstall
, context
)
497 self
.lock_layout_action
= add_action_bool(
498 self
, N_('Lock Layout'), self
.set_lock_layout
, False
501 self
.reset_layout_action
= add_action(
502 self
, N_('Reset Layout'), self
.reset_layout
505 # Create the application menu
506 self
.menubar
= QtWidgets
.QMenuBar(self
)
507 self
.setMenuBar(self
.menubar
)
510 add_menu
= qtutils
.add_menu
511 self
.file_menu
= add_menu(N_('&File'), self
.menubar
)
512 # File->Open Recent menu
513 self
.open_recent_menu
= self
.file_menu
.addMenu(N_('Open Recent'))
514 self
.open_recent_menu
.setIcon(icons
.folder())
515 self
.file_menu
.addAction(self
.open_repo_action
)
516 self
.file_menu
.addAction(self
.open_repo_new_action
)
517 self
.file_menu
.addSeparator()
518 self
.file_menu
.addAction(self
.new_repository_action
)
519 self
.file_menu
.addAction(self
.new_bare_repository_action
)
520 self
.file_menu
.addAction(self
.clone_repo_action
)
521 self
.file_menu
.addSeparator()
522 self
.file_menu
.addAction(self
.rescan_action
)
523 self
.file_menu
.addAction(self
.find_files_action
)
524 self
.file_menu
.addAction(self
.edit_remotes_action
)
525 self
.file_menu
.addAction(self
.browse_recently_modified_action
)
526 self
.file_menu
.addSeparator()
527 self
.file_menu
.addAction(self
.apply_patches_action
)
528 self
.file_menu
.addAction(self
.export_patches_action
)
529 self
.file_menu
.addAction(self
.save_tarball_action
)
531 # Git Annex / Git LFS
532 annex
= core
.find_executable('git-annex')
533 lfs
= core
.find_executable('git-lfs')
535 self
.file_menu
.addSeparator()
537 self
.file_menu
.addAction(self
.annex_init_action
)
539 self
.file_menu
.addAction(self
.lfs_init_action
)
541 self
.file_menu
.addSeparator()
542 self
.file_menu
.addAction(self
.preferences_action
)
543 self
.file_menu
.addAction(self
.quit_action
)
546 self
.edit_proxy
= edit_proxy
= FocusProxy(
547 editor
, editor
.summary
, editor
.description
555 bookmarkswidget
.tree
,
558 edit_proxy
.override('copy', copy_widgets
)
559 edit_proxy
.override('selectAll', copy_widgets
)
561 edit_menu
= self
.edit_menu
= add_menu(N_('&Edit'), self
.menubar
)
562 add_action(edit_menu
, N_('Undo'), edit_proxy
.undo
, hotkeys
.UNDO
)
563 add_action(edit_menu
, N_('Redo'), edit_proxy
.redo
, hotkeys
.REDO
)
564 edit_menu
.addSeparator()
565 add_action(edit_menu
, N_('Cut'), edit_proxy
.cut
, hotkeys
.CUT
)
566 add_action(edit_menu
, N_('Copy'), edit_proxy
.copy
, hotkeys
.COPY
)
567 add_action(edit_menu
, N_('Paste'), edit_proxy
.paste
, hotkeys
.PASTE
)
568 add_action(edit_menu
, N_('Delete'), edit_proxy
.delete
, hotkeys
.DELETE
)
569 edit_menu
.addSeparator()
571 edit_menu
, N_('Select All'), edit_proxy
.selectAll
, hotkeys
.SELECT_ALL
573 edit_menu
.addSeparator()
575 commitmsg
.add_menu_actions(edit_menu
, self
.commiteditor
.menu_actions
)
578 self
.actions_menu
= add_menu(N_('Actions'), self
.menubar
)
579 self
.actions_menu
.addAction(self
.fetch_action
)
580 self
.actions_menu
.addAction(self
.push_action
)
581 self
.actions_menu
.addAction(self
.pull_action
)
582 self
.actions_menu
.addAction(self
.stash_action
)
583 self
.actions_menu
.addSeparator()
584 self
.actions_menu
.addAction(self
.create_tag_action
)
585 self
.actions_menu
.addAction(self
.cherry_pick_action
)
586 self
.actions_menu
.addAction(self
.merge_local_action
)
587 self
.actions_menu
.addAction(self
.merge_abort_action
)
588 self
.actions_menu
.addSeparator()
589 self
.actions_menu
.addAction(self
.update_submodules_action
)
590 self
.actions_menu
.addAction(self
.add_submodule_action
)
591 self
.actions_menu
.addSeparator()
592 self
.actions_reset_menu
= self
.actions_menu
.addMenu(N_('Reset'))
593 self
.actions_reset_menu
.addAction(self
.reset_branch_head_action
)
594 self
.actions_reset_menu
.addAction(self
.reset_worktree_action
)
595 self
.actions_menu
.addSeparator()
596 self
.actions_menu
.addAction(self
.grep_action
)
597 self
.actions_menu
.addAction(self
.search_commits_action
)
600 self
.commit_menu
= add_menu(N_('Commit@@verb'), self
.menubar
)
601 self
.commit_menu
.setTitle(N_('Commit@@verb'))
602 self
.commit_menu
.addAction(self
.commiteditor
.commit_action
)
603 self
.commit_menu
.addAction(self
.commit_amend_action
)
604 self
.commit_menu
.addSeparator()
605 self
.commit_menu
.addAction(self
.stage_modified_action
)
606 self
.commit_menu
.addAction(self
.stage_untracked_action
)
607 self
.commit_menu
.addSeparator()
608 self
.commit_menu
.addAction(self
.unstage_all_action
)
609 self
.commit_menu
.addAction(self
.unstage_selected_action
)
610 self
.commit_menu
.addSeparator()
611 self
.commit_menu
.addAction(self
.load_commitmsg_action
)
612 self
.commit_menu
.addAction(self
.load_commitmsg_template_action
)
613 self
.commit_menu
.addAction(self
.prepare_commitmsg_hook_action
)
616 self
.diff_menu
= add_menu(N_('Diff'), self
.menubar
)
617 self
.diff_menu
.addAction(self
.diff_expression_action
)
618 self
.diff_menu
.addAction(self
.branch_compare_action
)
619 self
.diff_menu
.addSeparator()
620 self
.diff_menu
.addAction(self
.show_diffstat_action
)
623 self
.branch_menu
= add_menu(N_('Branch'), self
.menubar
)
624 self
.branch_menu
.addAction(self
.branch_review_action
)
625 self
.branch_menu
.addSeparator()
626 self
.branch_menu
.addAction(self
.create_branch_action
)
627 self
.branch_menu
.addAction(self
.checkout_branch_action
)
628 self
.branch_menu
.addAction(self
.delete_branch_action
)
629 self
.branch_menu
.addAction(self
.delete_remote_branch_action
)
630 self
.branch_menu
.addAction(self
.rename_branch_action
)
631 self
.branch_menu
.addSeparator()
632 self
.branch_menu
.addAction(self
.browse_branch_action
)
633 self
.branch_menu
.addAction(self
.browse_other_branch_action
)
634 self
.branch_menu
.addSeparator()
635 self
.branch_menu
.addAction(self
.visualize_current_action
)
636 self
.branch_menu
.addAction(self
.visualize_all_action
)
639 self
.rebase_menu
= add_menu(N_('Rebase'), self
.actions_menu
)
640 self
.rebase_menu
.addAction(self
.rebase_start_action
)
641 self
.rebase_menu
.addAction(self
.rebase_edit_todo_action
)
642 self
.rebase_menu
.addSeparator()
643 self
.rebase_menu
.addAction(self
.rebase_continue_action
)
644 self
.rebase_menu
.addAction(self
.rebase_skip_action
)
645 self
.rebase_menu
.addSeparator()
646 self
.rebase_menu
.addAction(self
.rebase_abort_action
)
649 self
.view_menu
= add_menu(N_('View'), self
.menubar
)
650 # pylint: disable=no-member
651 self
.view_menu
.aboutToShow
.connect(lambda: self
.build_view_menu(self
.view_menu
))
652 self
.setup_dockwidget_view_menu()
653 if utils
.is_darwin():
654 # TODO or self.menubar.setNativeMenuBar(False)
655 # Since native OSX menu doesn't show empty entries
656 self
.build_view_menu(self
.view_menu
)
659 self
.help_menu
= add_menu(N_('Help'), self
.menubar
)
660 self
.help_menu
.addAction(self
.help_docs_action
)
661 self
.help_menu
.addAction(self
.help_shortcuts_action
)
662 self
.help_menu
.addAction(self
.help_about_action
)
664 # Arrange dock widgets
665 bottom
= Qt
.BottomDockWidgetArea
666 top
= Qt
.TopDockWidgetArea
668 self
.addDockWidget(top
, self
.statusdock
)
669 self
.addDockWidget(top
, self
.commitdock
)
670 if self
.browser_dockable
:
671 self
.addDockWidget(top
, self
.browserdock
)
672 self
.tabifyDockWidget(self
.browserdock
, self
.commitdock
)
674 self
.addDockWidget(top
, self
.branchdock
)
675 self
.addDockWidget(top
, self
.submodulesdock
)
676 self
.addDockWidget(top
, self
.bookmarksdock
)
677 self
.addDockWidget(top
, self
.recentdock
)
679 self
.tabifyDockWidget(self
.branchdock
, self
.submodulesdock
)
680 self
.tabifyDockWidget(self
.submodulesdock
, self
.bookmarksdock
)
681 self
.tabifyDockWidget(self
.bookmarksdock
, self
.recentdock
)
682 self
.branchdock
.raise_()
684 self
.addDockWidget(bottom
, self
.diffdock
)
685 self
.addDockWidget(bottom
, self
.actionsdock
)
686 self
.addDockWidget(bottom
, self
.logdock
)
687 self
.tabifyDockWidget(self
.actionsdock
, self
.logdock
)
689 # Listen for model notifications
690 model
.add_observer(model
.message_updated
, self
.updated
.emit
)
691 model
.add_observer(model
.message_mode_changed
, lambda mode
: self
.updated
.emit())
693 prefs_model
.add_observer(
694 prefs_model
.message_config_updated
, self
._config
_updated
697 # Set a default value
698 self
.show_cursor_position(1, 0)
700 self
.commit_menu
.aboutToShow
.connect(self
.update_menu_actions
)
701 self
.open_recent_menu
.aboutToShow
.connect(self
.build_recent_menu
)
702 self
.commiteditor
.cursor_changed
.connect(self
.show_cursor_position
)
704 self
.diffeditor
.options_changed
.connect(self
.statuswidget
.refresh
)
705 self
.diffeditor
.up
.connect(self
.statuswidget
.move_up
)
706 self
.diffeditor
.down
.connect(self
.statuswidget
.move_down
)
708 self
.commiteditor
.up
.connect(self
.statuswidget
.move_up
)
709 self
.commiteditor
.down
.connect(self
.statuswidget
.move_down
)
711 self
.updated
.connect(self
.refresh
, type=Qt
.QueuedConnection
)
713 self
.config_actions_changed
.connect(
714 self
._install
_config
_actions
, type=Qt
.QueuedConnection
716 self
.init_state(settings
, self
.set_initial_size
)
718 # Route command output here
719 Interaction
.log_status
= self
.logwidget
.log_status
720 Interaction
.log
= self
.logwidget
.log
721 # Focus the status widget; this must be deferred
722 QtCore
.QTimer
.singleShot(0, self
.initialize
)
724 def initialize(self
):
725 context
= self
.context
726 git_version
= version
.git_version_str(context
)
730 git_version
+ '\n' + N_('git cola version %s') % version
.version()
734 error_msg
= N_('error: unable to execute git')
735 Interaction
.log(error_msg
)
738 self
.statuswidget
.setFocus()
740 title
= N_('error: unable to execute git')
744 details
= git
.win32_git_error_hint()
745 Interaction
.critical(title
, message
=msg
, details
=details
)
746 self
.context
.app
.exit(2)
748 def set_initial_size(self
):
749 # Default size; this is thrown out when save/restore is used
750 width
, height
= qtutils
.desktop_size()
751 self
.resize((width
* 3) // 4, height
)
752 self
.statuswidget
.set_initial_size()
753 self
.commiteditor
.set_initial_size()
755 def set_filter(self
, txt
):
756 self
.statuswidget
.set_filter(txt
)
759 def closeEvent(self
, event
):
760 """Save state in the settings"""
761 commit_msg
= self
.commiteditor
.commit_message(raw
=True)
762 self
.model
.save_commitmsg(msg
=commit_msg
)
763 standard
.MainWindow
.closeEvent(self
, event
)
765 def create_view_menu(self
):
766 menu
= qtutils
.create_menu(N_('View'), self
)
767 self
.build_view_menu(menu
)
770 def build_view_menu(self
, menu
):
772 menu
.addAction(self
.browse_action
)
773 menu
.addAction(self
.dag_action
)
776 popup_menu
= self
.createPopupMenu()
777 for menu_action
in popup_menu
.actions():
778 menu_action
.setParent(menu
)
779 menu
.addAction(menu_action
)
782 context
= self
.context
783 menu_action
= menu
.addAction(
784 N_('Add Toolbar'), partial(toolbar
.add_toolbar
, context
, self
)
786 menu_action
.setIcon(icons
.add())
799 if self
.browser_dockable
:
800 dockwidgets
.append(self
.browserdock
)
802 for dockwidget
in dockwidgets
:
803 # Associate the action with the shortcut
804 toggleview
= dockwidget
.toggleViewAction()
805 menu
.addAction(toggleview
)
808 menu
.addAction(self
.lock_layout_action
)
809 menu
.addAction(self
.reset_layout_action
)
813 def contextMenuEvent(self
, event
):
814 menu
= self
.create_view_menu()
815 menu
.exec_(event
.globalPos())
817 def build_recent_menu(self
):
818 settings
= Settings()
821 context
= self
.context
822 menu
= self
.open_recent_menu
824 worktree
= self
.git
.worktree()
825 for entry
in settings
.recent
:
826 directory
= entry
['path']
827 if directory
== worktree
:
828 # Omit the current worktree from the "Open Recent" menu.
831 text
= '%s %s %s' % (name
, uchr(0x2192), directory
)
832 menu
.addAction(text
, cmds
.run(cmd
, context
, directory
))
835 mode
= property(lambda self
: self
.model
.mode
)
837 def _config_updated(self
, _source
, config
, value
):
838 if config
== prefs
.FONTDIFF
:
841 if not font
.fromString(value
):
843 self
.logwidget
.setFont(font
)
844 self
.diffeditor
.setFont(font
)
845 self
.commiteditor
.setFont(font
)
847 elif config
== prefs
.TABWIDTH
:
848 # This can be set locally or globally, so we have to use the
849 # effective value otherwise we'll update when we shouldn't.
850 # For example, if this value is overridden locally, and the
851 # global value is tweaked, we should not update.
852 value
= prefs
.tabwidth(self
.context
)
853 self
.diffeditor
.set_tabwidth(value
)
854 self
.commiteditor
.set_tabwidth(value
)
856 elif config
== prefs
.EXPANDTAB
:
857 self
.commiteditor
.set_expandtab(value
)
859 elif config
== prefs
.LINEBREAK
:
860 # enables automatic line breaks
861 self
.commiteditor
.set_linebreak(value
)
863 elif config
== prefs
.SORT_BOOKMARKS
:
864 self
.bookmarksdock
.widget().reload_bookmarks()
866 elif config
== prefs
.TEXTWIDTH
:
867 # Use the effective value for the same reason as tabwidth.
868 value
= prefs
.textwidth(self
.context
)
869 self
.commiteditor
.set_textwidth(value
)
871 elif config
== prefs
.SHOW_PATH
:
872 # the path in the window title was toggled
873 self
.refresh_window_title()
875 def start(self
, context
):
876 """Do the expensive "get_config_actions()" call in the background"""
877 # Install .git-config-defined actions
878 task
= qtutils
.SimpleTask(self
, self
.get_config_actions
)
879 context
.runtask
.start(task
)
881 def get_config_actions(self
):
882 actions
= cfgactions
.get_config_actions(self
.context
)
883 self
.config_actions_changed
.emit(actions
)
885 def _install_config_actions(self
, names_and_shortcuts
):
886 """Install .gitconfig-defined actions"""
887 if not names_and_shortcuts
:
889 context
= self
.context
890 menu
= self
.actions_menu
892 for (name
, shortcut
) in names_and_shortcuts
:
893 callback
= cmds
.run(cmds
.RunConfigAction
, context
, name
)
894 menu_action
= menu
.addAction(name
, callback
)
896 menu_action
.setShortcut(shortcut
)
899 """Update the title with the current branch and directory name."""
900 curbranch
= self
.model
.currentbranch
901 curdir
= core
.getcwd()
902 is_merging
= self
.model
.is_merging
903 is_rebasing
= self
.model
.is_rebasing
905 msg
= N_('Repository: %s') % curdir
907 msg
+= N_('Branch: %s') % curbranch
912 'This repository is currently being rebased.\n'
913 'Resolve conflicts, commit changes, and run:\n'
920 'This repository is in the middle of a merge.\n'
921 'Resolve conflicts and commit changes.'
924 self
.refresh_window_title()
926 if self
.mode
== self
.model
.mode_amend
:
927 self
.commit_amend_action
.setChecked(True)
929 self
.commit_amend_action
.setChecked(False)
931 self
.commitdock
.setToolTip(msg
)
932 self
.commiteditor
.set_mode(self
.mode
)
933 self
.update_actions()
935 def refresh_window_title(self
):
936 """Refresh the window title when state changes"""
939 project
= self
.model
.project
940 curbranch
= self
.model
.currentbranch
941 is_merging
= self
.model
.is_merging
942 is_rebasing
= self
.model
.is_rebasing
947 alerts
.append(N_('Rebasing'))
949 alerts
.append(N_('Merging'))
951 if self
.mode
== self
.model
.mode_amend
:
952 alerts
.append(N_('Amending'))
955 alert_text
= (prefix
+ ' %s ' + suffix
+ ' ') % ', '.join(alerts
)
959 if self
.model
.cfg
.get(prefs
.SHOW_PATH
, True):
960 path_text
= self
.git
.worktree()
964 title
= '%s: %s %s%s' % (project
, curbranch
, alert_text
, path_text
)
965 self
.setWindowTitle(title
)
967 def update_actions(self
):
968 is_rebasing
= self
.model
.is_rebasing
969 self
.rebase_group
.setEnabled(is_rebasing
)
971 enabled
= not self
.model
.is_empty_repository()
972 self
.rename_branch_action
.setEnabled(enabled
)
973 self
.delete_branch_action
.setEnabled(enabled
)
975 self
.annex_init_action
.setEnabled(not self
.model
.annex
)
976 self
.lfs_init_action
.setEnabled(not self
.model
.lfs
)
978 def update_menu_actions(self
):
979 # Enable the Prepare Commit Message action if the hook exists
980 hook
= gitcmds
.prepare_commit_message_hook(self
.context
)
981 enabled
= os
.path
.exists(hook
)
982 self
.prepare_commitmsg_hook_action
.setEnabled(enabled
)
984 def export_state(self
):
985 state
= standard
.MainWindow
.export_state(self
)
986 show_status_filter
= self
.statuswidget
.filter_widget
.isVisible()
987 state
['show_status_filter'] = show_status_filter
988 state
['toolbars'] = self
.toolbar_state
.export_state()
989 state
['ref_sort'] = self
.model
.ref_sort
990 self
.diffviewer
.export_state(state
)
994 def apply_state(self
, state
):
995 """Imports data for save/restore"""
996 base_ok
= standard
.MainWindow
.apply_state(self
, state
)
997 lock_layout
= state
.get('lock_layout', False)
998 self
.lock_layout_action
.setChecked(lock_layout
)
1000 show_status_filter
= state
.get('show_status_filter', False)
1001 self
.statuswidget
.filter_widget
.setVisible(show_status_filter
)
1003 toolbars
= state
.get('toolbars', [])
1004 self
.toolbar_state
.apply_state(toolbars
)
1006 sort_key
= state
.get('ref_sort', 0)
1007 self
.model
.set_ref_sort(sort_key
)
1009 diff_ok
= self
.diffviewer
.apply_state(state
)
1010 return base_ok
and diff_ok
1012 def setup_dockwidget_view_menu(self
):
1013 # Hotkeys for toggling the dock widgets
1014 if utils
.is_darwin():
1019 (optkey
+ '+0', self
.logdock
),
1020 (optkey
+ '+1', self
.commitdock
),
1021 (optkey
+ '+2', self
.statusdock
),
1022 (optkey
+ '+3', self
.diffdock
),
1023 (optkey
+ '+4', self
.actionsdock
),
1024 (optkey
+ '+5', self
.bookmarksdock
),
1025 (optkey
+ '+6', self
.recentdock
),
1026 (optkey
+ '+7', self
.branchdock
),
1027 (optkey
+ '+8', self
.submodulesdock
),
1029 for shortcut
, dockwidget
in dockwidgets
:
1030 # Associate the action with the shortcut
1031 toggleview
= dockwidget
.toggleViewAction()
1032 toggleview
.setShortcut('Shift+' + shortcut
)
1034 def showdock(show
, dockwidget
=dockwidget
):
1037 dockwidget
.widget().setFocus()
1041 self
.addAction(toggleview
)
1042 qtutils
.connect_action_bool(toggleview
, showdock
)
1044 # Create a new shortcut Shift+<shortcut> that gives focus
1045 toggleview
= QtWidgets
.QAction(self
)
1046 toggleview
.setShortcut(shortcut
)
1048 def focusdock(dockwidget
=dockwidget
):
1049 focus_dock(dockwidget
)
1051 self
.addAction(toggleview
)
1052 qtutils
.connect_action(toggleview
, focusdock
)
1054 # These widgets warrant home-row hotkey status
1057 'Focus Commit Message',
1058 lambda: focus_dock(self
.commitdock
),
1064 'Focus Status Window',
1065 lambda: focus_dock(self
.statusdock
),
1066 hotkeys
.FOCUS_STATUS
,
1071 'Focus Diff Editor',
1072 lambda: focus_dock(self
.diffdock
),
1077 self
.dag
= dag
.git_dag(self
.context
, existing_view
=self
.dag
)
1079 def show_cursor_position(self
, rows
, cols
):
1080 display
= '%02d:%02d' % (rows
, cols
)
1087 background-color: yellow;
1091 background-color: #f83;
1095 background-color: red;
1103 cls
= 'second-warning'
1105 cls
= 'first-warning'
1108 div
= '<div class="%s">%s</div>' % (cls
, display
)
1109 self
.position_label
.setText(css
+ div
)
1112 class FocusProxy(object):
1113 """Proxy over child widgets and operate on the focused widget"""
1115 def __init__(self
, *widgets
):
1116 self
.widgets
= widgets
1119 def override(self
, name
, widgets
):
1120 self
.overrides
[name
] = widgets
1122 def focus(self
, name
):
1123 """Return the currently focused widget"""
1124 widgets
= self
.overrides
.get(name
, self
.widgets
)
1125 # The parent must be the parent of all the proxied widgets
1127 # The first widget is used as a fallback
1128 fallback
= widgets
[1]
1129 # We ignore the parent when delegating to child widgets
1130 widgets
= widgets
[1:]
1132 focus
= parent
.focusWidget()
1133 if focus
not in widgets
:
1137 def __getattr__(self
, name
):
1138 """Return a callback that calls a common child method"""
1141 focus
= self
.focus(name
)
1142 fn
= getattr(focus
, name
, None)
1149 """Specialized delete() to deal with QLineEdit vs QTextEdit"""
1150 focus
= self
.focus('delete')
1151 if hasattr(focus
, 'del_'):
1153 elif hasattr(focus
, 'textCursor'):
1154 focus
.textCursor().deleteChar()
1157 def show_dock(dockwidget
):
1159 dockwidget
.widget().setFocus()
1162 def focus_dock(dockwidget
):
1163 if get(dockwidget
.toggleViewAction()):
1164 show_dock(dockwidget
)
1166 dockwidget
.toggleViewAction().trigger()