Merge pull request #1387 from davvid/remote-dialog
[git-cola.git] / cola / widgets / browse.py
blob6472238842a007ea48a87bda8dc8749af258789e
1 import shlex
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 .. import cmds
15 from .. import core
16 from .. import difftool
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, show=True):
29 """Create a new worktree browser"""
30 view = Browser(context, parent, update=update)
31 if parent is None:
32 context.browser_windows.append(view)
33 view.closed.connect(context.browser_windows.remove)
34 model = GitRepoModel(context, view.tree)
35 view.set_model(model)
36 if update:
37 view.refresh()
38 if show:
39 view.show()
40 return view
43 def save_path(context, path, model):
44 """Choose an output filename based on the selected path"""
45 filename = qtutils.save_as(path)
46 if filename:
47 model.filename = filename
48 cmds.do(SaveBlob, context, model)
49 result = True
50 else:
51 result = False
52 return result
55 class Browser(standard.Widget):
56 """A repository branch file browser. Browses files provided by GitRepoModel"""
58 # Read-only mode property
59 mode = property(lambda self: self.model.mode)
61 def __init__(self, context, parent, update=True):
62 standard.Widget.__init__(self, parent)
63 self.tree = RepoTreeView(context, self)
64 self.mainlayout = qtutils.hbox(defs.no_margin, defs.spacing, self.tree)
65 self.setLayout(self.mainlayout)
67 self.model = context.model
68 self.model.updated.connect(self._updated_callback, type=Qt.QueuedConnection)
69 if parent is None:
70 qtutils.add_close_action(self)
71 if update:
72 self._updated_callback()
74 self.init_state(context.settings, self.resize, 720, 420)
76 def set_model(self, model):
77 """Set the model"""
78 self.tree.set_model(model)
80 def refresh(self):
81 """Refresh the model triggering view updates"""
82 self.tree.refresh()
84 def _updated_callback(self):
85 branch = self.model.currentbranch
86 curdir = core.getcwd()
87 msg = N_('Repository: %s') % curdir
88 msg += '\n'
89 msg += N_('Branch: %s') % branch
90 self.setToolTip(msg)
92 scope = {
93 'project': self.model.project,
94 'branch': branch,
96 title = N_('%(project)s: %(branch)s - Browse') % scope
97 if self.mode == self.model.mode_amend:
98 title += ' %s' % N_('(Amending)')
99 self.setWindowTitle(title)
102 class RepoTreeView(standard.TreeView):
103 """Provides a filesystem-like view of a git repository."""
105 def __init__(self, context, parent):
106 standard.TreeView.__init__(self, parent)
108 self.context = context
109 self.selection = context.selection
110 self.saved_selection = []
111 self.saved_current_path = None
112 self.saved_open_folders = set()
113 self.restoring_selection = False
114 self._columns_sized = False
116 self.setDragEnabled(True)
117 self.setRootIsDecorated(False)
118 self.setSortingEnabled(False)
119 self.setSelectionMode(self.ExtendedSelection)
121 # Observe model updates
122 model = context.model
123 model.about_to_update.connect(self.save_selection, type=Qt.QueuedConnection)
124 model.updated.connect(self.update_actions, type=Qt.QueuedConnection)
125 self.expanded.connect(self.index_expanded)
127 self.collapsed.connect(lambda idx: self.size_columns())
128 self.collapsed.connect(self.index_collapsed)
130 # Sync selection before the key press event changes the model index
131 queued = Qt.QueuedConnection
132 self.index_about_to_change.connect(self.sync_selection, type=queued)
134 self.action_history = qtutils.add_action_with_tooltip(
135 self,
136 N_('View History...'),
137 N_('View history for selected paths'),
138 self.view_history,
139 hotkeys.HISTORY,
142 self.action_stage = qtutils.add_action_with_tooltip(
143 self,
144 cmds.StageOrUnstage.name(),
145 N_('Stage/unstage selected paths for commit'),
146 cmds.run(cmds.StageOrUnstage, context),
147 hotkeys.STAGE_SELECTION,
150 self.action_untrack = qtutils.add_action_with_tooltip(
151 self,
152 N_('Untrack Selected'),
153 N_('Stop tracking paths'),
154 self.untrack_selected,
157 self.action_rename = qtutils.add_action_with_tooltip(
158 self, N_('Rename'), N_('Rename selected paths'), self.rename_selected
161 self.action_difftool = qtutils.add_action_with_tooltip(
162 self,
163 difftool.LaunchDifftool.name(),
164 N_('Launch git-difftool on the current path'),
165 cmds.run(difftool.LaunchDifftool, context),
166 hotkeys.DIFF,
169 self.action_difftool_predecessor = qtutils.add_action_with_tooltip(
170 self,
171 N_('Diff Against Predecessor...'),
172 N_('Launch git-difftool against previous versions'),
173 self.diff_predecessor,
174 hotkeys.DIFF_SECONDARY,
177 self.action_revert_unstaged = qtutils.add_action_with_tooltip(
178 self,
179 cmds.RevertUnstagedEdits.name(),
180 N_('Revert unstaged changes to selected paths'),
181 cmds.run(cmds.RevertUnstagedEdits, context),
182 hotkeys.REVERT,
183 hotkeys.REVERT_ALT,
186 self.action_revert_uncommitted = qtutils.add_action_with_tooltip(
187 self,
188 cmds.RevertUncommittedEdits.name(),
189 N_('Revert uncommitted changes to selected paths'),
190 cmds.run(cmds.RevertUncommittedEdits, context),
191 hotkeys.UNDO,
194 self.action_editor = qtutils.add_action_with_tooltip(
195 self,
196 cmds.LaunchEditor.name(),
197 N_('Edit selected paths'),
198 cmds.run(cmds.LaunchEditor, context),
199 hotkeys.EDIT,
202 self.action_blame = qtutils.add_action_with_tooltip(
203 self,
204 cmds.BlamePaths.name(),
205 N_('Blame selected paths'),
206 cmds.run(cmds.BlamePaths, context),
209 self.action_refresh = common.refresh_action(context, self)
211 self.action_default_app = common.default_app_action(
212 context, self, self.selected_paths
215 self.action_parent_dir = common.parent_dir_action(
216 context, self, self.selected_paths
219 self.action_terminal = common.terminal_action(
220 context, self, func=self.selected_paths
223 self.x_width = qtutils.text_width(self.font(), 'x')
224 self.size_columns(force=True)
226 def index_expanded(self, index):
227 """Update information about a directory as it is expanded."""
228 # Remember open folders so that we can restore them when refreshing
229 item = self.name_item_from_index(index)
230 self.saved_open_folders.add(item.path)
231 self.size_columns()
233 # update information about a directory as it is expanded
234 if item.cached:
235 return
236 path = item.path
238 model = self.model()
239 model.populate(item)
240 model.update_entry(path)
242 for row in range(item.rowCount()):
243 path = item.child(row, 0).path
244 model.update_entry(path)
246 item.cached = True
248 def index_collapsed(self, index):
249 item = self.name_item_from_index(index)
250 self.saved_open_folders.remove(item.path)
252 def refresh(self):
253 self.model().refresh()
255 def size_columns(self, force=False):
256 """Set the column widths."""
257 cfg = self.context.cfg
258 should_resize = cfg.get('cola.resizebrowsercolumns', default=False)
259 if not force and not should_resize:
260 return
261 self.resizeColumnToContents(0)
262 self.resizeColumnToContents(1)
263 self.resizeColumnToContents(2)
264 self.resizeColumnToContents(3)
265 self.resizeColumnToContents(4)
267 def sizeHintForColumn(self, column):
268 x_width = self.x_width
270 if column == 1:
271 # Status
272 size = x_width * 11
273 elif column == 2:
274 # Summary
275 size = x_width * 64
276 elif column == 3:
277 # Author
278 size = x_width * 18
279 elif column == 4:
280 # Age
281 size = x_width * 16
282 else:
283 # Filename and others use the actual content
284 size = super().sizeHintForColumn(column)
285 return size
287 def save_selection(self):
288 selection = self.selected_paths()
289 if selection:
290 self.saved_selection = selection
292 current = self.current_item()
293 if current:
294 self.saved_current_path = current.path
296 def restore(self):
297 selection = self.selectionModel()
298 flags = selection.Select | selection.Rows
300 self.restoring_selection = True
302 # Restore opened folders
303 model = self.model()
304 for path in sorted(self.saved_open_folders):
305 row = model.get(path)
306 if not row:
307 continue
308 index = row[0].index()
309 if index.isValid():
310 self.setExpanded(index, True)
312 # Restore the current item. We do this first, otherwise
313 # setCurrentIndex() can mess with the selection we set below
314 current_index = None
315 current_path = self.saved_current_path
316 if current_path:
317 row = model.get(current_path)
318 if row:
319 current_index = row[0].index()
321 if current_index and current_index.isValid():
322 self.setCurrentIndex(current_index)
324 # Restore selected items
325 for path in self.saved_selection:
326 row = model.get(path)
327 if not row:
328 continue
329 index = row[0].index()
330 if index.isValid():
331 self.scrollTo(index)
332 selection.select(index, flags)
334 self.restoring_selection = False
336 # Resize the columns once when cola.resizebrowsercolumns is False.
337 # This provides a good initial size since we will not be resizing
338 # the columns during expand/collapse.
339 if not self._columns_sized:
340 self._columns_sized = True
341 self.size_columns(force=True)
343 self.update_diff()
345 def update_actions(self):
346 """Enable/disable actions."""
347 selection = self.selected_paths()
348 selected = bool(selection)
349 staged = bool(self.selected_staged_paths(selection=selection))
350 modified = bool(self.selected_modified_paths(selection=selection))
351 unstaged = bool(self.selected_unstaged_paths(selection=selection))
352 tracked = bool(self.selected_tracked_paths(selection=selection))
353 revertable = staged or modified
355 self.action_editor.setEnabled(selected)
356 self.action_history.setEnabled(selected)
357 self.action_default_app.setEnabled(selected)
358 self.action_parent_dir.setEnabled(selected)
360 if self.action_terminal is not None:
361 self.action_terminal.setEnabled(selected)
363 self.action_stage.setEnabled(staged or unstaged)
364 self.action_untrack.setEnabled(tracked)
365 self.action_rename.setEnabled(tracked)
366 self.action_difftool.setEnabled(staged or modified)
367 self.action_difftool_predecessor.setEnabled(tracked)
368 self.action_revert_unstaged.setEnabled(revertable)
369 self.action_revert_uncommitted.setEnabled(revertable)
371 def contextMenuEvent(self, event):
372 """Create a context menu."""
373 self.update_actions()
374 menu = qtutils.create_menu(N_('Actions'), self)
375 menu.addAction(self.action_editor)
376 menu.addAction(self.action_stage)
377 menu.addSeparator()
378 menu.addAction(self.action_history)
379 menu.addAction(self.action_difftool)
380 menu.addAction(self.action_difftool_predecessor)
381 menu.addAction(self.action_blame)
382 menu.addSeparator()
383 menu.addAction(self.action_revert_unstaged)
384 menu.addAction(self.action_revert_uncommitted)
385 menu.addAction(self.action_untrack)
386 menu.addAction(self.action_rename)
387 menu.addSeparator()
388 menu.addAction(self.action_default_app)
389 menu.addAction(self.action_parent_dir)
391 if self.action_terminal is not None:
392 menu.addAction(self.action_terminal)
393 menu.exec_(self.mapToGlobal(event.pos()))
395 def mousePressEvent(self, event):
396 """Synchronize the selection on mouse-press."""
397 result = QtWidgets.QTreeView.mousePressEvent(self, event)
398 self.sync_selection()
399 return result
401 def sync_selection(self):
402 """Push selection into the selection model."""
403 staged = []
404 unmerged = []
405 modified = []
406 untracked = []
407 state = State(staged, unmerged, modified, untracked)
409 paths = self.selected_paths()
410 model = self.context.model
411 model_staged = utils.add_parents(model.staged)
412 model_modified = utils.add_parents(model.modified)
413 model_unmerged = utils.add_parents(model.unmerged)
414 model_untracked = utils.add_parents(model.untracked)
416 for path in paths:
417 if path in model_unmerged:
418 unmerged.append(path)
419 elif path in model_untracked:
420 untracked.append(path)
421 elif path in model_staged:
422 staged.append(path)
423 elif path in model_modified:
424 modified.append(path)
425 else:
426 staged.append(path)
427 # Push the new selection into the model.
428 self.selection.set_selection(state)
429 return paths
431 def selectionChanged(self, old, new):
432 """Override selectionChanged to update available actions."""
433 result = QtWidgets.QTreeView.selectionChanged(self, old, new)
434 if not self.restoring_selection:
435 self.update_actions()
436 self.update_diff()
437 return result
439 def update_diff(self):
440 context = self.context
441 model = context.model
442 paths = self.sync_selection()
443 if paths and self.model().path_is_interesting(paths[0]):
444 cached = paths[0] in model.staged
445 cmds.do(cmds.Diff, context, paths[0], cached)
447 def set_model(self, model):
448 """Set the concrete QAbstractItemModel instance."""
449 self.setModel(model)
450 model.restore.connect(self.restore, type=Qt.QueuedConnection)
452 def name_item_from_index(self, model_index):
453 """Return the name item corresponding to the model index."""
454 index = model_index.sibling(model_index.row(), 0)
455 return self.model().itemFromIndex(index)
457 def paths_from_indexes(self, indexes):
458 return qtutils.paths_from_indexes(
459 self.model(), indexes, item_type=GitRepoNameItem.TYPE
462 def selected_paths(self):
463 """Return the selected paths."""
464 return self.paths_from_indexes(self.selectedIndexes())
466 def selected_staged_paths(self, selection=None):
467 """Return selected staged paths."""
468 if selection is None:
469 selection = self.selected_paths()
470 model = self.context.model
471 staged = utils.add_parents(model.staged)
472 return [p for p in selection if p in staged]
474 def selected_modified_paths(self, selection=None):
475 """Return selected modified paths."""
476 if selection is None:
477 selection = self.selected_paths()
478 model = self.context.model
479 modified = utils.add_parents(model.modified)
480 return [p for p in selection if p in modified]
482 def selected_unstaged_paths(self, selection=None):
483 """Return selected unstaged paths."""
484 if selection is None:
485 selection = self.selected_paths()
486 model = self.context.model
487 modified = utils.add_parents(model.modified)
488 untracked = utils.add_parents(model.untracked)
489 unstaged = modified.union(untracked)
490 return [p for p in selection if p in unstaged]
492 def selected_tracked_paths(self, selection=None):
493 """Return selected tracked paths."""
494 if selection is None:
495 selection = self.selected_paths()
496 model = self.context.model
497 staged = set(self.selected_staged_paths(selection=selection))
498 modified = set(self.selected_modified_paths(selection=selection))
499 untracked = utils.add_parents(model.untracked)
500 tracked = staged.union(modified)
501 return [p for p in selection if p not in untracked or p in tracked]
503 def view_history(self):
504 """Launch the configured history browser path-limited to entries."""
505 paths = self.selected_paths()
506 cmds.do(cmds.VisualizePaths, self.context, paths)
508 def untrack_selected(self):
509 """Untrack selected paths."""
510 context = self.context
511 cmds.do(cmds.Untrack, context, self.selected_tracked_paths())
513 def rename_selected(self):
514 """Untrack selected paths."""
515 context = self.context
516 cmds.do(cmds.Rename, context, self.selected_tracked_paths())
518 def diff_predecessor(self):
519 """Diff paths against previous versions."""
520 context = self.context
521 paths = self.selected_tracked_paths()
522 args = ['--'] + paths
523 revs, summaries = gitcmds.log_helper(context, all=False, extra_args=args)
524 commits = select_commits(
525 context, N_('Select Previous Version'), revs, summaries, multiselect=False
527 if not commits:
528 return
529 commit = commits[0]
530 difftool.difftool_launch(context, left=commit, paths=paths)
532 def current_path(self):
533 """Return the path for the current item."""
534 index = self.currentIndex()
535 if not index.isValid():
536 return None
537 return self.name_item_from_index(index).path
540 class BrowseModel:
541 """Context data used for browsing branches via git-ls-tree"""
543 def __init__(self, ref, filename=None):
544 self.ref = ref
545 self.relpath = filename
546 self.filename = filename
549 class SaveBlob(cmds.ContextCommand):
550 def __init__(self, context, model):
551 super().__init__(context)
552 self.browse_model = model
554 def do(self):
555 git = self.context.git
556 model = self.browse_model
557 ref = f'{model.ref}:{model.relpath}'
558 with core.xopen(model.filename, 'wb') as fp:
559 status, output, err = git.show(ref, _stdout=fp)
561 out = '# git show {} >{}\n{}'.format(
562 shlex.quote(ref),
563 shlex.quote(model.filename),
564 output,
566 Interaction.command(N_('Error Saving File'), 'git show', status, out, err)
567 if status != 0:
568 return
570 msg = N_('Saved "%(filename)s" from "%(ref)s" to "%(destination)s"') % {
571 'filename': model.relpath,
572 'ref': model.ref,
573 'destination': model.filename,
575 Interaction.log_status(status, msg, '')
577 Interaction.information(
578 N_('File Saved'), N_('File saved to "%s"') % model.filename
582 class BrowseBranch(standard.Dialog):
583 @classmethod
584 def browse(cls, context, ref):
585 model = BrowseModel(ref)
586 dlg = cls(context, model, parent=qtutils.active_window())
587 dlg_model = GitTreeModel(context, ref, dlg)
588 dlg.setModel(dlg_model)
589 dlg.setWindowTitle(N_('Browsing %s') % model.ref)
590 dlg.show()
591 dlg.raise_()
592 if dlg.exec_() != dlg.Accepted:
593 return None
594 return dlg
596 def __init__(self, context, model, parent=None):
597 standard.Dialog.__init__(self, parent=parent)
598 if parent is not None:
599 self.setWindowModality(Qt.WindowModal)
601 # updated for use by commands
602 self.context = context
603 self.model = model
605 # widgets
606 self.tree = GitTreeWidget(parent=self)
607 self.close_button = qtutils.close_button()
609 text = N_('Save')
610 self.save = qtutils.create_button(text=text, enabled=False, default=True)
612 # layouts
613 self.btnlayt = qtutils.hbox(
614 defs.margin, defs.spacing, self.close_button, qtutils.STRETCH, self.save
617 self.layt = qtutils.vbox(defs.margin, defs.spacing, self.tree, self.btnlayt)
618 self.setLayout(self.layt)
620 # connections
621 self.tree.path_chosen.connect(self.save_path)
623 self.tree.selection_changed.connect(
624 self.selection_changed, type=Qt.QueuedConnection
627 qtutils.connect_button(self.close_button, self.close)
628 qtutils.connect_button(self.save, self.save_blob)
629 self.init_size(parent=parent)
631 def expandAll(self):
632 self.tree.expandAll()
634 def setModel(self, model):
635 self.tree.setModel(model)
637 def path_chosen(self, path, close=True):
638 """Update the model from the view"""
639 model = self.model
640 model.relpath = path
641 model.filename = path
642 if close:
643 self.accept()
645 def save_path(self, path):
646 """Choose an output filename based on the selected path"""
647 self.path_chosen(path, close=False)
648 if save_path(self.context, path, self.model):
649 self.accept()
651 def save_blob(self):
652 """Save the currently selected file"""
653 filenames = self.tree.selected_files()
654 if not filenames:
655 return
656 self.save_path(filenames[0])
658 def selection_changed(self):
659 """Update actions based on the current selection"""
660 filenames = self.tree.selected_files()
661 self.save.setEnabled(bool(filenames))
664 class GitTreeWidget(standard.TreeView):
665 selection_changed = Signal()
666 path_chosen = Signal(object)
668 def __init__(self, parent=None):
669 standard.TreeView.__init__(self, parent)
670 self.setHeaderHidden(True)
671 self.doubleClicked.connect(self.double_clicked)
673 def double_clicked(self, index):
674 item = self.model().itemFromIndex(index)
675 if item is None:
676 return
677 if item.is_dir:
678 return
679 self.path_chosen.emit(item.path)
681 def selected_files(self):
682 items = self.selected_items()
683 return [i.path for i in items if not i.is_dir]
685 def selectionChanged(self, old_selection, new_selection):
686 QtWidgets.QTreeView.selectionChanged(self, old_selection, new_selection)
687 self.selection_changed.emit()
689 def select_first_file(self):
690 """Select the first filename in the tree"""
691 model = self.model()
692 idx = self.indexAt(QtCore.QPoint(0, 0))
693 item = model.itemFromIndex(idx)
694 while idx and idx.isValid() and item and item.is_dir:
695 idx = self.indexBelow(idx)
696 item = model.itemFromIndex(idx)
698 if idx and idx.isValid() and item:
699 self.setCurrentIndex(idx)
702 class GitFileTreeModel(QtGui.QStandardItemModel):
703 """Presents a list of file paths as a hierarchical tree."""
705 def __init__(self, parent):
706 QtGui.QStandardItemModel.__init__(self, parent)
707 self.dir_entries = {'': self.invisibleRootItem()}
708 self.dir_rows = {}
710 def clear(self):
711 QtGui.QStandardItemModel.clear(self)
712 self.dir_rows = {}
713 self.dir_entries = {'': self.invisibleRootItem()}
715 def add_files(self, files):
716 """Add a list of files"""
717 add_file = self.add_file
718 for f in files:
719 add_file(f)
721 def add_file(self, path):
722 """Add a file to the model."""
723 dirname = utils.dirname(path)
724 dir_entries = self.dir_entries
725 try:
726 parent = dir_entries[dirname]
727 except KeyError:
728 parent = dir_entries[dirname] = self.create_dir_entry(dirname)
730 row_items = create_row(path, False)
731 parent.appendRow(row_items)
733 def add_directory(self, parent, path):
734 """Add a directory entry to the model."""
735 # Create model items
736 row_items = create_row(path, True)
738 try:
739 parent_path = parent.path
740 except AttributeError: # root QStandardItem
741 parent_path = ''
743 # Insert directories before file paths
744 try:
745 row = self.dir_rows[parent_path]
746 except KeyError:
747 row = self.dir_rows[parent_path] = 0
749 parent.insertRow(row, row_items)
750 self.dir_rows[parent_path] += 1
751 self.dir_entries[path] = row_items[0]
753 return row_items[0]
755 def create_dir_entry(self, dirname):
757 Create a directory entry for the model.
759 This ensures that directories are always listed before files.
762 entries = dirname.split('/')
763 curdir = []
764 parent = self.invisibleRootItem()
765 curdir_append = curdir.append
766 self_add_directory = self.add_directory
767 dir_entries = self.dir_entries
768 for entry in entries:
769 curdir_append(entry)
770 path = '/'.join(curdir)
771 try:
772 parent = dir_entries[path]
773 except KeyError:
774 grandparent = parent
775 parent = self_add_directory(grandparent, path)
776 dir_entries[path] = parent
777 return parent
780 def create_row(path, is_dir):
781 """Return a list of items representing a row."""
782 return [GitTreeItem(path, is_dir)]
785 class GitTreeModel(GitFileTreeModel):
786 def __init__(self, context, ref, parent):
787 GitFileTreeModel.__init__(self, parent)
788 self.context = context
789 self.ref = ref
790 self._initialize()
792 def _initialize(self):
793 """Iterate over git-ls-tree and create GitTreeItems."""
794 git = self.context.git
795 status, out, err = git.ls_tree('--full-tree', '-r', '-t', '-z', self.ref)
796 if status != 0:
797 Interaction.log_status(status, out, err)
798 return
800 if not out:
801 return
803 for line in out[:-1].split('\0'):
804 # .....6 ...4 ......................................40
805 # 040000 tree c127cde9a0c644a3a8fef449a244f47d5272dfa6 relative
806 # 100644 blob 139e42bf4acaa4927ec9be1ec55a252b97d3f1e2 relative/path
807 objtype = line[7]
808 relpath = line[6 + 1 + 4 + 1 + 40 + 1 :]
809 if objtype == 't':
810 parent = self.dir_entries[utils.dirname(relpath)]
811 self.add_directory(parent, relpath)
812 elif objtype == 'b':
813 self.add_file(relpath)
816 class GitTreeItem(QtGui.QStandardItem):
818 Represents a cell in a tree view.
820 Many GitRepoItems could map to a single repository path,
821 but this tree only has a single column.
822 Each GitRepoItem manages a different cell in the tree view.
826 def __init__(self, path, is_dir):
827 QtGui.QStandardItem.__init__(self)
828 self.is_dir = is_dir
829 self.path = path
830 self.setEditable(False)
831 self.setDragEnabled(False)
832 self.setText(utils.basename(path))
833 if is_dir:
834 icon = icons.directory()
835 else:
836 icon = icons.file_text()
837 self.setIcon(icon)