Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, Glenford Williams <gw_dev@outlook.com>
4 * Copyright (c) 2022, the SerenityOS developers.
5 * Copyright (c) 2022, networkException <networkexception@serenityos.org>
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 */
9
10#include <AK/StringBuilder.h>
11#include <LibGUI/Action.h>
12#include <LibGUI/HeaderView.h>
13#include <LibGUI/Menu.h>
14#include <LibGUI/Model.h>
15#include <LibGUI/ModelEditingDelegate.h>
16#include <LibGUI/Painter.h>
17#include <LibGUI/TableView.h>
18#include <LibGUI/Window.h>
19#include <LibGfx/Palette.h>
20
21REGISTER_WIDGET(GUI, TableView)
22
23namespace GUI {
24
25TableView::TableView()
26{
27 set_fill_with_background_color(true);
28 set_background_role(ColorRole::Base);
29 set_foreground_role(ColorRole::BaseText);
30}
31
32void TableView::paint_event(PaintEvent& event)
33{
34 Color widget_background_color = palette().color(background_role());
35 Frame::paint_event(event);
36
37 Painter painter(*this);
38 painter.add_clip_rect(frame_inner_rect());
39 painter.add_clip_rect(event.rect());
40 if (fill_with_background_color())
41 painter.fill_rect(event.rect(), widget_background_color);
42 painter.translate(frame_thickness(), frame_thickness());
43 painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
44
45 if (!model())
46 return;
47
48 auto selection_color = is_focused() ? palette().selection() : palette().inactive_selection();
49
50 int exposed_width = max(content_size().width(), width());
51 int x_offset = row_header().is_visible() ? row_header().width() : 0;
52 int y_offset = column_header().is_visible() ? column_header().height() : 0;
53
54 bool dummy;
55 int first_visible_row = index_at_event_position(frame_inner_rect().top_left().translated(x_offset, y_offset), dummy).row();
56 int last_visible_row = index_at_event_position(frame_inner_rect().bottom_right().translated(x_offset, y_offset), dummy).row();
57
58 if (first_visible_row == -1)
59 first_visible_row = 0;
60 if (last_visible_row == -1)
61 last_visible_row = model()->row_count() - 1;
62
63 int painted_item_index = first_visible_row;
64
65 for (int row_index = first_visible_row; row_index <= last_visible_row; ++row_index) {
66 bool is_selected_row = selection().contains_row(row_index);
67 int y = y_offset + painted_item_index * row_height();
68
69 Color background_color;
70 Color key_column_background_color;
71 if (is_selected_row && highlight_selected_rows()) {
72 background_color = selection_color;
73 key_column_background_color = selection_color;
74 } else {
75 if (alternating_row_colors() && (painted_item_index % 2)) {
76 background_color = widget_background_color.darkened(0.8f);
77 key_column_background_color = widget_background_color.darkened(0.7f);
78 } else {
79 background_color = widget_background_color;
80 key_column_background_color = widget_background_color.darkened(0.9f);
81 }
82 }
83
84 auto row_rect = this->row_rect(painted_item_index);
85 painter.fill_rect(row_rect, background_color);
86
87 int x = x_offset;
88 for (int column_index = 0; column_index < model()->column_count(); ++column_index) {
89 if (!column_header().is_section_visible(column_index))
90 continue;
91 int column_width = this->column_width(column_index);
92 bool is_key_column = m_key_column == column_index;
93 Gfx::IntRect cell_rect(horizontal_padding() + x, y, column_width, row_height());
94 auto cell_rect_for_fill = cell_rect.inflated(horizontal_padding() * 2, 0);
95 if (is_key_column && is_key_column_highlighted())
96 painter.fill_rect(cell_rect_for_fill, key_column_background_color);
97 auto cell_index = model()->index(row_index, column_index);
98
99 auto* delegate = column_painting_delegate(column_index);
100 if (delegate && delegate->should_paint(cell_index)) {
101 delegate->paint(painter, cell_rect, palette(), cell_index);
102 } else {
103 auto data = cell_index.data();
104 if (data.is_bitmap()) {
105 auto cell_constrained_bitmap_rect = data.as_bitmap().rect();
106 if (data.as_bitmap().rect().width() > column_width)
107 cell_constrained_bitmap_rect.set_width(column_width);
108 if (data.as_bitmap().rect().height() > row_height())
109 cell_constrained_bitmap_rect.set_height(row_height());
110 cell_rect.set_y(cell_rect.y() + (row_height() - cell_constrained_bitmap_rect.height()) / 2);
111 cell_rect.set_x(cell_rect.x() + (column_width - cell_constrained_bitmap_rect.width()) / 2);
112 painter.blit(cell_rect.location(), data.as_bitmap(), cell_constrained_bitmap_rect);
113 } else if (data.is_icon()) {
114 if (auto bitmap = data.as_icon().bitmap_for_size(16)) {
115 cell_rect.set_y(cell_rect.y() + (row_height() - bitmap->height()) / 2);
116 if (is_selected_row) {
117 auto tint = selection_color.with_alpha(100);
118 painter.blit_filtered(cell_rect.location(), *bitmap, bitmap->rect(), [&](auto src) { return src.blend(tint); });
119 } else if (m_hovered_index.is_valid() && cell_index.row() == m_hovered_index.row()) {
120 painter.blit_brightened(cell_rect.location(), *bitmap, bitmap->rect());
121 } else {
122 auto opacity = cell_index.data(ModelRole::IconOpacity).as_float_or(1.0f);
123 painter.blit(cell_rect.location(), *bitmap, bitmap->rect(), opacity);
124 }
125 }
126 } else {
127 if (!is_selected_row) {
128 auto cell_background_color = cell_index.data(ModelRole::BackgroundColor);
129 if (cell_background_color.is_valid())
130 painter.fill_rect(cell_rect_for_fill, cell_background_color.to_color(background_color));
131 }
132
133 auto text_alignment = cell_index.data(ModelRole::TextAlignment).to_text_alignment(Gfx::TextAlignment::CenterLeft);
134 draw_item_text(painter, cell_index, is_selected_row, cell_rect, data.to_deprecated_string(), font_for_index(cell_index), text_alignment, Gfx::TextElision::Right);
135 }
136 }
137
138 if (m_grid_style == GridStyle::Horizontal || m_grid_style == GridStyle::Both)
139 painter.draw_line(cell_rect_for_fill.bottom_left(), cell_rect_for_fill.bottom_right(), palette().ruler());
140 if (m_grid_style == GridStyle::Vertical || m_grid_style == GridStyle::Both)
141 painter.draw_line(cell_rect_for_fill.top_right(), cell_rect_for_fill.bottom_right(), palette().ruler());
142
143 if (selection_behavior() == SelectionBehavior::SelectItems && cell_index == cursor_index())
144 painter.draw_rect(cell_rect_for_fill, palette().text_cursor());
145
146 x += column_width + horizontal_padding() * 2;
147 }
148
149 if (is_focused() && selection_behavior() == SelectionBehavior::SelectRows && row_index == cursor_index().row()) {
150 painter.draw_rect(row_rect, widget_background_color);
151 painter.draw_focus_rect(row_rect, palette().focus_outline());
152 }
153
154 if (has_pending_drop() && selection_behavior() == SelectionBehavior::SelectRows && row_index == drop_candidate_index().row()) {
155 painter.draw_rect(row_rect, palette().selection(), true);
156 }
157 ++painted_item_index;
158 };
159
160 Gfx::IntRect unpainted_rect(0, column_header().height() + painted_item_index * row_height(), exposed_width, height());
161 if (fill_with_background_color())
162 painter.fill_rect(unpainted_rect, widget_background_color);
163}
164
165void TableView::second_paint_event(PaintEvent& event)
166{
167 if (!m_rubber_banding)
168 return;
169
170 Painter painter(*this);
171 painter.add_clip_rect(event.rect());
172 painter.add_clip_rect(widget_inner_rect());
173
174 // The rubber band rect always borders the widget inner to the left and right
175 auto rubber_band_left = widget_inner_rect().left();
176 auto rubber_band_right = widget_inner_rect().right() + 1;
177
178 auto rubber_band_rect = Gfx::IntRect::from_two_points({ rubber_band_left, m_rubber_band_origin }, { rubber_band_right, m_rubber_band_current });
179
180 painter.fill_rect(rubber_band_rect, palette().rubber_band_fill());
181 painter.draw_rect(rubber_band_rect, palette().rubber_band_border());
182}
183
184void TableView::keydown_event(KeyEvent& event)
185{
186 if (!model())
187 return AbstractTableView::keydown_event(event);
188
189 AbstractTableView::keydown_event(event);
190
191 if (event.is_accepted())
192 return;
193
194 auto is_delete = event.key() == Key_Delete;
195 auto is_backspace = event.key() == Key_Backspace;
196 auto is_clear = is_delete || is_backspace;
197 auto has_ctrl = event.modifiers() & KeyModifier::Mod_Ctrl;
198 if (is_editable() && edit_triggers() & EditTrigger::AnyKeyPressed && (event.code_point() != 0 || is_clear) && !has_ctrl) {
199 begin_editing(cursor_index());
200 if (m_editing_delegate) {
201 if (is_delete) {
202 if (selection().size() > 1) {
203 selection().for_each_index([&](GUI::ModelIndex& index) {
204 begin_editing(index);
205 m_editing_delegate->set_value(DeprecatedString {});
206 });
207 } else {
208 m_editing_delegate->set_value(DeprecatedString {});
209 }
210 } else if (is_backspace) {
211 m_editing_delegate->set_value(DeprecatedString::empty());
212 } else {
213 m_editing_delegate->set_value(event.text(), ModelEditingDelegate::SelectionBehavior::DoNotSelect);
214 }
215 }
216 }
217}
218
219void TableView::mousedown_event(MouseEvent& event)
220{
221 AbstractTableView::mousedown_event(event);
222
223 if (!model())
224 return;
225
226 if (event.button() != MouseButton::Primary)
227 return;
228
229 if (m_might_drag)
230 return;
231
232 if (selection_mode() == SelectionMode::MultiSelection) {
233 m_rubber_banding = true;
234 m_rubber_band_origin = event.position().y();
235 m_rubber_band_current = event.position().y();
236 }
237}
238
239void TableView::mouseup_event(MouseEvent& event)
240{
241 AbstractTableView::mouseup_event(event);
242
243 if (m_rubber_banding && event.button() == MouseButton::Primary) {
244 m_rubber_banding = false;
245 update();
246 }
247}
248
249void TableView::mousemove_event(MouseEvent& event)
250{
251 if (m_rubber_banding) {
252 // The rubber band rect cannot go outside the bounds of the rect enclosing all rows
253 m_rubber_band_current = clamp(event.position().y(), widget_inner_rect().top() + column_header().height(), widget_inner_rect().bottom() + 1);
254
255 int row_count = model()->row_count();
256
257 clear_selection();
258
259 set_suppress_update_on_selection_change(true);
260
261 for (int row = 0; row < row_count; ++row) {
262 auto index = model()->index(row);
263 VERIFY(index.is_valid());
264
265 int row_top = row * row_height() + column_header().height();
266 int row_bottom = row * row_height() + row_height() + column_header().height();
267
268 if ((m_rubber_band_origin > row_top && m_rubber_band_current < row_top) || (m_rubber_band_origin > row_bottom && m_rubber_band_current < row_bottom)) {
269 add_selection(index);
270 }
271 }
272
273 set_suppress_update_on_selection_change(false);
274
275 update();
276 }
277
278 AbstractTableView::mousemove_event(event);
279}
280
281void TableView::move_cursor(CursorMovement movement, SelectionUpdate selection_update)
282{
283 if (!model())
284 return;
285 auto& model = *this->model();
286 switch (movement) {
287 case CursorMovement::Left:
288 move_cursor_relative(0, -1, selection_update);
289 break;
290 case CursorMovement::Right:
291 move_cursor_relative(0, 1, selection_update);
292 break;
293 case CursorMovement::Up:
294 move_cursor_relative(-1, 0, selection_update);
295 break;
296 case CursorMovement::Down:
297 move_cursor_relative(1, 0, selection_update);
298 break;
299 case CursorMovement::Home: {
300 auto index = model.index(0, 0);
301 set_cursor(index, selection_update);
302 break;
303 }
304 case CursorMovement::End: {
305 auto index = model.index(model.row_count() - 1, 0);
306 set_cursor(index, selection_update);
307 break;
308 }
309 case CursorMovement::PageUp: {
310 int items_per_page = visible_content_rect().height() / row_height();
311 auto old_index = selection().first();
312 auto new_index = model.index(max(0, old_index.row() - items_per_page), old_index.column());
313 if (model.is_within_range(new_index))
314 set_cursor(new_index, selection_update);
315 break;
316 }
317 case CursorMovement::PageDown: {
318 int items_per_page = visible_content_rect().height() / row_height();
319 auto old_index = selection().first();
320 auto new_index = model.index(min(model.row_count() - 1, old_index.row() + items_per_page), old_index.column());
321 if (model.is_within_range(new_index))
322 set_cursor(new_index, selection_update);
323 break;
324 }
325 }
326}
327
328void TableView::set_grid_style(GridStyle style)
329{
330 if (m_grid_style == style)
331 return;
332 m_grid_style = style;
333 update();
334}
335
336}