class MainView(MainWindow): def __init__(self, model, parent=None, settings=None): MainWindow.__init__(self, parent) self.setAttribute(Qt.WA_MacMetalStyle) # Default size; this is thrown out when save/restore is used self.model = model self.settings = settings self.prefs_model = prefs_model = prefs.PreferencesModel() # The widget version is used by import/export_state(). # Change this whenever dockwidgets are removed. self.widget_version = 2 # Keeps track of merge messages we've seen self.merge_message_hash = '' # Runs asynchronous tasks self.task_runner = TaskRunner(self) self.progress = ProgressDialog('', '', self) cfg = gitcfg.current() self.browser_dockable = (cfg.get('cola.browserdockable') or cfg.get('cola.classicdockable')) if self.browser_dockable: self.browserdockwidget = create_dock(N_('Browser'), self) self.browserwidget = worktree_browser_widget(self) self.browserdockwidget.setWidget(self.browserwidget) # "Actions" widget self.actionsdockwidget = create_dock(N_('Actions'), self) self.actionsdockwidgetcontents = action.ActionButtons(self) self.actionsdockwidget.setWidget(self.actionsdockwidgetcontents) self.actionsdockwidget.toggleViewAction().setChecked(False) self.actionsdockwidget.hide() # "Repository Status" widget self.statusdockwidget = create_dock(N_('Status'), self) self.statuswidget = StatusWidget(self.statusdockwidget.titleBarWidget(), parent=self.statusdockwidget) self.statusdockwidget.setWidget(self.statuswidget) # "Switch Repository" widgets self.bookmarksdockwidget = create_dock(N_('Bookmarks'), self) self.bookmarkswidget = bookmarks.BookmarksWidget( bookmarks.BOOKMARKS, parent=self.bookmarksdockwidget) self.bookmarksdockwidget.setWidget(self.bookmarkswidget) self.recentdockwidget = create_dock(N_('Recent'), self) self.recentwidget = bookmarks.BookmarksWidget( bookmarks.RECENT_REPOS, parent=self.recentdockwidget) self.recentdockwidget.setWidget(self.recentwidget) self.recentdockwidget.hide() # "Commit Message Editor" widget self.position_label = QtGui.QLabel() font = qtutils.default_monospace_font() font.setPointSize(int(font.pointSize() * 0.8)) self.position_label.setFont(font) # make the position label fixed size to avoid layout issues fm = self.position_label.fontMetrics() width = fm.width('999:999') height = self.position_label.sizeHint().height() self.position_label.setFixedSize(width, height) self.commitdockwidget = create_dock(N_('Commit'), self) titlebar = self.commitdockwidget.titleBarWidget() titlebar.add_corner_widget(self.position_label) self.commitmsgeditor = CommitMessageEditor(model, self) self.commitdockwidget.setWidget(self.commitmsgeditor) # "Console" widget self.logwidget = LogWidget() self.logdockwidget = create_dock(N_('Console'), self) self.logdockwidget.setWidget(self.logwidget) self.logdockwidget.toggleViewAction().setChecked(False) self.logdockwidget.hide() # "Diff Viewer" widget self.diffdockwidget = create_dock(N_('Diff'), self) self.diffeditorwidget = DiffEditorWidget(self.diffdockwidget) self.diffeditor = self.diffeditorwidget.editor self.diffdockwidget.setWidget(self.diffeditorwidget) # All Actions self.unstage_all_action = add_action(self, N_('Unstage All'), cmds.run(cmds.UnstageAll)) self.unstage_all_action.setIcon(qtutils.icon('remove.svg')) self.unstage_selected_action = add_action(self, N_('Unstage From Commit'), cmds.run(cmds.UnstageSelected)) self.unstage_selected_action.setIcon(qtutils.icon('remove.svg')) self.show_diffstat_action = add_action(self, N_('Diffstat'), cmds.run(cmds.Diffstat), 'Alt+D') self.stage_modified_action = add_action(self, N_('Stage Changed Files To Commit'), cmds.run(cmds.StageModified), 'Alt+A') self.stage_modified_action.setIcon(qtutils.icon('add.svg')) self.stage_untracked_action = add_action(self, N_('Stage All Untracked'), cmds.run(cmds.StageUntracked), 'Alt+U') self.stage_untracked_action.setIcon(qtutils.icon('add.svg')) self.apply_patches_action = add_action(self, N_('Apply Patches...'), apply_patches) self.export_patches_action = add_action(self, N_('Export Patches...'), guicmds.export_patches, 'Alt+E') self.new_repository_action = add_action(self, N_('New Repository...'), guicmds.open_new_repo) self.new_repository_action.setIcon(qtutils.new_icon()) self.preferences_action = add_action(self, N_('Preferences'), self.preferences, QtGui.QKeySequence.Preferences) self.edit_remotes_action = add_action(self, N_('Edit Remotes...'), lambda: editremotes.remote_editor().exec_()) self.rescan_action = add_action(self, cmds.Refresh.name(), cmds.run(cmds.Refresh), cmds.Refresh.SHORTCUT) self.rescan_action.setIcon(qtutils.reload_icon()) self.browse_recently_modified_action = add_action(self, N_('Recently Modified Files...'), browse_recent_files, 'Shift+Ctrl+E') self.cherry_pick_action = add_action(self, N_('Cherry-Pick...'), guicmds.cherry_pick, 'Shift+Ctrl+C') self.load_commitmsg_action = add_action(self, N_('Load Commit Message...'), guicmds.load_commitmsg) self.save_tarball_action = add_action(self, N_('Save As Tarball/Zip...'), self.save_archive) self.quit_action = add_action(self, N_('Quit'), self.close, 'Ctrl+Q') self.grep_action = add_action(self, N_('Grep'), grep, 'Ctrl+G') self.merge_local_action = add_action(self, N_('Merge...'), merge.local_merge, 'Shift+Ctrl+M') self.merge_abort_action = add_action(self, N_('Abort Merge...'), merge.abort_merge) self.fetch_action = add_action(self, N_('Fetch...'), remote.fetch, 'Ctrl+F') self.push_action = add_action(self, N_('Push...'), remote.push, 'Ctrl+P') self.pull_action = add_action(self, N_('Pull...'), remote.pull, 'Shift+Ctrl+P') self.open_repo_action = add_action(self, N_('Open...'), guicmds.open_repo) self.open_repo_action.setIcon(qtutils.open_icon()) self.open_repo_new_action = add_action(self, N_('Open in New Window...'), guicmds.open_repo_in_new_window) self.open_repo_new_action.setIcon(qtutils.open_icon()) self.stash_action = add_action(self, N_('Stash...'), stash, 'Alt+Shift+S') self.clone_repo_action = add_action(self, N_('Clone...'), self.clone_repo) self.clone_repo_action.setIcon(qtutils.git_icon()) self.help_docs_action = add_action(self, N_('Documentation'), resources.show_html_docs, QtGui.QKeySequence.HelpContents) self.help_shortcuts_action = add_action(self, N_('Keyboard Shortcuts'), show_shortcuts, Qt.Key_Question) self.visualize_current_action = add_action(self, N_('Visualize Current Branch...'), cmds.run(cmds.VisualizeCurrent)) self.visualize_all_action = add_action(self, N_('Visualize All Branches...'), cmds.run(cmds.VisualizeAll)) self.search_commits_action = add_action(self, N_('Search...'), search) self.browse_branch_action = add_action(self, N_('Browse Current Branch...'), guicmds.browse_current) self.browse_other_branch_action = add_action(self, N_('Browse Other Branch...'), guicmds.browse_other) self.load_commitmsg_template_action = add_action(self, N_('Get Commit Message Template'), cmds.run(cmds.LoadCommitMessageFromTemplate)) self.help_about_action = add_action(self, N_('About'), launch_about_dialog) self.diff_expression_action = add_action(self, N_('Expression...'), guicmds.diff_expression) self.branch_compare_action = add_action(self, N_('Branches...'), compare_branches) self.create_tag_action = add_action(self, N_('Create Tag...'), create_tag) self.create_branch_action = add_action(self, N_('Create...'), create_new_branch, 'Ctrl+B') self.delete_branch_action = add_action(self, N_('Delete...'), guicmds.delete_branch) self.delete_remote_branch_action = add_action(self, N_('Delete Remote Branch...'), guicmds.delete_remote_branch) self.checkout_branch_action = add_action(self, N_('Checkout...'), guicmds.checkout_branch, 'Alt+B') self.branch_review_action = add_action(self, N_('Review...'), guicmds.review_branch) self.browse_action = add_action(self, N_('File Browser...'), worktree_browser) self.browse_action.setIcon(qtutils.git_icon()) self.dag_action = add_action(self, N_('DAG...'), self.git_dag) self.dag_action.setIcon(qtutils.git_icon()) self.rebase_start_action = add_action(self, N_('Start Interactive Rebase...'), self.rebase_start) self.rebase_edit_todo_action = add_action(self, N_('Edit...'), self.rebase_edit_todo) self.rebase_continue_action = add_action(self, N_('Continue'), self.rebase_continue) self.rebase_skip_action = add_action(self, N_('Skip Current Patch'), self.rebase_skip) self.rebase_abort_action = add_action(self, N_('Abort'), self.rebase_abort) # Relayed actions status_tree = self.statusdockwidget.widget().tree self.addAction(status_tree.delete_untracked_files_action) if not self.browser_dockable: # These shortcuts conflict with those from the # 'Browser' widget so don't register them when # the browser is a dockable tool. self.addAction(status_tree.revert_unstaged_edits_action) self.addAction(status_tree.revert_uncommitted_edits_action) self.addAction(status_tree.up_action) self.addAction(status_tree.down_action) self.addAction(status_tree.process_selection_action) self.lock_layout_action = add_action_bool(self, N_('Lock Layout'), self.set_lock_layout, False) # Create the application menu self.menubar = QtGui.QMenuBar(self) # File Menu self.file_menu = create_menu(N_('File'), self.menubar) self.open_recent_menu = self.file_menu.addMenu(N_('Open Recent')) self.open_recent_menu.setIcon(qtutils.open_icon()) self.file_menu.addAction(self.open_repo_action) self.file_menu.addAction(self.open_repo_new_action) self.file_menu.addAction(self.clone_repo_action) self.file_menu.addAction(self.new_repository_action) self.file_menu.addSeparator() self.file_menu.addAction(self.rescan_action) self.file_menu.addAction(self.edit_remotes_action) self.file_menu.addAction(self.browse_recently_modified_action) self.file_menu.addSeparator() self.file_menu.addAction(self.load_commitmsg_action) self.file_menu.addAction(self.load_commitmsg_template_action) self.file_menu.addSeparator() self.file_menu.addAction(self.apply_patches_action) self.file_menu.addAction(self.export_patches_action) self.file_menu.addAction(self.save_tarball_action) self.file_menu.addSeparator() self.file_menu.addAction(self.preferences_action) self.file_menu.addAction(self.quit_action) self.menubar.addAction(self.file_menu.menuAction()) # Actions menu self.actions_menu = create_menu(N_('Actions'), self.menubar) self.actions_menu.addAction(self.fetch_action) self.actions_menu.addAction(self.push_action) self.actions_menu.addAction(self.pull_action) self.actions_menu.addAction(self.stash_action) self.actions_menu.addSeparator() self.actions_menu.addAction(self.create_tag_action) self.actions_menu.addAction(self.cherry_pick_action) self.actions_menu.addAction(self.merge_local_action) self.actions_menu.addAction(self.merge_abort_action) self.actions_menu.addSeparator() self.actions_menu.addAction(self.grep_action) self.actions_menu.addAction(self.search_commits_action) self.menubar.addAction(self.actions_menu.menuAction()) # Staging Area Menu self.commit_menu = create_menu(N_('Staging Area'), self.menubar) self.commit_menu.setTitle(N_('Staging Area')) self.commit_menu.addAction(self.stage_modified_action) self.commit_menu.addAction(self.stage_untracked_action) self.commit_menu.addSeparator() self.commit_menu.addAction(self.unstage_all_action) self.commit_menu.addAction(self.unstage_selected_action) self.menubar.addAction(self.commit_menu.menuAction()) # Diff Menu self.diff_menu = create_menu(N_('Diff'), self.menubar) self.diff_menu.addAction(self.diff_expression_action) self.diff_menu.addAction(self.branch_compare_action) self.diff_menu.addSeparator() self.diff_menu.addAction(self.show_diffstat_action) self.menubar.addAction(self.diff_menu.menuAction()) # Branch Menu self.branch_menu = create_menu(N_('Branch'), self.menubar) self.branch_menu.addAction(self.branch_review_action) self.branch_menu.addSeparator() self.branch_menu.addAction(self.create_branch_action) self.branch_menu.addAction(self.checkout_branch_action) self.branch_menu.addAction(self.delete_branch_action) self.branch_menu.addAction(self.delete_remote_branch_action) self.branch_menu.addSeparator() self.branch_menu.addAction(self.browse_branch_action) self.branch_menu.addAction(self.browse_other_branch_action) self.branch_menu.addSeparator() self.branch_menu.addAction(self.visualize_current_action) self.branch_menu.addAction(self.visualize_all_action) self.menubar.addAction(self.branch_menu.menuAction()) # Rebase menu self.rebase_menu = create_menu(N_('Rebase'), self.actions_menu) self.rebase_menu.addAction(self.rebase_start_action) self.rebase_menu.addAction(self.rebase_edit_todo_action) self.rebase_menu.addSeparator() self.rebase_menu.addAction(self.rebase_continue_action) self.rebase_menu.addAction(self.rebase_skip_action) self.rebase_menu.addSeparator() self.rebase_menu.addAction(self.rebase_abort_action) self.menubar.addAction(self.rebase_menu.menuAction()) # View Menu self.view_menu = create_menu(N_('View'), self.menubar) self.view_menu.addAction(self.browse_action) self.view_menu.addAction(self.dag_action) self.view_menu.addSeparator() if self.browser_dockable: self.view_menu.addAction(self.browserdockwidget.toggleViewAction()) self.setup_dockwidget_view_menu() self.view_menu.addSeparator() self.view_menu.addAction(self.lock_layout_action) self.menubar.addAction(self.view_menu.menuAction()) # Help Menu self.help_menu = create_menu(N_('Help'), self.menubar) self.help_menu.addAction(self.help_docs_action) self.help_menu.addAction(self.help_shortcuts_action) self.help_menu.addAction(self.help_about_action) self.menubar.addAction(self.help_menu.menuAction()) # Set main menu self.setMenuBar(self.menubar) # Arrange dock widgets left = Qt.LeftDockWidgetArea right = Qt.RightDockWidgetArea bottom = Qt.BottomDockWidgetArea self.addDockWidget(left, self.commitdockwidget) if self.browser_dockable: self.addDockWidget(left, self.browserdockwidget) self.tabifyDockWidget(self.browserdockwidget, self.commitdockwidget) self.addDockWidget(left, self.diffdockwidget) self.addDockWidget(right, self.statusdockwidget) self.addDockWidget(right, self.bookmarksdockwidget) self.addDockWidget(right, self.recentdockwidget) self.addDockWidget(bottom, self.actionsdockwidget) self.addDockWidget(bottom, self.logdockwidget) self.tabifyDockWidget(self.actionsdockwidget, self.logdockwidget) # Listen for model notifications model.add_observer(model.message_updated, self._update) model.add_observer(model.message_mode_changed, lambda x: self._update()) prefs_model.add_observer(prefs_model.message_config_updated, self._config_updated) # Set a default value self.show_cursor_position(1, 0) self.connect(self.open_recent_menu, SIGNAL('aboutToShow()'), self.build_recent_menu) self.connect(self.commitmsgeditor, SIGNAL('cursorPosition(int,int)'), self.show_cursor_position) self.connect(self.diffeditor, SIGNAL('diff_options_updated()'), self.statuswidget.refresh) self.connect(self, SIGNAL('update'), self._update_callback) self.connect(self, SIGNAL('install_config_actions'), self._install_config_actions) # Install .git-config-defined actions self._config_task = None self.install_config_actions() # Restore saved settings if not self.restore_state(settings=settings): self.resize(987, 610) self.set_initial_size() self.statusdockwidget.widget().setFocus() # Route command output here Interaction.log_status = self.logwidget.log_status Interaction.log = self.logwidget.log Interaction.log(version.git_version_str() + '\n' + N_('git cola version %s') % version.version()) def set_initial_size(self): self.statuswidget.set_initial_size() self.commitmsgeditor.set_initial_size() def set_filter(self, txt): self.statuswidget.set_filter(txt) # Qt overrides def closeEvent(self, event): """Save state in the settings manager.""" commit_msg = self.commitmsgeditor.commit_message(raw=True) self.model.save_commitmsg(commit_msg) MainWindow.closeEvent(self, event) def build_recent_menu(self): settings = Settings() settings.load() recent = settings.recent cmd = cmds.OpenRepo menu = self.open_recent_menu menu.clear() for r in recent: name = os.path.basename(r) directory = os.path.dirname(r) text = '%s %s %s' % (name, unichr(0x2192), directory) menu.addAction(text, cmds.run(cmd, r)) # Accessors mode = property(lambda self: self.model.mode) def _config_updated(self, source, config, value): if config == prefs.FONTDIFF: # The diff font font = QtGui.QFont() if not font.fromString(value): return self.logwidget.setFont(font) self.diffeditor.setFont(font) self.commitmsgeditor.setFont(font) elif config == prefs.TABWIDTH: # variable-tab-width setting self.diffeditor.set_tabwidth(value) self.commitmsgeditor.set_tabwidth(value) elif config == prefs.LINEBREAK: # enables automatic line breaks self.commitmsgeditor.set_linebreak(value) elif config == prefs.TEXTWIDTH: # text width used for line wrapping self.commitmsgeditor.set_textwidth(value) def install_config_actions(self): """Install .gitconfig-defined actions""" self._config_task = self._start_config_actions_task() def _start_config_actions_task(self): """Do the expensive "get_config_actions()" call in the background""" class ConfigActionsTask(QtCore.QRunnable): def __init__(self, sender): QtCore.QRunnable.__init__(self) self._sender = sender def run(self): names = cfgactions.get_config_actions() self._sender.emit(SIGNAL('install_config_actions'), names) task = ConfigActionsTask(self) QtCore.QThreadPool.globalInstance().start(task) return task def _install_config_actions(self, names): """Install .gitconfig-defined actions""" if not names: return menu = self.actions_menu menu.addSeparator() for name in names: menu.addAction(name, cmds.run(cmds.RunConfigAction, name)) def _update(self): self.emit(SIGNAL('update')) def _update_callback(self): """Update the title with the current branch and directory name.""" alerts = [] branch = self.model.currentbranch curdir = core.getcwd() is_merging = self.model.is_merging is_rebasing = self.model.is_rebasing msg = N_('Repository: %s') % curdir msg += '\n' msg += N_('Branch: %s') % branch if is_rebasing: msg += '\n\n' msg += N_('This repository is currently being rebased.\n' 'Resolve conflicts, commit changes, and run:\n' ' Rebase > Continue') alerts.append(N_('Rebasing')) elif is_merging: msg += '\n\n' msg += N_('This repository is in the middle of a merge.\n' 'Resolve conflicts and commit changes.') alerts.append(N_('Merging')) if self.mode == self.model.mode_amend: alerts.append(N_('Amending')) l = unichr(0xab) r = unichr(0xbb) title = ('%s: %s %s%s' % ( self.model.project, branch, alerts and ((r+' %s '+l+' ') % ', '.join(alerts)) or '', self.model.git.worktree())) self.setWindowTitle(title) self.commitdockwidget.setToolTip(msg) self.commitmsgeditor.set_mode(self.mode) self.update_actions() if not self.model.amending(): # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(merge_msg_path) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cmds.do(cmds.LoadCommitMessageFromFile, merge_msg_path) def update_actions(self): is_rebasing = self.model.is_rebasing can_rebase = not is_rebasing self.rebase_start_action.setEnabled(can_rebase) self.rebase_edit_todo_action.setEnabled(is_rebasing) self.rebase_continue_action.setEnabled(is_rebasing) self.rebase_skip_action.setEnabled(is_rebasing) self.rebase_abort_action.setEnabled(is_rebasing) def export_state(self): state = MainWindow.export_state(self) show_status_filter = self.statuswidget.filter_widget.isVisible() state['show_status_filter'] = show_status_filter return state def apply_state(self, state): """Imports data for save/restore""" result = MainWindow.apply_state(self, state) self.lock_layout_action.setChecked(state.get('lock_layout', False)) show_status_filter = state.get('show_status_filter', False) self.statuswidget.filter_widget.setVisible(show_status_filter) return result def setup_dockwidget_view_menu(self): # Hotkeys for toggling the dock widgets if utils.is_darwin(): optkey = 'Meta' else: optkey = 'Ctrl' dockwidgets = ( (optkey + '+0', self.logdockwidget), (optkey + '+1', self.commitdockwidget), (optkey + '+2', self.statusdockwidget), (optkey + '+3', self.diffdockwidget), (optkey + '+4', self.actionsdockwidget), (optkey + '+5', self.bookmarksdockwidget), (optkey + '+6', self.recentdockwidget), ) for shortcut, dockwidget in dockwidgets: # Associate the action with the shortcut toggleview = dockwidget.toggleViewAction() toggleview.setShortcut('Shift+' + shortcut) self.view_menu.addAction(toggleview) def showdock(show, dockwidget=dockwidget): if show: dockwidget.raise_() dockwidget.widget().setFocus() else: self.setFocus() self.addAction(toggleview) connect_action_bool(toggleview, showdock) # Create a new shortcut Shift+<shortcut> that gives focus toggleview = QtGui.QAction(self) toggleview.setShortcut(shortcut) def focusdock(dockwidget=dockwidget, showdock=showdock): if dockwidget.toggleViewAction().isChecked(): showdock(True) else: dockwidget.toggleViewAction().trigger() self.addAction(toggleview) connect_action(toggleview, focusdock) def preferences(self): return preferences(model=self.prefs_model, parent=self) def git_dag(self): view = git_dag(self.model) view.show() view.raise_() def save_archive(self): ref = git.rev_parse('HEAD')[STDOUT] shortref = ref[:7] GitArchiveDialog.save_hashed_objects(ref, shortref, self) def show_cursor_position(self, rows, cols): display = ' %02d:%02d ' % (rows, cols) if cols > 78: display = ('<span style="color: white; ' ' background-color: red;"' '>%s</span>' % display) elif cols > 72: display = ('<span style="color: black; ' ' background-color: orange;"' '>%s</span>' % display) elif cols > 64: display = ('<span style="color: black; ' ' background-color: yellow;"' '>%s</span>' % display) else: display = ('<span style="color: grey;">%s</span>' % display) self.position_label.setText(display) def rebase_start(self): if self.model.staged or self.model.unmerged or self.model.modified: Interaction.information( N_('Unable to rebase'), N_('You cannot rebase with uncommitted changes.')) return branch = guicmds.choose_ref(N_('Select New Upstream'), N_('Interactive Rebase')) if not branch: return None self.model.is_rebasing = True self._update_callback() cmds.do(cmds.Rebase, branch) def rebase_edit_todo(self): cmds.do(cmds.RebaseEditTodo) def rebase_continue(self): cmds.do(cmds.RebaseContinue) def rebase_skip(self): cmds.do(cmds.RebaseSkip) def rebase_abort(self): cmds.do(cmds.RebaseAbort) def clone_repo(self): guicmds.clone_repo(self.task_runner, self.progress, guicmds.report_clone_repo_errors, True)
class MainView(MainWindow): def __init__(self, model, parent=None, settings=None): MainWindow.__init__(self, parent) self.setAttribute(Qt.WA_MacMetalStyle) # Default size; this is thrown out when save/restore is used self.model = model self.settings = settings self.prefs_model = prefs_model = prefs.PreferencesModel() # The widget version is used by import/export_state(). # Change this whenever dockwidgets are removed. self.widget_version = 2 # Keeps track of merge messages we've seen self.merge_message_hash = '' cfg = gitcfg.instance() self.browser_dockable = (cfg.get('cola.browserdockable') or cfg.get('cola.classicdockable')) if self.browser_dockable: self.browserdockwidget = create_dock(N_('Browser'), self) self.browserwidget = worktree_browser_widget(self) self.browserdockwidget.setWidget(self.browserwidget) # "Actions" widget self.actionsdockwidget = create_dock(N_('Actions'), self) self.actionsdockwidgetcontents = action.ActionButtons(self) self.actionsdockwidget.setWidget(self.actionsdockwidgetcontents) self.actionsdockwidget.toggleViewAction().setChecked(False) self.actionsdockwidget.hide() # "Repository Status" widget self.statuswidget = StatusWidget(self) self.statusdockwidget = create_dock(N_('Status'), self) self.statusdockwidget.setWidget(self.statuswidget) # "Switch Repository" widget self.bookmarksdockwidget = create_dock(N_('Bookmarks'), self) self.bookmarkswidget = BookmarksWidget(parent=self.bookmarksdockwidget) self.bookmarksdockwidget.setWidget(self.bookmarkswidget) # "Commit Message Editor" widget self.position_label = QtGui.QLabel() font = qtutils.default_monospace_font() font.setPointSize(int(font.pointSize() * 0.8)) self.position_label.setFont(font) # make the position label fixed size to avoid layout issues fm = self.position_label.fontMetrics() width = fm.width('999:999') height = self.position_label.sizeHint().height() self.position_label.setFixedSize(width, height) self.commitdockwidget = create_dock(N_('Commit'), self) titlebar = self.commitdockwidget.titleBarWidget() titlebar.add_corner_widget(self.position_label) self.commitmsgeditor = CommitMessageEditor(model, self) self.commitdockwidget.setWidget(self.commitmsgeditor) # "Console" widget self.logwidget = LogWidget() self.logdockwidget = create_dock(N_('Console'), self) self.logdockwidget.setWidget(self.logwidget) self.logdockwidget.toggleViewAction().setChecked(False) self.logdockwidget.hide() # "Diff Viewer" widget self.diffdockwidget = create_dock(N_('Diff'), self) self.diffeditor = DiffEditor(self.diffdockwidget) self.diffdockwidget.setWidget(self.diffeditor) # All Actions self.unstage_all_action = add_action(self, N_('Unstage All'), cmds.run(cmds.UnstageAll)) self.unstage_all_action.setIcon(qtutils.icon('remove.svg')) self.unstage_selected_action = add_action( self, N_('Unstage From Commit'), cmds.run(cmds.UnstageSelected)) self.unstage_selected_action.setIcon(qtutils.icon('remove.svg')) self.show_diffstat_action = add_action(self, N_('Diffstat'), cmds.run(cmds.Diffstat), 'Alt+D') self.stage_modified_action = add_action( self, N_('Stage Changed Files To Commit'), cmds.run(cmds.StageModified), 'Alt+A') self.stage_modified_action.setIcon(qtutils.icon('add.svg')) self.stage_untracked_action = add_action(self, N_('Stage All Untracked'), cmds.run(cmds.StageUntracked), 'Alt+U') self.stage_untracked_action.setIcon(qtutils.icon('add.svg')) self.apply_patches_action = add_action(self, N_('Apply Patches...'), apply_patches) self.export_patches_action = add_action(self, N_('Export Patches...'), guicmds.export_patches, 'Alt+E') self.new_repository_action = add_action(self, N_('New Repository...'), guicmds.open_new_repo) self.new_repository_action.setIcon(qtutils.new_icon()) self.preferences_action = add_action(self, N_('Preferences'), self.preferences, QtGui.QKeySequence.Preferences, 'Ctrl+O') self.edit_remotes_action = add_action( self, N_('Edit Remotes...'), lambda: editremotes.remote_editor().exec_()) self.rescan_action = add_action(self, cmds.Refresh.name(), cmds.run(cmds.Refresh), cmds.Refresh.SHORTCUT) self.rescan_action.setIcon(qtutils.reload_icon()) self.browse_recently_modified_action = add_action( self, N_('Recently Modified Files...'), browse_recent_files, 'Shift+Ctrl+E') self.cherry_pick_action = add_action(self, N_('Cherry-Pick...'), guicmds.cherry_pick, 'Shift+Ctrl+C') self.load_commitmsg_action = add_action(self, N_('Load Commit Message...'), guicmds.load_commitmsg) self.save_tarball_action = add_action(self, N_('Save As Tarball/Zip...'), self.save_archive) self.quit_action = add_action(self, N_('Quit'), self.close, 'Ctrl+Q') self.manage_bookmarks_action = add_action(self, N_('Bookmarks...'), self.manage_bookmarks) self.grep_action = add_action(self, N_('Grep'), grep, 'Ctrl+G') self.merge_local_action = add_action(self, N_('Merge...'), merge.local_merge) self.merge_abort_action = add_action(self, N_('Abort Merge...'), merge.abort_merge) self.fetch_action = add_action(self, N_('Fetch...'), remote.fetch, 'Ctrl+F') self.push_action = add_action(self, N_('Push...'), remote.push, 'Ctrl+P') self.pull_action = add_action(self, N_('Pull...'), remote.pull, 'Shift+Ctrl+P') self.open_repo_action = add_action(self, N_('Open...'), guicmds.open_repo) self.open_repo_action.setIcon(qtutils.open_icon()) self.open_repo_new_action = add_action(self, N_('Open in New Window...'), guicmds.open_repo_in_new_window) self.open_repo_new_action.setIcon(qtutils.open_icon()) self.stash_action = add_action(self, N_('Stash...'), stash, 'Alt+Shift+S') self.clone_repo_action = add_action(self, N_('Clone...'), guicmds.clone_repo) self.clone_repo_action.setIcon(qtutils.git_icon()) self.help_docs_action = add_action(self, N_('Documentation'), resources.show_html_docs, QtGui.QKeySequence.HelpContents) self.help_shortcuts_action = add_action(self, N_('Keyboard Shortcuts'), show_shortcuts, QtCore.Qt.Key_Question) self.visualize_current_action = add_action( self, N_('Visualize Current Branch...'), cmds.run(cmds.VisualizeCurrent)) self.visualize_all_action = add_action(self, N_('Visualize All Branches...'), cmds.run(cmds.VisualizeAll)) self.search_commits_action = add_action(self, N_('Search...'), search) self.browse_branch_action = add_action(self, N_('Browse Current Branch...'), guicmds.browse_current) self.browse_other_branch_action = add_action( self, N_('Browse Other Branch...'), guicmds.browse_other) self.load_commitmsg_template_action = add_action( self, N_('Get Commit Message Template'), cmds.run(cmds.LoadCommitMessageFromTemplate)) self.help_about_action = add_action(self, N_('About'), launch_about_dialog) self.diff_expression_action = add_action(self, N_('Expression...'), guicmds.diff_expression) self.branch_compare_action = add_action(self, N_('Branches...'), compare_branches) self.create_tag_action = add_action(self, N_('Create Tag...'), create_tag) self.create_branch_action = add_action(self, N_('Create...'), create_new_branch, 'Ctrl+B') self.delete_branch_action = add_action(self, N_('Delete...'), guicmds.delete_branch) self.delete_remote_branch_action = add_action( self, N_('Delete Remote Branch...'), guicmds.delete_remote_branch) self.checkout_branch_action = add_action(self, N_('Checkout...'), guicmds.checkout_branch, 'Alt+B') self.branch_review_action = add_action(self, N_('Review...'), guicmds.review_branch) self.browse_action = add_action(self, N_('File Browser...'), worktree_browser) self.browse_action.setIcon(qtutils.git_icon()) self.dag_action = add_action(self, N_('DAG...'), self.git_dag) self.dag_action.setIcon(qtutils.git_icon()) self.rebase_start_action = add_action( self, N_('Start Interactive Rebase...'), self.rebase_start) self.rebase_edit_todo_action = add_action(self, N_('Edit...'), self.rebase_edit_todo) self.rebase_continue_action = add_action(self, N_('Continue'), self.rebase_continue) self.rebase_skip_action = add_action(self, N_('Skip Current Patch'), self.rebase_skip) self.rebase_abort_action = add_action(self, N_('Abort'), self.rebase_abort) # Relayed actions status_tree = self.statusdockwidget.widget().tree self.addAction(status_tree.revert_unstaged_edits_action) self.addAction(status_tree.delete_untracked_files_action) if not self.browser_dockable: # These shortcuts conflict with those from the # 'Browser' widget so don't register them when # the browser is a dockable tool. self.addAction(status_tree.up_action) self.addAction(status_tree.down_action) self.addAction(status_tree.process_selection_action) self.lock_layout_action = add_action_bool(self, N_('Lock Layout'), self.set_lock_layout, False) # Create the application menu self.menubar = QtGui.QMenuBar(self) # File Menu self.file_menu = create_menu(N_('File'), self.menubar) self.open_recent_menu = self.file_menu.addMenu(N_('Open Recent')) self.open_recent_menu.setIcon(qtutils.open_icon()) self.file_menu.addAction(self.open_repo_action) self.file_menu.addAction(self.open_repo_new_action) self.file_menu.addAction(self.clone_repo_action) self.file_menu.addAction(self.new_repository_action) self.file_menu.addSeparator() self.file_menu.addAction(self.rescan_action) self.file_menu.addAction(self.edit_remotes_action) self.file_menu.addAction(self.browse_recently_modified_action) self.file_menu.addAction(self.manage_bookmarks_action) self.file_menu.addSeparator() self.file_menu.addAction(self.load_commitmsg_action) self.file_menu.addAction(self.load_commitmsg_template_action) self.file_menu.addSeparator() self.file_menu.addAction(self.apply_patches_action) self.file_menu.addAction(self.export_patches_action) self.file_menu.addAction(self.save_tarball_action) self.file_menu.addSeparator() self.file_menu.addAction(self.preferences_action) self.file_menu.addAction(self.quit_action) self.menubar.addAction(self.file_menu.menuAction()) # Actions menu self.actions_menu = create_menu(N_('Actions'), self.menubar) self.actions_menu.addAction(self.fetch_action) self.actions_menu.addAction(self.push_action) self.actions_menu.addAction(self.pull_action) self.actions_menu.addAction(self.stash_action) self.actions_menu.addSeparator() self.actions_menu.addAction(self.create_tag_action) self.actions_menu.addAction(self.cherry_pick_action) self.actions_menu.addAction(self.merge_local_action) self.actions_menu.addAction(self.merge_abort_action) self.actions_menu.addSeparator() self.actions_menu.addAction(self.grep_action) self.actions_menu.addAction(self.search_commits_action) self.menubar.addAction(self.actions_menu.menuAction()) # Staging Area Menu self.commit_menu = create_menu(N_('Staging Area'), self.menubar) self.commit_menu.setTitle(N_('Staging Area')) self.commit_menu.addAction(self.stage_modified_action) self.commit_menu.addAction(self.stage_untracked_action) self.commit_menu.addSeparator() self.commit_menu.addAction(self.unstage_all_action) self.commit_menu.addAction(self.unstage_selected_action) self.menubar.addAction(self.commit_menu.menuAction()) # Diff Menu self.diff_menu = create_menu(N_('Diff'), self.menubar) self.diff_menu.addAction(self.diff_expression_action) self.diff_menu.addAction(self.branch_compare_action) self.diff_menu.addSeparator() self.diff_menu.addAction(self.show_diffstat_action) self.menubar.addAction(self.diff_menu.menuAction()) # Branch Menu self.branch_menu = create_menu(N_('Branch'), self.menubar) self.branch_menu.addAction(self.branch_review_action) self.branch_menu.addSeparator() self.branch_menu.addAction(self.create_branch_action) self.branch_menu.addAction(self.checkout_branch_action) self.branch_menu.addAction(self.delete_branch_action) self.branch_menu.addAction(self.delete_remote_branch_action) self.branch_menu.addSeparator() self.branch_menu.addAction(self.browse_branch_action) self.branch_menu.addAction(self.browse_other_branch_action) self.branch_menu.addSeparator() self.branch_menu.addAction(self.visualize_current_action) self.branch_menu.addAction(self.visualize_all_action) self.menubar.addAction(self.branch_menu.menuAction()) # Rebase menu self.rebase_menu = create_menu(N_('Rebase'), self.actions_menu) self.rebase_menu.addAction(self.rebase_start_action) self.rebase_menu.addAction(self.rebase_edit_todo_action) self.rebase_menu.addSeparator() self.rebase_menu.addAction(self.rebase_continue_action) self.rebase_menu.addAction(self.rebase_skip_action) self.rebase_menu.addSeparator() self.rebase_menu.addAction(self.rebase_abort_action) self.menubar.addAction(self.rebase_menu.menuAction()) # View Menu self.view_menu = create_menu(N_('View'), self.menubar) self.view_menu.addAction(self.browse_action) self.view_menu.addAction(self.dag_action) self.view_menu.addSeparator() if self.browser_dockable: self.view_menu.addAction(self.browserdockwidget.toggleViewAction()) self.setup_dockwidget_view_menu() self.view_menu.addSeparator() self.view_menu.addAction(self.lock_layout_action) self.menubar.addAction(self.view_menu.menuAction()) # Help Menu self.help_menu = create_menu(N_('Help'), self.menubar) self.help_menu.addAction(self.help_docs_action) self.help_menu.addAction(self.help_shortcuts_action) self.help_menu.addAction(self.help_about_action) self.menubar.addAction(self.help_menu.menuAction()) # Set main menu self.setMenuBar(self.menubar) # Arrange dock widgets left = Qt.LeftDockWidgetArea right = Qt.RightDockWidgetArea bottom = Qt.BottomDockWidgetArea self.addDockWidget(left, self.commitdockwidget) if self.browser_dockable: self.addDockWidget(left, self.browserdockwidget) self.tabifyDockWidget(self.browserdockwidget, self.commitdockwidget) self.addDockWidget(left, self.diffdockwidget) self.addDockWidget(right, self.statusdockwidget) self.addDockWidget(right, self.bookmarksdockwidget) self.addDockWidget(bottom, self.actionsdockwidget) self.addDockWidget(bottom, self.logdockwidget) self.tabifyDockWidget(self.actionsdockwidget, self.logdockwidget) # Listen for model notifications model.add_observer(model.message_updated, self._update) model.add_observer(model.message_mode_changed, lambda x: self._update()) prefs_model.add_observer(prefs_model.message_config_updated, self._config_updated) # Set a default value self.show_cursor_position(1, 0) self.connect(self.open_recent_menu, SIGNAL('aboutToShow()'), self.build_recent_menu) self.connect(self.commitmsgeditor, SIGNAL('cursorPosition(int,int)'), self.show_cursor_position) self.connect(self.diffeditor, SIGNAL('diff_options_updated()'), self.statuswidget.refresh) self.connect(self, SIGNAL('update'), self._update_callback) self.connect(self, SIGNAL('install_config_actions'), self._install_config_actions) # Install .git-config-defined actions self._config_task = None self.install_config_actions() # Restore saved settings if not self.restore_state(settings=settings): self.resize(987, 610) self.set_initial_size() self.statusdockwidget.widget().setFocus() # Route command output here Interaction.log_status = self.logwidget.log_status Interaction.log = self.logwidget.log Interaction.log(version.git_version_str() + '\n' + N_('git cola version %s') % version.version()) def set_initial_size(self): self.statuswidget.set_initial_size() self.commitmsgeditor.set_initial_size() # Qt overrides def closeEvent(self, event): """Save state in the settings manager.""" commit_msg = self.commitmsgeditor.commit_message(raw=True) self.model.save_commitmsg(commit_msg) MainWindow.closeEvent(self, event) def build_recent_menu(self): settings = Settings() settings.load() recent = settings.recent cmd = cmds.OpenRepo menu = self.open_recent_menu menu.clear() for r in recent: name = os.path.basename(r) directory = os.path.dirname(r) text = '%s %s %s' % (name, unichr(0x2192), directory) menu.addAction(text, cmds.run(cmd, r)) # Accessors mode = property(lambda self: self.model.mode) def _config_updated(self, source, config, value): if config == prefs.FONTDIFF: # The diff font font = QtGui.QFont() if not font.fromString(value): return self.logwidget.setFont(font) self.diffeditor.setFont(font) self.commitmsgeditor.setFont(font) elif config == prefs.TABWIDTH: # variable-tab-width setting self.diffeditor.set_tabwidth(value) self.commitmsgeditor.set_tabwidth(value) elif config == prefs.LINEBREAK: # enables automatic line breaks self.commitmsgeditor.set_linebreak(value) elif config == prefs.TEXTWIDTH: # text width used for line wrapping self.commitmsgeditor.set_textwidth(value) def install_config_actions(self): """Install .gitconfig-defined actions""" self._config_task = self._start_config_actions_task() def _start_config_actions_task(self): """Do the expensive "get_config_actions()" call in the background""" class ConfigActionsTask(QtCore.QRunnable): def __init__(self, sender): QtCore.QRunnable.__init__(self) self._sender = sender def run(self): names = cfgactions.get_config_actions() self._sender.emit(SIGNAL('install_config_actions'), names) task = ConfigActionsTask(self) QtCore.QThreadPool.globalInstance().start(task) return task def _install_config_actions(self, names): """Install .gitconfig-defined actions""" if not names: return menu = self.actions_menu menu.addSeparator() for name in names: menu.addAction(name, cmds.run(cmds.RunConfigAction, name)) def _update(self): self.emit(SIGNAL('update')) def _update_callback(self): """Update the title with the current branch and directory name.""" alerts = [] branch = self.model.currentbranch curdir = core.getcwd() is_merging = self.model.is_merging is_rebasing = self.model.is_rebasing msg = N_('Repository: %s') % curdir msg += '\n' msg += N_('Branch: %s') % branch if is_rebasing: msg += '\n\n' msg += N_('This repository is currently being rebased.\n' 'Resolve conflicts, commit changes, and run:\n' ' Rebase > Continue') alerts.append(N_('Rebasing')) elif is_merging: msg += '\n\n' msg += N_('This repository is in the middle of a merge.\n' 'Resolve conflicts and commit changes.') alerts.append(N_('Merging')) if self.mode == self.model.mode_amend: alerts.append(N_('Amending')) l = unichr(0xab) r = unichr(0xbb) title = ('%s: %s %s%s' % (self.model.project, branch, alerts and ((r + ' %s ' + l + ' ') % ', '.join(alerts)) or '', self.model.git.worktree())) self.setWindowTitle(title) self.commitdockwidget.setToolTip(msg) self.commitmsgeditor.set_mode(self.mode) self.update_actions() if not self.model.amending(): # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(merge_msg_path) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cmds.do(cmds.LoadCommitMessageFromFile, merge_msg_path) def update_actions(self): is_rebasing = self.model.is_rebasing can_rebase = not is_rebasing self.rebase_start_action.setEnabled(can_rebase) self.rebase_edit_todo_action.setEnabled(is_rebasing) self.rebase_continue_action.setEnabled(is_rebasing) self.rebase_skip_action.setEnabled(is_rebasing) self.rebase_abort_action.setEnabled(is_rebasing) def apply_state(self, state): """Imports data for save/restore""" result = MainWindow.apply_state(self, state) self.lock_layout_action.setChecked(state.get('lock_layout', False)) return result def setup_dockwidget_view_menu(self): # Hotkeys for toggling the dock widgets if utils.is_darwin(): optkey = 'Meta' else: optkey = 'Ctrl' dockwidgets = ( (optkey + '+0', self.logdockwidget), (optkey + '+1', self.commitdockwidget), (optkey + '+2', self.statusdockwidget), (optkey + '+3', self.diffdockwidget), (optkey + '+4', self.actionsdockwidget), (optkey + '+5', self.bookmarksdockwidget), ) for shortcut, dockwidget in dockwidgets: # Associate the action with the shortcut toggleview = dockwidget.toggleViewAction() toggleview.setShortcut('Shift+' + shortcut) self.view_menu.addAction(toggleview) def showdock(show, dockwidget=dockwidget): if show: dockwidget.raise_() dockwidget.widget().setFocus() else: self.setFocus() self.addAction(toggleview) connect_action_bool(toggleview, showdock) # Create a new shortcut Shift+<shortcut> that gives focus toggleview = QtGui.QAction(self) toggleview.setShortcut(shortcut) def focusdock(dockwidget=dockwidget, showdock=showdock): if dockwidget.toggleViewAction().isChecked(): showdock(True) else: dockwidget.toggleViewAction().trigger() self.addAction(toggleview) connect_action(toggleview, focusdock) def preferences(self): return preferences(model=self.prefs_model, parent=self) def git_dag(self): view = git_dag(self.model) view.show() view.raise_() def save_archive(self): ref = git.rev_parse('HEAD')[STDOUT] shortref = ref[:7] GitArchiveDialog.save_hashed_objects(ref, shortref, self) def show_cursor_position(self, rows, cols): display = ' %02d:%02d ' % (rows, cols) if cols > 78: display = ('<span style="color: white; ' ' background-color: red;"' '>%s</span>' % display) elif cols > 72: display = ('<span style="color: black; ' ' background-color: orange;"' '>%s</span>' % display) elif cols > 64: display = ('<span style="color: black; ' ' background-color: yellow;"' '>%s</span>' % display) else: display = ('<span style="color: grey;">%s</span>' % display) self.position_label.setText(display) def manage_bookmarks(self): manage_bookmarks() self.bookmarkswidget.refresh() def rebase_start(self): branch = guicmds.choose_ref(N_('Select New Upstream'), N_('Interactive Rebase')) if not branch: return None self.model.is_rebasing = True self._update_callback() cmds.do(cmds.Rebase, branch) def rebase_edit_todo(self): cmds.do(cmds.RebaseEditTodo) def rebase_continue(self): cmds.do(cmds.RebaseContinue) def rebase_skip(self): cmds.do(cmds.RebaseSkip) def rebase_abort(self): cmds.do(cmds.RebaseAbort)
class MainView(MainWindow): def __init__(self, model, parent): MainWindow.__init__(self, parent) # Default size; this is thrown out when save/restore is used self.resize(987, 610) self.model = model self.prefs_model = prefs_model = prefs.PreferencesModel() # Internal field used by import/export_state(). # Change this whenever dockwidgets are removed. self.widget_version = 2 # Keeps track of merge messages we've seen self.merge_message_hash = '' self.setAcceptDrops(True) self.setAttribute(Qt.WA_MacMetalStyle) # Dockwidget options qtcompat.set_common_dock_options(self) cfg = gitcfg.instance() self.classic_dockable = (cfg.get('cola.browserdockable') or cfg.get('cola.classicdockable')) if self.classic_dockable: self.classicdockwidget = create_dock(N_('Browser'), self) self.classicwidget = classic_widget(self) self.classicdockwidget.setWidget(self.classicwidget) # "Actions" widget self.actionsdockwidget = create_dock(N_('Action'), self) self.actionsdockwidgetcontents = action.ActionButtons(self) self.actionsdockwidget.setWidget(self.actionsdockwidgetcontents) self.actionsdockwidget.toggleViewAction().setChecked(False) self.actionsdockwidget.hide() # "Repository Status" widget self.statuswidget = StatusWidget(self) self.statusdockwidget = create_dock(N_('Status'), self) self.statusdockwidget.setWidget(self.statuswidget) # "Commit Message Editor" widget self.position_label = QtGui.QLabel() font = qtutils.default_monospace_font() font.setPointSize(int(font.pointSize() * 0.8)) self.position_label.setFont(font) self.commitdockwidget = create_dock(N_('Commit'), self) titlebar = self.commitdockwidget.titleBarWidget() titlebar.add_corner_widget(self.position_label) self.commitmsgeditor = CommitMessageEditor(model, self) self.commitdockwidget.setWidget(self.commitmsgeditor) # "Console" widget self.logwidget = LogWidget() self.logdockwidget = create_dock(N_('Console'), self) self.logdockwidget.setWidget(self.logwidget) self.logdockwidget.toggleViewAction().setChecked(False) self.logdockwidget.hide() # "Diff Viewer" widget self.diffdockwidget = create_dock(N_('Diff'), self) self.diffeditor = DiffEditor(self.diffdockwidget) self.diffdockwidget.setWidget(self.diffeditor) # "Diff Options" tool menu self.diff_ignore_space_at_eol_action = add_action(self, N_('Ignore changes in whitespace at EOL'), self._update_diff_opts) self.diff_ignore_space_at_eol_action.setCheckable(True) self.diff_ignore_space_change_action = add_action(self, N_('Ignore changes in amount of whitespace'), self._update_diff_opts) self.diff_ignore_space_change_action.setCheckable(True) self.diff_ignore_all_space_action = add_action(self, N_('Ignore all whitespace'), self._update_diff_opts) self.diff_ignore_all_space_action.setCheckable(True) self.diff_function_context_action = add_action(self, N_('Show whole surrounding functions of changes'), self._update_diff_opts) self.diff_function_context_action.setCheckable(True) self.diffopts_button = create_toolbutton(text=N_('Options'), icon=options_icon(), tooltip=N_('Diff Options')) self.diffopts_menu = create_menu(N_('Diff Options'), self.diffopts_button) self.diffopts_menu.addAction(self.diff_ignore_space_at_eol_action) self.diffopts_menu.addAction(self.diff_ignore_space_change_action) self.diffopts_menu.addAction(self.diff_ignore_all_space_action) self.diffopts_menu.addAction(self.diff_function_context_action) self.diffopts_button.setMenu(self.diffopts_menu) self.diffopts_button.setPopupMode(QtGui.QToolButton.InstantPopup) titlebar = self.diffdockwidget.titleBarWidget() titlebar.add_corner_widget(self.diffopts_button) # All Actions self.menu_unstage_all = add_action(self, N_('Unstage All'), cmds.run(cmds.UnstageAll)) self.menu_unstage_all.setIcon(qtutils.icon('remove.svg')) self.menu_unstage_selected = add_action(self, N_('Unstage From Commit'), cmds.run(cmds.UnstageSelected)) self.menu_unstage_selected.setIcon(qtutils.icon('remove.svg')) self.menu_show_diffstat = add_action(self, N_('Diffstat'), cmds.run(cmds.Diffstat), 'Alt+D') self.menu_stage_modified = add_action(self, N_('Stage Changed Files To Commit'), cmds.run(cmds.StageModified), 'Alt+A') self.menu_stage_modified.setIcon(qtutils.icon('add.svg')) self.menu_stage_untracked = add_action(self, N_('Stage All Untracked'), cmds.run(cmds.StageUntracked), 'Alt+U') self.menu_stage_untracked.setIcon(qtutils.icon('add.svg')) self.menu_export_patches = add_action(self, N_('Export Patches...'), guicmds.export_patches, 'Alt+E') self.menu_preferences = add_action(self, N_('Preferences'), self.preferences, QtGui.QKeySequence.Preferences, 'Ctrl+O') self.menu_edit_remotes = add_action(self, N_('Edit Remotes...'), lambda: editremotes.edit().exec_()) self.menu_rescan = add_action(self, cmds.RescanAndRefresh.name(), cmds.run(cmds.RescanAndRefresh), cmds.RescanAndRefresh.SHORTCUT) self.menu_rescan.setIcon(qtutils.reload_icon()) self.menu_browse_recent = add_action(self, N_('Recently Modified Files...'), browse_recent, 'Shift+Ctrl+E') self.menu_cherry_pick = add_action(self, N_('Cherry-Pick...'), guicmds.cherry_pick, 'Ctrl+P') self.menu_load_commitmsg = add_action(self, N_('Load Commit Message...'), guicmds.load_commitmsg) self.menu_save_tarball = add_action(self, N_('Save As Tarball/Zip...'), self.save_archive) self.menu_quit = add_action(self, N_('Quit'), self.close, 'Ctrl+Q') self.menu_manage_bookmarks = add_action(self, N_('Bookmarks...'), manage_bookmarks) self.menu_grep = add_action(self, N_('Grep'), guicmds.grep, 'Ctrl+G') self.menu_merge_local = add_action(self, N_('Merge...'), merge.local_merge) self.menu_merge_abort = add_action(self, N_('Abort Merge...'), merge.abort_merge) self.menu_fetch = add_action(self, N_('Fetch...'), remote.fetch) self.menu_push = add_action(self, N_('Push...'), remote.push) self.menu_pull = add_action(self, N_('Pull...'), remote.pull) self.menu_open_repo = add_action(self, N_('Open...'), guicmds.open_repo) self.menu_open_repo.setIcon(qtutils.open_icon()) self.menu_stash = add_action(self, N_('Stash...'), stash.stash, 'Alt+Shift+S') self.menu_clone_repo = add_action(self, N_('Clone...'), guicmds.clone_repo) self.menu_clone_repo.setIcon(qtutils.git_icon()) self.menu_help_docs = add_action(self, N_('Documentation'), resources.show_html_docs, QtGui.QKeySequence.HelpContents) self.menu_help_shortcuts = add_action(self, N_('Keyboard Shortcuts'), show_shortcuts, QtCore.Qt.Key_Question) self.menu_visualize_current = add_action(self, N_('Visualize Current Branch...'), cmds.run(cmds.VisualizeCurrent)) self.menu_visualize_all = add_action(self, N_('Visualize All Branches...'), cmds.run(cmds.VisualizeAll)) self.menu_search_commits = add_action(self, N_('Search...'), search) self.menu_browse_branch = add_action(self, N_('Browse Current Branch...'), guicmds.browse_current) self.menu_browse_other_branch = add_action(self, N_('Browse Other Branch...'), guicmds.browse_other) self.menu_load_commitmsg_template = add_action(self, N_('Get Commit Message Template'), cmds.run(cmds.LoadCommitTemplate)) self.menu_help_about = add_action(self, N_('About'), launch_about_dialog) self.menu_diff_expression = add_action(self, N_('Expression...'), guicmds.diff_expression) self.menu_branch_compare = add_action(self, N_('Branches...'), compare_branches) self.menu_create_tag = add_action(self, N_('Create Tag...'), create_tag) self.menu_create_branch = add_action(self, N_('Create...'), create_new_branch, 'Ctrl+B') self.menu_delete_branch = add_action(self, N_('Delete...'), guicmds.branch_delete) self.menu_checkout_branch = add_action(self, N_('Checkout...'), guicmds.checkout_branch, 'Alt+B') self.menu_rebase_branch = add_action(self, N_('Rebase...'), guicmds.rebase) self.menu_branch_review = add_action(self, N_('Review...'), guicmds.review_branch) self.menu_classic = add_action(self, N_('Browser...'), cola_classic) self.menu_classic.setIcon(qtutils.git_icon()) self.menu_dag = add_action(self, N_('DAG...'), lambda: git_dag(self.model)) self.menu_dag.setIcon(qtutils.git_icon()) # Relayed actions if not self.classic_dockable: # These shortcuts conflict with those from the # 'Browser' widget so don't register them when # the browser is a dockable tool. status_tree = self.statusdockwidget.widget().tree self.addAction(status_tree.up) self.addAction(status_tree.down) self.addAction(status_tree.process_selection) # Create the application menu self.menubar = QtGui.QMenuBar(self) # File Menu self.file_menu = create_menu(N_('File'), self.menubar) self.file_menu.addAction(self.menu_preferences) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_open_repo) self.menu_open_recent = self.file_menu.addMenu(N_('Open Recent')) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_clone_repo) self.file_menu.addAction(self.menu_manage_bookmarks) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_edit_remotes) self.file_menu.addAction(self.menu_rescan) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_browse_recent) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_load_commitmsg) self.file_menu.addAction(self.menu_load_commitmsg_template) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_save_tarball) self.file_menu.addAction(self.menu_quit) # Add to menubar self.menubar.addAction(self.file_menu.menuAction()) # Commit Menu self.commit_menu = create_menu(N_('Commit@@verb'), self.menubar) self.commit_menu.setTitle(N_('Commit@@verb')) self.commit_menu.addAction(self.menu_stage_modified) self.commit_menu.addAction(self.menu_stage_untracked) self.commit_menu.addSeparator() self.commit_menu.addAction(self.menu_unstage_all) self.commit_menu.addAction(self.menu_unstage_selected) self.commit_menu.addSeparator() self.commit_menu.addAction(self.menu_search_commits) # Add to menubar self.menubar.addAction(self.commit_menu.menuAction()) # Branch Menu self.branch_menu = create_menu(N_('Branch'), self.menubar) self.branch_menu.addAction(self.menu_branch_review) self.branch_menu.addSeparator() self.branch_menu.addAction(self.menu_create_branch) self.branch_menu.addAction(self.menu_checkout_branch) self.branch_menu.addAction(self.menu_rebase_branch) self.branch_menu.addAction(self.menu_delete_branch) self.branch_menu.addSeparator() self.branch_menu.addAction(self.menu_browse_branch) self.branch_menu.addAction(self.menu_browse_other_branch) self.branch_menu.addSeparator() self.branch_menu.addAction(self.menu_visualize_current) self.branch_menu.addAction(self.menu_visualize_all) # Add to menubar self.menubar.addAction(self.branch_menu.menuAction()) # Actions menu self.actions_menu = create_menu(N_('Actions'), self.menubar) self.actions_menu.addAction(self.menu_merge_local) self.actions_menu.addAction(self.menu_stash) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_fetch) self.actions_menu.addAction(self.menu_push) self.actions_menu.addAction(self.menu_pull) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_create_tag) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_export_patches) self.actions_menu.addAction(self.menu_cherry_pick) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_merge_abort) self.actions_menu.addAction(self.menu_grep) # Add to menubar self.menubar.addAction(self.actions_menu.menuAction()) # Diff Menu self.diff_menu = create_menu(N_('Diff'), self.menubar) self.diff_menu.addAction(self.menu_diff_expression) self.diff_menu.addAction(self.menu_branch_compare) self.diff_menu.addSeparator() self.diff_menu.addAction(self.menu_show_diffstat) # Add to menubar self.menubar.addAction(self.diff_menu.menuAction()) # Tools Menu self.tools_menu = create_menu(N_('Tools'), self.menubar) self.tools_menu.addAction(self.menu_classic) self.tools_menu.addAction(self.menu_dag) self.tools_menu.addSeparator() if self.classic_dockable: self.tools_menu.addAction(self.classicdockwidget.toggleViewAction()) self.setup_dockwidget_tools_menu() self.menubar.addAction(self.tools_menu.menuAction()) # Help Menu self.help_menu = create_menu(N_('Help'), self.menubar) self.help_menu.addAction(self.menu_help_docs) self.help_menu.addAction(self.menu_help_shortcuts) self.help_menu.addAction(self.menu_help_about) # Add to menubar self.menubar.addAction(self.help_menu.menuAction()) # Set main menu self.setMenuBar(self.menubar) # Arrange dock widgets left = Qt.LeftDockWidgetArea right = Qt.RightDockWidgetArea bottom = Qt.BottomDockWidgetArea self.addDockWidget(left, self.commitdockwidget) if self.classic_dockable: self.addDockWidget(left, self.classicdockwidget) self.tabifyDockWidget(self.classicdockwidget, self.commitdockwidget) self.addDockWidget(left, self.diffdockwidget) self.addDockWidget(bottom, self.actionsdockwidget) self.addDockWidget(bottom, self.logdockwidget) self.tabifyDockWidget(self.actionsdockwidget, self.logdockwidget) self.addDockWidget(right, self.statusdockwidget) # Listen for model notifications model.add_observer(model.message_updated, self._update_view) prefs_model.add_observer(prefs_model.message_config_updated, self._config_updated) # Set a default value self.show_cursor_position(1, 0) self.connect(self.menu_open_recent, SIGNAL('aboutToShow()'), self.build_recent_menu) self.connect(self.commitmsgeditor, SIGNAL('cursorPosition(int,int)'), self.show_cursor_position) self.connect(self, SIGNAL('update'), self._update_callback) self.connect(self, SIGNAL('install_config_actions'), self._install_config_actions) # Install .git-config-defined actions self._config_task = None self.install_config_actions() self.dockwidgets = ( self.logdockwidget, self.commitdockwidget, self.statusdockwidget, self.diffdockwidget, self.actionsdockwidget, ) # Restore saved settings if not qtutils.apply_state(self): self.set_initial_size() self.statusdockwidget.widget().setFocus() # Route command output here Interaction.log_status = self.logwidget.log_status Interaction.log = self.logwidget.log Interaction.log(version.git_version_str() + '\n' + N_('git cola version %s') % version.version()) def set_initial_size(self): self.statuswidget.set_initial_size() self.commitmsgeditor.set_initial_size() # Qt overrides def closeEvent(self, event): """Save state in the settings manager.""" s = settings.Settings() s.add_recent(core.decode(os.getcwd())) qtutils.save_state(self, handler=s) commit_msg = self.commitmsgeditor.commit_message(raw=True) self.model.save_commitmsg(commit_msg) MainWindow.closeEvent(self, event) def build_recent_menu(self): recent = settings.Settings().recent menu = self.menu_open_recent menu.clear() for r in recent: name = os.path.basename(r) directory = os.path.dirname(r) text = '%s %s %s' % (name, unichr(0x2192), directory) menu.addAction(text, cmds.run(cmds.OpenRepo, r)) # Accessors mode = property(lambda self: self.model.mode) def _config_updated(self, source, config, value): if config == 'cola.fontdiff': # The diff font font = QtGui.QFont() if not font.fromString(value): return self.logwidget.setFont(font) self.diffeditor.setFont(font) self.commitmsgeditor.setFont(font) elif config == 'cola.tabwidth': # variable-tab-width setting self.diffeditor.set_tabwidth(value) self.commitmsgeditor.set_tabwidth(value) elif config == 'cola.linebreak': # enables automatic line breaks self.commitmsgeditor.set_linebreak(value) elif config == 'cola.textwidth': # text width used for line wrapping self.commitmsgeditor.set_textwidth(value) def install_config_actions(self): """Install .gitconfig-defined actions""" self._config_task = self._start_config_actions_task() def _start_config_actions_task(self): """Do the expensive "get_config_actions()" call in the background""" class ConfigActionsTask(QtCore.QRunnable): def __init__(self, sender): QtCore.QRunnable.__init__(self) self._sender = sender def run(self): names = cfgactions.get_config_actions() self._sender.emit(SIGNAL('install_config_actions'), names) task = ConfigActionsTask(self) QtCore.QThreadPool.globalInstance().start(task) return task def _install_config_actions(self, names): """Install .gitconfig-defined actions""" if not names: return menu = self.actions_menu menu.addSeparator() for name in names: menu.addAction(name, cmds.run(cmds.RunConfigAction, name)) def _update_view(self): self.emit(SIGNAL('update')) def _update_callback(self): """Update the title with the current branch and directory name.""" branch = self.model.currentbranch curdir = core.decode(os.getcwd()) msg = N_('Repository: %s') % curdir msg += '\n' msg += N_('Branch: %s') % branch self.commitdockwidget.setToolTip(msg) title = '%s: %s (%s)' % (self.model.project, branch, self.model.git.worktree()) if self.mode == self.model.mode_amend: title += ' (%s)' % N_('Amending') self.setWindowTitle(title) self.commitmsgeditor.set_mode(self.mode) if not self.model.amending(): # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(core.decode(merge_msg_path)) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cmds.do(cmds.LoadCommitMessage, core.decode(merge_msg_path)) def apply_state(self, state): """Imports data for save/restore""" # 1 is the widget version; change when widgets are added/removed MainWindow.apply_state(self, state) result = qtutils.apply_window_state(self, state, self.widget_version) for widget in self.dockwidgets: widget.titleBarWidget().update_tooltips() return result def export_state(self): """Exports data for save/restore""" state = MainWindow.export_state(self) return qtutils.export_window_state(self, state, self.widget_version) def setup_dockwidget_tools_menu(self): # Hotkeys for toggling the dock widgets if utils.is_darwin(): optkey = 'Meta' else: optkey = 'Ctrl' dockwidgets = ( (optkey + '+0', self.logdockwidget), (optkey + '+1', self.commitdockwidget), (optkey + '+2', self.statusdockwidget), (optkey + '+3', self.diffdockwidget), (optkey + '+4', self.actionsdockwidget), ) for shortcut, dockwidget in dockwidgets: # Associate the action with the shortcut toggleview = dockwidget.toggleViewAction() toggleview.setShortcut(shortcut) self.tools_menu.addAction(toggleview) def showdock(show, dockwidget=dockwidget): if show: dockwidget.raise_() dockwidget.widget().setFocus() else: self.setFocus() self.addAction(toggleview) connect_action_bool(toggleview, showdock) # Create a new shortcut Shift+<shortcut> that gives focus toggleview = QtGui.QAction(self) toggleview.setShortcut('Shift+' + shortcut) def focusdock(dockwidget=dockwidget, showdock=showdock): if dockwidget.toggleViewAction().isChecked(): showdock(True) else: dockwidget.toggleViewAction().trigger() self.addAction(toggleview) connect_action(toggleview, focusdock) def _update_diff_opts(self): space_at_eol = self.diff_ignore_space_at_eol_action.isChecked() space_change = self.diff_ignore_space_change_action.isChecked() all_space = self.diff_ignore_all_space_action.isChecked() function_context = self.diff_function_context_action.isChecked() gitcmds.update_diff_overrides(space_at_eol, space_change, all_space, function_context) self.statuswidget.refresh() def preferences(self): return prefs.preferences(model=self.prefs_model, parent=self) def save_archive(self): ref = git.rev_parse('HEAD') shortref = ref[:7] GitArchiveDialog.save(ref, shortref, self) def dragEnterEvent(self, event): """Accepts drops""" MainWindow.dragEnterEvent(self, event) event.acceptProposedAction() def dropEvent(self, event): """Apply dropped patches with git-am""" event.accept() urls = event.mimeData().urls() if not urls: return paths = map(lambda x: unicode(x.path()), urls) patches = [p for p in paths if p.endswith('.patch')] dirs = [p for p in paths if os.path.isdir(p)] dirs.sort() for d in dirs: patches.extend(self._gather_patches(d)) cmds.do(cmds.ApplyPatches, patches) def _gather_patches(self, path): """Find patches in a subdirectory""" patches = [] for root, subdirs, files in os.walk(path): for name in [f for f in files if f.endswith('.patch')]: patches.append(os.path.join(root, name)) return patches def show_cursor_position(self, rows, cols): display = ' %02d:%02d ' % (rows, cols) if cols > 78: display = ('<span style="color: white; ' ' background-color: red;"' '>%s</span>' % display) elif cols > 72: display = ('<span style="color: black; ' ' background-color: orange;"' '>%s</span>' % display) elif cols > 64: display = ('<span style="color: black; ' ' background-color: yellow;"' '>%s</span>' % display) else: display = ('<span style="color: grey;">%s</span>' % display) self.position_label.setText(display)
class MainView(MainWindow): def __init__(self, model, parent): MainWindow.__init__(self, parent) # Default size; this is thrown out when save/restore is used self.resize(987, 610) self.model = model self.prefs_model = prefs_model = PreferencesModel() # Internal field used by import/export_state(). # Change this whenever dockwidgets are removed. self.widget_version = 1 # Keeps track of merge messages we've seen self.merge_message_hash = '' self.setAcceptDrops(True) self.setAttribute(Qt.WA_MacMetalStyle) # Dockwidget options qtcompat.set_common_dock_options(self) cfg = gitcfg.instance() self.classic_dockable = (cfg.get('cola.browserdockable') or cfg.get('cola.classicdockable')) if self.classic_dockable: self.classicdockwidget = create_dock('Browser', self) self.classicwidget = classic_widget(self) self.classicdockwidget.setWidget(self.classicwidget) # "Actions" widget self.actionsdockwidget = create_dock('Action', self) self.actionsdockwidgetcontents = qt.QFlowLayoutWidget(self) layout = self.actionsdockwidgetcontents.layout() self.stage_button = create_button(text='Stage', layout=layout) self.unstage_button = create_button(text='Unstage', layout=layout) self.rescan_button = create_button(text='Rescan', layout=layout) self.fetch_button = create_button(text='Fetch...', layout=layout) self.push_button = create_button(text='Push...', layout=layout) self.pull_button = create_button(text='Pull...', layout=layout) self.stash_button = create_button(text='Stash...', layout=layout) layout.addStretch() self.actionsdockwidget.setWidget(self.actionsdockwidgetcontents) # "Repository Status" widget self.statusdockwidget = create_dock('Status', self) self.statusdockwidget.setWidget(StatusWidget(self)) # "Commit Message Editor" widget self.position_label = QtGui.QLabel() font = qtutils.default_monospace_font() font.setPointSize(int(font.pointSize() * 0.8)) self.position_label.setFont(font) self.commitdockwidget = create_dock('Commit', self) titlebar = self.commitdockwidget.titleBarWidget() titlebar.add_corner_widget(self.position_label) self.commitmsgeditor = CommitMessageEditor(model, self) relay_signal(self, self.commitmsgeditor, SIGNAL(signals.amend_mode)) relay_signal(self, self.commitmsgeditor, SIGNAL(signals.signoff)) relay_signal(self, self.commitmsgeditor, SIGNAL(signals.load_previous_message)) self.commitdockwidget.setWidget(self.commitmsgeditor) # "Console" widget self.logwidget = LogWidget() self.logdockwidget = create_dock('Console', self) self.logdockwidget.setWidget(self.logwidget) cola.notifier().connect(signals.log_cmd, self.logwidget.log) # "Diff Viewer" widget self.diffdockwidget = create_dock('Diff', self) self.diffeditor = DiffEditor(self.diffdockwidget) self.diffdockwidget.setWidget(self.diffeditor) # All Actions self.menu_unstage_all = add_action(self, 'Unstage All', emit(self, signals.unstage_all)) self.menu_unstage_all.setIcon(qtutils.icon('remove.svg')) self.menu_unstage_selected = add_action(self, 'Unstage From Commit', emit(self, signals.unstage_selected)) self.menu_unstage_selected.setIcon(qtutils.icon('remove.svg')) self.menu_show_diffstat = add_action(self, 'Diffstat', emit(self, signals.diffstat), 'Alt+D') self.menu_stage_modified = add_action(self, 'Stage Changed Files To Commit', emit(self, signals.stage_modified), 'Alt+A') self.menu_stage_modified.setIcon(qtutils.icon('add.svg')) self.menu_stage_untracked = add_action(self, 'Stage All Untracked', emit(self, signals.stage_untracked), 'Alt+U') self.menu_stage_untracked.setIcon(qtutils.icon('add.svg')) self.menu_export_patches = add_action(self, 'Export Patches...', guicmds.export_patches, 'Alt+E') self.menu_preferences = add_action(self, 'Preferences', lambda: preferences(model=prefs_model), QtGui.QKeySequence.Preferences, 'Ctrl+O') self.menu_edit_remotes = add_action(self, 'Edit Remotes...', lambda: editremotes.edit().exec_()) self.menu_rescan = add_action(self, 'Rescan', emit(self, signals.rescan_and_refresh), 'Ctrl+R') self.menu_rescan.setIcon(qtutils.reload_icon()) self.menu_browse_recent = add_action(self, 'Recently Modified Files...', browse_recent, 'Shift+Ctrl+E') self.menu_cherry_pick = add_action(self, 'Cherry-Pick...', guicmds.cherry_pick, 'Ctrl+P') self.menu_load_commitmsg = add_action(self, 'Load Commit Message...', guicmds.load_commitmsg) self.menu_save_tarball = add_action(self, 'Save As Tarball/Zip...', self.save_archive) self.menu_quit = add_action(self, 'Quit', self.close, 'Ctrl+Q') self.menu_manage_bookmarks = add_action(self, 'Bookmarks...', manage_bookmarks) self.menu_grep = add_action(self, 'Grep', guicmds.grep) self.menu_merge_local = add_action(self, 'Merge...', merge.local_merge) self.menu_merge_abort = add_action(self, 'Abort Merge...', merge.abort_merge) self.menu_fetch = add_action(self, 'Fetch...', remote.fetch) self.menu_push = add_action(self, 'Push...', remote.push) self.menu_pull = add_action(self, 'Pull...', remote.pull) self.menu_open_repo = add_action(self, 'Open...', guicmds.open_repo) self.menu_open_repo.setIcon(qtutils.open_icon()) self.menu_stash = add_action(self, 'Stash...', stash.stash, 'Alt+Shift+S') self.menu_clone_repo = add_action(self, 'Clone...', guicmds.clone_repo) self.menu_clone_repo.setIcon(qtutils.git_icon()) self.menu_help_docs = add_action(self, 'Documentation', resources.show_html_docs, QtGui.QKeySequence.HelpContents) self.menu_help_shortcuts = add_action(self, 'Keyboard Shortcuts', show_shortcuts, QtCore.Qt.Key_Question) self.menu_visualize_current = add_action(self, 'Visualize Current Branch...', emit(self, signals.visualize_current)) self.menu_visualize_all = add_action(self, 'Visualize All Branches...', emit(self, signals.visualize_all)) self.menu_search_commits = add_action(self, 'Search...', search) self.menu_browse_branch = add_action(self, 'Browse Current Branch...', guicmds.browse_current) self.menu_browse_other_branch = add_action(self, 'Browse Other Branch...', guicmds.browse_other) self.menu_load_commitmsg_template = add_action(self, 'Get Commit Message Template', emit(self, signals.load_commit_template)) self.menu_help_about = add_action(self, 'About', launch_about_dialog) self.menu_branch_diff = add_action(self, 'SHA-1...', guicmds.diff_revision) self.menu_diff_expression = add_action(self, 'Expression...', guicmds.diff_expression) self.menu_branch_compare = add_action(self, 'Branches...', compare_branches) self.menu_create_tag = add_action(self, 'Create Tag...', create_tag) self.menu_create_branch = add_action(self, 'Create...', create_new_branch, 'Ctrl+B') self.menu_delete_branch = add_action(self, 'Delete...', guicmds.branch_delete) self.menu_checkout_branch = add_action(self, 'Checkout...', guicmds.checkout_branch, 'Alt+B') self.menu_rebase_branch = add_action(self, 'Rebase...', guicmds.rebase) self.menu_branch_review = add_action(self, 'Review...', guicmds.review_branch) self.menu_classic = add_action(self, 'Browser...', cola_classic) self.menu_classic.setIcon(qtutils.git_icon()) self.menu_dag = add_action(self, 'DAG...', lambda: git_dag(self.model)) self.menu_dag.setIcon(qtutils.git_icon()) # Relayed actions if not self.classic_dockable: # These shortcuts conflict with those from the # 'Browser' widget so don't register them when # the browser is a dockable tool. status_tree = self.statusdockwidget.widget().tree self.addAction(status_tree.up) self.addAction(status_tree.down) self.addAction(status_tree.process_selection) self.addAction(status_tree.launch_difftool) # Create the application menu self.menubar = QtGui.QMenuBar(self) # File Menu self.file_menu = create_menu('&File', self.menubar) self.file_menu.addAction(self.menu_preferences) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_open_repo) self.menu_open_recent = self.file_menu.addMenu(tr('Open Recent')) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_clone_repo) self.file_menu.addAction(self.menu_manage_bookmarks) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_edit_remotes) self.file_menu.addAction(self.menu_rescan) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_browse_recent) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_load_commitmsg) self.file_menu.addAction(self.menu_load_commitmsg_template) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_save_tarball) self.file_menu.addAction(self.menu_quit) # Add to menubar self.menubar.addAction(self.file_menu.menuAction()) # Commit Menu self.commit_menu = create_menu('Co&mmit', self.menubar) self.commit_menu.setTitle(tr('Commit@@verb')) self.commit_menu.addAction(self.menu_stage_modified) self.commit_menu.addAction(self.menu_stage_untracked) self.commit_menu.addSeparator() self.commit_menu.addAction(self.menu_unstage_all) self.commit_menu.addAction(self.menu_unstage_selected) self.commit_menu.addSeparator() self.commit_menu.addAction(self.menu_search_commits) # Add to menubar self.menubar.addAction(self.commit_menu.menuAction()) # Branch Menu self.branch_menu = create_menu('B&ranch', self.menubar) self.branch_menu.addAction(self.menu_branch_review) self.branch_menu.addSeparator() self.branch_menu.addAction(self.menu_create_branch) self.branch_menu.addAction(self.menu_checkout_branch) self.branch_menu.addAction(self.menu_rebase_branch) self.branch_menu.addAction(self.menu_delete_branch) self.branch_menu.addSeparator() self.branch_menu.addAction(self.menu_browse_branch) self.branch_menu.addAction(self.menu_browse_other_branch) self.branch_menu.addSeparator() self.branch_menu.addAction(self.menu_visualize_current) self.branch_menu.addAction(self.menu_visualize_all) # Add to menubar self.menubar.addAction(self.branch_menu.menuAction()) # Actions menu self.actions_menu = create_menu('Act&ions', self.menubar) self.actions_menu.addAction(self.menu_merge_local) self.actions_menu.addAction(self.menu_stash) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_fetch) self.actions_menu.addAction(self.menu_push) self.actions_menu.addAction(self.menu_pull) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_create_tag) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_export_patches) self.actions_menu.addAction(self.menu_cherry_pick) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_merge_abort) self.actions_menu.addAction(self.menu_grep) # Add to menubar self.menubar.addAction(self.actions_menu.menuAction()) # Diff Menu self.diff_menu = create_menu('&Diff', self.menubar) self.diff_menu.addAction(self.menu_branch_diff) self.diff_menu.addAction(self.menu_diff_expression) self.diff_menu.addAction(self.menu_branch_compare) self.diff_menu.addSeparator() self.diff_menu.addAction(self.menu_show_diffstat) # Add to menubar self.menubar.addAction(self.diff_menu.menuAction()) # Tools Menu self.tools_menu = create_menu('&Tools', self.menubar) self.tools_menu.addAction(self.menu_classic) self.tools_menu.addAction(self.menu_dag) self.tools_menu.addSeparator() if self.classic_dockable: self.tools_menu.addAction(self.classicdockwidget.toggleViewAction()) self.setup_dockwidget_tools_menu() self.menubar.addAction(self.tools_menu.menuAction()) # Help Menu self.help_menu = create_menu('&Help', self.menubar) self.help_menu.addAction(self.menu_help_docs) self.help_menu.addAction(self.menu_help_shortcuts) self.help_menu.addAction(self.menu_help_about) # Add to menubar self.menubar.addAction(self.help_menu.menuAction()) # Set main menu self.setMenuBar(self.menubar) # Arrange dock widgets top = Qt.TopDockWidgetArea bottom = Qt.BottomDockWidgetArea self.addDockWidget(top, self.commitdockwidget) if self.classic_dockable: self.addDockWidget(top, self.classicdockwidget) self.addDockWidget(top, self.statusdockwidget) self.addDockWidget(top, self.actionsdockwidget) self.addDockWidget(bottom, self.logdockwidget) if self.classic_dockable: self.tabifyDockWidget(self.classicdockwidget, self.commitdockwidget) self.tabifyDockWidget(self.logdockwidget, self.diffdockwidget) # Listen for model notifications model.add_observer(model.message_updated, self._update_view) prefs_model.add_observer(prefs_model.message_config_updated, self._config_updated) # Set a default value self.show_cursor_position(1, 0) # Add button callbacks connect_button(self.rescan_button, emit(self, signals.rescan_and_refresh)) connect_button(self.fetch_button, remote.fetch) connect_button(self.push_button, remote.push) connect_button(self.pull_button, remote.pull) connect_button(self.stash_button, stash.stash) connect_button(self.stage_button, self.stage) connect_button(self.unstage_button, self.unstage) self.connect(self.menu_open_recent, SIGNAL('aboutToShow()'), self.build_recent_menu) self.connect(self.commitmsgeditor, SIGNAL('cursorPosition(int,int)'), self.show_cursor_position) self.connect(self, SIGNAL('update'), self._update_callback) self.connect(self, SIGNAL('install_config_actions'), self._install_config_actions) # Install .git-config-defined actions self._config_task = None self.install_config_actions() # Restore saved settings qtutils.apply_state(self) self.statusdockwidget.widget().setFocus() log(0, version.git_version_str() + '\ncola version ' + version.version()) # Qt overrides def closeEvent(self, event): """Save state in the settings manager.""" s = settings.Settings() s.add_recent(core.decode(os.getcwd())) qtutils.save_state(self, handler=s) MainWindow.closeEvent(self, event) def build_recent_menu(self): recent = settings.Settings().recent menu = self.menu_open_recent menu.clear() for r in recent: name = os.path.basename(r) directory = os.path.dirname(r) text = u'%s %s %s' % (name, unichr(0x2192), directory) menu.addAction(text, qtutils.SLOT(signals.open_repo, r)) # Accessors mode = property(lambda self: self.model.mode) def _config_updated(self, source, config, value): if config == 'cola.fontdiff': # The diff font font = QtGui.QFont() if not font.fromString(value): return self.logwidget.setFont(font) self.diffeditor.setFont(font) self.commitmsgeditor.setFont(font) elif config == 'cola.tabwidth': # variable-tab-width setting self.diffeditor.set_tabwidth(value) self.commitmsgeditor.set_tabwidth(value) elif config == 'cola.linebreak': # enables automatic line breaks self.commitmsgeditor.set_linebreak(value) elif config == 'cola.textwidth': # text width used for line wrapping self.commitmsgeditor.set_textwidth(value) def install_config_actions(self): """Install .gitconfig-defined actions""" self._config_task = self._start_config_actions_task() def _start_config_actions_task(self): """Do the expensive "get_config_actions()" call in the background""" class ConfigActionsTask(QtCore.QRunnable): def __init__(self, sender): QtCore.QRunnable.__init__(self) self._sender = sender def run(self): names = cfgactions.get_config_actions() self._sender.emit(SIGNAL('install_config_actions'), names) task = ConfigActionsTask(self) QtCore.QThreadPool.globalInstance().start(task) return task def _install_config_actions(self, names): """Install .gitconfig-defined actions""" if not names: return menu = self.actions_menu menu.addSeparator() for name in names: menu.addAction(name, emit(self, signals.run_config_action, name)) def _update_view(self): self.emit(SIGNAL('update')) def _update_callback(self): """Update the title with the current branch and directory name.""" branch = self.model.currentbranch curdir = core.decode(os.getcwd()) msg = 'Repository: %s\nBranch: %s' % (curdir, branch) self.commitdockwidget.setToolTip(msg) title = '%s: %s' % (self.model.project, branch) if self.mode == self.model.mode_amend: title += ' ** amending **' self.setWindowTitle(title) self.commitmsgeditor.set_mode(self.mode) if not self.model.amending(): # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(core.decode(merge_msg_path)) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cola.notifier().broadcast(signals.load_commit_message, core.decode(merge_msg_path)) def apply_state(self, state): """Imports data for save/restore""" # 1 is the widget version; change when widgets are added/removed MainWindow.apply_state(self, state) qtutils.apply_window_state(self, state, 1) def export_state(self): """Exports data for save/restore""" state = MainWindow.export_state(self) return qtutils.export_window_state(self, state, self.widget_version) def setup_dockwidget_tools_menu(self): # Hotkeys for toggling the dock widgets dockwidgets = ( ('Alt+0', self.logdockwidget), ('Alt+1', self.commitdockwidget), ('Alt+2', self.statusdockwidget), ('Alt+3', self.diffdockwidget), ('Alt+4', self.actionsdockwidget), ) for shortcut, dockwidget in dockwidgets: # Associate the action with the shortcut action = dockwidget.toggleViewAction() action.setShortcut(shortcut) self.tools_menu.addAction(action) def showdock(show, dockwidget=dockwidget): if show: dockwidget.raise_() dockwidget.widget().setFocus() else: self.setFocus() self.addAction(action) connect_action_bool(action, showdock) # Create a new shortcut Shift+<shortcut> that gives focus action = QtGui.QAction(self) action.setShortcut('Shift+' + shortcut) def focusdock(dockwidget=dockwidget, showdock=showdock): if dockwidget.toggleViewAction().isChecked(): showdock(True) else: dockwidget.toggleViewAction().trigger() self.addAction(action) connect_action(action, focusdock) def save_archive(self): ref = git.rev_parse('HEAD') shortref = ref[:7] GitArchiveDialog.save(ref, shortref, self) def stage(self): """Stage selected files, or all files if no selection exists.""" paths = cola.selection_model().unstaged if not paths: cola.notifier().broadcast(signals.stage_modified) else: cola.notifier().broadcast(signals.stage, paths) def unstage(self): """Unstage selected files, or all files if no selection exists.""" paths = cola.selection_model().staged if not paths: cola.notifier().broadcast(signals.unstage_all) else: cola.notifier().broadcast(signals.unstage, paths) def dragEnterEvent(self, event): """Accepts drops""" MainWindow.dragEnterEvent(self, event) event.acceptProposedAction() def dropEvent(self, event): """Apply dropped patches with git-am""" event.accept() urls = event.mimeData().urls() if not urls: return paths = map(lambda x: unicode(x.path()), urls) patches = [p for p in paths if p.endswith('.patch')] dirs = [p for p in paths if os.path.isdir(p)] dirs.sort() for d in dirs: patches.extend(self._gather_patches(d)) # Broadcast the patches to apply cola.notifier().broadcast(signals.apply_patches, patches) def _gather_patches(self, path): """Find patches in a subdirectory""" patches = [] for root, subdirs, files in os.walk(path): for name in [f for f in files if f.endswith('.patch')]: patches.append(os.path.join(root, name)) return patches def show_cursor_position(self, rows, cols): display = ' %02d:%02d ' % (rows, cols) if cols > 78: display = ('<span style="color: white; ' ' background-color: red;"' '>%s</span>' % display) elif cols > 72: display = ('<span style="color: black; ' ' background-color: orange;"' '>%s</span>' % display) elif cols > 64: display = ('<span style="color: black; ' ' background-color: yellow;"' '>%s</span>' % display) else: display = ('<span style="color: grey;">%s</span>' % display) self.position_label.setText(display)
class MainView(MainWindow): def __init__(self, model, parent=None): MainWindow.__init__(self, parent) self.setAttribute(Qt.WA_MacMetalStyle) # Default size; this is thrown out when save/restore is used self.resize(987, 610) self.model = model self.prefs_model = prefs_model = prefs.PreferencesModel() # The widget version is used by import/export_state(). # Change this whenever dockwidgets are removed. self.widget_version = 2 # Keeps track of merge messages we've seen self.merge_message_hash = "" cfg = gitcfg.instance() self.browser_dockable = cfg.get("cola.browserdockable") or cfg.get("cola.classicdockable") if self.browser_dockable: self.browserdockwidget = create_dock(N_("Browser"), self) self.browserwidget = worktree_browser_widget(self) self.browserdockwidget.setWidget(self.browserwidget) # "Actions" widget self.actionsdockwidget = create_dock(N_("Actions"), self) self.actionsdockwidgetcontents = action.ActionButtons(self) self.actionsdockwidget.setWidget(self.actionsdockwidgetcontents) self.actionsdockwidget.toggleViewAction().setChecked(False) self.actionsdockwidget.hide() # "Repository Status" widget self.statuswidget = StatusWidget(self) self.statusdockwidget = create_dock(N_("Status"), self) self.statusdockwidget.setWidget(self.statuswidget) # "Commit Message Editor" widget self.position_label = QtGui.QLabel() font = qtutils.default_monospace_font() font.setPointSize(int(font.pointSize() * 0.8)) self.position_label.setFont(font) self.commitdockwidget = create_dock(N_("Commit"), self) titlebar = self.commitdockwidget.titleBarWidget() titlebar.add_corner_widget(self.position_label) self.commitmsgeditor = CommitMessageEditor(model, self) self.commitdockwidget.setWidget(self.commitmsgeditor) # "Console" widget self.logwidget = LogWidget() self.logdockwidget = create_dock(N_("Console"), self) self.logdockwidget.setWidget(self.logwidget) self.logdockwidget.toggleViewAction().setChecked(False) self.logdockwidget.hide() # "Diff Viewer" widget self.diffdockwidget = create_dock(N_("Diff"), self) self.diffeditor = DiffEditor(self.diffdockwidget) self.diffdockwidget.setWidget(self.diffeditor) # "Diff Options" tool menu self.diff_ignore_space_at_eol_action = add_action( self, N_("Ignore changes in whitespace at EOL"), self._update_diff_opts ) self.diff_ignore_space_at_eol_action.setCheckable(True) self.diff_ignore_space_change_action = add_action( self, N_("Ignore changes in amount of whitespace"), self._update_diff_opts ) self.diff_ignore_space_change_action.setCheckable(True) self.diff_ignore_all_space_action = add_action(self, N_("Ignore all whitespace"), self._update_diff_opts) self.diff_ignore_all_space_action.setCheckable(True) self.diff_function_context_action = add_action( self, N_("Show whole surrounding functions of changes"), self._update_diff_opts ) self.diff_function_context_action.setCheckable(True) self.diffopts_button = create_toolbutton(text=N_("Options"), icon=options_icon(), tooltip=N_("Diff Options")) self.diffopts_menu = create_menu(N_("Diff Options"), self.diffopts_button) self.diffopts_menu.addAction(self.diff_ignore_space_at_eol_action) self.diffopts_menu.addAction(self.diff_ignore_space_change_action) self.diffopts_menu.addAction(self.diff_ignore_all_space_action) self.diffopts_menu.addAction(self.diff_function_context_action) self.diffopts_button.setMenu(self.diffopts_menu) self.diffopts_button.setPopupMode(QtGui.QToolButton.InstantPopup) titlebar = self.diffdockwidget.titleBarWidget() titlebar.add_corner_widget(self.diffopts_button) # All Actions self.unstage_all_action = add_action(self, N_("Unstage All"), cmds.run(cmds.UnstageAll)) self.unstage_all_action.setIcon(qtutils.icon("remove.svg")) self.unstage_selected_action = add_action(self, N_("Unstage From Commit"), cmds.run(cmds.UnstageSelected)) self.unstage_selected_action.setIcon(qtutils.icon("remove.svg")) self.show_diffstat_action = add_action(self, N_("Diffstat"), cmds.run(cmds.Diffstat), "Alt+D") self.stage_modified_action = add_action( self, N_("Stage Changed Files To Commit"), cmds.run(cmds.StageModified), "Alt+A" ) self.stage_modified_action.setIcon(qtutils.icon("add.svg")) self.stage_untracked_action = add_action( self, N_("Stage All Untracked"), cmds.run(cmds.StageUntracked), "Alt+U" ) self.stage_untracked_action.setIcon(qtutils.icon("add.svg")) self.apply_patches_action = add_action(self, N_("Apply Patches..."), apply_patches) self.export_patches_action = add_action(self, N_("Export Patches..."), guicmds.export_patches, "Alt+E") self.new_repository_action = add_action(self, N_("New Repository..."), guicmds.open_new_repo) self.new_repository_action.setIcon(qtutils.new_icon()) self.preferences_action = add_action( self, N_("Preferences"), self.preferences, QtGui.QKeySequence.Preferences, "Ctrl+O" ) self.edit_remotes_action = add_action(self, N_("Edit Remotes..."), lambda: editremotes.edit().exec_()) self.rescan_action = add_action(self, cmds.Refresh.name(), cmds.run(cmds.Refresh), cmds.Refresh.SHORTCUT) self.rescan_action.setIcon(qtutils.reload_icon()) self.browse_recently_modified_action = add_action( self, N_("Recently Modified Files..."), browse_recent, "Shift+Ctrl+E" ) self.cherry_pick_action = add_action(self, N_("Cherry-Pick..."), guicmds.cherry_pick, "Ctrl+P") self.load_commitmsg_action = add_action(self, N_("Load Commit Message..."), guicmds.load_commitmsg) self.save_tarball_action = add_action(self, N_("Save As Tarball/Zip..."), self.save_archive) self.quit_action = add_action(self, N_("Quit"), self.close, "Ctrl+Q") self.manage_bookmarks_action = add_action(self, N_("Bookmarks..."), manage_bookmarks) self.grep_action = add_action(self, N_("Grep"), grep, "Ctrl+G") self.merge_local_action = add_action(self, N_("Merge..."), merge.local_merge) self.merge_abort_action = add_action(self, N_("Abort Merge..."), merge.abort_merge) self.fetch_action = add_action(self, N_("Fetch..."), remote.fetch) self.push_action = add_action(self, N_("Push..."), remote.push) self.pull_action = add_action(self, N_("Pull..."), remote.pull) self.open_repo_action = add_action(self, N_("Open..."), guicmds.open_repo) self.open_repo_action.setIcon(qtutils.open_icon()) self.stash_action = add_action(self, N_("Stash..."), stash, "Alt+Shift+S") self.clone_repo_action = add_action(self, N_("Clone..."), guicmds.clone_repo) self.clone_repo_action.setIcon(qtutils.git_icon()) self.help_docs_action = add_action( self, N_("Documentation"), resources.show_html_docs, QtGui.QKeySequence.HelpContents ) self.help_shortcuts_action = add_action(self, N_("Keyboard Shortcuts"), show_shortcuts, QtCore.Qt.Key_Question) self.visualize_current_action = add_action( self, N_("Visualize Current Branch..."), cmds.run(cmds.VisualizeCurrent) ) self.visualize_all_action = add_action(self, N_("Visualize All Branches..."), cmds.run(cmds.VisualizeAll)) self.search_commits_action = add_action(self, N_("Search..."), search) self.browse_branch_action = add_action(self, N_("Browse Current Branch..."), guicmds.browse_current) self.browse_other_branch_action = add_action(self, N_("Browse Other Branch..."), guicmds.browse_other) self.load_commitmsg_template_action = add_action( self, N_("Get Commit Message Template"), cmds.run(cmds.LoadCommitMessageFromTemplate) ) self.help_about_action = add_action(self, N_("About"), launch_about_dialog) self.diff_expression_action = add_action(self, N_("Expression..."), guicmds.diff_expression) self.branch_compare_action = add_action(self, N_("Branches..."), compare_branches) self.create_tag_action = add_action(self, N_("Create Tag..."), create_tag) self.create_branch_action = add_action(self, N_("Create..."), create_new_branch, "Ctrl+B") self.delete_branch_action = add_action(self, N_("Delete..."), guicmds.delete_branch) self.delete_remote_branch_action = add_action(self, N_("Delete Remote Branch..."), guicmds.delete_remote_branch) self.checkout_branch_action = add_action(self, N_("Checkout..."), guicmds.checkout_branch, "Alt+B") self.branch_review_action = add_action(self, N_("Review..."), guicmds.review_branch) self.browse_action = add_action(self, N_("Browser..."), worktree_browser) self.browse_action.setIcon(qtutils.git_icon()) self.dag_action = add_action(self, N_("DAG..."), lambda: git_dag(self.model).show()) self.dag_action.setIcon(qtutils.git_icon()) self.rebase_start_action = add_action(self, N_("Start Interactive Rebase..."), self.rebase_start) self.rebase_edit_todo_action = add_action(self, N_("Edit..."), self.rebase_edit_todo) self.rebase_continue_action = add_action(self, N_("Continue"), self.rebase_continue) self.rebase_skip_action = add_action(self, N_("Skip Current Patch"), self.rebase_skip) self.rebase_abort_action = add_action(self, N_("Abort"), self.rebase_abort) # Relayed actions status_tree = self.statusdockwidget.widget().tree self.addAction(status_tree.revert_unstaged_edits_action) if not self.browser_dockable: # These shortcuts conflict with those from the # 'Browser' widget so don't register them when # the browser is a dockable tool. self.addAction(status_tree.up) self.addAction(status_tree.down) self.addAction(status_tree.process_selection) self.lock_layout_action = add_action_bool(self, N_("Lock Layout"), self.set_lock_layout, False) # Create the application menu self.menubar = QtGui.QMenuBar(self) # File Menu self.file_menu = create_menu(N_("File"), self.menubar) self.file_menu.addAction(self.open_repo_action) self.open_recent_action = self.file_menu.addMenu(N_("Open Recent")) self.open_recent_action.setIcon(qtutils.open_icon()) self.file_menu.addAction(self.clone_repo_action) self.file_menu.addAction(self.new_repository_action) self.file_menu.addSeparator() self.file_menu.addAction(self.rescan_action) self.file_menu.addAction(self.edit_remotes_action) self.file_menu.addAction(self.browse_recently_modified_action) self.file_menu.addAction(self.manage_bookmarks_action) self.file_menu.addSeparator() self.file_menu.addAction(self.load_commitmsg_action) self.file_menu.addAction(self.load_commitmsg_template_action) self.file_menu.addSeparator() self.file_menu.addAction(self.apply_patches_action) self.file_menu.addAction(self.export_patches_action) self.file_menu.addAction(self.save_tarball_action) self.file_menu.addSeparator() self.file_menu.addAction(self.preferences_action) self.file_menu.addAction(self.quit_action) self.menubar.addAction(self.file_menu.menuAction()) # Actions menu self.actions_menu = create_menu(N_("Actions"), self.menubar) self.actions_menu.addAction(self.fetch_action) self.actions_menu.addAction(self.push_action) self.actions_menu.addAction(self.pull_action) self.actions_menu.addAction(self.stash_action) self.actions_menu.addSeparator() self.actions_menu.addAction(self.create_tag_action) self.actions_menu.addAction(self.cherry_pick_action) self.actions_menu.addAction(self.merge_local_action) self.actions_menu.addAction(self.merge_abort_action) self.actions_menu.addSeparator() self.actions_menu.addAction(self.grep_action) self.actions_menu.addAction(self.search_commits_action) self.menubar.addAction(self.actions_menu.menuAction()) # Index Menu self.commit_menu = create_menu(N_("Index"), self.menubar) self.commit_menu.setTitle(N_("Index")) self.commit_menu.addAction(self.stage_modified_action) self.commit_menu.addAction(self.stage_untracked_action) self.commit_menu.addSeparator() self.commit_menu.addAction(self.unstage_all_action) self.commit_menu.addAction(self.unstage_selected_action) self.menubar.addAction(self.commit_menu.menuAction()) # Diff Menu self.diff_menu = create_menu(N_("Diff"), self.menubar) self.diff_menu.addAction(self.diff_expression_action) self.diff_menu.addAction(self.branch_compare_action) self.diff_menu.addSeparator() self.diff_menu.addAction(self.show_diffstat_action) self.menubar.addAction(self.diff_menu.menuAction()) # Branch Menu self.branch_menu = create_menu(N_("Branch"), self.menubar) self.branch_menu.addAction(self.branch_review_action) self.branch_menu.addSeparator() self.branch_menu.addAction(self.create_branch_action) self.branch_menu.addAction(self.checkout_branch_action) self.branch_menu.addAction(self.delete_branch_action) self.branch_menu.addAction(self.delete_remote_branch_action) self.branch_menu.addSeparator() self.branch_menu.addAction(self.browse_branch_action) self.branch_menu.addAction(self.browse_other_branch_action) self.branch_menu.addSeparator() self.branch_menu.addAction(self.visualize_current_action) self.branch_menu.addAction(self.visualize_all_action) self.menubar.addAction(self.branch_menu.menuAction()) # Rebase menu self.rebase_menu = create_menu(N_("Rebase"), self.actions_menu) self.rebase_menu.addAction(self.rebase_start_action) self.rebase_menu.addAction(self.rebase_edit_todo_action) self.rebase_menu.addSeparator() self.rebase_menu.addAction(self.rebase_continue_action) self.rebase_menu.addAction(self.rebase_skip_action) self.rebase_menu.addSeparator() self.rebase_menu.addAction(self.rebase_abort_action) self.menubar.addAction(self.rebase_menu.menuAction()) # View Menu self.view_menu = create_menu(N_("View"), self.menubar) self.view_menu.addAction(self.browse_action) self.view_menu.addAction(self.dag_action) self.view_menu.addSeparator() if self.browser_dockable: self.view_menu.addAction(self.browserdockwidget.toggleViewAction()) self.setup_dockwidget_view_menu() self.view_menu.addSeparator() self.view_menu.addAction(self.lock_layout_action) self.menubar.addAction(self.view_menu.menuAction()) # Help Menu self.help_menu = create_menu(N_("Help"), self.menubar) self.help_menu.addAction(self.help_docs_action) self.help_menu.addAction(self.help_shortcuts_action) self.help_menu.addAction(self.help_about_action) self.menubar.addAction(self.help_menu.menuAction()) # Set main menu self.setMenuBar(self.menubar) # Arrange dock widgets left = Qt.LeftDockWidgetArea right = Qt.RightDockWidgetArea bottom = Qt.BottomDockWidgetArea self.addDockWidget(left, self.commitdockwidget) if self.browser_dockable: self.addDockWidget(left, self.browserdockwidget) self.tabifyDockWidget(self.browserdockwidget, self.commitdockwidget) self.addDockWidget(left, self.diffdockwidget) self.addDockWidget(bottom, self.actionsdockwidget) self.addDockWidget(bottom, self.logdockwidget) self.tabifyDockWidget(self.actionsdockwidget, self.logdockwidget) self.addDockWidget(right, self.statusdockwidget) # Listen for model notifications model.add_observer(model.message_updated, self._update) model.add_observer(model.message_mode_changed, lambda x: self._update()) prefs_model.add_observer(prefs_model.message_config_updated, self._config_updated) # Set a default value self.show_cursor_position(1, 0) self.connect(self.open_recent_action, SIGNAL("aboutToShow()"), self.build_recent_menu) self.connect(self.commitmsgeditor, SIGNAL("cursorPosition(int,int)"), self.show_cursor_position) self.connect(self, SIGNAL("update"), self._update_callback) self.connect(self, SIGNAL("install_config_actions"), self._install_config_actions) # Install .git-config-defined actions self._config_task = None self.install_config_actions() # Restore saved settings if not qtutils.apply_state(self): self.set_initial_size() self.statusdockwidget.widget().setFocus() # Route command output here Interaction.log_status = self.logwidget.log_status Interaction.log = self.logwidget.log Interaction.log(version.git_version_str() + "\n" + N_("git cola version %s") % version.version()) def set_initial_size(self): self.statuswidget.set_initial_size() self.commitmsgeditor.set_initial_size() # Qt overrides def closeEvent(self, event): """Save state in the settings manager.""" commit_msg = self.commitmsgeditor.commit_message(raw=True) self.model.save_commitmsg(commit_msg) MainWindow.closeEvent(self, event) def build_recent_menu(self): recent = settings.Settings().recent menu = self.open_recent_action menu.clear() for r in recent: name = os.path.basename(r) directory = os.path.dirname(r) text = "%s %s %s" % (name, unichr(0x2192), directory) menu.addAction(text, cmds.run(cmds.OpenRepo, r)) # Accessors mode = property(lambda self: self.model.mode) def _config_updated(self, source, config, value): if config == prefs.FONTDIFF: # The diff font font = QtGui.QFont() if not font.fromString(value): return self.logwidget.setFont(font) self.diffeditor.setFont(font) self.commitmsgeditor.setFont(font) elif config == prefs.TABWIDTH: # variable-tab-width setting self.diffeditor.set_tabwidth(value) self.commitmsgeditor.set_tabwidth(value) elif config == prefs.LINEBREAK: # enables automatic line breaks self.commitmsgeditor.set_linebreak(value) elif config == prefs.TEXTWIDTH: # text width used for line wrapping self.commitmsgeditor.set_textwidth(value) def install_config_actions(self): """Install .gitconfig-defined actions""" self._config_task = self._start_config_actions_task() def _start_config_actions_task(self): """Do the expensive "get_config_actions()" call in the background""" class ConfigActionsTask(QtCore.QRunnable): def __init__(self, sender): QtCore.QRunnable.__init__(self) self._sender = sender def run(self): names = cfgactions.get_config_actions() self._sender.emit(SIGNAL("install_config_actions"), names) task = ConfigActionsTask(self) QtCore.QThreadPool.globalInstance().start(task) return task def _install_config_actions(self, names): """Install .gitconfig-defined actions""" if not names: return menu = self.actions_menu menu.addSeparator() for name in names: menu.addAction(name, cmds.run(cmds.RunConfigAction, name)) def _update(self): self.emit(SIGNAL("update")) def _update_callback(self): """Update the title with the current branch and directory name.""" alerts = [] branch = self.model.currentbranch curdir = core.getcwd() is_merging = self.model.is_merging is_rebasing = self.model.is_rebasing msg = N_("Repository: %s") % curdir msg += "\n" msg += N_("Branch: %s") % branch if is_rebasing: msg += "\n\n" msg += N_( "This repository is currently being rebased.\n" "Resolve conflicts, commit changes, and run:\n" " Rebase > Continue" ) alerts.append(N_("Rebasing")) elif is_merging: msg += "\n\n" msg += N_("This repository is in the middle of a merge.\n" "Resolve conflicts and commit changes.") alerts.append(N_("Merging")) if self.mode == self.model.mode_amend: alerts.append(N_("Amending")) l = unichr(0xAB) r = unichr(0xBB) title = "%s: %s %s%s" % ( self.model.project, branch, alerts and ((r + " %s " + l + " ") % ", ".join(alerts)) or "", self.model.git.worktree(), ) self.setWindowTitle(title) self.commitdockwidget.setToolTip(msg) self.commitmsgeditor.set_mode(self.mode) self.update_actions() if not self.model.amending(): # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(merge_msg_path) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cmds.do(cmds.LoadCommitMessageFromFile, merge_msg_path) def update_actions(self): is_rebasing = self.model.is_rebasing can_rebase = not is_rebasing self.rebase_start_action.setEnabled(can_rebase) self.rebase_edit_todo_action.setEnabled(is_rebasing) self.rebase_continue_action.setEnabled(is_rebasing) self.rebase_skip_action.setEnabled(is_rebasing) self.rebase_abort_action.setEnabled(is_rebasing) def apply_state(self, state): """Imports data for save/restore""" result = MainWindow.apply_state(self, state) self.lock_layout_action.setChecked(state.get("lock_layout", False)) return result def setup_dockwidget_view_menu(self): # Hotkeys for toggling the dock widgets if utils.is_darwin(): optkey = "Meta" else: optkey = "Ctrl" dockwidgets = ( (optkey + "+0", self.logdockwidget), (optkey + "+1", self.commitdockwidget), (optkey + "+2", self.statusdockwidget), (optkey + "+3", self.diffdockwidget), (optkey + "+4", self.actionsdockwidget), ) for shortcut, dockwidget in dockwidgets: # Associate the action with the shortcut toggleview = dockwidget.toggleViewAction() toggleview.setShortcut("Shift+" + shortcut) self.view_menu.addAction(toggleview) def showdock(show, dockwidget=dockwidget): if show: dockwidget.raise_() dockwidget.widget().setFocus() else: self.setFocus() self.addAction(toggleview) connect_action_bool(toggleview, showdock) # Create a new shortcut Shift+<shortcut> that gives focus toggleview = QtGui.QAction(self) toggleview.setShortcut(shortcut) def focusdock(dockwidget=dockwidget, showdock=showdock): if dockwidget.toggleViewAction().isChecked(): showdock(True) else: dockwidget.toggleViewAction().trigger() self.addAction(toggleview) connect_action(toggleview, focusdock) def _update_diff_opts(self): space_at_eol = self.diff_ignore_space_at_eol_action.isChecked() space_change = self.diff_ignore_space_change_action.isChecked() all_space = self.diff_ignore_all_space_action.isChecked() function_context = self.diff_function_context_action.isChecked() gitcmds.update_diff_overrides(space_at_eol, space_change, all_space, function_context) self.statuswidget.refresh() def preferences(self): return preferences(model=self.prefs_model, parent=self) def save_archive(self): ref = git.rev_parse("HEAD")[STDOUT] shortref = ref[:7] GitArchiveDialog.save(ref, shortref, self) def show_cursor_position(self, rows, cols): display = " %02d:%02d " % (rows, cols) if cols > 78: display = '<span style="color: white; ' ' background-color: red;"' ">%s</span>" % display elif cols > 72: display = '<span style="color: black; ' ' background-color: orange;"' ">%s</span>" % display elif cols > 64: display = '<span style="color: black; ' ' background-color: yellow;"' ">%s</span>" % display else: display = '<span style="color: grey;">%s</span>' % display self.position_label.setText(display) def rebase_start(self): branch = guicmds.choose_ref(N_("Select New Upstream"), N_("Interactive Rebase")) if not branch: return None self.model.is_rebasing = True self._update_callback() cmds.do(cmds.Rebase, branch) def rebase_edit_todo(self): cmds.do(cmds.RebaseEditTodo) def rebase_continue(self): cmds.do(cmds.RebaseContinue) def rebase_skip(self): cmds.do(cmds.RebaseSkip) def rebase_abort(self): cmds.do(cmds.RebaseAbort)
class MainView(MainWindow): def __init__(self, model, parent): MainWindow.__init__(self, parent) # Default size; this is thrown out when save/restore is used self.resize(987, 610) self.model = model self.prefs_model = prefs_model = prefs.PreferencesModel() # Internal field used by import/export_state(). # Change this whenever dockwidgets are removed. self.widget_version = 2 # Keeps track of merge messages we've seen self.merge_message_hash = '' self.setAcceptDrops(True) self.setAttribute(Qt.WA_MacMetalStyle) cfg = gitcfg.instance() self.classic_dockable = (cfg.get('cola.browserdockable') or cfg.get('cola.classicdockable')) if self.classic_dockable: self.classicdockwidget = create_dock(N_('Browser'), self) self.classicwidget = classic_widget(self) self.classicdockwidget.setWidget(self.classicwidget) # "Actions" widget self.actionsdockwidget = create_dock(N_('Action'), self) self.actionsdockwidgetcontents = action.ActionButtons(self) self.actionsdockwidget.setWidget(self.actionsdockwidgetcontents) self.actionsdockwidget.toggleViewAction().setChecked(False) self.actionsdockwidget.hide() # "Repository Status" widget self.statuswidget = StatusWidget(self) self.statusdockwidget = create_dock(N_('Status'), self) self.statusdockwidget.setWidget(self.statuswidget) # "Commit Message Editor" widget self.position_label = QtGui.QLabel() font = qtutils.default_monospace_font() font.setPointSize(int(font.pointSize() * 0.8)) self.position_label.setFont(font) self.commitdockwidget = create_dock(N_('Commit'), self) titlebar = self.commitdockwidget.titleBarWidget() titlebar.add_corner_widget(self.position_label) self.commitmsgeditor = CommitMessageEditor(model, self) self.commitdockwidget.setWidget(self.commitmsgeditor) # "Console" widget self.logwidget = LogWidget() self.logdockwidget = create_dock(N_('Console'), self) self.logdockwidget.setWidget(self.logwidget) self.logdockwidget.toggleViewAction().setChecked(False) self.logdockwidget.hide() # "Diff Viewer" widget self.diffdockwidget = create_dock(N_('Diff'), self) self.diffeditor = DiffEditor(self.diffdockwidget) self.diffdockwidget.setWidget(self.diffeditor) # "Diff Options" tool menu self.diff_ignore_space_at_eol_action = add_action(self, N_('Ignore changes in whitespace at EOL'), self._update_diff_opts) self.diff_ignore_space_at_eol_action.setCheckable(True) self.diff_ignore_space_change_action = add_action(self, N_('Ignore changes in amount of whitespace'), self._update_diff_opts) self.diff_ignore_space_change_action.setCheckable(True) self.diff_ignore_all_space_action = add_action(self, N_('Ignore all whitespace'), self._update_diff_opts) self.diff_ignore_all_space_action.setCheckable(True) self.diff_function_context_action = add_action(self, N_('Show whole surrounding functions of changes'), self._update_diff_opts) self.diff_function_context_action.setCheckable(True) self.diffopts_button = create_toolbutton(text=N_('Options'), icon=options_icon(), tooltip=N_('Diff Options')) self.diffopts_menu = create_menu(N_('Diff Options'), self.diffopts_button) self.diffopts_menu.addAction(self.diff_ignore_space_at_eol_action) self.diffopts_menu.addAction(self.diff_ignore_space_change_action) self.diffopts_menu.addAction(self.diff_ignore_all_space_action) self.diffopts_menu.addAction(self.diff_function_context_action) self.diffopts_button.setMenu(self.diffopts_menu) self.diffopts_button.setPopupMode(QtGui.QToolButton.InstantPopup) titlebar = self.diffdockwidget.titleBarWidget() titlebar.add_corner_widget(self.diffopts_button) # All Actions self.menu_unstage_all = add_action(self, N_('Unstage All'), cmds.run(cmds.UnstageAll)) self.menu_unstage_all.setIcon(qtutils.icon('remove.svg')) self.menu_unstage_selected = add_action(self, N_('Unstage From Commit'), cmds.run(cmds.UnstageSelected)) self.menu_unstage_selected.setIcon(qtutils.icon('remove.svg')) self.menu_show_diffstat = add_action(self, N_('Diffstat'), cmds.run(cmds.Diffstat), 'Alt+D') self.menu_stage_modified = add_action(self, N_('Stage Changed Files To Commit'), cmds.run(cmds.StageModified), 'Alt+A') self.menu_stage_modified.setIcon(qtutils.icon('add.svg')) self.menu_stage_untracked = add_action(self, N_('Stage All Untracked'), cmds.run(cmds.StageUntracked), 'Alt+U') self.menu_stage_untracked.setIcon(qtutils.icon('add.svg')) self.menu_export_patches = add_action(self, N_('Export Patches...'), guicmds.export_patches, 'Alt+E') self.menu_preferences = add_action(self, N_('Preferences'), self.preferences, QtGui.QKeySequence.Preferences, 'Ctrl+O') self.menu_edit_remotes = add_action(self, N_('Edit Remotes...'), lambda: editremotes.edit().exec_()) self.menu_rescan = add_action(self, cmds.Refresh.name(), cmds.run(cmds.Refresh), cmds.Refresh.SHORTCUT) self.menu_rescan.setIcon(qtutils.reload_icon()) self.menu_browse_recent = add_action(self, N_('Recently Modified Files...'), browse_recent, 'Shift+Ctrl+E') self.menu_cherry_pick = add_action(self, N_('Cherry-Pick...'), guicmds.cherry_pick, 'Ctrl+P') self.menu_load_commitmsg = add_action(self, N_('Load Commit Message...'), guicmds.load_commitmsg) self.menu_save_tarball = add_action(self, N_('Save As Tarball/Zip...'), self.save_archive) self.menu_quit = add_action(self, N_('Quit'), self.close, 'Ctrl+Q') self.menu_manage_bookmarks = add_action(self, N_('Bookmarks...'), manage_bookmarks) self.menu_grep = add_action(self, N_('Grep'), guicmds.grep, 'Ctrl+G') self.menu_merge_local = add_action(self, N_('Merge...'), merge.local_merge) self.menu_merge_abort = add_action(self, N_('Abort Merge...'), merge.abort_merge) self.menu_fetch = add_action(self, N_('Fetch...'), remote.fetch) self.menu_push = add_action(self, N_('Push...'), remote.push) self.menu_pull = add_action(self, N_('Pull...'), remote.pull) self.menu_open_repo = add_action(self, N_('Open...'), guicmds.open_repo) self.menu_open_repo.setIcon(qtutils.open_icon()) self.menu_stash = add_action(self, N_('Stash...'), stash.stash, 'Alt+Shift+S') self.menu_clone_repo = add_action(self, N_('Clone...'), guicmds.clone_repo) self.menu_clone_repo.setIcon(qtutils.git_icon()) self.menu_help_docs = add_action(self, N_('Documentation'), resources.show_html_docs, QtGui.QKeySequence.HelpContents) self.menu_help_shortcuts = add_action(self, N_('Keyboard Shortcuts'), show_shortcuts, QtCore.Qt.Key_Question) self.menu_visualize_current = add_action(self, N_('Visualize Current Branch...'), cmds.run(cmds.VisualizeCurrent)) self.menu_visualize_all = add_action(self, N_('Visualize All Branches...'), cmds.run(cmds.VisualizeAll)) self.menu_search_commits = add_action(self, N_('Search...'), search) self.menu_browse_branch = add_action(self, N_('Browse Current Branch...'), guicmds.browse_current) self.menu_browse_other_branch = add_action(self, N_('Browse Other Branch...'), guicmds.browse_other) self.menu_load_commitmsg_template = add_action(self, N_('Get Commit Message Template'), cmds.run(cmds.LoadCommitTemplate)) self.menu_help_about = add_action(self, N_('About'), launch_about_dialog) self.menu_diff_expression = add_action(self, N_('Expression...'), guicmds.diff_expression) self.menu_branch_compare = add_action(self, N_('Branches...'), compare_branches) self.menu_create_tag = add_action(self, N_('Create Tag...'), create_tag) self.menu_create_branch = add_action(self, N_('Create...'), create_new_branch, 'Ctrl+B') self.menu_delete_branch = add_action(self, N_('Delete...'), guicmds.delete_branch) self.menu_delete_remote_branch = add_action(self, N_('Delete Remote Branch...'), guicmds.delete_remote_branch) self.menu_checkout_branch = add_action(self, N_('Checkout...'), guicmds.checkout_branch, 'Alt+B') self.menu_rebase_branch = add_action(self, N_('Rebase...'), guicmds.rebase) self.menu_branch_review = add_action(self, N_('Review...'), guicmds.review_branch) self.menu_classic = add_action(self, N_('Browser...'), cola_classic) self.menu_classic.setIcon(qtutils.git_icon()) self.menu_dag = add_action(self, N_('DAG...'), lambda: git_dag(self.model).show()) self.menu_dag.setIcon(qtutils.git_icon()) # Relayed actions if not self.classic_dockable: # These shortcuts conflict with those from the # 'Browser' widget so don't register them when # the browser is a dockable tool. status_tree = self.statusdockwidget.widget().tree self.addAction(status_tree.up) self.addAction(status_tree.down) self.addAction(status_tree.process_selection) # Create the application menu self.menubar = QtGui.QMenuBar(self) # File Menu self.file_menu = create_menu(N_('File'), self.menubar) self.file_menu.addAction(self.menu_preferences) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_open_repo) self.menu_open_recent = self.file_menu.addMenu(N_('Open Recent')) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_clone_repo) self.file_menu.addAction(self.menu_manage_bookmarks) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_edit_remotes) self.file_menu.addAction(self.menu_rescan) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_browse_recent) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_load_commitmsg) self.file_menu.addAction(self.menu_load_commitmsg_template) self.file_menu.addSeparator() self.file_menu.addAction(self.menu_save_tarball) self.file_menu.addAction(self.menu_export_patches) self.file_menu.addAction(self.menu_quit) # Add to menubar self.menubar.addAction(self.file_menu.menuAction()) # Commit Menu self.commit_menu = create_menu(N_('Index'), self.menubar) self.commit_menu.setTitle(N_('Index')) self.commit_menu.addAction(self.menu_stage_modified) self.commit_menu.addAction(self.menu_stage_untracked) self.commit_menu.addSeparator() self.commit_menu.addAction(self.menu_unstage_all) self.commit_menu.addAction(self.menu_unstage_selected) # Add to menubar self.menubar.addAction(self.commit_menu.menuAction()) # Branch Menu self.branch_menu = create_menu(N_('Branch'), self.menubar) self.branch_menu.addAction(self.menu_branch_review) self.branch_menu.addSeparator() self.branch_menu.addAction(self.menu_create_branch) self.branch_menu.addAction(self.menu_checkout_branch) self.branch_menu.addAction(self.menu_rebase_branch) self.branch_menu.addAction(self.menu_delete_branch) self.branch_menu.addAction(self.menu_delete_remote_branch) self.branch_menu.addSeparator() self.branch_menu.addAction(self.menu_browse_branch) self.branch_menu.addAction(self.menu_browse_other_branch) self.branch_menu.addSeparator() self.branch_menu.addAction(self.menu_visualize_current) self.branch_menu.addAction(self.menu_visualize_all) # Add to menubar self.menubar.addAction(self.branch_menu.menuAction()) # Actions menu self.actions_menu = create_menu(N_('Actions'), self.menubar) self.actions_menu.addAction(self.menu_fetch) self.actions_menu.addAction(self.menu_push) self.actions_menu.addAction(self.menu_pull) self.actions_menu.addAction(self.menu_stash) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_create_tag) self.actions_menu.addAction(self.menu_cherry_pick) self.actions_menu.addAction(self.menu_merge_local) self.actions_menu.addAction(self.menu_merge_abort) self.actions_menu.addSeparator() self.actions_menu.addAction(self.menu_grep) self.actions_menu.addAction(self.menu_search_commits) # Add to menubar self.menubar.addAction(self.actions_menu.menuAction()) # Diff Menu self.diff_menu = create_menu(N_('Diff'), self.menubar) self.diff_menu.addAction(self.menu_diff_expression) self.diff_menu.addAction(self.menu_branch_compare) self.diff_menu.addSeparator() self.diff_menu.addAction(self.menu_show_diffstat) # Add to menubar self.menubar.addAction(self.diff_menu.menuAction()) # Tools Menu self.tools_menu = create_menu(N_('Tools'), self.menubar) self.tools_menu.addAction(self.menu_classic) self.tools_menu.addAction(self.menu_dag) self.tools_menu.addSeparator() if self.classic_dockable: self.tools_menu.addAction(self.classicdockwidget.toggleViewAction()) self.setup_dockwidget_tools_menu() self.menubar.addAction(self.tools_menu.menuAction()) # Help Menu self.help_menu = create_menu(N_('Help'), self.menubar) self.help_menu.addAction(self.menu_help_docs) self.help_menu.addAction(self.menu_help_shortcuts) self.help_menu.addAction(self.menu_help_about) # Add to menubar self.menubar.addAction(self.help_menu.menuAction()) # Set main menu self.setMenuBar(self.menubar) # Arrange dock widgets left = Qt.LeftDockWidgetArea right = Qt.RightDockWidgetArea bottom = Qt.BottomDockWidgetArea self.addDockWidget(left, self.commitdockwidget) if self.classic_dockable: self.addDockWidget(left, self.classicdockwidget) self.tabifyDockWidget(self.classicdockwidget, self.commitdockwidget) self.addDockWidget(left, self.diffdockwidget) self.addDockWidget(bottom, self.actionsdockwidget) self.addDockWidget(bottom, self.logdockwidget) self.tabifyDockWidget(self.actionsdockwidget, self.logdockwidget) self.addDockWidget(right, self.statusdockwidget) # Listen for model notifications model.add_observer(model.message_updated, self._update_view) prefs_model.add_observer(prefs_model.message_config_updated, self._config_updated) # Set a default value self.show_cursor_position(1, 0) self.connect(self.menu_open_recent, SIGNAL('aboutToShow()'), self.build_recent_menu) self.connect(self.commitmsgeditor, SIGNAL('cursorPosition(int,int)'), self.show_cursor_position) self.connect(self, SIGNAL('update'), self._update_callback) self.connect(self, SIGNAL('install_config_actions'), self._install_config_actions) # Install .git-config-defined actions self._config_task = None self.install_config_actions() # Restore saved settings if not qtutils.apply_state(self): self.set_initial_size() self.statusdockwidget.widget().setFocus() # Route command output here Interaction.log_status = self.logwidget.log_status Interaction.log = self.logwidget.log Interaction.log(version.git_version_str() + '\n' + N_('git cola version %s') % version.version()) def set_initial_size(self): self.statuswidget.set_initial_size() self.commitmsgeditor.set_initial_size() # Qt overrides def closeEvent(self, event): """Save state in the settings manager.""" commit_msg = self.commitmsgeditor.commit_message(raw=True) self.model.save_commitmsg(commit_msg) MainWindow.closeEvent(self, event) def build_recent_menu(self): recent = settings.Settings().recent menu = self.menu_open_recent menu.clear() for r in recent: name = os.path.basename(r) directory = os.path.dirname(r) text = '%s %s %s' % (name, unichr(0x2192), directory) menu.addAction(text, cmds.run(cmds.OpenRepo, r)) # Accessors mode = property(lambda self: self.model.mode) def _config_updated(self, source, config, value): if config == 'cola.fontdiff': # The diff font font = QtGui.QFont() if not font.fromString(value): return self.logwidget.setFont(font) self.diffeditor.setFont(font) self.commitmsgeditor.setFont(font) elif config == 'cola.tabwidth': # variable-tab-width setting self.diffeditor.set_tabwidth(value) self.commitmsgeditor.set_tabwidth(value) elif config == 'cola.linebreak': # enables automatic line breaks self.commitmsgeditor.set_linebreak(value) elif config == 'cola.textwidth': # text width used for line wrapping self.commitmsgeditor.set_textwidth(value) def install_config_actions(self): """Install .gitconfig-defined actions""" self._config_task = self._start_config_actions_task() def _start_config_actions_task(self): """Do the expensive "get_config_actions()" call in the background""" class ConfigActionsTask(QtCore.QRunnable): def __init__(self, sender): QtCore.QRunnable.__init__(self) self._sender = sender def run(self): names = cfgactions.get_config_actions() self._sender.emit(SIGNAL('install_config_actions'), names) task = ConfigActionsTask(self) QtCore.QThreadPool.globalInstance().start(task) return task def _install_config_actions(self, names): """Install .gitconfig-defined actions""" if not names: return menu = self.actions_menu menu.addSeparator() for name in names: menu.addAction(name, cmds.run(cmds.RunConfigAction, name)) def _update_view(self): self.emit(SIGNAL('update')) def _update_callback(self): """Update the title with the current branch and directory name.""" branch = self.model.currentbranch curdir = core.decode(os.getcwd()) msg = N_('Repository: %s') % curdir msg += '\n' msg += N_('Branch: %s') % branch self.commitdockwidget.setToolTip(msg) title = '%s: %s (%s)' % (self.model.project, branch, self.model.git.worktree()) if self.mode == self.model.mode_amend: title += ' (%s)' % N_('Amending') self.setWindowTitle(title) self.commitmsgeditor.set_mode(self.mode) if not self.model.amending(): # Check if there's a message file in .git/ merge_msg_path = gitcmds.merge_message_path() if merge_msg_path is None: return merge_msg_hash = utils.checksum(core.decode(merge_msg_path)) if merge_msg_hash == self.merge_message_hash: return self.merge_message_hash = merge_msg_hash cmds.do(cmds.LoadCommitMessage, core.decode(merge_msg_path)) def apply_state(self, state): """Imports data for save/restore""" # 1 is the widget version; change when widgets are added/removed result = MainWindow.apply_state(self, state) for widget in self.dockwidgets: widget.titleBarWidget().update_tooltips() return result def setup_dockwidget_tools_menu(self): # Hotkeys for toggling the dock widgets if utils.is_darwin(): optkey = 'Meta' else: optkey = 'Ctrl' dockwidgets = ( (optkey + '+0', self.logdockwidget), (optkey + '+1', self.commitdockwidget), (optkey + '+2', self.statusdockwidget), (optkey + '+3', self.diffdockwidget), (optkey + '+4', self.actionsdockwidget), ) for shortcut, dockwidget in dockwidgets: # Associate the action with the shortcut toggleview = dockwidget.toggleViewAction() toggleview.setShortcut(shortcut) self.tools_menu.addAction(toggleview) def showdock(show, dockwidget=dockwidget): if show: dockwidget.raise_() dockwidget.widget().setFocus() else: self.setFocus() self.addAction(toggleview) connect_action_bool(toggleview, showdock) # Create a new shortcut Shift+<shortcut> that gives focus toggleview = QtGui.QAction(self) toggleview.setShortcut('Shift+' + shortcut) def focusdock(dockwidget=dockwidget, showdock=showdock): if dockwidget.toggleViewAction().isChecked(): showdock(True) else: dockwidget.toggleViewAction().trigger() self.addAction(toggleview) connect_action(toggleview, focusdock) def _update_diff_opts(self): space_at_eol = self.diff_ignore_space_at_eol_action.isChecked() space_change = self.diff_ignore_space_change_action.isChecked() all_space = self.diff_ignore_all_space_action.isChecked() function_context = self.diff_function_context_action.isChecked() gitcmds.update_diff_overrides(space_at_eol, space_change, all_space, function_context) self.statuswidget.refresh() def preferences(self): return prefs.preferences(model=self.prefs_model, parent=self) def save_archive(self): ref = git.rev_parse('HEAD') shortref = ref[:7] GitArchiveDialog.save(ref, shortref, self) def dragEnterEvent(self, event): """Accepts drops""" MainWindow.dragEnterEvent(self, event) event.acceptProposedAction() def dropEvent(self, event): """Apply dropped patches with git-am""" event.accept() urls = event.mimeData().urls() if not urls: return paths = map(lambda x: unicode(x.path()), urls) patches = [p for p in paths if p.endswith('.patch')] dirs = [p for p in paths if os.path.isdir(p)] dirs.sort() for d in dirs: patches.extend(self._gather_patches(d)) cmds.do(cmds.ApplyPatches, patches) def _gather_patches(self, path): """Find patches in a subdirectory""" patches = [] for root, subdirs, files in os.walk(path): for name in [f for f in files if f.endswith('.patch')]: patches.append(os.path.join(root, name)) return patches def show_cursor_position(self, rows, cols): display = ' %02d:%02d ' % (rows, cols) if cols > 78: display = ('<span style="color: white; ' ' background-color: red;"' '>%s</span>' % display) elif cols > 72: display = ('<span style="color: black; ' ' background-color: orange;"' '>%s</span>' % display) elif cols > 64: display = ('<span style="color: black; ' ' background-color: yellow;"' '>%s</span>' % display) else: display = ('<span style="color: grey;">%s</span>' % display) self.position_label.setText(display)