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
)
124 # "Submodule" widgets
125 self
.submodulesdock
= create_dock(
126 N_('Submodules'), self
,
127 fn
=partial(submodules
.SubmodulesWidget
, context
))
128 self
.submoduleswidget
= self
.submodulesdock
.widget()
130 # "Commit Message Editor" widget
131 self
.position_label
= QtWidgets
.QLabel()
132 self
.position_label
.setAlignment(Qt
.AlignCenter
)
133 font
= qtutils
.default_monospace_font()
134 font
.setPointSize(int(font
.pointSize() * 0.8))
135 self
.position_label
.setFont(font
)
137 # make the position label fixed size to avoid layout issues
138 fm
= self
.position_label
.fontMetrics()
139 width
= fm
.width('99:999') + defs
.spacing
140 self
.position_label
.setMinimumWidth(width
)
142 editor
= commitmsg
.CommitMessageEditor(context
, self
)
143 self
.commiteditor
= editor
144 self
.commitdock
= create_dock(N_('Commit'), self
, widget
=editor
)
145 titlebar
= self
.commitdock
.titleBarWidget()
146 titlebar
.add_corner_widget(self
.position_label
)
149 self
.logwidget
= log
.LogWidget(context
)
150 self
.logdock
= create_dock(N_('Console'), self
, widget
=self
.logwidget
)
151 qtutils
.hide_dock(self
.logdock
)
153 # "Diff Viewer" widget
154 self
.diffdock
= create_dock(
156 fn
=lambda dock
: diff
.Viewer(context
, parent
=dock
))
157 self
.diffviewer
= self
.diffdock
.widget()
158 self
.diffviewer
.set_diff_type(self
.model
.diff_type
)
160 self
.diffeditor
= self
.diffviewer
.text
161 titlebar
= self
.diffdock
.titleBarWidget()
162 titlebar
.add_corner_widget(self
.diffviewer
.options
)
165 add_action
= qtutils
.add_action
166 add_action_bool
= qtutils
.add_action_bool
168 self
.commit_amend_action
= add_action_bool(
169 self
, N_('Amend Last Commit'),
170 partial(cmds
.do
, cmds
.AmendMode
, context
), False)
171 self
.commit_amend_action
.setShortcut(hotkeys
.AMEND
)
172 self
.commit_amend_action
.setShortcutContext(Qt
.WidgetShortcut
)
174 self
.unstage_all_action
= add_action(
175 self
, N_('Unstage All'), cmds
.run(cmds
.UnstageAll
, context
))
176 self
.unstage_all_action
.setIcon(icons
.remove())
178 self
.unstage_selected_action
= add_action(
179 self
, N_('Unstage From Commit'),
180 cmds
.run(cmds
.UnstageSelected
, context
))
181 self
.unstage_selected_action
.setIcon(icons
.remove())
183 self
.show_diffstat_action
= add_action(
184 self
, N_('Diffstat'), self
.statuswidget
.select_header
,
187 self
.stage_modified_action
= add_action(
188 self
, N_('Stage Changed Files To Commit'),
189 cmds
.run(cmds
.StageModified
, context
), hotkeys
.STAGE_MODIFIED
)
190 self
.stage_modified_action
.setIcon(icons
.add())
192 self
.stage_untracked_action
= add_action(
193 self
, N_('Stage All Untracked'),
194 cmds
.run(cmds
.StageUntracked
, context
), hotkeys
.STAGE_UNTRACKED
)
195 self
.stage_untracked_action
.setIcon(icons
.add())
197 self
.apply_patches_action
= add_action(
198 self
, N_('Apply Patches...'),
199 partial(patch
.apply_patches
, context
))
201 self
.export_patches_action
= add_action(
202 self
, N_('Export Patches...'),
203 partial(guicmds
.export_patches
, context
), hotkeys
.EXPORT
)
205 self
.new_repository_action
= add_action(
206 self
, N_('New Repository...'),
207 partial(guicmds
.open_new_repo
, context
))
208 self
.new_repository_action
.setIcon(icons
.new())
210 self
.new_bare_repository_action
= add_action(
211 self
, N_('New Bare Repository...'),
212 partial(guicmds
.new_bare_repo
, context
))
213 self
.new_bare_repository_action
.setIcon(icons
.new())
216 prefs_widget
.preferences
, context
, parent
=self
, model
=prefs_model
)
217 self
.preferences_action
= add_action(
218 self
, N_('Preferences'), prefs_fn
, QtGui
.QKeySequence
.Preferences
)
220 self
.edit_remotes_action
= add_action(
221 self
, N_('Edit Remotes...'), partial(editremotes
.editor
, context
))
223 self
.rescan_action
= add_action(
224 self
, cmds
.Refresh
.name(), cmds
.run(cmds
.Refresh
, context
),
225 *hotkeys
.REFRESH_HOTKEYS
)
226 self
.rescan_action
.setIcon(icons
.sync())
228 self
.find_files_action
= add_action(
229 self
, N_('Find Files'), partial(finder
.finder
, context
),
230 hotkeys
.FINDER
, hotkeys
.FINDER_SECONDARY
)
231 self
.find_files_action
.setIcon(icons
.zoom_in())
233 self
.browse_recently_modified_action
= add_action(
234 self
, N_('Recently Modified Files...'),
235 partial(recent
.browse_recent_files
, context
),
236 hotkeys
.EDIT_SECONDARY
)
238 self
.cherry_pick_action
= add_action(
239 self
, N_('Cherry-Pick...'), partial(guicmds
.cherry_pick
, context
),
242 self
.load_commitmsg_action
= add_action(
243 self
, N_('Load Commit Message...'),
244 partial(guicmds
.load_commitmsg
, context
))
246 self
.prepare_commitmsg_hook_action
= add_action(
247 self
, N_('Prepare Commit Message'),
248 cmds
.run(cmds
.PrepareCommitMessageHook
, context
),
249 hotkeys
.PREPARE_COMMIT_MESSAGE
)
251 self
.save_tarball_action
= add_action(
252 self
, N_('Save As Tarball/Zip...'),
253 partial(archive
.save_archive
, context
, self
))
255 self
.quit_action
= add_action(
256 self
, N_('Quit'), self
.close
, hotkeys
.QUIT
)
258 self
.grep_action
= add_action(
259 self
, N_('Grep'), partial(grep
.grep
, context
), hotkeys
.GREP
)
261 self
.merge_local_action
= add_action(
262 self
, N_('Merge...'), partial(merge
.local_merge
, context
),
265 self
.merge_abort_action
= add_action(
266 self
, N_('Abort Merge...'), cmds
.run(cmds
.AbortMerge
, context
))
268 self
.update_submodules_action
= add_action(
269 self
, N_('Update All Submodules...'),
270 cmds
.run(cmds
.SubmodulesUpdate
, context
))
272 self
.fetch_action
= add_action(
273 self
, N_('Fetch...'), partial(remote
.fetch
, context
),
275 self
.push_action
= add_action(
276 self
, N_('Push...'), partial(remote
.push
, context
), hotkeys
.PUSH
)
277 self
.pull_action
= add_action(
278 self
, N_('Pull...'), partial(remote
.pull
, context
), hotkeys
.PULL
)
280 self
.open_repo_action
= add_action(
282 partial(guicmds
.open_repo
, context
), hotkeys
.OPEN
)
283 self
.open_repo_action
.setIcon(icons
.folder())
285 self
.open_repo_new_action
= add_action(
286 self
, N_('Open in New Window...'),
287 partial(guicmds
.open_repo_in_new_window
, context
))
288 self
.open_repo_new_action
.setIcon(icons
.folder())
290 self
.stash_action
= add_action(
291 self
, N_('Stash...'), partial(stash
.view
, context
), hotkeys
.STASH
)
293 self
.reset_branch_head_action
= add_action(
294 self
, N_('Reset Branch Head'),
295 partial(guicmds
.reset_branch_head
, context
))
297 self
.reset_worktree_action
= add_action(
298 self
, N_('Reset Worktree'),
299 partial(guicmds
.reset_worktree
, context
))
301 self
.clone_repo_action
= add_action(
302 self
, N_('Clone...'),
303 partial(clone
.clone
, context
, settings
=settings
))
304 self
.clone_repo_action
.setIcon(icons
.repo())
306 self
.help_docs_action
= add_action(
307 self
, N_('Documentation'), resources
.show_html_docs
,
308 QtGui
.QKeySequence
.HelpContents
)
310 self
.help_shortcuts_action
= add_action(
311 self
, N_('Keyboard Shortcuts'), about
.show_shortcuts
,
314 self
.visualize_current_action
= add_action(
315 self
, N_('Visualize Current Branch...'),
316 cmds
.run(cmds
.VisualizeCurrent
, context
))
317 self
.visualize_all_action
= add_action(
318 self
, N_('Visualize All Branches...'),
319 cmds
.run(cmds
.VisualizeAll
, context
))
320 self
.search_commits_action
= add_action(
321 self
, N_('Search...'), partial(search
.search
, context
))
323 self
.browse_branch_action
= add_action(
324 self
, N_('Browse Current Branch...'),
325 partial(guicmds
.browse_current
, context
))
326 self
.browse_other_branch_action
= add_action(
327 self
, N_('Browse Other Branch...'),
328 partial(guicmds
.browse_other
, context
))
329 self
.load_commitmsg_template_action
= add_action(
330 self
, N_('Get Commit Message Template'),
331 cmds
.run(cmds
.LoadCommitMessageFromTemplate
, context
))
332 self
.help_about_action
= add_action(
333 self
, N_('About'), partial(about
.about_dialog
, context
))
335 self
.diff_expression_action
= add_action(
336 self
, N_('Expression...'),
337 partial(guicmds
.diff_expression
, context
))
338 self
.branch_compare_action
= add_action(
339 self
, N_('Branches...'),
340 partial(compare
.compare_branches
, context
))
342 self
.create_tag_action
= add_action(
343 self
, N_('Create Tag...'),
344 partial(createtag
.create_tag
, context
, settings
=settings
))
346 self
.create_branch_action
= add_action(
347 self
, N_('Create...'),
348 partial(createbranch
.create_new_branch
, context
,
351 self
.create_branch_action
.setIcon(icons
.branch())
353 self
.delete_branch_action
= add_action(
354 self
, N_('Delete...'),
355 partial(guicmds
.delete_branch
, context
))
357 self
.delete_remote_branch_action
= add_action(
358 self
, N_('Delete Remote Branch...'),
359 partial(guicmds
.delete_remote_branch
, context
))
361 self
.rename_branch_action
= add_action(
362 self
, N_('Rename Branch...'),
363 partial(guicmds
.rename_branch
, context
))
365 self
.checkout_branch_action
= add_action(
366 self
, N_('Checkout...'),
367 partial(guicmds
.checkout_branch
, context
),
369 self
.branch_review_action
= add_action(
370 self
, N_('Review...'),
371 partial(guicmds
.review_branch
, context
))
373 self
.browse_action
= add_action(
374 self
, N_('File Browser...'),
375 partial(browse
.worktree_browser
, context
))
376 self
.browse_action
.setIcon(icons
.cola())
378 self
.dag_action
= add_action(self
, N_('DAG...'), self
.git_dag
)
379 self
.dag_action
.setIcon(icons
.cola())
381 self
.rebase_start_action
= add_action(
382 self
, N_('Start Interactive Rebase...'),
383 cmds
.run(cmds
.Rebase
, context
), hotkeys
.REBASE_START_AND_CONTINUE
)
385 self
.rebase_edit_todo_action
= add_action(
386 self
, N_('Edit...'), cmds
.run(cmds
.RebaseEditTodo
, context
))
388 self
.rebase_continue_action
= add_action(
389 self
, N_('Continue'), cmds
.run(cmds
.RebaseContinue
, context
),
390 hotkeys
.REBASE_START_AND_CONTINUE
)
392 self
.rebase_skip_action
= add_action(
393 self
, N_('Skip Current Patch'), cmds
.run(cmds
.RebaseSkip
, context
))
395 self
.rebase_abort_action
= add_action(
396 self
, N_('Abort'), cmds
.run(cmds
.RebaseAbort
, context
))
398 # For "Start Rebase" only, reverse the first argument to setEnabled()
399 # so that we can operate on it as a group.
400 # We can do this because can_rebase == not is_rebasing
401 self
.rebase_start_action_proxy
= utils
.Proxy(
402 self
.rebase_start_action
,
403 setEnabled
=lambda x
: self
.rebase_start_action
.setEnabled(not x
))
405 self
.rebase_group
= utils
.Group(self
.rebase_start_action_proxy
,
406 self
.rebase_edit_todo_action
,
407 self
.rebase_continue_action
,
408 self
.rebase_skip_action
,
409 self
.rebase_abort_action
)
411 self
.annex_init_action
= qtutils
.add_action(
412 self
, N_('Initialize Git Annex'),
413 cmds
.run(cmds
.AnnexInit
, context
))
415 self
.lfs_init_action
= qtutils
.add_action(
416 self
, N_('Initialize Git LFS'), cmds
.run(cmds
.LFSInstall
, context
))
418 self
.lock_layout_action
= add_action_bool(
419 self
, N_('Lock Layout'), self
.set_lock_layout
, False)
421 # Create the application menu
422 self
.menubar
= QtWidgets
.QMenuBar(self
)
423 self
.setMenuBar(self
.menubar
)
426 add_menu
= qtutils
.add_menu
427 self
.file_menu
= add_menu(N_('&File'), self
.menubar
)
428 # File->Open Recent menu
429 self
.open_recent_menu
= self
.file_menu
.addMenu(N_('Open Recent'))
430 self
.open_recent_menu
.setIcon(icons
.folder())
431 self
.file_menu
.addAction(self
.open_repo_action
)
432 self
.file_menu
.addAction(self
.open_repo_new_action
)
433 self
.file_menu
.addSeparator()
434 self
.file_menu
.addAction(self
.new_repository_action
)
435 self
.file_menu
.addAction(self
.new_bare_repository_action
)
436 self
.file_menu
.addAction(self
.clone_repo_action
)
437 self
.file_menu
.addSeparator()
438 self
.file_menu
.addAction(self
.rescan_action
)
439 self
.file_menu
.addAction(self
.find_files_action
)
440 self
.file_menu
.addAction(self
.edit_remotes_action
)
441 self
.file_menu
.addAction(self
.browse_recently_modified_action
)
442 self
.file_menu
.addSeparator()
443 self
.file_menu
.addAction(self
.apply_patches_action
)
444 self
.file_menu
.addAction(self
.export_patches_action
)
445 self
.file_menu
.addAction(self
.save_tarball_action
)
447 # Git Annex / Git LFS
448 annex
= core
.find_executable('git-annex')
449 lfs
= core
.find_executable('git-lfs')
451 self
.file_menu
.addSeparator()
453 self
.file_menu
.addAction(self
.annex_init_action
)
455 self
.file_menu
.addAction(self
.lfs_init_action
)
457 self
.file_menu
.addSeparator()
458 self
.file_menu
.addAction(self
.preferences_action
)
459 self
.file_menu
.addAction(self
.quit_action
)
462 self
.edit_proxy
= edit_proxy
= (
463 FocusProxy(editor
, editor
.summary
, editor
.description
))
466 self
, editor
.summary
, editor
.description
, self
.diffeditor
,
467 bookmarkswidget
.tree
, recentwidget
.tree
,
469 edit_proxy
.override('copy', copy_widgets
)
470 edit_proxy
.override('selectAll', copy_widgets
)
472 edit_menu
= self
.edit_menu
= add_menu(N_('&Edit'), self
.menubar
)
473 add_action(edit_menu
, N_('Undo'), edit_proxy
.undo
, hotkeys
.UNDO
)
474 add_action(edit_menu
, N_('Redo'), edit_proxy
.redo
, hotkeys
.REDO
)
475 edit_menu
.addSeparator()
476 add_action(edit_menu
, N_('Cut'), edit_proxy
.cut
, hotkeys
.CUT
)
477 add_action(edit_menu
, N_('Copy'), edit_proxy
.copy
, hotkeys
.COPY
)
478 add_action(edit_menu
, N_('Paste'), edit_proxy
.paste
, hotkeys
.PASTE
)
479 add_action(edit_menu
, N_('Delete'), edit_proxy
.delete
, hotkeys
.DELETE
)
480 edit_menu
.addSeparator()
481 add_action(edit_menu
, N_('Select All'), edit_proxy
.selectAll
,
483 edit_menu
.addSeparator()
485 commitmsg
.add_menu_actions(edit_menu
, self
.commiteditor
.menu_actions
)
488 self
.actions_menu
= add_menu(N_('Actions'), self
.menubar
)
489 self
.actions_menu
.addAction(self
.fetch_action
)
490 self
.actions_menu
.addAction(self
.push_action
)
491 self
.actions_menu
.addAction(self
.pull_action
)
492 self
.actions_menu
.addAction(self
.stash_action
)
493 self
.actions_menu
.addSeparator()
494 self
.actions_menu
.addAction(self
.create_tag_action
)
495 self
.actions_menu
.addAction(self
.cherry_pick_action
)
496 self
.actions_menu
.addAction(self
.merge_local_action
)
497 self
.actions_menu
.addAction(self
.merge_abort_action
)
498 self
.actions_menu
.addSeparator()
499 self
.actions_menu
.addAction(self
.update_submodules_action
)
500 self
.actions_menu
.addSeparator()
501 self
.actions_reset_menu
= self
.actions_menu
.addMenu(N_('Reset'))
502 self
.actions_reset_menu
.addAction(self
.reset_branch_head_action
)
503 self
.actions_reset_menu
.addAction(self
.reset_worktree_action
)
504 self
.actions_menu
.addSeparator()
505 self
.actions_menu
.addAction(self
.grep_action
)
506 self
.actions_menu
.addAction(self
.search_commits_action
)
509 self
.commit_menu
= add_menu(N_('Commit@@verb'), self
.menubar
)
510 self
.commit_menu
.setTitle(N_('Commit@@verb'))
511 self
.commit_menu
.addAction(self
.commiteditor
.commit_action
)
512 self
.commit_menu
.addAction(self
.commit_amend_action
)
513 self
.commit_menu
.addSeparator()
514 self
.commit_menu
.addAction(self
.stage_modified_action
)
515 self
.commit_menu
.addAction(self
.stage_untracked_action
)
516 self
.commit_menu
.addSeparator()
517 self
.commit_menu
.addAction(self
.unstage_all_action
)
518 self
.commit_menu
.addAction(self
.unstage_selected_action
)
519 self
.commit_menu
.addSeparator()
520 self
.commit_menu
.addAction(self
.load_commitmsg_action
)
521 self
.commit_menu
.addAction(self
.load_commitmsg_template_action
)
522 self
.commit_menu
.addAction(self
.prepare_commitmsg_hook_action
)
525 self
.diff_menu
= add_menu(N_('Diff'), self
.menubar
)
526 self
.diff_menu
.addAction(self
.diff_expression_action
)
527 self
.diff_menu
.addAction(self
.branch_compare_action
)
528 self
.diff_menu
.addSeparator()
529 self
.diff_menu
.addAction(self
.show_diffstat_action
)
532 self
.branch_menu
= add_menu(N_('Branch'), self
.menubar
)
533 self
.branch_menu
.addAction(self
.branch_review_action
)
534 self
.branch_menu
.addSeparator()
535 self
.branch_menu
.addAction(self
.create_branch_action
)
536 self
.branch_menu
.addAction(self
.checkout_branch_action
)
537 self
.branch_menu
.addAction(self
.delete_branch_action
)
538 self
.branch_menu
.addAction(self
.delete_remote_branch_action
)
539 self
.branch_menu
.addAction(self
.rename_branch_action
)
540 self
.branch_menu
.addSeparator()
541 self
.branch_menu
.addAction(self
.browse_branch_action
)
542 self
.branch_menu
.addAction(self
.browse_other_branch_action
)
543 self
.branch_menu
.addSeparator()
544 self
.branch_menu
.addAction(self
.visualize_current_action
)
545 self
.branch_menu
.addAction(self
.visualize_all_action
)
548 self
.rebase_menu
= add_menu(N_('Rebase'), self
.actions_menu
)
549 self
.rebase_menu
.addAction(self
.rebase_start_action
)
550 self
.rebase_menu
.addAction(self
.rebase_edit_todo_action
)
551 self
.rebase_menu
.addSeparator()
552 self
.rebase_menu
.addAction(self
.rebase_continue_action
)
553 self
.rebase_menu
.addAction(self
.rebase_skip_action
)
554 self
.rebase_menu
.addSeparator()
555 self
.rebase_menu
.addAction(self
.rebase_abort_action
)
558 self
.view_menu
= add_menu(N_('View'), self
.menubar
)
559 self
.view_menu
.aboutToShow
.connect(
560 lambda: self
.build_view_menu(self
.view_menu
))
561 self
.setup_dockwidget_view_menu()
562 if utils
.is_darwin():
563 # TODO or self.menubar.setNativeMenuBar(False)
564 # Since native OSX menu doesn't show empty entries
565 self
.build_view_menu(self
.view_menu
)
568 self
.help_menu
= add_menu(N_('Help'), self
.menubar
)
569 self
.help_menu
.addAction(self
.help_docs_action
)
570 self
.help_menu
.addAction(self
.help_shortcuts_action
)
571 self
.help_menu
.addAction(self
.help_about_action
)
573 # Arrange dock widgets
574 bottom
= Qt
.BottomDockWidgetArea
575 top
= Qt
.TopDockWidgetArea
577 self
.addDockWidget(top
, self
.statusdock
)
578 self
.addDockWidget(top
, self
.commitdock
)
579 if self
.browser_dockable
:
580 self
.addDockWidget(top
, self
.browserdock
)
581 self
.tabifyDockWidget(self
.browserdock
, self
.commitdock
)
582 self
.addDockWidget(top
, self
.bookmarksdock
)
583 self
.addDockWidget(top
, self
.branchdock
)
584 self
.addDockWidget(top
, self
.recentdock
)
585 self
.addDockWidget(bottom
, self
.diffdock
)
586 self
.addDockWidget(bottom
, self
.actionsdock
)
587 self
.addDockWidget(bottom
, self
.logdock
)
588 self
.tabifyDockWidget(self
.actionsdock
, self
.logdock
)
590 # Listen for model notifications
591 model
.add_observer(model
.message_updated
, self
.updated
.emit
)
592 model
.add_observer(model
.message_mode_changed
,
593 lambda mode
: self
.updated
.emit())
595 prefs_model
.add_observer(prefs_model
.message_config_updated
,
596 self
._config
_updated
)
598 # Set a default value
599 self
.show_cursor_position(1, 0)
601 self
.commit_menu
.aboutToShow
.connect(self
.update_menu_actions
)
602 self
.open_recent_menu
.aboutToShow
.connect(self
.build_recent_menu
)
603 self
.commiteditor
.cursor_changed
.connect(self
.show_cursor_position
)
605 self
.diffeditor
.options_changed
.connect(self
.statuswidget
.refresh
)
606 self
.diffeditor
.up
.connect(self
.statuswidget
.move_up
)
607 self
.diffeditor
.down
.connect(self
.statuswidget
.move_down
)
609 self
.commiteditor
.up
.connect(self
.statuswidget
.move_up
)
610 self
.commiteditor
.down
.connect(self
.statuswidget
.move_down
)
612 self
.updated
.connect(self
.refresh
, type=Qt
.QueuedConnection
)
614 self
.config_actions_changed
.connect(self
._install
_config
_actions
,
615 type=Qt
.QueuedConnection
)
616 self
.init_state(settings
, self
.set_initial_size
)
618 # Route command output here
619 Interaction
.log_status
= self
.logwidget
.log_status
620 Interaction
.log
= self
.logwidget
.log
621 # Focus the status widget; this must be deferred
622 QtCore
.QTimer
.singleShot(0, self
.initialize
)
624 def initialize(self
):
625 context
= self
.context
626 git_version
= version
.git_version_str(context
)
629 Interaction
.log(git_version
+ '\n' +
630 N_('git cola version %s') % version
.version())
633 error_msg
= N_('error: unable to execute git')
634 Interaction
.log(error_msg
)
637 self
.statuswidget
.setFocus()
639 title
= N_('error: unable to execute git')
643 details
= git
.win32_git_error_hint()
644 Interaction
.critical(title
, message
=msg
, details
=details
)
645 self
.context
.app
.exit(2)
647 def set_initial_size(self
):
648 # Default size; this is thrown out when save/restore is used
649 width
, height
= qtutils
.desktop_size()
650 self
.resize((width
*3)//4, height
)
651 self
.statuswidget
.set_initial_size()
652 self
.commiteditor
.set_initial_size()
654 def set_filter(self
, txt
):
655 self
.statuswidget
.set_filter(txt
)
658 def closeEvent(self
, event
):
659 """Save state in the settings"""
660 commit_msg
= self
.commiteditor
.commit_message(raw
=True)
661 self
.model
.save_commitmsg(msg
=commit_msg
)
662 standard
.MainWindow
.closeEvent(self
, event
)
664 def create_view_menu(self
):
665 menu
= qtutils
.create_menu(N_('View'), self
)
666 self
.build_view_menu(menu
)
669 def build_view_menu(self
, menu
):
671 menu
.addAction(self
.browse_action
)
672 menu
.addAction(self
.dag_action
)
675 popup_menu
= self
.createPopupMenu()
676 for menu_action
in popup_menu
.actions():
677 menu_action
.setParent(menu
)
678 menu
.addAction(menu_action
)
681 context
= self
.context
682 menu_action
= menu
.addAction(
683 N_('Add Toolbar'), partial(toolbar
.add_toolbar
, context
, self
))
684 menu_action
.setIcon(icons
.add())
697 if self
.browser_dockable
:
698 dockwidgets
.append(self
.browserdock
)
700 for dockwidget
in dockwidgets
:
701 # Associate the action with the shortcut
702 toggleview
= dockwidget
.toggleViewAction()
703 menu
.addAction(toggleview
)
706 menu
.addAction(self
.lock_layout_action
)
710 def contextMenuEvent(self
, event
):
711 menu
= self
.create_view_menu()
712 menu
.exec_(event
.globalPos())
714 def build_recent_menu(self
):
715 settings
= Settings()
718 context
= self
.context
719 menu
= self
.open_recent_menu
721 for entry
in settings
.recent
:
723 directory
= entry
['path']
724 text
= '%s %s %s' % (name
, uchr(0x2192), directory
)
725 menu
.addAction(text
, cmds
.run(cmd
, context
, directory
))
728 mode
= property(lambda self
: self
.model
.mode
)
730 def _config_updated(self
, _source
, config
, value
):
731 if config
== prefs
.FONTDIFF
:
734 if not font
.fromString(value
):
736 self
.logwidget
.setFont(font
)
737 self
.diffeditor
.setFont(font
)
738 self
.commiteditor
.setFont(font
)
740 elif config
== prefs
.TABWIDTH
:
741 # This can be set locally or globally, so we have to use the
742 # effective value otherwise we'll update when we shouldn't.
743 # For example, if this value is overridden locally, and the
744 # global value is tweaked, we should not update.
745 value
= prefs
.tabwidth(self
.context
)
746 self
.diffeditor
.set_tabwidth(value
)
747 self
.commiteditor
.set_tabwidth(value
)
749 elif config
== prefs
.EXPANDTAB
:
750 self
.commiteditor
.set_expandtab(value
)
752 elif config
== prefs
.LINEBREAK
:
753 # enables automatic line breaks
754 self
.commiteditor
.set_linebreak(value
)
756 elif config
== prefs
.SORT_BOOKMARKS
:
757 self
.bookmarksdock
.widget().reload_bookmarks()
759 elif config
== prefs
.TEXTWIDTH
:
760 # Use the effective value for the same reason as tabwidth.
761 value
= prefs
.textwidth(self
.context
)
762 self
.commiteditor
.set_textwidth(value
)
764 elif config
== prefs
.SHOW_PATH
:
765 # the path in the window title was toggled
766 self
.refresh_window_title()
768 def start(self
, context
):
769 """Do the expensive "get_config_actions()" call in the background"""
770 # Install .git-config-defined actions
771 task
= qtutils
.SimpleTask(self
, self
.get_config_actions
)
772 context
.runtask
.start(task
)
774 def get_config_actions(self
):
775 actions
= cfgactions
.get_config_actions(self
.context
)
776 self
.config_actions_changed
.emit(actions
)
778 def _install_config_actions(self
, names_and_shortcuts
):
779 """Install .gitconfig-defined actions"""
780 if not names_and_shortcuts
:
782 context
= self
.context
783 menu
= self
.actions_menu
785 for (name
, shortcut
) in names_and_shortcuts
:
786 callback
= cmds
.run(cmds
.RunConfigAction
, context
, name
)
787 menu_action
= menu
.addAction(name
, callback
)
789 menu_action
.setShortcut(shortcut
)
792 """Update the title with the current branch and directory name."""
793 curbranch
= self
.model
.currentbranch
794 curdir
= core
.getcwd()
795 is_merging
= self
.model
.is_merging
796 is_rebasing
= self
.model
.is_rebasing
798 msg
= N_('Repository: %s') % curdir
800 msg
+= N_('Branch: %s') % curbranch
804 msg
+= N_('This repository is currently being rebased.\n'
805 'Resolve conflicts, commit changes, and run:\n'
806 ' Rebase > Continue')
810 msg
+= N_('This repository is in the middle of a merge.\n'
811 'Resolve conflicts and commit changes.')
813 self
.refresh_window_title()
815 if self
.mode
== self
.model
.mode_amend
:
816 self
.commit_amend_action
.setChecked(True)
818 self
.commit_amend_action
.setChecked(False)
820 self
.commitdock
.setToolTip(msg
)
821 self
.commiteditor
.set_mode(self
.mode
)
822 self
.update_actions()
824 def refresh_window_title(self
):
825 """Refresh the window title when state changes"""
828 project
= self
.model
.project
829 curbranch
= self
.model
.currentbranch
830 is_merging
= self
.model
.is_merging
831 is_rebasing
= self
.model
.is_rebasing
836 alerts
.append(N_('Rebasing'))
838 alerts
.append(N_('Merging'))
840 if self
.mode
== self
.model
.mode_amend
:
841 alerts
.append(N_('Amending'))
844 alert_text
= (prefix
+ ' %s ' + suffix
+ ' ') % ', '.join(alerts
)
848 if self
.model
.cfg
.get(prefs
.SHOW_PATH
, True):
849 path_text
= self
.git
.worktree()
853 title
= '%s: %s %s%s' % (project
, curbranch
, alert_text
, path_text
)
854 self
.setWindowTitle(title
)
856 def update_actions(self
):
857 is_rebasing
= self
.model
.is_rebasing
858 self
.rebase_group
.setEnabled(is_rebasing
)
860 enabled
= not self
.model
.is_empty_repository()
861 self
.rename_branch_action
.setEnabled(enabled
)
862 self
.delete_branch_action
.setEnabled(enabled
)
864 self
.annex_init_action
.setEnabled(not self
.model
.annex
)
865 self
.lfs_init_action
.setEnabled(not self
.model
.lfs
)
867 def update_menu_actions(self
):
868 # Enable the Prepare Commit Message action if the hook exists
869 hook
= gitcmds
.prepare_commit_message_hook(self
.context
)
870 enabled
= os
.path
.exists(hook
)
871 self
.prepare_commitmsg_hook_action
.setEnabled(enabled
)
873 def export_state(self
):
874 state
= standard
.MainWindow
.export_state(self
)
875 show_status_filter
= self
.statuswidget
.filter_widget
.isVisible()
876 state
['show_status_filter'] = show_status_filter
877 state
['toolbars'] = self
.toolbar_state
.export_state()
878 self
.diffviewer
.export_state(state
)
882 def apply_state(self
, state
):
883 """Imports data for save/restore"""
884 base_ok
= standard
.MainWindow
.apply_state(self
, state
)
885 lock_layout
= state
.get('lock_layout', False)
886 self
.lock_layout_action
.setChecked(lock_layout
)
888 show_status_filter
= state
.get('show_status_filter', False)
889 self
.statuswidget
.filter_widget
.setVisible(show_status_filter
)
891 toolbars
= state
.get('toolbars', [])
892 self
.toolbar_state
.apply_state(toolbars
)
894 diff_ok
= self
.diffviewer
.apply_state(state
)
895 return base_ok
and diff_ok
897 def setup_dockwidget_view_menu(self
):
898 # Hotkeys for toggling the dock widgets
899 if utils
.is_darwin():
904 (optkey
+ '+0', self
.logdock
),
905 (optkey
+ '+1', self
.commitdock
),
906 (optkey
+ '+2', self
.statusdock
),
907 (optkey
+ '+3', self
.diffdock
),
908 (optkey
+ '+4', self
.actionsdock
),
909 (optkey
+ '+5', self
.bookmarksdock
),
910 (optkey
+ '+6', self
.recentdock
),
911 (optkey
+ '+7', self
.branchdock
),
912 (optkey
+ '+8', self
.submodulesdock
)
914 for shortcut
, dockwidget
in dockwidgets
:
915 # Associate the action with the shortcut
916 toggleview
= dockwidget
.toggleViewAction()
917 toggleview
.setShortcut('Shift+' + shortcut
)
919 def showdock(show
, dockwidget
=dockwidget
):
922 dockwidget
.widget().setFocus()
926 self
.addAction(toggleview
)
927 qtutils
.connect_action_bool(toggleview
, showdock
)
929 # Create a new shortcut Shift+<shortcut> that gives focus
930 toggleview
= QtWidgets
.QAction(self
)
931 toggleview
.setShortcut(shortcut
)
933 def focusdock(dockwidget
=dockwidget
):
934 focus_dock(dockwidget
)
935 self
.addAction(toggleview
)
936 qtutils
.connect_action(toggleview
, focusdock
)
938 # These widgets warrant home-row hotkey status
939 qtutils
.add_action(self
, 'Focus Commit Message',
940 lambda: focus_dock(self
.commitdock
),
943 qtutils
.add_action(self
, 'Focus Status Window',
944 lambda: focus_dock(self
.statusdock
),
945 hotkeys
.FOCUS_STATUS
)
947 qtutils
.add_action(self
, 'Focus Diff Editor',
948 lambda: focus_dock(self
.diffdock
),
952 self
.dag
= dag
.git_dag(self
.context
, existing_view
=self
.dag
)
954 def show_cursor_position(self
, rows
, cols
):
955 display
= '%02d:%02d' % (rows
, cols
)
962 background-color: yellow;
966 background-color: #f83;
970 background-color: red;
978 cls
= 'second-warning'
980 cls
= 'first-warning'
983 div
= ('<div class="%s">%s</div>' % (cls
, display
))
984 self
.position_label
.setText(css
+ div
)
987 class FocusProxy(object):
988 """Proxy over child widgets and operate on the focused widget"""
990 def __init__(self
, *widgets
):
991 self
.widgets
= widgets
994 def override(self
, name
, widgets
):
995 self
.overrides
[name
] = widgets
997 def focus(self
, name
):
998 """Return the currently focused widget"""
999 widgets
= self
.overrides
.get(name
, self
.widgets
)
1000 # The parent must be the parent of all the proxied widgets
1002 # The first widget is used as a fallback
1003 fallback
= widgets
[1]
1004 # We ignore the parent when delegating to child widgets
1005 widgets
= widgets
[1:]
1007 focus
= parent
.focusWidget()
1008 if focus
not in widgets
:
1012 def __getattr__(self
, name
):
1013 """Return a callback that calls a common child method"""
1015 focus
= self
.focus(name
)
1016 fn
= getattr(focus
, name
, None)
1022 """Specialized delete() to deal with QLineEdit vs QTextEdit"""
1023 focus
= self
.focus('delete')
1024 if hasattr(focus
, 'del_'):
1026 elif hasattr(focus
, 'textCursor'):
1027 focus
.textCursor().deleteChar()
1030 def show_dock(dockwidget
):
1032 dockwidget
.widget().setFocus()
1035 def focus_dock(dockwidget
):
1036 if get(dockwidget
.toggleViewAction()):
1037 show_dock(dockwidget
)
1039 dockwidget
.toggleViewAction().trigger()