GTreeView.cpp 8.2 KB

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