widgets: pylint tweaks, docstrings and use queued connections
[git-cola.git] / cola / widgets / browse.py
blobf62fa1a53de01d24dfca4f20b6d5848014cc14e6
1 from __future__ import absolute_import, division, print_function, unicode_literals
2 import shlex
4 from qtpy.QtCore import Qt
5 from qtpy.QtCore import Signal
6 from qtpy import QtCore
7 from qtpy import QtGui
8 from qtpy import QtWidgets
10 from ..models.browse import GitRepoModel
11 from ..models.browse import GitRepoNameItem
12 from ..models.selection import State
13 from ..i18n import N_
14 from ..interaction import Interaction
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, 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"""
57 # Read-only mode property
58 mode = property(lambda self: self.model.mode)
60 def __init__(self, context, parent, update=True):
61 standard.Widget.__init__(self, parent)
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.model = context.model
67 self.model.updated.connect(self._updated_callback, type=Qt.QueuedConnection)
68 if parent is None:
69 qtutils.add_close_action(self)
70 if update:
71 self._updated_callback()
73 self.init_state(context.settings, self.resize, 720, 420)
75 def set_model(self, model):
76 """Set the model"""
77 self.tree.set_model(model)
79 def refresh(self):
80 """Refresh the model triggering view updates"""
81 self.tree.refresh()
83 def _updated_callback(self):
84 branch = self.model.currentbranch
85 curdir = core.getcwd()
86 msg = N_('Repository: %s') % curdir
87 msg += '\n'
88 msg += N_('Branch: %s') % branch
89 self.setToolTip(msg)
91 scope = dict(project=self.model.project, branch=branch)
92 title = N_('%(project)s: %(branch)s - Browse') % scope
93 if self.mode == self.model.mode_amend:
94 title += ' %s' % N_('(Amending)')
95 self.setWindowTitle(title)
98 # pylint: disable=too-many-ancestors
99 class RepoTreeView(standard.TreeView):
100 """Provides a filesystem-like view of a git repository."""
102 def __init__(self, context, parent):
103 standard.TreeView.__init__(self, parent)
105 self.context = context
106 self.selection = context.selection
107 self.saved_selection = []
108 self.saved_current_path = None
109 self.saved_open_folders = set()
110 self.restoring_selection = False
111 self._columns_sized = False
113 self.setDragEnabled(True)
114 self.setRootIsDecorated(False)
115 self.setSortingEnabled(False)
116 self.setSelectionMode(self.ExtendedSelection)
118 # Observe model updates
119 model = context.model
120 model.about_to_update.connect(self.save_selection, type=Qt.QueuedConnection)
121 model.updated.connect(self.update_actions, type=Qt.QueuedConnection)
122 self.expanded.connect(self.index_expanded)
124 self.collapsed.connect(lambda idx: self.size_columns())
125 self.collapsed.connect(self.index_collapsed)
127 # Sync selection before the key press event changes the model index
128 queued = Qt.QueuedConnection
129 self.index_about_to_change.connect(self.sync_selection, type=queued)
131 self.action_history = qtutils.add_action_with_status_tip(
132 self,
133 N_('View History...'),
134 N_('View history for selected paths'),
135 self.view_history,
136 hotkeys.HISTORY,
139 self.action_stage = qtutils.add_action_with_status_tip(
140 self,
141 cmds.StageOrUnstage.name(),
142 N_('Stage/unstage selected paths for commit'),
143 cmds.run(cmds.StageOrUnstage, context),
144 hotkeys.STAGE_SELECTION,
147 self.action_untrack = qtutils.add_action_with_status_tip(
148 self,
149 N_('Untrack Selected'),
150 N_('Stop tracking paths'),
151 self.untrack_selected,
154 self.action_rename = qtutils.add_action_with_status_tip(
155 self, N_('Rename'), N_('Rename selected paths'), self.rename_selected
158 self.action_difftool = qtutils.add_action_with_status_tip(
159 self,
160 cmds.LaunchDifftool.name(),
161 N_('Launch git-difftool on the current path'),
162 cmds.run(cmds.LaunchDifftool, context),
163 hotkeys.DIFF,
166 self.action_difftool_predecessor = qtutils.add_action_with_status_tip(
167 self,
168 N_('Diff Against Predecessor...'),
169 N_('Launch git-difftool against previous versions'),
170 self.diff_predecessor,
171 hotkeys.DIFF_SECONDARY,
174 self.action_revert_unstaged = qtutils.add_action_with_status_tip(
175 self,
176 cmds.RevertUnstagedEdits.name(),
177 N_('Revert unstaged changes to selected paths'),
178 cmds.run(cmds.RevertUnstagedEdits, context),
179 hotkeys.REVERT,
182 self.action_revert_uncommitted = qtutils.add_action_with_status_tip(
183 self,
184 cmds.RevertUncommittedEdits.name(),
185 N_('Revert uncommitted changes to selected paths'),
186 cmds.run(cmds.RevertUncommittedEdits, context),
187 hotkeys.UNDO,
190 self.action_editor = qtutils.add_action_with_status_tip(
191 self,
192 cmds.LaunchEditor.name(),
193 N_('Edit selected paths'),
194 cmds.run(cmds.LaunchEditor, context),
195 hotkeys.EDIT,
198 self.action_blame = qtutils.add_action_with_status_tip(
199 self,
200 cmds.BlamePaths.name(),
201 N_('Blame selected paths'),
202 cmds.run(cmds.BlamePaths, context),
205 self.action_refresh = common.refresh_action(context, self)
207 self.action_default_app = common.default_app_action(
208 context, self, self.selected_paths
211 self.action_parent_dir = common.parent_dir_action(
212 context, self, self.selected_paths
215 self.action_terminal = common.terminal_action(
216 context, self, func=self.selected_paths
219 self.x_width = QtGui.QFontMetrics(self.font()).width('x')
220 self.size_columns(force=True)
222 def index_expanded(self, index):
223 """Update information about a directory as it is expanded."""
224 # Remember open folders so that we can restore them when refreshing
225 item = self.name_item_from_index(index)
226 self.saved_open_folders.add(item.path)
227 self.size_columns()
229 # update information about a directory as it is expanded
230 if item.cached:
231 return
232 path = item.path
234 model = self.model()
235 model.populate(item)
236 model.update_entry(path)
238 for row in range(item.rowCount()):
239 path = item.child(row, 0).path
240 model.update_entry(path)
242 item.cached = True
244 def index_collapsed(self, index):
245 item = self.name_item_from_index(index)
246 self.saved_open_folders.remove(item.path)
248 def refresh(self):
249 self.model().refresh()
251 def size_columns(self, force=False):
252 """Set the column widths."""
253 cfg = self.context.cfg
254 should_resize = cfg.get('cola.resizebrowsercolumns', default=False)
255 if not force and not should_resize:
256 return
257 self.resizeColumnToContents(0)
258 self.resizeColumnToContents(1)
259 self.resizeColumnToContents(2)
260 self.resizeColumnToContents(3)
261 self.resizeColumnToContents(4)
263 def sizeHintForColumn(self, column):
264 x_width = self.x_width
266 if column == 1:
267 # Status
268 size = x_width * 11
269 elif column == 2:
270 # Summary
271 size = x_width * 64
272 elif column == 3:
273 # Author
274 size = x_width * 18
275 elif column == 4:
276 # Age
277 size = x_width * 16
278 else:
279 # Filename and others use the actual content
280 size = super(RepoTreeView, self).sizeHintForColumn(column)
281 return size
283 def save_selection(self):
284 selection = self.selected_paths()
285 if selection:
286 self.saved_selection = selection
288 current = self.current_item()
289 if current:
290 self.saved_current_path = current.path
292 def restore(self):
293 selection = self.selectionModel()
294 flags = selection.Select | selection.Rows
296 self.restoring_selection = True
298 # Restore opened folders
299 model = self.model()
300 for path in sorted(self.saved_open_folders):
301 row = model.get(path)
302 if not row:
303 continue
304 index = row[0].index()
305 if index.isValid():
306 self.setExpanded(index, True)
308 # Restore the current item. We do this first, otherwise
309 # setCurrentIndex() can mess with the selection we set below
310 current_index = None
311 current_path = self.saved_current_path
312 if current_path:
313 row = model.get(current_path)
314 if row:
315 current_index = row[0].index()
317 if current_index and current_index.isValid():
318 self.setCurrentIndex(current_index)
320 # Restore selected items
321 for path in self.saved_selection:
322 row = model.get(path)
323 if not row:
324 continue
325 index = row[0].index()
326 if index.isValid():
327 self.scrollTo(index)
328 selection.select(index, flags)
330 self.restoring_selection = False
332 # Resize the columns once when cola.resizebrowsercolumns is False.
333 # This provides a good initial size since we will not be resizing
334 # the columns during expand/collapse.
335 if not self._columns_sized:
336 self._columns_sized = True
337 self.size_columns(force=True)
339 self.update_diff()
341 def update_actions(self):
342 """Enable/disable actions."""
343 selection = self.selected_paths()
344 selected = bool(selection)
345 staged = bool(self.selected_staged_paths(selection=selection))
346 modified = bool(self.selected_modified_paths(selection=selection))
347 unstaged = bool(self.selected_unstaged_paths(selection=selection))
348 tracked = bool(self.selected_tracked_paths(selection=selection))
349 revertable = staged or modified
351 self.action_editor.setEnabled(selected)
352 self.action_history.setEnabled(selected)
353 self.action_default_app.setEnabled(selected)
354 self.action_parent_dir.setEnabled(selected)
356 if self.action_terminal is not None:
357 self.action_terminal.setEnabled(selected)
359 self.action_stage.setEnabled(staged or unstaged)
360 self.action_untrack.setEnabled(tracked)
361 self.action_rename.setEnabled(tracked)
362 self.action_difftool.setEnabled(staged or modified)
363 self.action_difftool_predecessor.setEnabled(tracked)
364 self.action_revert_unstaged.setEnabled(revertable)
365 self.action_revert_uncommitted.setEnabled(revertable)
367 def contextMenuEvent(self, event):
368 """Create a context menu."""
369 self.update_actions()
370 menu = qtutils.create_menu(N_('Actions'), self)
371 menu.addAction(self.action_editor)
372 menu.addAction(self.action_stage)
373 menu.addSeparator()
374 menu.addAction(self.action_history)
375 menu.addAction(self.action_difftool)
376 menu.addAction(self.action_difftool_predecessor)
377 menu.addAction(self.action_blame)
378 menu.addSeparator()
379 menu.addAction(self.action_revert_unstaged)
380 menu.addAction(self.action_revert_uncommitted)
381 menu.addAction(self.action_untrack)
382 menu.addAction(self.action_rename)
383 menu.addSeparator()
384 menu.addAction(self.action_default_app)
385 menu.addAction(self.action_parent_dir)
387 if self.action_terminal is not None:
388 menu.addAction(self.action_terminal)
389 menu.exec_(self.mapToGlobal(event.pos()))
391 def mousePressEvent(self, event):
392 """Synchronize the selection on mouse-press."""
393 result = QtWidgets.QTreeView.mousePressEvent(self, event)
394 self.sync_selection()
395 return result
397 def sync_selection(self):
398 """Push selection into the selection model."""
399 staged = []
400 unmerged = []
401 modified = []
402 untracked = []
403 state = State(staged, unmerged, modified, untracked)
405 paths = self.selected_paths()
406 model = self.context.model
407 model_staged = utils.add_parents(model.staged)
408 model_modified = utils.add_parents(model.modified)
409 model_unmerged = utils.add_parents(model.unmerged)
410 model_untracked = utils.add_parents(model.untracked)
412 for path in paths:
413 if path in model_unmerged:
414 unmerged.append(path)
415 elif path in model_untracked:
416 untracked.append(path)
417 elif path in model_staged:
418 staged.append(path)
419 elif path in model_modified:
420 modified.append(path)
421 else:
422 staged.append(path)
423 # Push the new selection into the model.
424 self.selection.set_selection(state)
425 return paths
427 def selectionChanged(self, old, new):
428 """Override selectionChanged to update available actions."""
429 result = QtWidgets.QTreeView.selectionChanged(self, old, new)
430 if not self.restoring_selection:
431 self.update_actions()
432 self.update_diff()
433 return result
435 def update_diff(self):
436 context = self.context
437 model = context.model
438 paths = self.sync_selection()
439 if paths and self.model().path_is_interesting(paths[0]):
440 cached = paths[0] in model.staged
441 cmds.do(cmds.Diff, context, paths[0], cached)
443 def set_model(self, model):
444 """Set the concrete QAbstractItemModel instance."""
445 self.setModel(model)
446 model.restore.connect(self.restore, type=Qt.QueuedConnection)
448 def name_item_from_index(self, model_index):
449 """Return the name item corresponding to the model index."""
450 index = model_index.sibling(model_index.row(), 0)
451 return self.model().itemFromIndex(index)
453 def paths_from_indexes(self, indexes):
454 return qtutils.paths_from_indexes(
455 self.model(), indexes, item_type=GitRepoNameItem.TYPE
458 def selected_paths(self):
459 """Return the selected paths."""
460 return self.paths_from_indexes(self.selectedIndexes())
462 def selected_staged_paths(self, selection=None):
463 """Return selected staged paths."""
464 if selection is None:
465 selection = self.selected_paths()
466 model = self.context.model
467 staged = utils.add_parents(model.staged)
468 return [p for p in selection if p in staged]
470 def selected_modified_paths(self, selection=None):
471 """Return selected modified paths."""
472 if selection is None:
473 selection = self.selected_paths()
474 model = self.context.model
475 modified = utils.add_parents(model.modified)
476 return [p for p in selection if p in modified]
478 def selected_unstaged_paths(self, selection=None):
479 """Return selected unstaged paths."""
480 if selection is None:
481 selection = self.selected_paths()
482 model = self.context.model
483 modified = utils.add_parents(model.modified)
484 untracked = utils.add_parents(model.untracked)
485 unstaged = modified.union(untracked)
486 return [p for p in selection if p in unstaged]
488 def selected_tracked_paths(self, selection=None):
489 """Return selected tracked paths."""
490 if selection is None:
491 selection = self.selected_paths()
492 model = self.context.model
493 staged = set(self.selected_staged_paths(selection=selection))
494 modified = set(self.selected_modified_paths(selection=selection))
495 untracked = utils.add_parents(model.untracked)
496 tracked = staged.union(modified)
497 return [p for p in selection if p not in untracked or p in tracked]
499 def view_history(self):
500 """Launch the configured history browser path-limited to entries."""
501 paths = self.selected_paths()
502 cmds.do(cmds.VisualizePaths, self.context, paths)
504 def untrack_selected(self):
505 """untrack selected paths."""
506 context = self.context
507 cmds.do(cmds.Untrack, context, self.selected_tracked_paths())
509 def rename_selected(self):
510 """untrack selected paths."""
511 context = self.context
512 cmds.do(cmds.Rename, context, self.selected_tracked_paths())
514 def diff_predecessor(self):
515 """Diff paths against previous versions."""
516 context = self.context
517 paths = self.selected_tracked_paths()
518 args = ['--'] + paths
519 revs, summaries = gitcmds.log_helper(context, all=False, extra_args=args)
520 commits = select_commits(
521 context, N_('Select Previous Version'), revs, summaries, multiselect=False
523 if not commits:
524 return
525 commit = commits[0]
526 cmds.difftool_launch(context, left=commit, paths=paths)
528 def current_path(self):
529 """Return the path for the current item."""
530 index = self.currentIndex()
531 if not index.isValid():
532 return None
533 return self.name_item_from_index(index).path
536 class BrowseModel(object):
537 """Context data used for browsing branches via git-ls-tree"""
539 def __init__(self, ref, filename=None):
540 self.ref = ref
541 self.relpath = filename
542 self.filename = filename
545 class SaveBlob(cmds.ContextCommand):
546 def __init__(self, context, model):
547 super(SaveBlob, self).__init__(context)
548 self.browse_model = model
550 def do(self):
551 git = self.context.git
552 model = self.browse_model
553 ref = '%s:%s' % (model.ref, model.relpath)
554 with core.xopen(model.filename, 'wb') as fp:
555 status, output, err = git.show(ref, _stdout=fp)
557 out = '# git show %s >%s\n%s' % (
558 shlex.quote(ref),
559 shlex.quote(model.filename),
560 output,
562 Interaction.command(N_('Error Saving File'), 'git show', status, out, err)
563 if status != 0:
564 return
566 msg = N_('Saved "%(filename)s" from "%(ref)s" to "%(destination)s"') % dict(
567 filename=model.relpath, ref=model.ref, destination=model.filename
569 Interaction.log_status(status, msg, '')
571 Interaction.information(
572 N_('File Saved'), N_('File saved to "%s"') % model.filename
576 class BrowseBranch(standard.Dialog):
577 @classmethod
578 def browse(cls, context, ref):
579 model = BrowseModel(ref)
580 dlg = cls(context, model, parent=qtutils.active_window())
581 dlg_model = GitTreeModel(context, ref, dlg)
582 dlg.setModel(dlg_model)
583 dlg.setWindowTitle(N_('Browsing %s') % model.ref)
584 dlg.show()
585 dlg.raise_()
586 if dlg.exec_() != dlg.Accepted:
587 return None
588 return dlg
590 def __init__(self, context, model, parent=None):
591 standard.Dialog.__init__(self, parent=parent)
592 if parent is not None:
593 self.setWindowModality(Qt.WindowModal)
595 # updated for use by commands
596 self.context = context
597 self.model = model
599 # widgets
600 self.tree = GitTreeWidget(parent=self)
601 self.close_button = qtutils.close_button()
603 text = N_('Save')
604 self.save = qtutils.create_button(text=text, enabled=False, default=True)
606 # layouts
607 self.btnlayt = qtutils.hbox(
608 defs.margin, defs.spacing, self.close_button, qtutils.STRETCH, self.save
611 self.layt = qtutils.vbox(defs.margin, defs.spacing, self.tree, self.btnlayt)
612 self.setLayout(self.layt)
614 # connections
615 self.tree.path_chosen.connect(self.save_path)
617 self.tree.selection_changed.connect(
618 self.selection_changed, type=Qt.QueuedConnection
621 qtutils.connect_button(self.close_button, self.close)
622 qtutils.connect_button(self.save, self.save_blob)
623 self.init_size(parent=parent)
625 def expandAll(self):
626 self.tree.expandAll()
628 def setModel(self, model):
629 self.tree.setModel(model)
631 def path_chosen(self, path, close=True):
632 """Update the model from the view"""
633 model = self.model
634 model.relpath = path
635 model.filename = path
636 if close:
637 self.accept()
639 def save_path(self, path):
640 """Choose an output filename based on the selected path"""
641 self.path_chosen(path, close=False)
642 if save_path(self.context, path, self.model):
643 self.accept()
645 def save_blob(self):
646 """Save the currently selected file"""
647 filenames = self.tree.selected_files()
648 if not filenames:
649 return
650 self.save_path(filenames[0])
652 def selection_changed(self):
653 """Update actions based on the current selection"""
654 filenames = self.tree.selected_files()
655 self.save.setEnabled(bool(filenames))
658 # pylint: disable=too-many-ancestors
659 class GitTreeWidget(standard.TreeView):
661 selection_changed = Signal()
662 path_chosen = Signal(object)
664 def __init__(self, parent=None):
665 standard.TreeView.__init__(self, parent)
666 self.setHeaderHidden(True)
667 # pylint: disable=no-member
668 self.doubleClicked.connect(self.double_clicked)
670 def double_clicked(self, index):
671 item = self.model().itemFromIndex(index)
672 if item is None:
673 return
674 if item.is_dir:
675 return
676 self.path_chosen.emit(item.path)
678 def selected_files(self):
679 items = self.selected_items()
680 return [i.path for i in items if not i.is_dir]
682 def selectionChanged(self, old_selection, new_selection):
683 QtWidgets.QTreeView.selectionChanged(self, old_selection, new_selection)
684 self.selection_changed.emit()
686 def select_first_file(self):
687 """Select the first filename in the tree"""
688 model = self.model()
689 idx = self.indexAt(QtCore.QPoint(0, 0))
690 item = model.itemFromIndex(idx)
691 while idx and idx.isValid() and item and item.is_dir:
692 idx = self.indexBelow(idx)
693 item = model.itemFromIndex(idx)
695 if idx and idx.isValid() and item:
696 self.setCurrentIndex(idx)
699 class GitFileTreeModel(QtGui.QStandardItemModel):
700 """Presents a list of file paths as a hierarchical tree."""
702 def __init__(self, parent):
703 QtGui.QStandardItemModel.__init__(self, parent)
704 self.dir_entries = {'': self.invisibleRootItem()}
705 self.dir_rows = {}
707 def clear(self):
708 QtGui.QStandardItemModel.clear(self)
709 self.dir_rows = {}
710 self.dir_entries = {'': self.invisibleRootItem()}
712 def add_files(self, files):
713 """Add a list of files"""
714 add_file = self.add_file
715 for f in files:
716 add_file(f)
718 def add_file(self, path):
719 """Add a file to the model."""
720 dirname = utils.dirname(path)
721 dir_entries = self.dir_entries
722 try:
723 parent = dir_entries[dirname]
724 except KeyError:
725 parent = dir_entries[dirname] = self.create_dir_entry(dirname)
727 row_items = create_row(path, False)
728 parent.appendRow(row_items)
730 def add_directory(self, parent, path):
731 """Add a directory entry to the model."""
732 # Create model items
733 row_items = create_row(path, True)
735 try:
736 parent_path = parent.path
737 except AttributeError: # root QStandardItem
738 parent_path = ''
740 # Insert directories before file paths
741 try:
742 row = self.dir_rows[parent_path]
743 except KeyError:
744 row = self.dir_rows[parent_path] = 0
746 parent.insertRow(row, row_items)
747 self.dir_rows[parent_path] += 1
748 self.dir_entries[path] = row_items[0]
750 return row_items[0]
752 def create_dir_entry(self, dirname):
754 Create a directory entry for the model.
756 This ensures that directories are always listed before files.
759 entries = dirname.split('/')
760 curdir = []
761 parent = self.invisibleRootItem()
762 curdir_append = curdir.append
763 self_add_directory = self.add_directory
764 dir_entries = self.dir_entries
765 for entry in entries:
766 curdir_append(entry)
767 path = '/'.join(curdir)
768 try:
769 parent = dir_entries[path]
770 except KeyError:
771 grandparent = parent
772 parent = self_add_directory(grandparent, path)
773 dir_entries[path] = parent
774 return parent
777 def create_row(path, is_dir):
778 """Return a list of items representing a row."""
779 return [GitTreeItem(path, is_dir)]
782 class GitTreeModel(GitFileTreeModel):
783 def __init__(self, context, ref, parent):
784 GitFileTreeModel.__init__(self, parent)
785 self.context = context
786 self.ref = ref
787 self._initialize()
789 def _initialize(self):
790 """Iterate over git-ls-tree and create GitTreeItems."""
791 git = self.context.git
792 status, out, err = git.ls_tree('--full-tree', '-r', '-t', '-z', self.ref)
793 if status != 0:
794 Interaction.log_status(status, out, err)
795 return
797 if not out:
798 return
800 for line in out[:-1].split('\0'):
801 # .....6 ...4 ......................................40
802 # 040000 tree c127cde9a0c644a3a8fef449a244f47d5272dfa6 relative
803 # 100644 blob 139e42bf4acaa4927ec9be1ec55a252b97d3f1e2 relative/path
804 objtype = line[7]
805 relpath = line[6 + 1 + 4 + 1 + 40 + 1 :]
806 if objtype == 't':
807 parent = self.dir_entries[utils.dirname(relpath)]
808 self.add_directory(parent, relpath)
809 elif objtype == 'b':
810 self.add_file(relpath)
813 class GitTreeItem(QtGui.QStandardItem):
815 Represents a cell in a treeview.
817 Many GitRepoItems could map to a single repository path,
818 but this tree only has a single column.
819 Each GitRepoItem manages a different cell in the tree view.
823 def __init__(self, path, is_dir):
824 QtGui.QStandardItem.__init__(self)
825 self.is_dir = is_dir
826 self.path = path
827 self.setEditable(False)
828 self.setDragEnabled(False)
829 self.setText(utils.basename(path))
830 if is_dir:
831 icon = icons.directory()
832 else:
833 icon = icons.file_text()
834 self.setIcon(icon)