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(context
, parent
=self
,
88 show
=False, update
=False)
89 self
.browserdock
= create_dock(N_('Browser'), self
, widget
=browser
)
92 self
.actionsdock
= create_dock(
93 N_('Actions'), self
, widget
=action
.ActionButtons(context
, self
))
94 qtutils
.hide_dock(self
.actionsdock
)
96 # "Repository Status" widget
97 self
.statusdock
= create_dock(
99 fn
=lambda dock
: status
.StatusWidget(
100 context
, dock
.titleBarWidget(), dock
))
101 self
.statuswidget
= self
.statusdock
.widget()
103 # "Switch Repository" widgets
104 self
.bookmarksdock
= create_dock(
105 N_('Favorites'), self
,
106 fn
=lambda dock
: bookmarks
.bookmark(context
, dock
))
107 bookmarkswidget
= self
.bookmarksdock
.widget()
108 qtutils
.hide_dock(self
.bookmarksdock
)
110 self
.recentdock
= create_dock(
112 fn
=lambda dock
: bookmarks
.recent(context
, dock
))
113 recentwidget
= self
.recentdock
.widget()
114 qtutils
.hide_dock(self
.recentdock
)
115 bookmarkswidget
.connect_to(recentwidget
)
118 self
.branchdock
= create_dock(
119 N_('Branches'), self
, fn
=partial(branch
.BranchesWidget
, context
))
120 self
.branchwidget
= self
.branchdock
.widget()
121 titlebar
= self
.branchdock
.titleBarWidget()
122 titlebar
.add_corner_widget(self
.branchwidget
.filter_button
)
123 titlebar
.add_corner_widget(self
.branchwidget
.sort_order_button
)
125 # "Submodule" widgets
126 self
.submodulesdock
= create_dock(
127 N_('Submodules'), self
,
128 fn
=partial(submodules
.SubmodulesWidget
, context
))
129 self
.submoduleswidget
= self
.submodulesdock
.widget()
131 # "Commit Message Editor" widget
132 self
.position_label
= QtWidgets
.QLabel()
133 self
.position_label
.setAlignment(Qt
.AlignCenter
)
134 font
= qtutils
.default_monospace_font()
135 font
.setPointSize(int(font
.pointSize() * 0.8))
136 self
.position_label
.setFont(font
)
138 # make the position label fixed size to avoid layout issues
139 fm
= self
.position_label
.fontMetrics()
140 width
= fm
.width('99:999') + defs
.spacing
141 self
.position_label
.setMinimumWidth(width
)
143 editor
= commitmsg
.CommitMessageEditor(context
, self
)
144 self
.commiteditor
= editor
145 self
.commitdock
= create_dock(N_('Commit'), self
, widget
=editor
)
146 titlebar
= self
.commitdock
.titleBarWidget()
147 titlebar
.add_corner_widget(self
.position_label
)
150 self
.logwidget
= log
.LogWidget(context
)
151 self
.logdock
= create_dock(N_('Console'), self
, widget
=self
.logwidget
)
152 qtutils
.hide_dock(self
.logdock
)
154 # "Diff Viewer" widget
155 self
.diffdock
= create_dock(
157 fn
=lambda dock
: diff
.Viewer(context
, parent
=dock
))
158 self
.diffviewer
= self
.diffdock
.widget()
159 self
.diffviewer
.set_diff_type(self
.model
.diff_type
)
161 self
.diffeditor
= self
.diffviewer
.text
162 titlebar
= self
.diffdock
.titleBarWidget()
163 titlebar
.add_corner_widget(self
.diffviewer
.options
)
166 add_action
= qtutils
.add_action
167 add_action_bool
= qtutils
.add_action_bool
169 self
.commit_amend_action
= add_action_bool(
170 self
, N_('Amend Last Commit'),
171 partial(cmds
.do
, cmds
.AmendMode
, context
), False)
172 self
.commit_amend_action
.setShortcut(hotkeys
.AMEND
)
173 self
.commit_amend_action
.setShortcutContext(Qt
.WidgetShortcut
)
175 self
.unstage_all_action
= add_action(
176 self
, N_('Unstage All'), cmds
.run(cmds
.UnstageAll
, context
))
177 self
.unstage_all_action
.setIcon(icons
.remove())
179 self
.unstage_selected_action
= add_action(
180 self
, N_('Unstage From Commit'),
181 cmds
.run(cmds
.UnstageSelected
, context
))
182 self
.unstage_selected_action
.setIcon(icons
.remove())
184 self
.show_diffstat_action
= add_action(
185 self
, N_('Diffstat'), self
.statuswidget
.select_header
,
188 self
.stage_modified_action
= add_action(
189 self
, N_('Stage Changed Files To Commit'),
190 cmds
.run(cmds
.StageModified
, context
), hotkeys
.STAGE_MODIFIED
)
191 self
.stage_modified_action
.setIcon(icons
.add())
193 self
.stage_untracked_action
= add_action(
194 self
, N_('Stage All Untracked'),
195 cmds
.run(cmds
.StageUntracked
, context
), hotkeys
.STAGE_UNTRACKED
)
196 self
.stage_untracked_action
.setIcon(icons
.add())
198 self
.apply_patches_action
= add_action(
199 self
, N_('Apply Patches...'),
200 partial(patch
.apply_patches
, context
))
202 self
.export_patches_action
= add_action(
203 self
, N_('Export Patches...'),
204 partial(guicmds
.export_patches
, context
), hotkeys
.EXPORT
)
206 self
.new_repository_action
= add_action(
207 self
, N_('New Repository...'),
208 partial(guicmds
.open_new_repo
, context
))
209 self
.new_repository_action
.setIcon(icons
.new())
211 self
.new_bare_repository_action
= add_action(
212 self
, N_('New Bare Repository...'),
213 partial(guicmds
.new_bare_repo
, context
))
214 self
.new_bare_repository_action
.setIcon(icons
.new())
217 prefs_widget
.preferences
, context
, parent
=self
, model
=prefs_model
)
218 self
.preferences_action
= add_action(
219 self
, N_('Preferences'), prefs_fn
, QtGui
.QKeySequence
.Preferences
)
221 self
.edit_remotes_action
= add_action(
222 self
, N_('Edit Remotes...'), partial(editremotes
.editor
, context
))
224 self
.rescan_action
= add_action(
225 self
, cmds
.Refresh
.name(), cmds
.run(cmds
.Refresh
, context
),
226 *hotkeys
.REFRESH_HOTKEYS
)
227 self
.rescan_action
.setIcon(icons
.sync())
229 self
.find_files_action
= add_action(
230 self
, N_('Find Files'), partial(finder
.finder
, context
),
231 hotkeys
.FINDER
, hotkeys
.FINDER_SECONDARY
)
232 self
.find_files_action
.setIcon(icons
.zoom_in())
234 self
.browse_recently_modified_action
= add_action(
235 self
, N_('Recently Modified Files...'),
236 partial(recent
.browse_recent_files
, context
),
237 hotkeys
.EDIT_SECONDARY
)
239 self
.cherry_pick_action
= add_action(
240 self
, N_('Cherry-Pick...'), partial(guicmds
.cherry_pick
, context
),
243 self
.load_commitmsg_action
= add_action(
244 self
, N_('Load Commit Message...'),
245 partial(guicmds
.load_commitmsg
, context
))
247 self
.prepare_commitmsg_hook_action
= add_action(
248 self
, N_('Prepare Commit Message'),
249 cmds
.run(cmds
.PrepareCommitMessageHook
, context
),
250 hotkeys
.PREPARE_COMMIT_MESSAGE
)
252 self
.save_tarball_action
= add_action(
253 self
, N_('Save As Tarball/Zip...'),
254 partial(archive
.save_archive
, context
))
256 self
.quit_action
= add_action(
257 self
, N_('Quit'), self
.close
, hotkeys
.QUIT
)
259 self
.grep_action
= add_action(
260 self
, N_('Grep'), partial(grep
.grep
, context
), hotkeys
.GREP
)
262 self
.merge_local_action
= add_action(
263 self
, N_('Merge...'), partial(merge
.local_merge
, context
),
266 self
.merge_abort_action
= add_action(
267 self
, N_('Abort Merge...'), cmds
.run(cmds
.AbortMerge
, context
))
269 self
.update_submodules_action
= add_action(
270 self
, N_('Update All Submodules...'),
271 cmds
.run(cmds
.SubmodulesUpdate
, context
))
273 self
.fetch_action
= add_action(
274 self
, N_('Fetch...'), partial(remote
.fetch
, context
),
276 self
.push_action
= add_action(
277 self
, N_('Push...'), partial(remote
.push
, context
), hotkeys
.PUSH
)
278 self
.pull_action
= add_action(
279 self
, N_('Pull...'), partial(remote
.pull
, context
), hotkeys
.PULL
)
281 self
.open_repo_action
= add_action(
283 partial(guicmds
.open_repo
, context
), hotkeys
.OPEN
)
284 self
.open_repo_action
.setIcon(icons
.folder())
286 self
.open_repo_new_action
= add_action(
287 self
, N_('Open in New Window...'),
288 partial(guicmds
.open_repo_in_new_window
, context
))
289 self
.open_repo_new_action
.setIcon(icons
.folder())
291 self
.stash_action
= add_action(
292 self
, N_('Stash...'), partial(stash
.view
, context
), hotkeys
.STASH
)
294 self
.reset_branch_head_action
= add_action(
295 self
, N_('Reset Branch Head'),
296 partial(guicmds
.reset_branch_head
, context
))
298 self
.reset_worktree_action
= add_action(
299 self
, N_('Reset Worktree'),
300 partial(guicmds
.reset_worktree
, context
))
302 self
.clone_repo_action
= add_action(
303 self
, N_('Clone...'),
304 partial(clone
.clone
, context
, settings
=settings
))
305 self
.clone_repo_action
.setIcon(icons
.repo())
307 self
.help_docs_action
= add_action(
308 self
, N_('Documentation'), resources
.show_html_docs
,
309 QtGui
.QKeySequence
.HelpContents
)
311 self
.help_shortcuts_action
= add_action(
312 self
, N_('Keyboard Shortcuts'), about
.show_shortcuts
,
315 self
.visualize_current_action
= add_action(
316 self
, N_('Visualize Current Branch...'),
317 cmds
.run(cmds
.VisualizeCurrent
, context
))
318 self
.visualize_all_action
= add_action(
319 self
, N_('Visualize All Branches...'),
320 cmds
.run(cmds
.VisualizeAll
, context
))
321 self
.search_commits_action
= add_action(
322 self
, N_('Search...'), partial(search
.search
, context
))
324 self
.browse_branch_action
= add_action(
325 self
, N_('Browse Current Branch...'),
326 partial(guicmds
.browse_current
, context
))
327 self
.browse_other_branch_action
= add_action(
328 self
, N_('Browse Other Branch...'),
329 partial(guicmds
.browse_other
, context
))
330 self
.load_commitmsg_template_action
= add_action(
331 self
, N_('Get Commit Message Template'),
332 cmds
.run(cmds
.LoadCommitMessageFromTemplate
, context
))
333 self
.help_about_action
= add_action(
334 self
, N_('About'), partial(about
.about_dialog
, context
))
336 self
.diff_expression_action
= add_action(
337 self
, N_('Expression...'),
338 partial(guicmds
.diff_expression
, context
))
339 self
.branch_compare_action
= add_action(
340 self
, N_('Branches...'),
341 partial(compare
.compare_branches
, context
))
343 self
.create_tag_action
= add_action(
344 self
, N_('Create Tag...'),
345 partial(createtag
.create_tag
, context
, settings
=settings
))
347 self
.create_branch_action
= add_action(
348 self
, N_('Create...'),
349 partial(createbranch
.create_new_branch
, context
,
352 self
.create_branch_action
.setIcon(icons
.branch())
354 self
.delete_branch_action
= add_action(
355 self
, N_('Delete...'),
356 partial(guicmds
.delete_branch
, context
))
358 self
.delete_remote_branch_action
= add_action(
359 self
, N_('Delete Remote Branch...'),
360 partial(guicmds
.delete_remote_branch
, context
))
362 self
.rename_branch_action
= add_action(
363 self
, N_('Rename Branch...'),
364 partial(guicmds
.rename_branch
, context
))
366 self
.checkout_branch_action
= add_action(
367 self
, N_('Checkout...'),
368 partial(guicmds
.checkout_branch
, context
),
370 self
.branch_review_action
= add_action(
371 self
, N_('Review...'),
372 partial(guicmds
.review_branch
, context
))
374 self
.browse_action
= add_action(
375 self
, N_('File Browser...'),
376 partial(browse
.worktree_browser
, context
))
377 self
.browse_action
.setIcon(icons
.cola())
379 self
.dag_action
= add_action(self
, N_('DAG...'), self
.git_dag
)
380 self
.dag_action
.setIcon(icons
.cola())
382 self
.rebase_start_action
= add_action(
383 self
, N_('Start Interactive Rebase...'),
384 cmds
.run(cmds
.Rebase
, context
), hotkeys
.REBASE_START_AND_CONTINUE
)
386 self
.rebase_edit_todo_action
= add_action(
387 self
, N_('Edit...'), cmds
.run(cmds
.RebaseEditTodo
, context
))
389 self
.rebase_continue_action
= add_action(
390 self
, N_('Continue'), cmds
.run(cmds
.RebaseContinue
, context
),
391 hotkeys
.REBASE_START_AND_CONTINUE
)
393 self
.rebase_skip_action
= add_action(
394 self
, N_('Skip Current Patch'), cmds
.run(cmds
.RebaseSkip
, context
))
396 self
.rebase_abort_action
= add_action(
397 self
, N_('Abort'), cmds
.run(cmds
.RebaseAbort
, context
))
399 # For "Start Rebase" only, reverse the first argument to setEnabled()
400 # so that we can operate on it as a group.
401 # We can do this because can_rebase == not is_rebasing
402 self
.rebase_start_action_proxy
= utils
.Proxy(
403 self
.rebase_start_action
,
404 setEnabled
=lambda x
: self
.rebase_start_action
.setEnabled(not x
))
406 self
.rebase_group
= utils
.Group(self
.rebase_start_action_proxy
,
407 self
.rebase_edit_todo_action
,
408 self
.rebase_continue_action
,
409 self
.rebase_skip_action
,
410 self
.rebase_abort_action
)
412 self
.annex_init_action
= qtutils
.add_action(
413 self
, N_('Initialize Git Annex'),
414 cmds
.run(cmds
.AnnexInit
, context
))
416 self
.lfs_init_action
= qtutils
.add_action(
417 self
, N_('Initialize Git LFS'), cmds
.run(cmds
.LFSInstall
, context
))
419 self
.lock_layout_action
= add_action_bool(
420 self
, N_('Lock Layout'), self
.set_lock_layout
, False)
422 # Create the application menu
423 self
.menubar
= QtWidgets
.QMenuBar(self
)
424 self
.setMenuBar(self
.menubar
)
427 add_menu
= qtutils
.add_menu
428 self
.file_menu
= add_menu(N_('&File'), self
.menubar
)
429 # File->Open Recent menu
430 self
.open_recent_menu
= self
.file_menu
.addMenu(N_('Open Recent'))
431 self
.open_recent_menu
.setIcon(icons
.folder())
432 self
.file_menu
.addAction(self
.open_repo_action
)
433 self
.file_menu
.addAction(self
.open_repo_new_action
)
434 self
.file_menu
.addSeparator()
435 self
.file_menu
.addAction(self
.new_repository_action
)
436 self
.file_menu
.addAction(self
.new_bare_repository_action
)
437 self
.file_menu
.addAction(self
.clone_repo_action
)
438 self
.file_menu
.addSeparator()
439 self
.file_menu
.addAction(self
.rescan_action
)
440 self
.file_menu
.addAction(self
.find_files_action
)
441 self
.file_menu
.addAction(self
.edit_remotes_action
)
442 self
.file_menu
.addAction(self
.browse_recently_modified_action
)
443 self
.file_menu
.addSeparator()
444 self
.file_menu
.addAction(self
.apply_patches_action
)
445 self
.file_menu
.addAction(self
.export_patches_action
)
446 self
.file_menu
.addAction(self
.save_tarball_action
)
448 # Git Annex / Git LFS
449 annex
= core
.find_executable('git-annex')
450 lfs
= core
.find_executable('git-lfs')
452 self
.file_menu
.addSeparator()
454 self
.file_menu
.addAction(self
.annex_init_action
)
456 self
.file_menu
.addAction(self
.lfs_init_action
)
458 self
.file_menu
.addSeparator()
459 self
.file_menu
.addAction(self
.preferences_action
)
460 self
.file_menu
.addAction(self
.quit_action
)
463 self
.edit_proxy
= edit_proxy
= (
464 FocusProxy(editor
, editor
.summary
, editor
.description
))
467 self
, editor
.summary
, editor
.description
, self
.diffeditor
,
468 bookmarkswidget
.tree
, recentwidget
.tree
,
470 edit_proxy
.override('copy', copy_widgets
)
471 edit_proxy
.override('selectAll', copy_widgets
)
473 edit_menu
= self
.edit_menu
= add_menu(N_('&Edit'), self
.menubar
)
474 add_action(edit_menu
, N_('Undo'), edit_proxy
.undo
, hotkeys
.UNDO
)
475 add_action(edit_menu
, N_('Redo'), edit_proxy
.redo
, hotkeys
.REDO
)
476 edit_menu
.addSeparator()
477 add_action(edit_menu
, N_('Cut'), edit_proxy
.cut
, hotkeys
.CUT
)
478 add_action(edit_menu
, N_('Copy'), edit_proxy
.copy
, hotkeys
.COPY
)
479 add_action(edit_menu
, N_('Paste'), edit_proxy
.paste
, hotkeys
.PASTE
)
480 add_action(edit_menu
, N_('Delete'), edit_proxy
.delete
, hotkeys
.DELETE
)
481 edit_menu
.addSeparator()
482 add_action(edit_menu
, N_('Select All'), edit_proxy
.selectAll
,
484 edit_menu
.addSeparator()
486 commitmsg
.add_menu_actions(edit_menu
, self
.commiteditor
.menu_actions
)
489 self
.actions_menu
= add_menu(N_('Actions'), self
.menubar
)
490 self
.actions_menu
.addAction(self
.fetch_action
)
491 self
.actions_menu
.addAction(self
.push_action
)
492 self
.actions_menu
.addAction(self
.pull_action
)
493 self
.actions_menu
.addAction(self
.stash_action
)
494 self
.actions_menu
.addSeparator()
495 self
.actions_menu
.addAction(self
.create_tag_action
)
496 self
.actions_menu
.addAction(self
.cherry_pick_action
)
497 self
.actions_menu
.addAction(self
.merge_local_action
)
498 self
.actions_menu
.addAction(self
.merge_abort_action
)
499 self
.actions_menu
.addSeparator()
500 self
.actions_menu
.addAction(self
.update_submodules_action
)
501 self
.actions_menu
.addSeparator()
502 self
.actions_reset_menu
= self
.actions_menu
.addMenu(N_('Reset'))
503 self
.actions_reset_menu
.addAction(self
.reset_branch_head_action
)
504 self
.actions_reset_menu
.addAction(self
.reset_worktree_action
)
505 self
.actions_menu
.addSeparator()
506 self
.actions_menu
.addAction(self
.grep_action
)
507 self
.actions_menu
.addAction(self
.search_commits_action
)
510 self
.commit_menu
= add_menu(N_('Commit@@verb'), self
.menubar
)
511 self
.commit_menu
.setTitle(N_('Commit@@verb'))
512 self
.commit_menu
.addAction(self
.commiteditor
.commit_action
)
513 self
.commit_menu
.addAction(self
.commit_amend_action
)
514 self
.commit_menu
.addSeparator()
515 self
.commit_menu
.addAction(self
.stage_modified_action
)
516 self
.commit_menu
.addAction(self
.stage_untracked_action
)
517 self
.commit_menu
.addSeparator()
518 self
.commit_menu
.addAction(self
.unstage_all_action
)
519 self
.commit_menu
.addAction(self
.unstage_selected_action
)
520 self
.commit_menu
.addSeparator()
521 self
.commit_menu
.addAction(self
.load_commitmsg_action
)
522 self
.commit_menu
.addAction(self
.load_commitmsg_template_action
)
523 self
.commit_menu
.addAction(self
.prepare_commitmsg_hook_action
)
526 self
.diff_menu
= add_menu(N_('Diff'), self
.menubar
)
527 self
.diff_menu
.addAction(self
.diff_expression_action
)
528 self
.diff_menu
.addAction(self
.branch_compare_action
)
529 self
.diff_menu
.addSeparator()
530 self
.diff_menu
.addAction(self
.show_diffstat_action
)
533 self
.branch_menu
= add_menu(N_('Branch'), self
.menubar
)
534 self
.branch_menu
.addAction(self
.branch_review_action
)
535 self
.branch_menu
.addSeparator()
536 self
.branch_menu
.addAction(self
.create_branch_action
)
537 self
.branch_menu
.addAction(self
.checkout_branch_action
)
538 self
.branch_menu
.addAction(self
.delete_branch_action
)
539 self
.branch_menu
.addAction(self
.delete_remote_branch_action
)
540 self
.branch_menu
.addAction(self
.rename_branch_action
)
541 self
.branch_menu
.addSeparator()
542 self
.branch_menu
.addAction(self
.browse_branch_action
)
543 self
.branch_menu
.addAction(self
.browse_other_branch_action
)
544 self
.branch_menu
.addSeparator()
545 self
.branch_menu
.addAction(self
.visualize_current_action
)
546 self
.branch_menu
.addAction(self
.visualize_all_action
)
549 self
.rebase_menu
= add_menu(N_('Rebase'), self
.actions_menu
)
550 self
.rebase_menu
.addAction(self
.rebase_start_action
)
551 self
.rebase_menu
.addAction(self
.rebase_edit_todo_action
)
552 self
.rebase_menu
.addSeparator()
553 self
.rebase_menu
.addAction(self
.rebase_continue_action
)
554 self
.rebase_menu
.addAction(self
.rebase_skip_action
)
555 self
.rebase_menu
.addSeparator()
556 self
.rebase_menu
.addAction(self
.rebase_abort_action
)
559 self
.view_menu
= add_menu(N_('View'), self
.menubar
)
560 # pylint: disable=no-member
561 self
.view_menu
.aboutToShow
.connect(
562 lambda: self
.build_view_menu(self
.view_menu
))
563 self
.setup_dockwidget_view_menu()
564 if utils
.is_darwin():
565 # TODO or self.menubar.setNativeMenuBar(False)
566 # Since native OSX menu doesn't show empty entries
567 self
.build_view_menu(self
.view_menu
)
570 self
.help_menu
= add_menu(N_('Help'), self
.menubar
)
571 self
.help_menu
.addAction(self
.help_docs_action
)
572 self
.help_menu
.addAction(self
.help_shortcuts_action
)
573 self
.help_menu
.addAction(self
.help_about_action
)
575 # Arrange dock widgets
576 bottom
= Qt
.BottomDockWidgetArea
577 top
= Qt
.TopDockWidgetArea
579 self
.addDockWidget(top
, self
.statusdock
)
580 self
.addDockWidget(top
, self
.commitdock
)
581 if self
.browser_dockable
:
582 self
.addDockWidget(top
, self
.browserdock
)
583 self
.tabifyDockWidget(self
.browserdock
, self
.commitdock
)
585 self
.addDockWidget(top
, self
.branchdock
)
586 self
.addDockWidget(top
, self
.submodulesdock
)
587 self
.addDockWidget(top
, self
.bookmarksdock
)
588 self
.addDockWidget(top
, self
.recentdock
)
590 self
.tabifyDockWidget(self
.branchdock
, self
.submodulesdock
)
591 self
.tabifyDockWidget(self
.submodulesdock
, self
.bookmarksdock
)
592 self
.tabifyDockWidget(self
.bookmarksdock
, self
.recentdock
)
593 self
.branchdock
.raise_()
595 self
.addDockWidget(bottom
, self
.diffdock
)
596 self
.addDockWidget(bottom
, self
.actionsdock
)
597 self
.addDockWidget(bottom
, self
.logdock
)
598 self
.tabifyDockWidget(self
.actionsdock
, self
.logdock
)
600 # Listen for model notifications
601 model
.add_observer(model
.message_updated
, self
.updated
.emit
)
602 model
.add_observer(model
.message_mode_changed
,
603 lambda mode
: self
.updated
.emit())
605 prefs_model
.add_observer(prefs_model
.message_config_updated
,
606 self
._config
_updated
)
608 # Set a default value
609 self
.show_cursor_position(1, 0)
611 self
.commit_menu
.aboutToShow
.connect(self
.update_menu_actions
)
612 self
.open_recent_menu
.aboutToShow
.connect(self
.build_recent_menu
)
613 self
.commiteditor
.cursor_changed
.connect(self
.show_cursor_position
)
615 self
.diffeditor
.options_changed
.connect(self
.statuswidget
.refresh
)
616 self
.diffeditor
.up
.connect(self
.statuswidget
.move_up
)
617 self
.diffeditor
.down
.connect(self
.statuswidget
.move_down
)
619 self
.commiteditor
.up
.connect(self
.statuswidget
.move_up
)
620 self
.commiteditor
.down
.connect(self
.statuswidget
.move_down
)
622 self
.updated
.connect(self
.refresh
, type=Qt
.QueuedConnection
)
624 self
.config_actions_changed
.connect(self
._install
_config
_actions
,
625 type=Qt
.QueuedConnection
)
626 self
.init_state(settings
, self
.set_initial_size
)
628 # Route command output here
629 Interaction
.log_status
= self
.logwidget
.log_status
630 Interaction
.log
= self
.logwidget
.log
631 # Focus the status widget; this must be deferred
632 QtCore
.QTimer
.singleShot(0, self
.initialize
)
634 def initialize(self
):
635 context
= self
.context
636 git_version
= version
.git_version_str(context
)
639 Interaction
.log(git_version
+ '\n' +
640 N_('git cola version %s') % version
.version())
643 error_msg
= N_('error: unable to execute git')
644 Interaction
.log(error_msg
)
647 self
.statuswidget
.setFocus()
649 title
= N_('error: unable to execute git')
653 details
= git
.win32_git_error_hint()
654 Interaction
.critical(title
, message
=msg
, details
=details
)
655 self
.context
.app
.exit(2)
657 def set_initial_size(self
):
658 # Default size; this is thrown out when save/restore is used
659 width
, height
= qtutils
.desktop_size()
660 self
.resize((width
*3)//4, height
)
661 self
.statuswidget
.set_initial_size()
662 self
.commiteditor
.set_initial_size()
664 def set_filter(self
, txt
):
665 self
.statuswidget
.set_filter(txt
)
668 def closeEvent(self
, event
):
669 """Save state in the settings"""
670 commit_msg
= self
.commiteditor
.commit_message(raw
=True)
671 self
.model
.save_commitmsg(msg
=commit_msg
)
672 standard
.MainWindow
.closeEvent(self
, event
)
674 def create_view_menu(self
):
675 menu
= qtutils
.create_menu(N_('View'), self
)
676 self
.build_view_menu(menu
)
679 def build_view_menu(self
, menu
):
681 menu
.addAction(self
.browse_action
)
682 menu
.addAction(self
.dag_action
)
685 popup_menu
= self
.createPopupMenu()
686 for menu_action
in popup_menu
.actions():
687 menu_action
.setParent(menu
)
688 menu
.addAction(menu_action
)
691 context
= self
.context
692 menu_action
= menu
.addAction(
693 N_('Add Toolbar'), partial(toolbar
.add_toolbar
, context
, self
))
694 menu_action
.setIcon(icons
.add())
707 if self
.browser_dockable
:
708 dockwidgets
.append(self
.browserdock
)
710 for dockwidget
in dockwidgets
:
711 # Associate the action with the shortcut
712 toggleview
= dockwidget
.toggleViewAction()
713 menu
.addAction(toggleview
)
716 menu
.addAction(self
.lock_layout_action
)
720 def contextMenuEvent(self
, event
):
721 menu
= self
.create_view_menu()
722 menu
.exec_(event
.globalPos())
724 def build_recent_menu(self
):
725 settings
= Settings()
728 context
= self
.context
729 menu
= self
.open_recent_menu
731 worktree
= self
.git
.worktree()
732 for entry
in settings
.recent
:
733 directory
= entry
['path']
734 if directory
== worktree
:
735 # Omit the current worktree from the "Open Recent" menu.
738 text
= '%s %s %s' % (name
, uchr(0x2192), directory
)
739 menu
.addAction(text
, cmds
.run(cmd
, context
, directory
))
742 mode
= property(lambda self
: self
.model
.mode
)
744 def _config_updated(self
, _source
, config
, value
):
745 if config
== prefs
.FONTDIFF
:
748 if not font
.fromString(value
):
750 self
.logwidget
.setFont(font
)
751 self
.diffeditor
.setFont(font
)
752 self
.commiteditor
.setFont(font
)
754 elif config
== prefs
.TABWIDTH
:
755 # This can be set locally or globally, so we have to use the
756 # effective value otherwise we'll update when we shouldn't.
757 # For example, if this value is overridden locally, and the
758 # global value is tweaked, we should not update.
759 value
= prefs
.tabwidth(self
.context
)
760 self
.diffeditor
.set_tabwidth(value
)
761 self
.commiteditor
.set_tabwidth(value
)
763 elif config
== prefs
.EXPANDTAB
:
764 self
.commiteditor
.set_expandtab(value
)
766 elif config
== prefs
.LINEBREAK
:
767 # enables automatic line breaks
768 self
.commiteditor
.set_linebreak(value
)
770 elif config
== prefs
.SORT_BOOKMARKS
:
771 self
.bookmarksdock
.widget().reload_bookmarks()
773 elif config
== prefs
.TEXTWIDTH
:
774 # Use the effective value for the same reason as tabwidth.
775 value
= prefs
.textwidth(self
.context
)
776 self
.commiteditor
.set_textwidth(value
)
778 elif config
== prefs
.SHOW_PATH
:
779 # the path in the window title was toggled
780 self
.refresh_window_title()
782 def start(self
, context
):
783 """Do the expensive "get_config_actions()" call in the background"""
784 # Install .git-config-defined actions
785 task
= qtutils
.SimpleTask(self
, self
.get_config_actions
)
786 context
.runtask
.start(task
)
788 def get_config_actions(self
):
789 actions
= cfgactions
.get_config_actions(self
.context
)
790 self
.config_actions_changed
.emit(actions
)
792 def _install_config_actions(self
, names_and_shortcuts
):
793 """Install .gitconfig-defined actions"""
794 if not names_and_shortcuts
:
796 context
= self
.context
797 menu
= self
.actions_menu
799 for (name
, shortcut
) in names_and_shortcuts
:
800 callback
= cmds
.run(cmds
.RunConfigAction
, context
, name
)
801 menu_action
= menu
.addAction(name
, callback
)
803 menu_action
.setShortcut(shortcut
)
806 """Update the title with the current branch and directory name."""
807 curbranch
= self
.model
.currentbranch
808 curdir
= core
.getcwd()
809 is_merging
= self
.model
.is_merging
810 is_rebasing
= self
.model
.is_rebasing
812 msg
= N_('Repository: %s') % curdir
814 msg
+= N_('Branch: %s') % curbranch
818 msg
+= N_('This repository is currently being rebased.\n'
819 'Resolve conflicts, commit changes, and run:\n'
820 ' Rebase > Continue')
824 msg
+= N_('This repository is in the middle of a merge.\n'
825 'Resolve conflicts and commit changes.')
827 self
.refresh_window_title()
829 if self
.mode
== self
.model
.mode_amend
:
830 self
.commit_amend_action
.setChecked(True)
832 self
.commit_amend_action
.setChecked(False)
834 self
.commitdock
.setToolTip(msg
)
835 self
.commiteditor
.set_mode(self
.mode
)
836 self
.update_actions()
838 def refresh_window_title(self
):
839 """Refresh the window title when state changes"""
842 project
= self
.model
.project
843 curbranch
= self
.model
.currentbranch
844 is_merging
= self
.model
.is_merging
845 is_rebasing
= self
.model
.is_rebasing
850 alerts
.append(N_('Rebasing'))
852 alerts
.append(N_('Merging'))
854 if self
.mode
== self
.model
.mode_amend
:
855 alerts
.append(N_('Amending'))
858 alert_text
= (prefix
+ ' %s ' + suffix
+ ' ') % ', '.join(alerts
)
862 if self
.model
.cfg
.get(prefs
.SHOW_PATH
, True):
863 path_text
= self
.git
.worktree()
867 title
= '%s: %s %s%s' % (project
, curbranch
, alert_text
, path_text
)
868 self
.setWindowTitle(title
)
870 def update_actions(self
):
871 is_rebasing
= self
.model
.is_rebasing
872 self
.rebase_group
.setEnabled(is_rebasing
)
874 enabled
= not self
.model
.is_empty_repository()
875 self
.rename_branch_action
.setEnabled(enabled
)
876 self
.delete_branch_action
.setEnabled(enabled
)
878 self
.annex_init_action
.setEnabled(not self
.model
.annex
)
879 self
.lfs_init_action
.setEnabled(not self
.model
.lfs
)
881 def update_menu_actions(self
):
882 # Enable the Prepare Commit Message action if the hook exists
883 hook
= gitcmds
.prepare_commit_message_hook(self
.context
)
884 enabled
= os
.path
.exists(hook
)
885 self
.prepare_commitmsg_hook_action
.setEnabled(enabled
)
887 def export_state(self
):
888 state
= standard
.MainWindow
.export_state(self
)
889 show_status_filter
= self
.statuswidget
.filter_widget
.isVisible()
890 state
['show_status_filter'] = show_status_filter
891 state
['toolbars'] = self
.toolbar_state
.export_state()
892 state
['ref_sort'] = self
.model
.ref_sort
893 self
.diffviewer
.export_state(state
)
897 def apply_state(self
, state
):
898 """Imports data for save/restore"""
899 base_ok
= standard
.MainWindow
.apply_state(self
, state
)
900 lock_layout
= state
.get('lock_layout', False)
901 self
.lock_layout_action
.setChecked(lock_layout
)
903 show_status_filter
= state
.get('show_status_filter', False)
904 self
.statuswidget
.filter_widget
.setVisible(show_status_filter
)
906 toolbars
= state
.get('toolbars', [])
907 self
.toolbar_state
.apply_state(toolbars
)
909 sort_key
= state
.get('ref_sort', 0)
910 self
.model
.set_ref_sort(sort_key
)
912 diff_ok
= self
.diffviewer
.apply_state(state
)
913 return base_ok
and diff_ok
915 def setup_dockwidget_view_menu(self
):
916 # Hotkeys for toggling the dock widgets
917 if utils
.is_darwin():
922 (optkey
+ '+0', self
.logdock
),
923 (optkey
+ '+1', self
.commitdock
),
924 (optkey
+ '+2', self
.statusdock
),
925 (optkey
+ '+3', self
.diffdock
),
926 (optkey
+ '+4', self
.actionsdock
),
927 (optkey
+ '+5', self
.bookmarksdock
),
928 (optkey
+ '+6', self
.recentdock
),
929 (optkey
+ '+7', self
.branchdock
),
930 (optkey
+ '+8', self
.submodulesdock
)
932 for shortcut
, dockwidget
in dockwidgets
:
933 # Associate the action with the shortcut
934 toggleview
= dockwidget
.toggleViewAction()
935 toggleview
.setShortcut('Shift+' + shortcut
)
937 def showdock(show
, dockwidget
=dockwidget
):
940 dockwidget
.widget().setFocus()
944 self
.addAction(toggleview
)
945 qtutils
.connect_action_bool(toggleview
, showdock
)
947 # Create a new shortcut Shift+<shortcut> that gives focus
948 toggleview
= QtWidgets
.QAction(self
)
949 toggleview
.setShortcut(shortcut
)
951 def focusdock(dockwidget
=dockwidget
):
952 focus_dock(dockwidget
)
953 self
.addAction(toggleview
)
954 qtutils
.connect_action(toggleview
, focusdock
)
956 # These widgets warrant home-row hotkey status
957 qtutils
.add_action(self
, 'Focus Commit Message',
958 lambda: focus_dock(self
.commitdock
),
961 qtutils
.add_action(self
, 'Focus Status Window',
962 lambda: focus_dock(self
.statusdock
),
963 hotkeys
.FOCUS_STATUS
)
965 qtutils
.add_action(self
, 'Focus Diff Editor',
966 lambda: focus_dock(self
.diffdock
),
970 self
.dag
= dag
.git_dag(self
.context
, existing_view
=self
.dag
)
972 def show_cursor_position(self
, rows
, cols
):
973 display
= '%02d:%02d' % (rows
, cols
)
980 background-color: yellow;
984 background-color: #f83;
988 background-color: red;
996 cls
= 'second-warning'
998 cls
= 'first-warning'
1001 div
= ('<div class="%s">%s</div>' % (cls
, display
))
1002 self
.position_label
.setText(css
+ div
)
1005 class FocusProxy(object):
1006 """Proxy over child widgets and operate on the focused widget"""
1008 def __init__(self
, *widgets
):
1009 self
.widgets
= widgets
1012 def override(self
, name
, widgets
):
1013 self
.overrides
[name
] = widgets
1015 def focus(self
, name
):
1016 """Return the currently focused widget"""
1017 widgets
= self
.overrides
.get(name
, self
.widgets
)
1018 # The parent must be the parent of all the proxied widgets
1020 # The first widget is used as a fallback
1021 fallback
= widgets
[1]
1022 # We ignore the parent when delegating to child widgets
1023 widgets
= widgets
[1:]
1025 focus
= parent
.focusWidget()
1026 if focus
not in widgets
:
1030 def __getattr__(self
, name
):
1031 """Return a callback that calls a common child method"""
1033 focus
= self
.focus(name
)
1034 fn
= getattr(focus
, name
, None)
1040 """Specialized delete() to deal with QLineEdit vs QTextEdit"""
1041 focus
= self
.focus('delete')
1042 if hasattr(focus
, 'del_'):
1044 elif hasattr(focus
, 'textCursor'):
1045 focus
.textCursor().deleteChar()
1048 def show_dock(dockwidget
):
1050 dockwidget
.widget().setFocus()
1053 def focus_dock(dockwidget
):
1054 if get(dockwidget
.toggleViewAction()):
1055 show_dock(dockwidget
)
1057 dockwidget
.toggleViewAction().trigger()