Permalink
Browse files

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 <davvid@gmail.com>
  • Loading branch information...
1 parent 6e8c135 commit 2e5c32841cb8a450f1dfe3b60c718228f289ff51 @davvid davvid committed Nov 12, 2008

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -109,6 +109,6 @@ def translate(*args):
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_())
View
@@ -379,7 +379,7 @@ def init_browser_data(self):
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 @@ def stage_modified(self):
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 @@ def reset(self, *items):
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 @@ def get_workdir_state(self, amend=False):
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).
View
@@ -8,6 +8,7 @@
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()
View
@@ -37,9 +37,13 @@ def reset_syntax(self):
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 @@ def init(self, parent=None):
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 @@ def init(self, parent=None):
# 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 @@ def set_info(self, txt):
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()
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.

0 comments on commit 2e5c328

Please sign in to comment.