fsbrowser_item.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. from pathlib import Path
  2. from typing import Dict
  3. from PySide6.QtCore import (
  4. Qt,
  5. )
  6. from PySide6.QtWidgets import (
  7. QTreeWidgetItem,
  8. )
  9. from .resource_provider import gooey_resources
  10. class FSBrowserItem(QTreeWidgetItem):
  11. PathObjRole = Qt.UserRole + 765
  12. def __init__(self, parent=None):
  13. # DO NOT USE DIRECTLY, GO THROUGH from_lsdir_result()
  14. super().__init__(
  15. parent,
  16. type=QTreeWidgetItem.UserType + 145,
  17. )
  18. self._child_lookup = None
  19. def __str__(self):
  20. return f'FSBrowserItem<{self.pathobj}>'
  21. @property
  22. def pathobj(self):
  23. p = self.data(0, FSBrowserItem.PathObjRole)
  24. if p is None:
  25. raise RuntimeError('TreeWidgetItem has no path set')
  26. return p
  27. @property
  28. def datalad_type(self):
  29. return self.data(1, Qt.EditRole)
  30. def data(self, column: int, role: int):
  31. if column == 0 and role in (Qt.DisplayRole, Qt.EditRole):
  32. # for now, we do not distinguish the two, maybe never will
  33. # but the default implementation also does this, so we match
  34. # behavior explicitly until we know better
  35. return self.pathobj.name
  36. # fall back on default implementation
  37. return super().data(column, role)
  38. def __getitem__(self, name: str):
  39. if self._child_lookup is None:
  40. self._child_lookup = {
  41. child.data(0, Qt.EditRole): child
  42. for child in self.children_()
  43. }
  44. return self._child_lookup.get(name)
  45. def _register_child(self, name, item):
  46. if self._child_lookup is None:
  47. self._child_lookup = {}
  48. self._child_lookup[name] = item
  49. def removeChild(self, item):
  50. super().removeChild(item)
  51. del self._child_lookup[item.pathobj.name]
  52. def children_(self):
  53. # get all pointers to children at once, other wise removing
  54. # one from the parent while the generator is running invalidates
  55. # the indices
  56. for c in [self.child(ci) for ci in range(self.childCount())]:
  57. yield c
  58. def update_from_status_result(self, res: Dict):
  59. state = res.get('state')
  60. if res.get('status') == 'error' and 'message' in res and state is None:
  61. # something went wrong, we got no state info, but we have a message
  62. state = res['message']
  63. state_icon = 'file-annex'
  64. if res.get('key'):
  65. state_icon = 'file-git'
  66. if state:
  67. self.setData(2, Qt.EditRole, state)
  68. icon = self._getIcon(state)
  69. if icon:
  70. self.setIcon(2, self._getIcon(state))
  71. type_ = res.get('type')
  72. if type_ == 'file':
  73. # get the right icon, fall back on 'file'
  74. self.setIcon(0, self._getIcon(state_icon))
  75. if type_:
  76. self.setData(1, Qt.EditRole, type_)
  77. def update_from_lsdir_result(self, res: Dict):
  78. # This sets
  79. # - type column
  80. # - child indicator
  81. # - icons TODO check which and how
  82. # - disabled-mode
  83. #
  84. # Resets
  85. # - state column for directories
  86. path_type = res['type']
  87. self.setData(1, Qt.EditRole, path_type)
  88. self._setItemIcons(res)
  89. if res.get('status') == 'error' \
  90. and res.get('message') == 'Permissions denied':
  91. # we cannot get info on it, reflect in UI
  92. self.setDisabled(True)
  93. # also prevent expansion if there are no children yet
  94. if not self.childCount():
  95. self.setChildIndicatorPolicy(
  96. FSBrowserItem.DontShowIndicator)
  97. # END HERE
  98. return
  99. # ensure we are on
  100. self.setDisabled(False)
  101. if path_type in ('directory', 'dataset'):
  102. # show an expansion indiciator, even when we do not have
  103. # children in the item yet
  104. self.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator)
  105. if path_type == 'directory':
  106. # a regular directory with proper permissions has no state
  107. self.setData(2, Qt.EditRole, '')
  108. @classmethod
  109. def from_lsdir_result(cls, res: Dict, parent=None):
  110. item = FSBrowserItem(parent=parent)
  111. path = Path(res['path'])
  112. if hasattr(parent, '_register_child'):
  113. parent._register_child(path.name, item)
  114. item.setData(0, FSBrowserItem.PathObjRole, path)
  115. item.update_from_lsdir_result(res)
  116. return item
  117. def _setItemIcons(self, res):
  118. # Set 'type' icon
  119. item_type = res['type']
  120. if item_type != 'file':
  121. icon = self._getIcon(item_type)
  122. if icon:
  123. self.setIcon(0, icon)
  124. # Set other icon types: TODO
  125. def _getIcon(self, item_type):
  126. """Gets icon associated with item type"""
  127. icon_mapping = {
  128. 'dataset': 'dataset-closed',
  129. 'directory': 'directory-closed',
  130. 'file': 'file',
  131. 'file-annex': 'file-annex',
  132. 'file-git': 'file-git',
  133. # opportunistic guess?
  134. 'symlink': 'file-annex',
  135. 'untracked': 'untracked',
  136. 'clean': 'clean',
  137. 'modified': 'modified',
  138. 'deleted': 'untracked',
  139. 'unknown': 'untracked',
  140. 'added': 'modified',
  141. }
  142. # TODO have a fallback icon, when we do not know a specific type
  143. # rather than crashing. Maybe a ?, maybe something blank?
  144. icon_name = icon_mapping.get(item_type, None)
  145. if icon_name:
  146. return gooey_resources.get_icon(icon_name)