maint: prefer functions over methods
[git-cola.git] / cola / widgets / browse.py
blob2e58dbe938787607bbf8191b887a5922caea6edf
1 from __future__ import division, absolute_import, unicode_literals
3 from qtpy.QtCore import Qt
4 from qtpy.QtCore import Signal
5 from qtpy import QtCore
6 from qtpy import QtGui
7 from qtpy import QtWidgets
9 from ..models.browse import GitRepoModel
10 from ..models.browse import GitRepoNameItem
11 from ..models.selection import State
12 from ..i18n import N_
13 from ..interaction import Interaction
14 from ..models import browse
15 from .. import cmds
16 from .. import core
17 from .. import gitcmds
18 from .. import hotkeys
19 from .. import icons
20 from .. import utils
21 from .. import qtutils
22 from .selectcommits import select_commits
23 from . import common
24 from . import defs
25 from . import standard
28 def worktree_browser(context, parent=None, update=True,
29 settings=None, show=True):
30 """Create a new worktree browser"""
31 view = Browser(context, parent, update=update, settings=settings)
32 model = GitRepoModel(context, view.tree)
33 view.set_model(model)
34 if update:
35 view.refresh()
36 if show:
37 view.show()
38 return view
41 def save_path(context, path, model):
42 """Choose an output filename based on the selected path"""
43 filename = qtutils.save_as(path)
44 if filename:
45 model.filename = filename
46 cmds.do(SaveBlob, context, model)
47 result = True
48 else:
49 result = False
50 return result
53 class Browser(standard.Widget):
54 updated = Signal()
56 # Read-only mode property
57 mode = property(lambda self: self.model.mode)
59 def __init__(self, context, parent, update=True, settings=None):
60 standard.Widget.__init__(self, parent)
61 self.settings = settings
62 self.tree = RepoTreeView(context, self)
63 self.mainlayout = qtutils.hbox(defs.no_margin, defs.spacing, self.tree)
64 self.setLayout(self.mainlayout)
66 self.updated.connect(self._updated_callback, type=Qt.QueuedConnection)
68 self.model = context.model
69 self.model.add_observer(self.model.message_updated, self.model_updated)
70 if parent is None:
71 qtutils.add_close_action(self)
72 if update:
73 self.model_updated()
75 self.init_state(settings, self.resize, 720, 420)
77 def set_model(self, model):
78 """Set the model"""
79 self.tree.set_model(model)
81 def refresh(self):
82 """Refresh the model triggering view updates"""
83 self.tree.refresh()
85 def model_updated(self):
86 """Update the title with the current branch and directory name."""
87 self.updated.emit()
89 def _updated_callback(self):
90 branch = self.model.currentbranch
91 curdir = core.getcwd()
92 msg = N_('Repository: %s') % curdir
93 msg += '\n'
94 msg += N_('Branch: %s') % branch
95 self.setToolTip(msg)
97 scope = dict(project=self.model.project, branch=branch)
98 title = N_('%(project)s: %(branch)s - Browse') % scope
99 if self.mode == self.model.mode_amend:
100 title += ' %s' % N_('(Amending)')
101 self.setWindowTitle(title)
104 class RepoTreeView(standard.TreeView):
105 """Provides a filesystem-like view of a git repository."""
107 about_to_update = Signal()
108 updated = Signal()
110 def __init__(self, context, parent):
111 standard.TreeView.__init__(self, parent)
113 self.context = context
114 self.selection = context.selection
115 self.saved_selection = []
116 self.saved_current_path = None
117 self.saved_open_folders = set()
118 self.restoring_selection = False
120 self.info_event_type = browse.GitRepoInfoEvent.TYPE
122 self.setDragEnabled(True)
123 self.setRootIsDecorated(False)
124 self.setSortingEnabled(False)
125 self.setSelectionMode(self.ExtendedSelection)
127 # Observe model updates
128 model = context.model
129 model.add_observer(model.message_about_to_update,
130 self.emit_about_to_update)
131 model.add_observer(model.message_updated, self.emit_update)
133 self.about_to_update.connect(self.save_selection,
134 type=Qt.QueuedConnection)
135 self.updated.connect(self.update_actions, type=Qt.QueuedConnection)
137 self.expanded.connect(self.index_expanded)
139 self.collapsed.connect(lambda idx: self.size_columns())
140 self.collapsed.connect(self.index_collapsed)
142 # Sync selection before the key press event changes the model index
143 queued = Qt.QueuedConnection
144 self.index_about_to_change.connect(self.sync_selection, type=queued)
146 self.action_history = qtutils.add_action_with_status_tip(
147 self, N_('View History...'),
148 N_('View history for selected paths'),
149 self.view_history, hotkeys.HISTORY)
151 self.action_stage = qtutils.add_action_with_status_tip(
152 self, cmds.StageOrUnstage.name(),
153 N_('Stage/unstage selected paths for commit'),
154 cmds.run(cmds.StageOrUnstage, context),
155 hotkeys.STAGE_SELECTION)
157 self.action_untrack = qtutils.add_action_with_status_tip(
158 self, N_('Untrack Selected'), N_('Stop tracking paths'),
159 self.untrack_selected)
161 self.action_rename = qtutils.add_action_with_status_tip(
162 self, N_('Rename'), N_('Rename selected paths'),
163 self.rename_selected)
165 self.action_difftool = qtutils.add_action_with_status_tip(
166 self, cmds.LaunchDifftool.name(),
167 N_('Launch git-difftool on the current path'),
168 cmds.run(cmds.LaunchDifftool, context), hotkeys.DIFF)
170 self.action_difftool_predecessor = qtutils.add_action_with_status_tip(
171 self, N_('Diff Against Predecessor...'),
172 N_('Launch git-difftool against previous versions'),
173 self.diff_predecessor, hotkeys.DIFF_SECONDARY)
175 self.action_revert_unstaged = qtutils.add_action_with_status_tip(
176 self, cmds.RevertUnstagedEdits.name(),
177 N_('Revert unstaged changes to selected paths'),
178 cmds.run(cmds.RevertUnstagedEdits, context), hotkeys.REVERT)
180 self.action_revert_uncommitted = qtutils.add_action_with_status_tip(
181 self, cmds.RevertUncommittedEdits.name(),
182 N_('Revert uncommitted changes to selected paths'),
183 cmds.run(cmds.RevertUncommittedEdits, context), hotkeys.UNDO)
185 self.action_editor = qtutils.add_action_with_status_tip(
186 self, cmds.LaunchEditor.name(),
187 N_('Edit selected paths'),
188 cmds.run(cmds.LaunchEditor, context), hotkeys.EDIT)
190 self.action_refresh = common.refresh_action(context, self)
192 if not utils.is_win32():
193 self.action_default_app = common.default_app_action(
194 context, self, self.selected_paths)
196 self.action_parent_dir = common.parent_dir_action(
197 context, self, self.selected_paths)
199 self.action_terminal = common.terminal_action(
200 context, self, self.selected_paths)
202 self.x_width = QtGui.QFontMetrics(self.font()).width('x')
203 self.size_columns()
205 def index_expanded(self, index):
206 """Update information about a directory as it is expanded."""
207 # Remember open folders so that we can restore them when refreshing
208 item = self.name_item_from_index(index)
209 self.saved_open_folders.add(item.path)
210 self.size_columns()
212 # update information about a directory as it is expanded
213 if item.cached:
214 return
215 path = item.path
217 model = self.model()
218 model.populate(item)
219 model.update_entry(path)
221 for row in range(item.rowCount()):
222 path = item.child(row, 0).path
223 model.update_entry(path)
225 item.cached = True
227 def index_collapsed(self, index):
228 item = self.name_item_from_index(index)
229 self.saved_open_folders.remove(item.path)
231 def refresh(self):
232 self.model().refresh()
234 def size_columns(self):
235 """Set the column widths."""
236 self.resizeColumnToContents(0)
237 self.resizeColumnToContents(1)
238 self.resizeColumnToContents(2)
239 self.resizeColumnToContents(3)
240 self.resizeColumnToContents(4)
242 def sizeHintForColumn(self, column):
243 x_width = self.x_width
245 if column == 1:
246 # Status
247 size = x_width * 11
248 elif column == 2:
249 # Summary
250 size = x_width * 64
251 elif column == 3:
252 # Author
253 size = x_width * 18
254 elif column == 4:
255 # Age
256 size = x_width * 16
257 else:
258 # Filename and others use the actual content
259 size = super(RepoTreeView, self).sizeHintForColumn(column)
260 return size
262 def emit_update(self):
263 self.updated.emit()
265 def emit_about_to_update(self):
266 self.about_to_update.emit()
268 def save_selection(self):
269 selection = self.selected_paths()
270 if selection:
271 self.saved_selection = selection
273 current = self.current_item()
274 if current:
275 self.saved_current_path = current.path
277 def restore(self):
278 selection = self.selectionModel()
279 flags = selection.Select | selection.Rows
281 self.restoring_selection = True
283 # Restore opened folders
284 model = self.model()
285 for path in sorted(self.saved_open_folders):
286 row = model.get(path)
287 if not row:
288 continue
289 index = row[0].index()
290 if index.isValid():
291 self.setExpanded(index, True)
293 # Restore the current item. We do this first, otherwise
294 # setCurrentIndex() can mess with the selection we set below
295 current_index = None
296 current_path = self.saved_current_path
297 if current_path:
298 row = model.get(current_path)
299 if row:
300 current_index = row[0].index()
302 if current_index and current_index.isValid():
303 self.setCurrentIndex(current_index)
305 # Restore selected items
306 for path in self.saved_selection:
307 row = model.get(path)
308 if not row:
309 continue
310 index = row[0].index()
311 if index.isValid():
312 self.scrollTo(index)
313 selection.select(index, flags)
315 self.restoring_selection = False
317 self.size_columns()
318 self.update_diff()
320 def event(self, ev):
321 """Respond to GitRepoInfoEvents"""
322 if ev.type() == self.info_event_type:
323 ev.accept()
324 self.apply_data(ev.data)
325 return super(RepoTreeView, self).event(ev)
327 def apply_data(self, data):
328 entry = self.model().get(data[0])
329 if entry:
330 entry[1].set_status(data[1])
331 entry[2].setText(data[2])
332 entry[3].setText(data[3])
333 entry[4].setText(data[4])
335 def update_actions(self):
336 """Enable/disable actions."""
337 selection = self.selected_paths()
338 selected = bool(selection)
339 staged = bool(self.selected_staged_paths(selection=selection))
340 modified = bool(self.selected_modified_paths(selection=selection))
341 unstaged = bool(self.selected_unstaged_paths(selection=selection))
342 tracked = bool(self.selected_tracked_paths(selection=selection))
343 revertable = staged or modified
345 self.action_editor.setEnabled(selected)
346 self.action_history.setEnabled(selected)
347 if not utils.is_win32():
348 self.action_default_app.setEnabled(selected)
349 self.action_parent_dir.setEnabled(selected)
350 self.action_terminal.setEnabled(selected)
352 self.action_stage.setEnabled(staged or unstaged)
353 self.action_untrack.setEnabled(tracked)
354 self.action_rename.setEnabled(tracked)
355 self.action_difftool.setEnabled(staged or modified)
356 self.action_difftool_predecessor.setEnabled(tracked)
357 self.action_revert_unstaged.setEnabled(revertable)
358 self.action_revert_uncommitted.setEnabled(revertable)
360 def contextMenuEvent(self, event):
361 """Create a context menu."""
362 self.update_actions()
363 menu = qtutils.create_menu(N_('Actions'), self)
364 menu.addAction(self.action_editor)
365 menu.addAction(self.action_stage)
366 menu.addSeparator()
367 menu.addAction(self.action_history)
368 menu.addAction(self.action_difftool)
369 menu.addAction(self.action_difftool_predecessor)
370 menu.addSeparator()
371 menu.addAction(self.action_revert_unstaged)
372 menu.addAction(self.action_revert_uncommitted)
373 menu.addAction(self.action_untrack)
374 menu.addAction(self.action_rename)
375 if not utils.is_win32():
376 menu.addSeparator()
377 menu.addAction(self.action_default_app)
378 menu.addAction(self.action_parent_dir)
379 menu.addAction(self.action_terminal)
380 menu.exec_(self.mapToGlobal(event.pos()))
382 def mousePressEvent(self, event):
383 """Synchronize the selection on mouse-press."""
384 result = QtWidgets.QTreeView.mousePressEvent(self, event)
385 self.sync_selection()
386 return result
388 def sync_selection(self):
389 """Push selection into the selection model."""
390 staged = []
391 unmerged = []
392 modified = []
393 untracked = []
394 state = State(staged, unmerged, modified, untracked)
396 paths = self.selected_paths()
397 model = self.context.model
398 model_staged = utils.add_parents(model.staged)
399 model_modified = utils.add_parents(model.modified)
400 model_unmerged = utils.add_parents(model.unmerged)
401 model_untracked = utils.add_parents(model.untracked)
403 for path in paths:
404 if path in model_unmerged:
405 unmerged.append(path)
406 elif path in model_untracked:
407 untracked.append(path)
408 elif path in model_staged:
409 staged.append(path)
410 elif path in model_modified:
411 modified.append(path)
412 else:
413 staged.append(path)
414 # Push the new selection into the model.
415 self.selection.set_selection(state)
416 return paths
418 def selectionChanged(self, old, new):
419 """Override selectionChanged to update available actions."""
420 result = QtWidgets.QTreeView.selectionChanged(self, old, new)
421 if not self.restoring_selection:
422 self.update_actions()
423 self.update_diff()
424 return result
426 def update_diff(self):
427 context = self.context
428 model = context.model
429 paths = self.sync_selection()
430 if paths and self.model().path_is_interesting(paths[0]):
431 cached = paths[0] in model.staged
432 cmds.do(cmds.Diff, context, paths[0], cached)
434 def set_model(self, model):
435 """Set the concrete QAbstractItemModel instance."""
436 self.setModel(model)
437 model.restore.connect(self.restore, type=Qt.QueuedConnection)
439 def name_item_from_index(self, model_index):
440 """Return the name item corresponding to the model index."""
441 index = model_index.sibling(model_index.row(), 0)
442 return self.model().itemFromIndex(index)
444 def paths_from_indexes(self, indexes):
445 return qtutils.paths_from_indexes(self.model(), indexes,
446 item_type=GitRepoNameItem.TYPE)
448 def selected_paths(self):
449 """Return the selected paths."""
450 return self.paths_from_indexes(self.selectedIndexes())
452 def selected_staged_paths(self, selection=None):
453 """Return selected staged paths."""
454 if selection is None:
455 selection = self.selected_paths()
456 model = self.context.model
457 staged = utils.add_parents(model.staged)
458 return [p for p in selection if p in staged]
460 def selected_modified_paths(self, selection=None):
461 """Return selected modified paths."""
462 if selection is None:
463 selection = self.selected_paths()
464 model = self.context.model
465 modified = utils.add_parents(model.modified)
466 return [p for p in selection if p in modified]
468 def selected_unstaged_paths(self, selection=None):
469 """Return selected unstaged paths."""
470 if selection is None:
471 selection = self.selected_paths()
472 model = self.context.model
473 modified = utils.add_parents(model.modified)
474 untracked = utils.add_parents(model.untracked)
475 unstaged = modified.union(untracked)
476 return [p for p in selection if p in unstaged]
478 def selected_tracked_paths(self, selection=None):
479 """Return selected tracked paths."""
480 if selection is None:
481 selection = self.selected_paths()
482 model = self.context.model
483 staged = set(self.selected_staged_paths(selection=selection))
484 modified = set(self.selected_modified_paths(selection=selection))
485 untracked = utils.add_parents(model.untracked)
486 tracked = staged.union(modified)
487 return [p for p in selection
488 if p not in untracked or p in tracked]
490 def view_history(self):
491 """Launch the configured history browser path-limited to entries."""
492 paths = self.selected_paths()
493 cmds.do(cmds.VisualizePaths, self.context, paths)
495 def untrack_selected(self):
496 """untrack selected paths."""
497 context = self.context
498 cmds.do(cmds.Untrack, context, self.selected_tracked_paths())
500 def rename_selected(self):
501 """untrack selected paths."""
502 context = self.context
503 cmds.do(cmds.Rename, context, self.selected_tracked_paths())
505 def diff_predecessor(self):
506 """Diff paths against previous versions."""
507 context = self.context
508 paths = self.selected_tracked_paths()
509 args = ['--'] + paths
510 revs, summaries = gitcmds.log_helper(
511 context, all=False, extra_args=args)
512 commits = select_commits(
513 context, N_('Select Previous Version'), revs, summaries,
514 multiselect=False)
515 if not commits:
516 return
517 commit = commits[0]
518 cmds.difftool_launch(context, left=commit, paths=paths)
520 def current_path(self):
521 """Return the path for the current item."""
522 index = self.currentIndex()
523 if not index.isValid():
524 return None
525 return self.name_item_from_index(index).path
528 class BrowseModel(object):
529 """Context data used for browsing branches via git-ls-tree"""
531 def __init__(self, ref, filename=None):
532 self.ref = ref
533 self.relpath = filename
534 self.filename = filename
537 class SaveBlob(cmds.ContextCommand):
539 def __init__(self, context, model):
540 super(SaveBlob, self).__init__(context)
541 self.browse_model = model
543 def do(self):
544 git = self.context.git
545 model = self.browse_model
546 ref = '%s:%s' % (model.ref, model.relpath)
547 with core.xopen(model.filename, 'wb') as fp:
548 status, _, _ = git.show(ref, _stdout=fp)
550 msg = (N_('Saved "%(filename)s" from "%(ref)s" to "%(destination)s"') %
551 dict(filename=model.relpath,
552 ref=model.ref,
553 destination=model.filename))
554 Interaction.log_status(status, msg, '')
556 Interaction.information(
557 N_('File Saved'),
558 N_('File saved to "%s"') % model.filename)
561 class BrowseBranch(standard.Dialog):
563 @classmethod
564 def browse(cls, context, ref):
565 model = BrowseModel(ref)
566 dlg = cls(context, model, parent=qtutils.active_window())
567 dlg_model = GitTreeModel(context, ref, dlg)
568 dlg.setModel(dlg_model)
569 dlg.setWindowTitle(N_('Browsing %s') % model.ref)
570 dlg.show()
571 dlg.raise_()
572 if dlg.exec_() != dlg.Accepted:
573 return None
574 return dlg
576 def __init__(self, context, model, parent=None):
577 standard.Dialog.__init__(self, parent=parent)
578 if parent is not None:
579 self.setWindowModality(Qt.WindowModal)
581 # updated for use by commands
582 self.context = context
583 self.model = model
585 # widgets
586 self.tree = GitTreeWidget(parent=self)
587 self.close_button = qtutils.close_button()
589 text = N_('Save')
590 self.save = qtutils.create_button(text=text, enabled=False,
591 default=True)
593 # layouts
594 self.btnlayt = qtutils.hbox(defs.margin, defs.spacing,
595 self.close_button, qtutils.STRETCH,
596 self.save)
598 self.layt = qtutils.vbox(defs.margin, defs.spacing,
599 self.tree, self.btnlayt)
600 self.setLayout(self.layt)
602 # connections
603 self.tree.path_chosen.connect(self.save_path)
605 self.tree.selection_changed.connect(self.selection_changed,
606 type=Qt.QueuedConnection)
608 qtutils.connect_button(self.close_button, self.close)
609 qtutils.connect_button(self.save, self.save_blob)
610 self.init_size(parent=parent)
612 def expandAll(self):
613 self.tree.expandAll()
615 def setModel(self, model):
616 self.tree.setModel(model)
618 def path_chosen(self, path, close=True):
619 """Update the model from the view"""
620 model = self.model
621 model.relpath = path
622 model.filename = path
623 if close:
624 self.accept()
626 def save_path(self, path):
627 """Choose an output filename based on the selected path"""
628 self.path_chosen(path, close=False)
629 if save_path(self.context, path, self.model):
630 self.accept()
632 def save_blob(self):
633 """Save the currently selected file"""
634 filenames = self.tree.selected_files()
635 if not filenames:
636 return
637 self.save_path(filenames[0])
639 def selection_changed(self):
640 """Update actions based on the current selection"""
641 filenames = self.tree.selected_files()
642 self.save.setEnabled(bool(filenames))
645 class GitTreeWidget(standard.TreeView):
647 selection_changed = Signal()
648 path_chosen = Signal(object)
650 def __init__(self, parent=None):
651 standard.TreeView.__init__(self, parent)
652 self.setHeaderHidden(True)
653 self.doubleClicked.connect(self.double_clicked)
655 def double_clicked(self, index):
656 item = self.model().itemFromIndex(index)
657 if item is None:
658 return
659 if item.is_dir:
660 return
661 self.path_chosen.emit(item.path)
663 def selected_files(self):
664 items = self.selected_items()
665 return [i.path for i in items if not i.is_dir]
667 def selectionChanged(self, old_selection, new_selection):
668 QtWidgets.QTreeView.selectionChanged(self, old_selection, new_selection)
669 self.selection_changed.emit()
671 def select_first_file(self):
672 """Select the first filename in the tree"""
673 model = self.model()
674 idx = self.indexAt(QtCore.QPoint(0, 0))
675 item = model.itemFromIndex(idx)
676 while idx and idx.isValid() and item and item.is_dir:
677 idx = self.indexBelow(idx)
678 item = model.itemFromIndex(idx)
680 if idx and idx.isValid() and item:
681 self.setCurrentIndex(idx)
684 class GitFileTreeModel(QtGui.QStandardItemModel):
685 """Presents a list of file paths as a hierarchical tree."""
687 def __init__(self, parent):
688 QtGui.QStandardItemModel.__init__(self, parent)
689 self.dir_entries = {'': self.invisibleRootItem()}
690 self.dir_rows = {}
692 def clear(self):
693 QtGui.QStandardItemModel.clear(self)
694 self.dir_rows = {}
695 self.dir_entries = {'': self.invisibleRootItem()}
697 def add_files(self, files):
698 """Add a list of files"""
699 add_file = self.add_file
700 for f in files:
701 add_file(f)
703 def add_file(self, path):
704 """Add a file to the model."""
705 dirname = utils.dirname(path)
706 dir_entries = self.dir_entries
707 try:
708 parent = dir_entries[dirname]
709 except KeyError:
710 parent = dir_entries[dirname] = self.create_dir_entry(dirname)
712 row_items = create_row(path, False)
713 parent.appendRow(row_items)
715 def add_directory(self, parent, path):
716 """Add a directory entry to the model."""
717 # Create model items
718 row_items = create_row(path, True)
720 try:
721 parent_path = parent.path
722 except AttributeError: # root QStandardItem
723 parent_path = ''
725 # Insert directories before file paths
726 try:
727 row = self.dir_rows[parent_path]
728 except KeyError:
729 row = self.dir_rows[parent_path] = 0
731 parent.insertRow(row, row_items)
732 self.dir_rows[parent_path] += 1
733 self.dir_entries[path] = row_items[0]
735 return row_items[0]
737 def create_dir_entry(self, dirname):
739 Create a directory entry for the model.
741 This ensures that directories are always listed before files.
744 entries = dirname.split('/')
745 curdir = []
746 parent = self.invisibleRootItem()
747 curdir_append = curdir.append
748 self_add_directory = self.add_directory
749 dir_entries = self.dir_entries
750 for entry in entries:
751 curdir_append(entry)
752 path = '/'.join(curdir)
753 try:
754 parent = dir_entries[path]
755 except KeyError:
756 grandparent = parent
757 parent = self_add_directory(grandparent, path)
758 dir_entries[path] = parent
759 return parent
762 def create_row(path, is_dir):
763 """Return a list of items representing a row."""
764 return [GitTreeItem(path, is_dir)]
767 class GitTreeModel(GitFileTreeModel):
769 def __init__(self, context, ref, parent):
770 GitFileTreeModel.__init__(self, parent)
771 self.context = context
772 self.ref = ref
773 self._initialize()
775 def _initialize(self):
776 """Iterate over git-ls-tree and create GitTreeItems."""
777 git = self.context.git
778 status, out, err = git.ls_tree(
779 '--full-tree', '-r', '-t', '-z', self.ref)
780 if status != 0:
781 Interaction.log_status(status, out, err)
782 return
784 if not out:
785 return
787 for line in out[:-1].split('\0'):
788 # .....6 ...4 ......................................40
789 # 040000 tree c127cde9a0c644a3a8fef449a244f47d5272dfa6 relative
790 # 100644 blob 139e42bf4acaa4927ec9be1ec55a252b97d3f1e2 relative/path
791 objtype = line[7]
792 relpath = line[6 + 1 + 4 + 1 + 40 + 1:]
793 if objtype == 't':
794 parent = self.dir_entries[utils.dirname(relpath)]
795 self.add_directory(parent, relpath)
796 elif objtype == 'b':
797 self.add_file(relpath)
800 class GitTreeItem(QtGui.QStandardItem):
802 Represents a cell in a treeview.
804 Many GitRepoItems could map to a single repository path,
805 but this tree only has a single column.
806 Each GitRepoItem manages a different cell in the tree view.
809 def __init__(self, path, is_dir):
810 QtGui.QStandardItem.__init__(self)
811 self.is_dir = is_dir
812 self.path = path
813 self.setEditable(False)
814 self.setDragEnabled(False)
815 self.setText(utils.basename(path))
816 if is_dir:
817 icon = icons.directory()
818 else:
819 icon = icons.file_text()
820 self.setIcon(icon)