GTreeView.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. #include <LibGUI/GTreeView.h>
  2. #include <LibGUI/GPainter.h>
  3. #include <LibGUI/GScrollBar.h>
  4. struct Node {
  5. String text;
  6. Node* parent { nullptr };
  7. Vector<Node*> children;
  8. };
  9. class TestModel : public GModel {
  10. public:
  11. static Retained<TestModel> create() { return adopt(*new TestModel); }
  12. TestModel();
  13. virtual int row_count(const GModelIndex& = GModelIndex()) const override;
  14. virtual int column_count(const GModelIndex& = GModelIndex()) const override;
  15. virtual GVariant data(const GModelIndex&, Role = Role::Display) const override;
  16. virtual void update() override;
  17. virtual GModelIndex index(int row, int column = 0, const GModelIndex& parent = GModelIndex()) const override;
  18. virtual ColumnMetadata column_metadata(int) const override{ return { 100 }; }
  19. Node* m_root { nullptr };
  20. };
  21. Node* make_little_tree(int depth, Node* parent)
  22. {
  23. static int next_id = 0;
  24. Node* node = new Node;
  25. node->text = String::format("Node #%d", next_id++);
  26. node->parent = parent;
  27. if (depth)
  28. node->children.append(make_little_tree(depth - 1, node));
  29. return node;
  30. }
  31. GModelIndex TestModel::index(int row, int column, const GModelIndex& parent) const
  32. {
  33. if (!parent.is_valid())
  34. return create_index(row, column, m_root);
  35. auto& node = *(Node*)parent.internal_data();
  36. return create_index(row, column, node.children[row]);
  37. }
  38. TestModel::TestModel()
  39. {
  40. m_root = new Node;
  41. m_root->text = "Root";
  42. m_root->children.append(make_little_tree(3, m_root));
  43. m_root->children.append(make_little_tree(2, m_root));
  44. m_root->children.append(make_little_tree(1, m_root));
  45. }
  46. int TestModel::row_count(const GModelIndex& index) const
  47. {
  48. if (!index.is_valid())
  49. return 1;
  50. auto& node = *(const Node*)index.internal_data();
  51. return node.children.size();
  52. }
  53. int TestModel::column_count(const GModelIndex&) const
  54. {
  55. return 1;
  56. }
  57. void TestModel::update()
  58. {
  59. }
  60. GVariant TestModel::data(const GModelIndex& index, Role role) const
  61. {
  62. if (!index.is_valid())
  63. return { };
  64. auto& node = *(const Node*)index.internal_data();
  65. if (role == GModel::Role::Display) {
  66. return node.text;
  67. }
  68. if (role == GModel::Role::Icon) {
  69. if (node.children.is_empty())
  70. return GIcon::default_icon("filetype-unknown");
  71. return GIcon::default_icon("filetype-folder");
  72. }
  73. return { };
  74. }
  75. struct GTreeView::MetadataForIndex {
  76. bool open { false };
  77. };
  78. GTreeView::MetadataForIndex& GTreeView::ensure_metadata_for_index(const GModelIndex& index) const
  79. {
  80. ASSERT(index.is_valid());
  81. auto it = m_view_metadata.find(index.internal_data());
  82. if (it != m_view_metadata.end())
  83. return *it->value;
  84. auto new_metadata = make<MetadataForIndex>();
  85. auto& new_metadata_ref = *new_metadata;
  86. m_view_metadata.set(index.internal_data(), move(new_metadata));
  87. return new_metadata_ref;
  88. }
  89. GTreeView::GTreeView(GWidget* parent)
  90. : GAbstractView(parent)
  91. {
  92. set_frame_shape(GFrame::Shape::Container);
  93. set_frame_shadow(GFrame::Shadow::Sunken);
  94. set_frame_thickness(2);
  95. set_model(TestModel::create());
  96. }
  97. GTreeView::~GTreeView()
  98. {
  99. }
  100. GModelIndex GTreeView::index_at_content_position(const Point& position) const
  101. {
  102. if (!model())
  103. return { };
  104. auto& model = *this->model();
  105. int indent_level = 0;
  106. int y_offset = 0;
  107. GModelIndex result;
  108. Function<bool(const GModelIndex&, GModelIndex&)> hit_test_index = [&] (const GModelIndex& index, GModelIndex& result) {
  109. if (index.is_valid()) {
  110. auto& metadata = ensure_metadata_for_index(index);
  111. auto& node = *(const Node*)index.internal_data();
  112. int x_offset = indent_level * indent_width_in_pixels();
  113. auto data = model.data(index, GModel::Role::Display);
  114. Rect rect = { x_offset, y_offset, icon_size() + icon_spacing() + font().width(data.to_string()), item_height() };
  115. dbgprintf("%s %s (%s)\n", data.to_string().characters(), rect.to_string().characters(), metadata.open ? "open" : "closed");
  116. y_offset += item_height();
  117. if (rect.contains(position)) {
  118. result = index;
  119. return true;
  120. }
  121. // NOTE: Skip traversing children if this index is closed!
  122. if (!metadata.open)
  123. return false;
  124. }
  125. ++indent_level;
  126. for (int i = 0; i < model.row_count(index); ++i) {
  127. auto child_index = model.index(i, 0, index);
  128. if (hit_test_index(child_index, result))
  129. return true;
  130. }
  131. --indent_level;
  132. return false;
  133. };
  134. hit_test_index(model.index(0, 0, GModelIndex()), result);
  135. return result;
  136. }
  137. void GTreeView::mousedown_event(GMouseEvent& event)
  138. {
  139. if (!model())
  140. return;
  141. auto& model = *this->model();
  142. auto adjusted_position = event.position().translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness());
  143. auto index = index_at_content_position(adjusted_position);
  144. if (!index.is_valid()) {
  145. dbgprintf("GTV::mousedown: No valid index at %s (adjusted to: %s)\n", event.position().to_string().characters(), adjusted_position.to_string().characters());
  146. return;
  147. }
  148. dbgprintf("GTV::mousedown: Index %d,%d {%p}] at %s (adjusted to: %s)\n", index.row(), index.column(), index.internal_data(), event.position().to_string().characters(), adjusted_position.to_string().characters());
  149. auto& metadata = ensure_metadata_for_index(index);
  150. if (model.row_count(index)) {
  151. metadata.open = !metadata.open;
  152. dbgprintf("GTV::mousedown: toggle index %d,%d {%p} open: %d -> %d\n", index.row(), index.column(), index.internal_data(), !metadata.open, metadata.open);
  153. update();
  154. }
  155. }
  156. void GTreeView::paint_event(GPaintEvent& event)
  157. {
  158. GFrame::paint_event(event);
  159. GPainter painter(*this);
  160. painter.add_clip_rect(frame_inner_rect());
  161. painter.add_clip_rect(event.rect());
  162. painter.fill_rect(event.rect(), Color::White);
  163. painter.translate(frame_inner_rect().location());
  164. if (!model())
  165. return;
  166. auto& model = *this->model();
  167. int indent_level = 0;
  168. int y_offset = 0;
  169. Function<void(const GModelIndex&)> render_index = [&] (const GModelIndex& index) {
  170. if (index.is_valid()) {
  171. auto& metadata = ensure_metadata_for_index(index);
  172. int x_offset = indent_level * indent_width_in_pixels();
  173. auto node_text = model.data(index, GModel::Role::Display).to_string();
  174. Rect rect = {
  175. x_offset, y_offset,
  176. icon_size() + icon_spacing() + font().width(node_text), item_height()
  177. };
  178. painter.fill_rect(rect, Color::LightGray);
  179. Rect icon_rect = { rect.x(), rect.y(), icon_size(), icon_size() };
  180. auto icon = model.data(index, GModel::Role::Icon);
  181. if (icon.is_icon()) {
  182. if (auto* bitmap = icon.as_icon().bitmap_for_size(icon_size()))
  183. painter.blit(rect.location(), *bitmap, bitmap->rect());
  184. }
  185. Rect text_rect = {
  186. icon_rect.right() + 1 + icon_spacing(), rect.y(),
  187. rect.width() - icon_size() - icon_spacing(), rect.height()
  188. };
  189. painter.draw_text(text_rect, node_text, TextAlignment::CenterLeft, Color::Black);
  190. y_offset += item_height();
  191. // NOTE: Skip traversing children if this index is closed!
  192. if (!metadata.open)
  193. return;
  194. }
  195. ++indent_level;
  196. for (int i = 0; i < model.row_count(index); ++i) {
  197. auto child_index = model.index(i, 0, index);
  198. render_index(child_index);
  199. }
  200. --indent_level;
  201. };
  202. render_index(model.index(0, 0, GModelIndex()));
  203. }