fsbrowser_item.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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, path, 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.setData(0, FSBrowserItem.PathObjRole, path)
  19. self._child_lookup = None
  20. def __str__(self):
  21. return f'FSBrowserItem<{self.pathobj}>'
  22. @property
  23. def pathobj(self):
  24. p = self.data(0, FSBrowserItem.PathObjRole)
  25. if p is None:
  26. raise RuntimeError('TreeWidgetItem has no path set')
  27. return p
  28. @property
  29. def datalad_type(self):
  30. return self.data(1, Qt.EditRole)
  31. def set_item_type(self, type_: str, icon: str or None = None):
  32. prev_type = self.data(1, Qt.EditRole)
  33. if prev_type == type_:
  34. # nothing to do, we already have this type set
  35. return
  36. self.setData(1, Qt.EditRole, type_)
  37. icon = gooey_resources.get_best_icon(icon or type_)
  38. if icon:
  39. # yes, this goes to the first column
  40. self.setIcon(0, icon)
  41. def set_item_state(self, state: str, icon: str or None = None):
  42. prev_state = self.data(2, Qt.EditRole)
  43. if prev_state == state:
  44. # nothing to do, we already have this type set
  45. return
  46. self.setData(2, Qt.EditRole, '' if state is None else state)
  47. icon = gooey_resources.get_best_icon(icon or state)
  48. if icon:
  49. self.setIcon(2, icon)
  50. def data(self, column: int, role: int):
  51. if column == 0 and role in (Qt.DisplayRole, Qt.EditRole):
  52. # for now, we do not distinguish the two, maybe never will
  53. # but the default implementation also does this, so we match
  54. # behavior explicitly until we know better
  55. return self.pathobj.name
  56. # fall back on default implementation
  57. return super().data(column, role)
  58. def __getitem__(self, name: str):
  59. if self._child_lookup is None:
  60. self._child_lookup = {
  61. child.data(0, Qt.EditRole): child
  62. for child in self.children_()
  63. }
  64. return self._child_lookup.get(name)
  65. def _register_child(self, name, item):
  66. if self._child_lookup is None:
  67. self._child_lookup = {}
  68. self._child_lookup[name] = item
  69. def removeChild(self, item):
  70. # we needed to implement this to be able to update the lookup
  71. super().removeChild(item)
  72. del self._child_lookup[item.pathobj.name]
  73. def children_(self):
  74. # get all pointers to children at once, other wise removing
  75. # one from the parent while the generator is running invalidates
  76. # the indices
  77. for c in [self.child(ci) for ci in range(self.childCount())]:
  78. yield c
  79. def update_from_status_result(self, res: Dict):
  80. state = res.get('state')
  81. if res.get('status') == 'error' and 'message' in res and state is None:
  82. # something went wrong, we got no state info, but we have a message
  83. state = res['message']
  84. if state:
  85. if state == 'deleted':
  86. # TODO test if removal would have trigger child node removal
  87. # also
  88. self.setChildIndicatorPolicy(
  89. FSBrowserItem.DontShowIndicator)
  90. self.set_item_state(state)
  91. # update type info
  92. type_ = res.get('type')
  93. if type_:
  94. # guess by type, by default
  95. type_icon = None
  96. if type_ == 'file':
  97. # for files we can further differentiate
  98. type_icon = 'file-git'
  99. if res.get('key'):
  100. type_icon = 'file-annex'
  101. # disambiguate from a file in Git
  102. type_ = 'annexed-file'
  103. self.set_item_type(type_, icon=type_icon)
  104. def update_from_lsdir_result(self, res: Dict):
  105. # This sets
  106. # - type column
  107. # - child indicator
  108. # - icons TODO check which and how
  109. # - disabled-mode
  110. #
  111. # Resets
  112. # - state column for directories
  113. path_type = res['type']
  114. self.set_item_type(path_type)
  115. if res.get('status') == 'error' \
  116. and res.get('message') == 'Permissions denied':
  117. # we cannot get info on it, reflect in UI
  118. self.setDisabled(True)
  119. # also prevent expansion if there are no children yet
  120. if not self.childCount():
  121. self.setChildIndicatorPolicy(
  122. FSBrowserItem.DontShowIndicator)
  123. # END HERE
  124. return
  125. # ensure we are on
  126. self.setDisabled(False)
  127. if path_type in ('directory', 'dataset'):
  128. # show an expansion indiciator, even when we do not have
  129. # children in the item yet
  130. self.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator)
  131. if path_type == 'directory':
  132. # a regular directory with proper permissions has no state
  133. self.set_item_state(None)
  134. @classmethod
  135. def from_lsdir_result(cls, res: Dict, parent=None):
  136. path = Path(res['path'])
  137. item = FSBrowserItem(path, parent=parent)
  138. if hasattr(parent, '_register_child'):
  139. parent._register_child(path.name, item)
  140. item.update_from_lsdir_result(res)
  141. return item