From 2e5c32841cb8a450f1dfe3b60c718228f289ff51 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Wed, 12 Nov 2008 02:47:53 -0800 Subject: [PATCH] cola: completely rework the cola user interface git-cola now provides a repository status view that closely matches the output of 'git status'. The staged and unstaged lists were replaced by a single tree view. Likewise, rest of the UI was simplified to account for this -- e.g. there are no more dock widgets in use. New icons were added for the Staged, Modified, and Unmerged categories. Signed-off-by: David Aguilar --- cola/controllers/__init__.py | 367 ++++++++++------ cola/main.py | 2 +- cola/models.py | 14 +- cola/qtutils.py | 38 +- cola/views/main.py | 176 +++++++- share/cola/icons/modified.png | Bin 0 -> 229 bytes share/cola/icons/plus.png | Bin 0 -> 229 bytes share/cola/icons/unmerged.png | Bin 0 -> 242 bytes ui/main.ui | 990 ++++++++++++++++-------------------------- 9 files changed, 812 insertions(+), 775 deletions(-) create mode 100644 share/cola/icons/modified.png create mode 100644 share/cola/icons/plus.png create mode 100644 share/cola/icons/unmerged.png diff --git a/cola/controllers/__init__.py b/cola/controllers/__init__.py index 9e8653fd..0a8de297 100644 --- a/cola/controllers/__init__.py +++ b/cola/controllers/__init__.py @@ -71,24 +71,17 @@ class Controller(QObserver): # Parent-less log window qtutils.LOGGER = logger() - # double-click callbacks - self.unstaged_doubleclick = self.stage_selected - self.staged_doubleclick = self.unstage_selected - # Unstaged changes context menu - view.unstaged.contextMenuEvent = self.unstaged_context_menu_event - view.staged.contextMenuEvent = self.staged_context_menu_event + view.status_tree.contextMenuEvent = self.tree_context_menu_event # Diff display context menu view.display_text.contextMenuEvent = self.diff_context_menu_event # Binds model params to their equivalent view widget - self.add_observables('commitmsg', 'staged', 'unstaged', - 'show_untracked') + self.add_observables('commitmsg')#, 'staged', 'unstaged', + #'show_untracked') # When a model attribute changes, this runs a specific action - self.add_actions(staged = self.action_staged) - self.add_actions(unstaged = self.action_unstaged) self.add_actions(global_cola_fontdiff = self.update_diff_font) self.add_actions(global_cola_fontui = self.update_ui_font) @@ -100,11 +93,7 @@ class Controller(QObserver): fetch_button = self.fetch, push_button = self.push, pull_button = self.pull, - # List Widgets - staged = self.diff_staged, - unstaged = self.diff_unstaged, # Checkboxes - show_untracked = self.rescan, amend_radio = self.load_prev_msg_and_rescan, new_commit_radio = self.clear_and_rescan, @@ -196,8 +185,11 @@ class Controller(QObserver): view.moveEvent = self.move_event view.resizeEvent = self.resize_event view.closeEvent = self.quit_app - view.staged.mousePressEvent = self.click_staged - view.unstaged.mousePressEvent = self.click_unstaged + + view.status_tree.mousePressEvent = self.click_tree + self.connect(view.status_tree, + 'itemDoubleClicked(QTreeWidgetItem*, int)', + self.doubleclick_tree) # Toolbar log button self.connect(view.toolbar_show_log, @@ -211,21 +203,117 @@ class Controller(QObserver): self.start_inotify_thread() ##################################################################### - # handle when the listitem icons are clicked - def click_event(self, widget, action_callback, event): - result = QtGui.QListWidget.mousePressEvent(widget, event) - xpos = event.pos().x() - if xpos > 5 and xpos < 20: - action_callback() - return result + # handle when the status tree is clicked + def get_staged_item(self): + staged = self.model.get_staged() + staged = self.view.get_staged(staged) + if staged: + return staged[0] + else: + return None + + def get_unstaged_item(self): + unstaged = self.model.get_unstaged() + unstaged = self.view.get_unstaged(unstaged) + if unstaged: + return unstaged[0] + else: + return None + + def get_selection(self): + staged = self.model.get_staged() + staged = self.view.get_staged(staged) + modified = self.model.get_modified() + modified = self.view.get_modified(modified) + unmerged = self.model.get_unmerged() + unmerged = self.view.get_unmerged(unmerged) + untracked = self.model.get_untracked() + untracked = self.view.get_untracked(untracked) + return (staged, modified, unmerged, untracked) - def click_staged(self, event): - return self.click_event(self.view.staged, - self.unstage_selected, event) + def get_single_selection(self): + """Scans across staged, modified, etc. and returns only a single item. + """ + staged, modified, unmerged, untracked = self.get_selection() + s = None + m = None + um = None + ut = None + if staged: + s = staged[0] + elif modified: + m = modified[0] + elif unmerged: + um = unmerged[0] + elif untracked: + ut = untracked[0] + return s, m, um, ut + + def doubleclick_tree(self, item, column): + staged, modified, unmerged, untracked = self.get_selection() + if staged: + self.model.reset_helper(staged) + elif modified: + self.model.add_or_remove(modified) + elif untracked: + self.model.add_or_remove(untracked) + self.rescan() - def click_unstaged(self, event): - return self.click_event(self.view.unstaged, - self.stage_selected, event) + def click_tree(self, event): + tree = self.view.status_tree + result = QtGui.QTreeWidget.mousePressEvent(tree, event) + item = tree.itemAt(event.pos()) + if not item: + self.reset_mode() + self.view.reset_display() + items = self.view.status_tree.selectedItems() + for i in items: + i.setSelected(False) + return result + parent = item.parent() + if not parent: + idx = self.view.status_tree.indexOfTopLevelItem(item) + diff = 'no diff' + if idx == self.view.IDX_STAGED: + diff = (self.model.git.diff(cached=True, stat=True, + with_raw_output=True) + '\n\n' + + self.model.git.diff(cached=True)) + elif idx == self.view.IDX_MODIFIED: + diff = (self.model.git.diff(stat=True, + with_raw_output=True) + '\n\n' + + self.model.git.diff()) + elif idx == self.view.IDX_UNMERGED: + diff = '%s unmerged file(s)' % len(self.model.get_unmerged()) + elif idx == self.view.IDX_UNTRACKED: + untracked = self.model.get_untracked() + diff = '%s untracked file(s)' % len(untracked) + if untracked: + diff += '\n\n' + diff += 'possible .gitignore rule(s):\n' + diff += '-' * 78 + diff += '\n' + for u in untracked: + diff += '/'+ u + '\n' + diff + '\n' + self.view.set_display(diff) + return result + staged, idx = self.view.get_index_for_item(item) + if idx == -1: + return result + self.view_diff_for_row(idx, staged) + # handle when the icons are clicked + xpos = event.pos().x() + if xpos > 42 and xpos < 58: + if staged: + items = self.model.get_staged() + selected = self.view.get_staged(items) + self.model.reset_helper(selected) + else: + items = self.model.get_unstaged() + selected = self.view.get_unstaged(items) + self.model.add_or_remove(selected) + self.rescan() + return result ##################################################################### # event() is called in response to messages from the inotify thread @@ -377,27 +465,31 @@ class Controller(QObserver): def get_selected_filename(self, staged=False): if staged: - return self.model.get_staged_item() + return self.get_staged_item() else: - return self.model.get_unstaged_item() + return self.get_unstaged_item() - def view_diff(self, staged=True): + def set_mode(self, staged): if staged: if self.view.amend_is_checked(): self.mode = Controller.MODE_AMEND else: self.mode = Controller.MODE_INDEX - widget = self.view.staged else: self.mode = Controller.MODE_WORKTREE - widget = self.view.unstaged - row, selected = qtutils.get_selected_row(widget) + + def view_diff(self, staged=True): + idx, selected = self.view.get_selection() if not selected: self.reset_mode() self.view.reset_display() return + self.view_diff_for_row(idx, staged) + + def view_diff_for_row(self, idx, staged): + self.set_mode(staged) ref = self.get_diff_ref() - diff, status, filename = self.model.get_diff_details(row, ref, + diff, status, filename = self.model.get_diff_details(idx, ref, staged=staged) self.view.set_display(diff) self.view.set_info(self.tr(status)) @@ -538,10 +630,13 @@ class Controller(QObserver): """Populates view widgets with results from 'git status.'""" # save entire selection - unstaged = qtutils.get_selection_list(self.view.unstaged, - self.model.get_unstaged()) - staged = qtutils.get_selection_list(self.view.staged, - self.model.get_staged()) + staged = self.view.get_staged(self.model.get_staged()) + modified = self.view.get_modified(self.model.get_modified()) + unmerged = self.view.get_unmerged(self.model.get_unmerged()) + untracked = self.view.get_untracked(self.model.get_untracked()) + + # unstaged is an aggregate + unstaged = modified + unmerged + untracked scrollbar = self.view.display_text.verticalScrollBar() scrollvalue = scrollbar.value() @@ -550,33 +645,57 @@ class Controller(QObserver): # get new values self.model.update_status(amend=self.view.amend_is_checked()) + # Setup initial tree items + self.view.set_staged(self.model.get_staged()) + self.view.set_modified(self.model.get_modified()) + self.view.set_unmerged(self.model.get_unmerged()) + self.view.set_untracked(self.model.get_untracked()) + # restore selection - updated_unstaged = self.model.get_unstaged() updated_staged = self.model.get_staged() - + updated_modified = self.model.get_modified() + updated_unmerged = self.model.get_unmerged() + updated_untracked = self.model.get_untracked() + # unstaged is an aggregate + updated_unstaged = (updated_modified + + updated_unmerged + + updated_untracked) + showdiff = False if mode == Controller.MODE_WORKTREE: - for item in unstaged: - if item in updated_unstaged: - idx = updated_unstaged.index(item) - item = self.view.unstaged.item(idx) - if item: - item.setSelected(True) - self.view.unstaged.setItemSelected(item, True) - self.view.unstaged.setCurrentItem(item) - self.view_diff(False) - scrollbar.setValue(scrollvalue) + if unstaged: + for item in unstaged: + if item in updated_unstaged: + idx = updated_unstaged.index(item) + item = self.view.get_unstaged_item(idx) + if item: + showdiff = True + item.setSelected(True) + self.view.status_tree.setCurrentItem(item) + self.view.status_tree.setItemSelected(item, True) + scrollbar.setValue(scrollvalue) + if showdiff: + self.view_diff(False) + else: + self.reset_mode() + self.view.reset_display() elif mode in (Controller.MODE_INDEX, Controller.MODE_AMEND): - for item in staged: - if item in updated_staged: - idx = updated_staged.index(item) - item = self.view.staged.item(idx) - if item: - item.setSelected(True) - self.view.staged.setItemSelected(item, True) - self.view.staged.setCurrentItem(item) - self.view_diff(True) - scrollbar.setValue(scrollvalue) + if staged: + for item in staged: + if item in updated_staged: + idx = updated_staged.index(item) + item = self.view.get_staged_item(idx) + if item: + showdiff = True + item.setSelected(True) + self.view.status_tree.setCurrentItem(item) + self.view.status_tree.setItemSelected(item, True) + scrollbar.setValue(scrollvalue) + if showdiff: + self.view_diff(True) + else: + self.reset_mode() + self.view.reset_display() # Update the title with the current branch self.view.setWindowTitle('%s [%s]' % ( @@ -651,9 +770,8 @@ class Controller(QObserver): self.branch = branch self.filename = filename - def process_diff_selection(self, items, widget, - cached=True, selected=False, - apply_to_worktree=False, + def process_diff_selection(self, selected=False, + staged=True, apply_to_worktree=False, reverse=False): if self.mode == Controller.MODE_BRANCH: @@ -668,12 +786,12 @@ class Controller(QObserver): apply_to_worktree=True) self.rescan() else: - filename = qtutils.get_selected_item(widget, items) + filename = self.get_selected_filename(staged) if not filename: return parser = utils.DiffParser(self.model, filename=filename, - cached=cached, + cached=staged, reverse=apply_to_worktree) offset, selection = self.view.diff_selection() parser.process_diff_selection(selected, offset, selection, @@ -688,10 +806,7 @@ class Controller(QObserver): 'Continue?', default=False): return - self.process_diff_selection(self.model.get_unstaged(), - self.view.unstaged, - apply_to_worktree=True, - cached=False, + self.process_diff_selection(staged=False, apply_to_worktree=True, reverse=True) def undo_selection(self): @@ -702,34 +817,20 @@ class Controller(QObserver): 'Continue?', default=False): return - self.process_diff_selection(self.model.get_unstaged(), - self.view.unstaged, - apply_to_worktree=True, - cached=False, - reverse=True, - selected=True) + self.process_diff_selection(staged=False, apply_to_worktree=True, + reverse=True, selected=True) def stage_hunk(self): - self.process_diff_selection(self.model.get_unstaged(), - self.view.unstaged, - cached=False) + self.process_diff_selection(staged=False) def stage_hunk_selection(self): - self.process_diff_selection(self.model.get_unstaged(), - self.view.unstaged, - cached=False, - selected=True) + self.process_diff_selection(staged=False, selected=True) def unstage_hunk(self, cached=True): - self.process_diff_selection(self.model.get_staged(), - self.view.staged, - cached=True) + self.process_diff_selection(staged=True) def unstage_hunk_selection(self): - self.process_diff_selection(self.model.get_staged(), - self.view.staged, - cached=True, - selected=True) + self.process_diff_selection(staged=True, selected=True) # ####################################################################### # end diff gui @@ -738,30 +839,24 @@ class Controller(QObserver): def stage_selected(self,*rest): """Use "git add" to add items to the git index. This is a thin wrapper around map_to_listwidget.""" - command = self.model.add_or_remove - widget = self.view.unstaged - items = self.model.get_unstaged() - self.map_to_listwidget(command, widget, items) + unstaged = self.model.get_unstaged() + selected = self.view.get_unstaged(unstaged) + if not selected: + return + self.log(self.model.add_or_remove(selected), quiet=True) # *rest handles being called from different signals def unstage_selected(self, *rest): """Use "git reset" to remove items from the git index. This is a thin wrapper around map_to_listwidget.""" - command = self.model.reset_helper - widget = self.view.staged - items = self.model.get_staged() - self.map_to_listwidget(command, widget, items) + staged = self.model.get_staged() + selected = self.view.get_staged(staged) + self.log(self.model.reset_helper(selected), quiet=True) def undo_changes(self): """Reverts local changes back to whatever's in HEAD.""" - widget = self.view.unstaged - items = self.model.get_unstaged() - potential_items = qtutils.get_selection_list(widget, items) - items_to_undo = [] - untracked = self.model.get_untracked() - for item in potential_items: - if item not in untracked: - items_to_undo.append(item) + modified = self.model.get_modified() + items_to_undo = self.view.get_modified(modified) if items_to_undo: if not qtutils.question(self.view, 'Destroy Local Changes?', @@ -820,47 +915,41 @@ class Controller(QObserver): output = command(*apply_items) self.log(output, quiet=True) - def staged_context_menu_event(self, event): - menu = self.staged_context_menu_setup() - staged = self.view.staged - menu.exec_(staged.mapToGlobal(event.pos())) + def tree_context_menu_event(self, event): + menu = self.tree_context_menu_setup() + menu.exec_(self.view.status_tree.mapToGlobal(event.pos())) + + def tree_context_menu_setup(self): + staged, modified, unmerged, untracked = self.get_single_selection() - def staged_context_menu_setup(self): menu = QMenu(self.view) - menu.addAction(self.tr('Unstage Selected'), self.unstage_selected) - menu.addSeparator() - menu.addAction(self.tr('Launch Editor'), - lambda: self.edit_file(staged=True)) - menu.addAction(self.tr('Launch Diff Tool'), - lambda: self.edit_diff(staged=True)) - return menu - def unstaged_context_menu_event(self, event): - menu = self.unstaged_context_menu_setup() - unstaged = self.view.unstaged - menu.exec_(unstaged.mapToGlobal(event.pos())) + if staged: + menu.addAction(self.tr('Unstage Selected'), self.unstage_selected) + menu.addSeparator() + menu.addAction(self.tr('Launch Editor'), + lambda: self.edit_file(staged=True)) + menu.addAction(self.tr('Launch Diff Tool'), + lambda: self.edit_diff(staged=True)) + return menu - def unstaged_context_menu_setup(self): - unstaged_item = qtutils.get_selected_item(self.view.unstaged, - self.model.get_unstaged()) - is_tracked = unstaged_item not in self.model.get_untracked() - is_unmerged = unstaged_item in self.model.get_unmerged() enable_staging = self.mode == Controller.MODE_WORKTREE - enable_undo = enable_staging and is_tracked - menu = QMenu(self.view) - if enable_staging and not is_unmerged: + if (modified or unmerged or untracked) and enable_staging: menu.addAction(self.tr('Stage Selected'), self.stage_selected) menu.addSeparator() - if is_unmerged and not utils.is_broken(): + + if unmerged and not utils.is_broken(): menu.addAction(self.tr('Launch Merge Tool'), self.mergetool) menu.addAction(self.tr('Launch Editor'), lambda: self.edit_file(staged=False)) - if enable_staging and not is_unmerged: + + if (modified or untracked) and enable_staging: menu.addAction(self.tr('Launch Diff Tool'), lambda: self.edit_diff(staged=False)) - if enable_undo and not is_unmerged: + + if modified and enable_staging: menu.addSeparator() menu.addAction(self.tr('Undo All Changes'), self.undo_changes) return menu @@ -872,16 +961,10 @@ class Controller(QObserver): def diff_context_menu_setup(self): menu = QMenu(self.view) + staged, modified, unmerged, untracked = self.get_single_selection() if self.mode == Controller.MODE_WORKTREE: - unstaged_item =\ - qtutils.get_selected_item(self.view.unstaged, - self.model.get_unstaged()) - is_tracked= (unstaged_item - and unstaged_item not in self.model.get_untracked()) - is_unmerged = (unstaged_item - and unstaged_item in self.model.get_unmerged()) - if is_tracked and not is_unmerged: + if modified: menu.addAction(self.tr('Stage Hunk For Commit'), self.stage_hunk) menu.addAction(self.tr('Stage Selected Lines'), diff --git a/cola/main.py b/cola/main.py index f2f5fb01..709989f5 100644 --- a/cola/main.py +++ b/cola/main.py @@ -109,6 +109,6 @@ def main(): valid = model.use_worktree(gitdir) os.chdir(model.git.get_work_tree()) - ctl = Controller(model, view) view.show() + ctl = Controller(model, view) sys.exit(app.exec_()) diff --git a/cola/models.py b/cola/models.py index 523936d1..21daa78a 100644 --- a/cola/models.py +++ b/cola/models.py @@ -379,7 +379,7 @@ class Model(model.Model): self.subtree_sha1s.append(self.sha1s[idx]) self.subtree_names.append(name) - def add_or_remove(self, *to_process): + def add_or_remove(self, to_process): """Invokes 'git add' to index the filenames in to_process that exist and 'git rm' for those that do not exist.""" @@ -622,7 +622,7 @@ class Model(model.Model): return output def stage_untracked(self): - output = self.git.add(self.get_untracked()) + output = self.git.add(*self.get_untracked()) self.update_status() return output @@ -632,8 +632,14 @@ class Model(model.Model): return output def unstage_all(self): - self.git.reset('--', *self.get_staged()) + output = self.git.reset() self.update_status() + return output + + def stage_all(self): + output = self.git.add(v=True,u=True) + self.update_status() + return output def save_gui_settings(self): self.config_set('cola.geometry', utils.get_geom(), local=False) @@ -901,7 +907,7 @@ class Model(model.Model): return (staged, unstaged, untracked, unmerged) - def reset_helper(self, *args): + def reset_helper(self, args): """Removes files from the index. This handles the git init case, which is why it's not just git.reset(name). diff --git a/cola/qtutils.py b/cola/qtutils.py index 7d6aabe8..8dcc8397 100644 --- a/cola/qtutils.py +++ b/cola/qtutils.py @@ -8,6 +8,7 @@ from PyQt4.QtGui import QFileDialog from PyQt4.QtGui import QIcon from PyQt4.QtGui import QTreeWidget from PyQt4.QtGui import QListWidgetItem +from PyQt4.QtGui import QTreeWidgetItem from PyQt4.QtGui import QMessageBox from cola import utils @@ -58,6 +59,13 @@ def create_listwidget_item(text, filename): item.setText(text) return item +def create_treewidget_item(text, filename): + icon = QIcon(filename) + item = QTreeWidgetItem() + item.setIcon(0, icon) + item.setText(0, text) + return item + def information(title, message=None): """Launches a QMessageBox information with the provided title and message.""" @@ -94,6 +102,18 @@ def get_selection_list(listwidget, items): selected.append(item) return selected +def get_tree_selection(treeitem, items): + """Returns an array of model items that correspond to + the selected QListWidget indices.""" + selected = [] + itemcount = treeitem.childCount() + widgetitems = [ treeitem.child(idx) for idx in range(itemcount) ] + + for item, widgetitem in zip(items[:len(widgetitems)], widgetitems): + if widgetitem.isSelected(): + selected.append(item) + return selected + def get_selected_item(list_widget, items): row, selected = get_selected_row(list_widget) if selected and row < len(items): @@ -166,7 +186,7 @@ def set_items(widget, items): def tr(txt): return unicode(QtGui.qApp.translate('', txt)) -def get_icon_file(filename, staged, untracked): +def get_icon_file(filename, staged=False, untracked=False): if staged: if os.path.exists(filename.encode('utf-8')): icon_file = utils.get_icon('staged.png') @@ -178,17 +198,25 @@ def get_icon_file(filename, staged, untracked): icon_file = utils.get_file_icon(filename) return icon_file -def get_icon_for_file(filename, staged, untracked): - icon_file = get_icon_file(filename, staged, untracked) +def get_icon_for_file(filename, staged=False, untracked=False): + icon_file = get_icon_file(filename, staged=staged, untracked=untracked) return get_icon(icon_file) -def create_item(filename, staged, untracked=False): +def create_listitem(filename, staged=False, untracked=False): """Given a filename, return a QListWidgetItem suitable for adding to a QListWidget. "staged" and "untracked" controls whether to use the appropriate icons.""" icon_file = get_icon_file(filename, staged, untracked) return create_listwidget_item(filename, icon_file) +def create_treeitem(filename, staged=False, untracked=False): + """Given a filename, return a QListWidgetItem suitable + for adding to a QListWidget. "staged" and "untracked" + controls whether to use the appropriate icons.""" + icon_file = get_icon_file(filename, staged=staged, untracked=untracked) + return create_treewidget_item(filename, icon_file) + + def create_txt_item(txt): item = QListWidgetItem() item.setText(txt) @@ -207,7 +235,7 @@ def update_listwidget(widget, items, staged=True, """Populate a QListWidget with custom icon items.""" if not append: widget.clear() - add_items(widget, [ create_item(i, staged, untracked) for i in items ]) + add_items(widget, [ create_listitem(i, staged, untracked) for i in items ]) def set_listwidget_strings(widget, items): widget.clear() diff --git a/cola/views/main.py b/cola/views/main.py index 5f41df6d..12efdac6 100644 --- a/cola/views/main.py +++ b/cola/views/main.py @@ -37,9 +37,13 @@ def CreateStandardView(uiclass, qtclass, *classes): class View(CreateStandardView(Ui_main, QMainWindow)): """The main cola interface.""" + IDX_STAGED = 0 + IDX_MODIFIED = 1 + IDX_UNMERGED = 2 + IDX_UNTRACKED = 3 + IDX_END = 4 + def init(self, parent=None): - self.staged.setAlternatingRowColors(True) - self.unstaged.setAlternatingRowColors(True) self.set_display = self.display_text.setText self.amend_is_checked = self.amend_radio.isChecked self.action_undo = self.commitmsg.undo @@ -51,8 +55,6 @@ class View(CreateStandardView(Ui_main, QMainWindow)): self.commit_button.setText(qtutils.tr('Commit@@verb')) self.commit_menu.setTitle(qtutils.tr('Commit@@verb')) - self.tabifyDockWidget(self.diff_dock, self.editor_dock) - # Default to creating a new commit(i.e. not an amend commit) self.new_commit_radio.setChecked(True) self.toolbar_show_log =\ @@ -63,24 +65,169 @@ class View(CreateStandardView(Ui_main, QMainWindow)): # Diff/patch syntax highlighter self.syntax = DiffSyntaxHighlighter(self.display_text.document()) - # Handle the vertical checkbox action - self.connect(self.vertical_checkbox, - SIGNAL('clicked(bool)'), - self.handle_vertical_checkbox) - # Display the current column self.connect(self.commitmsg, SIGNAL('cursorPositionChanged()'), self.show_current_column) + # Install default icons + self.setup_icons() + + # Initialize the seen tree widget indexes + self._seen_indexes = set() + # Initialize the GUI to show 'Column: 00' self.show_current_column() - def handle_vertical_checkbox(self, checked): - if checked: - self.splitter.setOrientation(Qt.Vertical) + def set_staged(self, items): + """Adds items to the 'Staged' subtree.""" + self._set_subtree(items, View.IDX_STAGED, staged=True) + + def set_modified(self, items): + """Adds items to the 'Modified' subtree.""" + self._set_subtree(items, View.IDX_MODIFIED) + + def set_unmerged(self, items): + """Adds items to the 'Unmerged' subtree.""" + self._set_subtree(items, View.IDX_UNMERGED) + + def set_untracked(self, items): + """Adds items to the 'Untracked' subtree.""" + self._set_subtree(items, View.IDX_UNTRACKED) + + def _set_subtree(self, items, idx, staged=False, untracked=False): + parent = self.status_tree.topLevelItem(idx) + parent.takeChildren() + for item in items: + treeitem = qtutils.create_treeitem(item, + staged=staged, + untracked=untracked) + parent.addChild(treeitem) + if idx not in self._seen_indexes and items: + self._seen_indexes.add(idx) + self.status_tree.expandItem(parent) + if items: + self.status_tree.setItemHidden(parent, False) else: - self.splitter.setOrientation(Qt.Horizontal) + self.status_tree.setItemHidden(parent, True) + + def expand_status(self): + for idx in xrange(0, View.IDX_END): + item = self.status_tree.topLevelItem(idx) + if item: + self.status_tree.expandItem(item) + + def get_index_for_item(self, item): + """Given an item, returns the index of the item. + The indexes for unstaged items are grouped such that + the index of unmerged[1] = len(modified) + 1, etc. + """ + if not item: + return False, -1 + parent = item.parent() + if not parent: + return False, -1 + tree = self.status_tree + pidx = tree.indexOfTopLevelItem(parent) + if pidx == View.IDX_STAGED: + return True, parent.indexOfChild(item) + elif pidx == View.IDX_MODIFIED: + return False, parent.indexOfChild(item) + + count = tree.topLevelItem(View.IDX_MODIFIED).childCount() + if pidx == View.IDX_UNMERGED: + return False, count + parent.indexOfChild(item) + count += tree.topLevelItem(View.IDX_UNMERGED).childCount() + if pidx == View.IDX_UNTRACKED: + return False, count + parent.indexOfChild(item) + return False, -1 + + def get_selection(self): + tree = self.status_tree + item = tree.currentItem() + parent = item.parent() + if not parent: + return -1, False + pidx = tree.indexOfTopLevelItem(parent) + if pidx == View.IDX_STAGED or pidx == View.IDX_MODIFIED: + idx = parent.indexOfChild(item) + return idx, tree.isItemSelected(item) + elif pidx == View.IDX_UNMERGED: + num_modified = tree.topLevelItem(View.IDX_MODIFIED).childCount() + return idx + num_modified, tree.isItemSelected(item) + elif pidx == View.IDX_UNTRACKED: + num_modified = tree.topLevelItem(View.IDX_MODIFIED).childCount() + num_unmerged = tree.topLevelItem(View.IDX_UNMERGED).childCount() + return idx + num_modified + num_unmerged, tree.isItemSelected(item) + return -1, False + + def get_staged_item(self, itemidx): + return self._get_subtree_item(View.IDX_STAGED, itemidx) + + def get_modified_item(self, itemidx): + return self._get_subtree_item(View.IDX_MODIFIED, itemidx) + + def get_unstaged_item(self, itemidx): + tree = self.status_tree + # is it modified? + item = tree.topLevelItem(View.IDX_MODIFIED) + count = item.childCount() + if itemidx < count: + return item.child(itemidx) + # is it unmerged? + item = tree.topLevelItem(View.IDX_UNMERGED) + count += item.childCount() + if itemidx < count: + return item.child(itemidx) + # is it untracked? + item = tree.topLevelItem(View.IDX_UNTRACKED) + count += item.childCount() + if itemidx < count: + return item.child(itemidx) + # Nope.. + return None + + def _get_subtree_item(self, idx, itemidx): + parent = self.status_tree.topLevelItem(idx) + return parent.child(itemidx) + + def get_unstaged(self, items): + tree = self.status_tree + num_modified = tree.topLevelItem(View.IDX_MODIFIED).childCount() + num_unmerged = tree.topLevelItem(View.IDX_UNMERGED).childCount() + modified = self.get_modified(items) + unmerged = self.get_unmerged(items[num_modified:]) + untracked = self.get_untracked(items[num_modified+num_unmerged:]) + return modified + unmerged + untracked + + def get_staged(self, items): + return self._get_subtree_selection(View.IDX_STAGED, items) + + def get_modified(self, items): + return self._get_subtree_selection(View.IDX_MODIFIED, items) + + def get_unmerged(self, items): + return self._get_subtree_selection(View.IDX_UNMERGED, items) + + def get_untracked(self, items): + return self._get_subtree_selection(View.IDX_UNTRACKED, items) + + def _get_subtree_selection(self, idx, items): + item = self.status_tree.topLevelItem(idx) + return qtutils.get_tree_selection(item, items) + + def setup_icons(self): + staged = self.status_tree.topLevelItem(View.IDX_STAGED) + staged.setIcon(0, qtutils.get_icon('plus.png')) + + modified = self.status_tree.topLevelItem(View.IDX_MODIFIED) + modified.setIcon(0, qtutils.get_icon('modified.png')) + + unmerged = self.status_tree.topLevelItem(View.IDX_UNMERGED) + unmerged.setIcon(0, qtutils.get_icon('unmerged.png')) + + untracked = self.status_tree.topLevelItem(View.IDX_UNTRACKED) + untracked.setIcon(0, qtutils.get_icon('untracked.png')) def set_info(self, txt): try: @@ -91,8 +238,7 @@ class View(CreateStandardView(Ui_main, QMainWindow)): def show_editor(self): self.editor_dock.raise_() def show_diff(self): - self.diff_dock.raise_() - + self.tabwidget.setCurrentIndex(0) def action_cut(self): self.action_copy() self.action_delete() diff --git a/share/cola/icons/modified.png b/share/cola/icons/modified.png new file mode 100644 index 0000000000000000000000000000000000000000..e59294796a718ab7e26676f2ce8a6eb9e090ff7c GIT binary patch literal 229 zcwXxa@N?(olHy`uVBq!ia0vp^B0wz0!VDyh@)w!{DaPU;cPEB*=VV?2IidkRA+GsF zxj^P>1_oneW0T1`GiT2H|NsBRxr^U}RF?$#1v5B2yO9Rua29w(7Bet#3xhBt!>l&XUe%*<>G2Hwm~Pf}Qn&oZ3xDP%U9Ea)>~Lu6NXcl4r#3l)1bH8eOv zCrp!);z(ZRI75ITNljC8WunnU5r-`cjUB@hj5e-zl}MA}Vsvmf;&5VQW>8*c!{n1Y R>mtw|22WQ%mvv4FO#lQVK2iVx literal 0 HcwPel00001 diff --git a/share/cola/icons/plus.png b/share/cola/icons/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..4c44bf907f7956c04b0c5ec04b9f1a58cbca14d4 GIT binary patch literal 229 zcwXxa@N?(olHy`uVBq!ia0vp^B0wz0!VDyh@)w!{DaPU;cPEB*=VV?2IidkRA+GsF zxj?3|v9Us;Eazc{nKNhp|NsAE0lzUwbxDw4FoVOh8)-leXMsm#F#`j)FbFd;%$g$s z6pZn7aSY+Oo*cl$%*v(^;LY5$M1{rJm+_#FlY!%*gtSGA7Af^;YG`nzE?e5h)-+Qy zFEcY>8RvwVZRbiDnK+CkJ2P`0<+QC7o0&O>Eq&R;RIOGgh6@vUngR|mFx0NL+I3Oe RqZMcmgQu&X%Q~loCIF~$LI3~& literal 0 HcwPel00001 diff --git a/share/cola/icons/unmerged.png b/share/cola/icons/unmerged.png new file mode 100644 index 0000000000000000000000000000000000000000..423d88d78cf42cb1eca2bf947373137313d6f2c2 GIT binary patch literal 242 zcwXxa@N?(olHy`uVBq!ia0vp^B0wz0!VDyh@)w!{DaPU;cPEB*=VV?2IidkRA+GsF zxj^P>1_oneW0T1`GiT2H|NsBRxr^U}RF?$#1v5B2yO9Rua29w(7Bet#3xhBt!>l%QkU@*Ys&aS04dVCA~5u*9d~MX-TGPx0@5fhO4#>*q~Kef&vh*Tu%b z_p>`@*M6>@w 0 0 - 716 - 485 + 621 + 281 QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks|QMainWindow::ForceTabbedDocks - - - 363 - 26 - 353 - 444 - - 1 1 - + + 4 + + 0 - + + 0 + + + 6 + + 0 - - - 0 - 1 - - - - - 1 - 1 - - - - QFrame::NoFrame - - - QFrame::Plain - - Qt::Vertical - - - false + Qt::Horizontal - 2 + 5 - - - - 0 - - - - - - - - 0 - 0 - - - - - 121 - 14 - - - - Unstaged Changes + + + + 1 + 0 + + + + 0 + + + + Diff View + + + + 0 + + + 4 + + + 2 + + + 4 + + + 4 + + + + + + 0 + 0 + + + + + 1 + 1 + + + + true + + + QTextEdit::NoWrap + + + true + + + false + + + 2 + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + Commit + + + + 4 + + + 4 + + + 2 + + + 4 + + + 4 + + + + + + 0 + 0 + + + + + 0 + 10 + + + + + 3 - - - - - - Show Untracked Files + + QLayout::SetNoConstraint - - true + + 0 - - - - - - - - - 0 - 0 - - - - true - - - QAbstractItemView::ExtendedSelection - - - QListView::ListMode - - - true - - - - + + + + + 0 + 0 + + + + + 0 + 22 + + + + Stage Changed + + + false + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + Sign Off + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + Commit@@verb + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + Fetch + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + Push + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + Pull + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 1 + 4 + + + + + + + + + + + Qt::AlignCenter + + + + + stage_button + signoff_button + commit_button + fetch_button + push_button + pull_button + column_label + + + + + + + 0 + + + + + + 0 + 0 + + + + QTextEdit::NoWrap + + + false + + + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 1 + 1 + + + + + + + + New Commit + + + + + + + Amend Last Commit + + + + + + + + + widget_4 + + - - - - 0 + + + + 0 + 0 + + + + QAbstractItemView::ExtendedSelection + + + true + + + + Repository Status - - - - - - - 0 - 0 - - - - Staged Changes (Will Commit) - - - - - - - Vertical - - - true - - - - - - - - - - 0 - 0 - - - - true - - - QAbstractItemView::ExtendedSelection - - - QListView::ListMode - - - true - - - - + + + + Staged + + + + + Modified + + + + + Unmerged + + + + + Untracked + + @@ -191,8 +424,8 @@ 0 0 - 716 - 26 + 621 + 22 @@ -338,469 +571,10 @@ - - - - 0 - 26 - 357 - 200 - - - - - 0 - 0 - - - - - 0 - 0 - - - - false - - - Diff View: - - - 1 - - - - - 0 - 20 - 357 - 180 - - - - - 0 - - - QLayout::SetNoConstraint - - - 6 - - - 0 - - - 0 - - - 4 - - - - - - 0 - 0 - - - - - 111 - 50 - - - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 1 - 1 - - - - true - - - QTextEdit::NoWrap - - - true - - - false - - - 2 - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - - - - - - 0 - 232 - 357 - 238 - - - - - 0 - 0 - - - - false - - - Commit Message: - - - 1 - - - - - 0 - 20 - 357 - 218 - - - - - 0 - 0 - - - - - 0 - - - QLayout::SetNoConstraint - - - 6 - - - 0 - - - 0 - - - 4 - - - - - - 0 - 1 - - - - - 10 - 100 - - - - - 6 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 10 - - - - - 3 - - - QLayout::SetNoConstraint - - - 0 - - - - - - 0 - 0 - - - - - 0 - 22 - - - - Stage Changed - - - false - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - Sign Off - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - Commit@@verb - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - Fetch - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - Push - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - Pull - - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 1 - 4 - - - - - - - - - - - Qt::AlignCenter - - - - - - - - - - - 0 - 0 - - - - - 0 - - - QLayout::SetNoConstraint - - - 0 - - - - - - 0 - 0 - - - - QTextEdit::NoWrap - - - false - - - - - - - 0 - - - 4 - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 1 - 1 - - - - - - - - New Commit - - - - - - - Amend Last Commit - - - - - - - - - - - - - - true - - - 0 - 470 - 716 - 15 - - toolbar -- 2.11.4.GIT