Serenity Operating System
at master 491 lines 16 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <LibCore/Object.h> 9#include <LibGUI/AbstractTableView.h> 10#include <LibGUI/Action.h> 11#include <LibGUI/Button.h> 12#include <LibGUI/HeaderView.h> 13#include <LibGUI/Menu.h> 14#include <LibGUI/Model.h> 15#include <LibGUI/Painter.h> 16#include <LibGUI/Window.h> 17#include <LibGfx/Palette.h> 18 19namespace GUI { 20 21AbstractTableView::AbstractTableView() 22{ 23 REGISTER_BOOL_PROPERTY("column_headers_visible", column_headers_visible, set_column_headers_visible); 24 25 set_selection_behavior(SelectionBehavior::SelectRows); 26 m_corner_button = add<Button>(); 27 m_corner_button->move_to_back(); 28 m_corner_button->set_background_role(Gfx::ColorRole::ThreedShadow1); 29 m_corner_button->set_fill_with_background_color(true); 30 m_column_header = add<HeaderView>(*this, Gfx::Orientation::Horizontal); 31 m_column_header->move_to_back(); 32 m_column_header->on_resize_doubleclick = [this](auto column) { 33 auto_resize_column(column); 34 }; 35 m_row_header = add<HeaderView>(*this, Gfx::Orientation::Vertical); 36 m_row_header->move_to_back(); 37 m_row_header->set_visible(false); 38 set_should_hide_unnecessary_scrollbars(true); 39} 40 41void AbstractTableView::select_all() 42{ 43 selection().clear(); 44 for (int item_index = 0; item_index < item_count(); ++item_index) { 45 auto index = model()->index(item_index); 46 selection().add(index); 47 } 48} 49 50void AbstractTableView::auto_resize_column(int column) 51{ 52 if (!model()) 53 return; 54 55 if (!column_header().is_section_visible(column)) 56 return; 57 58 auto& model = *this->model(); 59 int row_count = model.row_count(); 60 61 int header_width = m_column_header->font().width(model.column_name(column)); 62 if (column == m_key_column && model.is_column_sortable(column)) 63 header_width += HeaderView::sorting_arrow_width + HeaderView::sorting_arrow_offset; 64 65 int column_width = header_width; 66 bool is_empty = true; 67 for (int row = 0; row < row_count; ++row) { 68 auto cell_data = model.index(row, column).data(); 69 int cell_width = 0; 70 if (cell_data.is_icon()) { 71 cell_width = cell_data.as_icon().bitmap_for_size(16)->width(); 72 } else if (cell_data.is_bitmap()) { 73 cell_width = cell_data.as_bitmap().width(); 74 } else if (cell_data.is_valid()) { 75 cell_width = font().width(cell_data.to_deprecated_string()); 76 } 77 if (is_empty && cell_width > 0) 78 is_empty = false; 79 column_width = max(column_width, cell_width); 80 } 81 82 auto default_column_size = column_header().default_section_size(column); 83 if (is_empty && column_header().is_default_section_size_initialized(column)) 84 column_header().set_section_size(column, default_column_size); 85 else 86 column_header().set_section_size(column, column_width); 87} 88void AbstractTableView::update_column_sizes() 89{ 90 if (!model()) 91 return; 92 93 auto& model = *this->model(); 94 int column_count = model.column_count(); 95 int row_count = model.row_count(); 96 97 for (int column = 0; column < column_count; ++column) { 98 if (!column_header().is_section_visible(column)) 99 continue; 100 int header_width = m_column_header->font().width(model.column_name(column)); 101 if (column == m_key_column && model.is_column_sortable(column)) 102 header_width += HeaderView::sorting_arrow_width + HeaderView::sorting_arrow_offset; 103 int column_width = header_width; 104 for (int row = 0; row < row_count; ++row) { 105 auto cell_data = model.index(row, column).data(); 106 int cell_width = 0; 107 if (cell_data.is_icon()) { 108 if (auto bitmap = cell_data.as_icon().bitmap_for_size(16)) 109 cell_width = bitmap->width(); 110 } else if (cell_data.is_bitmap()) { 111 cell_width = cell_data.as_bitmap().width(); 112 } else if (cell_data.is_valid()) { 113 cell_width = font().width(cell_data.to_deprecated_string()); 114 } 115 column_width = max(column_width, cell_width); 116 } 117 column_header().set_section_size(column, max(m_column_header->section_size(column), column_width)); 118 } 119} 120 121void AbstractTableView::update_row_sizes() 122{ 123 if (!model()) 124 return; 125 126 auto& model = *this->model(); 127 int row_count = model.row_count(); 128 129 for (int row = 0; row < row_count; ++row) { 130 if (!row_header().is_section_visible(row)) 131 continue; 132 row_header().set_section_size(row, row_height()); 133 } 134} 135 136void AbstractTableView::update_content_size() 137{ 138 if (!model()) 139 return set_content_size({}); 140 141 int content_width = 0; 142 int column_count = model()->column_count(); 143 144 for (int i = 0; i < column_count; ++i) { 145 if (column_header().is_section_visible(i)) 146 content_width += column_width(i) + horizontal_padding() * 2; 147 } 148 int content_height = item_count() * row_height(); 149 150 set_content_size({ content_width, content_height }); 151 int row_width = row_header().is_visible() ? row_header().width() : 0; 152 int column_height = column_header().is_visible() ? column_header().height() : 0; 153 set_size_occupied_by_fixed_elements({ row_width, column_height }); 154 layout_headers(); 155} 156 157TableCellPaintingDelegate* AbstractTableView::column_painting_delegate(int column) const 158{ 159 // FIXME: This should return a const pointer I think.. 160 return const_cast<TableCellPaintingDelegate*>(m_column_painting_delegate.get(column).value_or(nullptr)); 161} 162 163void AbstractTableView::set_column_painting_delegate(int column, OwnPtr<TableCellPaintingDelegate> delegate) 164{ 165 if (!delegate) 166 m_column_painting_delegate.remove(column); 167 else 168 m_column_painting_delegate.set(column, move(delegate)); 169} 170 171int AbstractTableView::column_width(int column_index) const 172{ 173 if (!model()) 174 return 0; 175 return m_column_header->section_size(column_index); 176} 177 178void AbstractTableView::set_column_width(int column, int width) 179{ 180 column_header().set_section_size(column, width); 181} 182 183int AbstractTableView::minimum_column_width(int) 184{ 185 return 2; 186} 187 188int AbstractTableView::minimum_row_height(int) 189{ 190 return 2; 191} 192 193Gfx::TextAlignment AbstractTableView::column_header_alignment(int column_index) const 194{ 195 if (!model()) 196 return Gfx::TextAlignment::CenterLeft; 197 return m_column_header->section_alignment(column_index); 198} 199 200void AbstractTableView::set_column_header_alignment(int column, Gfx::TextAlignment alignment) 201{ 202 column_header().set_section_alignment(column, alignment); 203} 204 205void AbstractTableView::mousedown_event(MouseEvent& event) 206{ 207 m_tab_moves = 0; 208 if (!model()) 209 return AbstractView::mousedown_event(event); 210 211 if (event.button() != MouseButton::Primary) 212 return AbstractView::mousedown_event(event); 213 214 bool is_toggle; 215 auto index = index_at_event_position(event.position(), is_toggle); 216 217 if (index.is_valid() && is_toggle && model()->row_count(index)) { 218 toggle_index(index); 219 return; 220 } 221 222 AbstractView::mousedown_event(event); 223} 224 225ModelIndex AbstractTableView::index_at_event_position(Gfx::IntPoint position, bool& is_toggle) const 226{ 227 is_toggle = false; 228 if (!model()) 229 return {}; 230 231 auto adjusted_position = this->adjusted_position(position); 232 for (int row = 0, row_count = model()->row_count(); row < row_count; ++row) { 233 if (!row_rect(row).contains(adjusted_position)) 234 continue; 235 for (int column = 0, column_count = model()->column_count(); column < column_count; ++column) { 236 if (!content_rect(row, column).contains(adjusted_position)) 237 continue; 238 return model()->index(row, column); 239 } 240 return model()->index(row, 0); 241 } 242 return {}; 243} 244 245ModelIndex AbstractTableView::index_at_event_position(Gfx::IntPoint position) const 246{ 247 bool is_toggle; 248 auto index = index_at_event_position(position, is_toggle); 249 return is_toggle ? ModelIndex() : index; 250} 251 252int AbstractTableView::item_count() const 253{ 254 if (!model()) 255 return 0; 256 return model()->row_count(); 257} 258 259void AbstractTableView::move_cursor_relative(int vertical_steps, int horizontal_steps, SelectionUpdate selection_update) 260{ 261 if (!model()) 262 return; 263 auto& model = *this->model(); 264 ModelIndex new_index; 265 if (cursor_index().is_valid()) { 266 new_index = model.index(cursor_index().row() + vertical_steps, cursor_index().column() + horizontal_steps); 267 } else { 268 new_index = model.index(0, 0); 269 } 270 if (new_index.is_valid()) { 271 set_cursor(new_index, selection_update); 272 } 273} 274 275void AbstractTableView::scroll_into_view(ModelIndex const& index, bool scroll_horizontally, bool scroll_vertically) 276{ 277 Gfx::IntRect rect; 278 switch (selection_behavior()) { 279 case SelectionBehavior::SelectItems: 280 rect = content_rect(index); 281 if (row_header().is_visible()) 282 rect.set_left(rect.left() - row_header().width()); 283 break; 284 case SelectionBehavior::SelectRows: 285 rect = row_rect(index.row()); 286 break; 287 } 288 if (column_header().is_visible()) 289 rect.set_top(rect.top() - column_header().height()); 290 AbstractScrollableWidget::scroll_into_view(rect, scroll_horizontally, scroll_vertically); 291} 292 293void AbstractTableView::context_menu_event(ContextMenuEvent& event) 294{ 295 if (!model()) 296 return; 297 298 bool is_toggle; 299 auto index = index_at_event_position(event.position(), is_toggle); 300 if (index.is_valid()) { 301 if (!selection().contains(index)) 302 selection().set(index); 303 } else { 304 selection().clear(); 305 } 306 if (on_context_menu_request) 307 on_context_menu_request(index, event); 308} 309 310Gfx::IntRect AbstractTableView::paint_invalidation_rect(ModelIndex const& index) const 311{ 312 if (!index.is_valid()) 313 return {}; 314 return row_rect(index.row()); 315} 316 317Gfx::IntRect AbstractTableView::content_rect(int row, int column) const 318{ 319 auto row_rect = this->row_rect(row); 320 int x = 0; 321 for (int i = 0; i < column; ++i) 322 x += column_width(i) + horizontal_padding() * 2; 323 324 return { row_rect.x() + x, row_rect.y(), column_width(column) + horizontal_padding() * 2, row_height() }; 325} 326 327Gfx::IntRect AbstractTableView::content_rect(ModelIndex const& index) const 328{ 329 return content_rect(index.row(), index.column()); 330} 331 332Gfx::IntRect AbstractTableView::content_rect_minus_scrollbars(ModelIndex const& index) const 333{ 334 auto naive_content_rect = content_rect(index.row(), index.column()); 335 return { naive_content_rect.x() - horizontal_scrollbar().value(), naive_content_rect.y() - vertical_scrollbar().value(), naive_content_rect.width(), naive_content_rect.height() }; 336} 337 338Gfx::IntRect AbstractTableView::row_rect(int item_index) const 339{ 340 return { row_header().is_visible() ? row_header().width() : 0, 341 (column_header().is_visible() ? column_header().height() : 0) + (item_index * row_height()), 342 max(content_size().width(), width()), 343 row_height() }; 344} 345 346Gfx::IntPoint AbstractTableView::adjusted_position(Gfx::IntPoint position) const 347{ 348 return position.translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness()); 349} 350 351void AbstractTableView::model_did_update(unsigned flags) 352{ 353 AbstractView::model_did_update(flags); 354 update_row_sizes(); 355 if (!(flags & Model::UpdateFlag::DontResizeColumns)) 356 update_column_sizes(); 357 358 update_content_size(); 359 update(); 360} 361 362void AbstractTableView::resize_event(ResizeEvent& event) 363{ 364 AbstractView::resize_event(event); 365 layout_headers(); 366} 367 368void AbstractTableView::header_did_change_section_size(Badge<HeaderView>, Gfx::Orientation, int, int) 369{ 370 update_content_size(); 371 update(); 372} 373 374void AbstractTableView::header_did_change_section_visibility(Badge<HeaderView>, Gfx::Orientation, int, bool) 375{ 376 update_content_size(); 377 update(); 378} 379 380void AbstractTableView::set_default_column_width(int column, int width) 381{ 382 column_header().set_default_section_size(column, width); 383} 384 385void AbstractTableView::set_column_visible(int column, bool visible) 386{ 387 column_header().set_section_visible(column, visible); 388} 389 390void AbstractTableView::set_column_headers_visible(bool visible) 391{ 392 column_header().set_visible(visible); 393} 394 395bool AbstractTableView::column_headers_visible() const 396{ 397 return column_header().is_visible(); 398} 399 400void AbstractTableView::did_scroll() 401{ 402 AbstractView::did_scroll(); 403 layout_headers(); 404} 405 406void AbstractTableView::layout_headers() 407{ 408 if (column_header().is_visible()) { 409 int row_header_width = row_header().is_visible() ? row_header().width() : 0; 410 int vertical_scrollbar_width = vertical_scrollbar().is_visible() ? vertical_scrollbar().width() : 0; 411 412 int x = frame_thickness() + row_header_width - horizontal_scrollbar().value(); 413 int y = frame_thickness(); 414 int width = max(content_width(), rect().width() - frame_thickness() * 2 - row_header_width - vertical_scrollbar_width); 415 416 column_header().set_relative_rect(x, y, width, column_header().effective_min_size().height().as_int()); 417 } 418 419 if (row_header().is_visible()) { 420 int column_header_height = column_header().is_visible() ? column_header().height() : 0; 421 int horizontal_scrollbar_height = horizontal_scrollbar().is_visible() ? horizontal_scrollbar().height() : 0; 422 423 int x = frame_thickness(); 424 int y = frame_thickness() + column_header_height - vertical_scrollbar().value(); 425 int height = max(content_height(), rect().height() - frame_thickness() * 2 - column_header_height - horizontal_scrollbar_height); 426 427 row_header().set_relative_rect(x, y, row_header().effective_min_size().width().as_int(), height); 428 } 429 430 if (row_header().is_visible() && column_header().is_visible()) { 431 m_corner_button->set_relative_rect(frame_thickness(), frame_thickness(), row_header().width(), column_header().height()); 432 m_corner_button->set_visible(true); 433 } else { 434 m_corner_button->set_visible(false); 435 } 436} 437 438void AbstractTableView::keydown_event(KeyEvent& event) 439{ 440 if (is_tab_key_navigation_enabled()) { 441 if (!event.modifiers() && event.key() == KeyCode::Key_Tab) { 442 move_cursor(CursorMovement::Right, SelectionUpdate::Set); 443 event.accept(); 444 ++m_tab_moves; 445 return; 446 } else if (is_navigation(event)) { 447 if (event.key() == KeyCode::Key_Return) { 448 move_cursor_relative(0, -m_tab_moves, SelectionUpdate::Set); 449 } 450 m_tab_moves = 0; 451 } 452 453 if (event.modifiers() == KeyModifier::Mod_Shift && event.key() == KeyCode::Key_Tab) { 454 move_cursor(CursorMovement::Left, SelectionUpdate::Set); 455 event.accept(); 456 return; 457 } 458 } 459 460 AbstractView::keydown_event(event); 461} 462 463bool AbstractTableView::is_navigation(GUI::KeyEvent& event) 464{ 465 switch (event.key()) { 466 case KeyCode::Key_Tab: 467 case KeyCode::Key_Left: 468 case KeyCode::Key_Right: 469 case KeyCode::Key_Up: 470 case KeyCode::Key_Down: 471 case KeyCode::Key_Return: 472 case KeyCode::Key_Home: 473 case KeyCode::Key_End: 474 case KeyCode::Key_PageUp: 475 case KeyCode::Key_PageDown: 476 return true; 477 default: 478 return false; 479 } 480} 481 482Gfx::IntPoint AbstractTableView::automatic_scroll_delta_from_position(Gfx::IntPoint pos) const 483{ 484 if (pos.y() > column_header().height() + autoscroll_threshold()) 485 return AbstractScrollableWidget::automatic_scroll_delta_from_position(pos); 486 487 Gfx::IntPoint position_excluding_header = { pos.x(), pos.y() - column_header().height() }; 488 return AbstractScrollableWidget::automatic_scroll_delta_from_position(position_excluding_header); 489} 490 491}