Serenity Operating System
at hosted 353 lines 12 kB view raw
1/* 2 * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <LibGUI/ColumnsView.h> 28#include <LibGUI/Model.h> 29#include <LibGUI/Painter.h> 30#include <LibGUI/ScrollBar.h> 31#include <LibGfx/CharacterBitmap.h> 32#include <LibGfx/Palette.h> 33 34namespace GUI { 35 36static const char* s_arrow_bitmap_data = { 37 " " 38 " # " 39 " ## " 40 " ### " 41 " #### " 42 " ### " 43 " ## " 44 " # " 45 " " 46}; 47static const int s_arrow_bitmap_width = 9; 48static const int s_arrow_bitmap_height = 9; 49 50ColumnsView::ColumnsView() 51{ 52 set_fill_with_background_color(true); 53 set_background_role(ColorRole::Base); 54 set_foreground_role(ColorRole::BaseText); 55 m_columns.append({ {}, 0 }); 56} 57 58ColumnsView::~ColumnsView() 59{ 60} 61 62void ColumnsView::select_all() 63{ 64 Vector<Column> columns_for_selection; 65 selection().for_each_index([&](auto& index) { 66 for (auto& column : m_columns) { 67 if (column.parent_index == index.parent()) { 68 columns_for_selection.append(column); 69 return; 70 } 71 } 72 ASSERT_NOT_REACHED(); 73 }); 74 75 for (Column& column : columns_for_selection) { 76 int row_count = model()->row_count(column.parent_index); 77 for (int row = 0; row < row_count; row++) { 78 ModelIndex index = model()->index(row, m_model_column, column.parent_index); 79 selection().add(index); 80 } 81 } 82} 83 84void ColumnsView::paint_event(PaintEvent& event) 85{ 86 AbstractView::paint_event(event); 87 88 if (!model()) 89 return; 90 91 Painter painter(*this); 92 painter.add_clip_rect(frame_inner_rect()); 93 painter.add_clip_rect(event.rect()); 94 painter.translate(frame_thickness(), frame_thickness()); 95 painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); 96 97 int column_x = 0; 98 99 for (size_t i = 0; i < m_columns.size(); i++) { 100 auto& column = m_columns[i]; 101 auto* next_column = i + 1 == m_columns.size() ? nullptr : &m_columns[i + 1]; 102 103 ASSERT(column.width > 0); 104 105 int row_count = model()->row_count(column.parent_index); 106 for (int row = 0; row < row_count; row++) { 107 ModelIndex index = model()->index(row, m_model_column, column.parent_index); 108 ASSERT(index.is_valid()); 109 110 bool is_selected_row = selection().contains(index); 111 112 Color background_color = palette().color(background_role()); 113 Color text_color = palette().color(foreground_role()); 114 115 if (next_column != nullptr && next_column->parent_index == index) { 116 background_color = palette().inactive_selection(); 117 text_color = palette().inactive_selection_text(); 118 } 119 120 if (is_selected_row) { 121 background_color = palette().selection(); 122 text_color = palette().selection_text(); 123 } 124 125 Gfx::Rect row_rect { column_x, row * item_height(), column.width, item_height() }; 126 painter.fill_rect(row_rect, background_color); 127 128 auto icon = model()->data(index, Model::Role::Icon); 129 Gfx::Rect icon_rect = { column_x + icon_spacing(), 0, icon_size(), icon_size() }; 130 icon_rect.center_vertically_within(row_rect); 131 if (icon.is_icon()) { 132 if (auto* bitmap = icon.as_icon().bitmap_for_size(icon_size())) { 133 if (m_hovered_index.is_valid() && m_hovered_index.parent() == index.parent() && m_hovered_index.row() == index.row()) 134 painter.blit_brightened(icon_rect.location(), *bitmap, bitmap->rect()); 135 else 136 painter.blit(icon_rect.location(), *bitmap, bitmap->rect()); 137 } 138 } 139 140 Gfx::Rect text_rect = { 141 icon_rect.right() + 1 + icon_spacing(), row * item_height(), 142 column.width - icon_spacing() - icon_size() - icon_spacing() - icon_spacing() - s_arrow_bitmap_width - icon_spacing(), item_height() 143 }; 144 auto text = model()->data(index).to_string(); 145 painter.draw_text(text_rect, text, Gfx::TextAlignment::CenterLeft, text_color); 146 147 bool expandable = model()->row_count(index) > 0; 148 if (expandable) { 149 Gfx::Rect arrow_rect = { 150 text_rect.right() + 1 + icon_spacing(), 0, 151 s_arrow_bitmap_width, s_arrow_bitmap_height 152 }; 153 arrow_rect.center_vertically_within(row_rect); 154 static auto& arrow_bitmap = Gfx::CharacterBitmap::create_from_ascii(s_arrow_bitmap_data, s_arrow_bitmap_width, s_arrow_bitmap_height).leak_ref(); 155 painter.draw_bitmap(arrow_rect.location(), arrow_bitmap, text_color); 156 } 157 } 158 159 int separator_height = content_size().height(); 160 if (height() > separator_height) 161 separator_height = height(); 162 painter.draw_line({ column_x + column.width, 0 }, { column_x + column.width, separator_height }, palette().button()); 163 column_x += column.width + 1; 164 } 165} 166 167void ColumnsView::push_column(ModelIndex& parent_index) 168{ 169 ASSERT(model()); 170 171 // Drop columns at the end. 172 ModelIndex grandparent = model()->parent_index(parent_index); 173 for (int i = m_columns.size() - 1; i > 0; i--) { 174 if (m_columns[i].parent_index == grandparent) 175 break; 176 m_columns.shrink(i); 177 dbg() << "Dropping column " << i; 178 } 179 180 // Add the new column. 181 dbg() << "Adding a new column"; 182 m_columns.append({ parent_index, 0 }); 183 update_column_sizes(); 184 update(); 185} 186 187void ColumnsView::update_column_sizes() 188{ 189 if (!model()) 190 return; 191 192 int total_width = 0; 193 int total_height = 0; 194 195 for (auto& column : m_columns) { 196 int row_count = model()->row_count(column.parent_index); 197 198 int column_height = row_count * item_height(); 199 if (column_height > total_height) 200 total_height = column_height; 201 202 column.width = 10; 203 for (int row = 0; row < row_count; row++) { 204 ModelIndex index = model()->index(row, m_model_column, column.parent_index); 205 ASSERT(index.is_valid()); 206 auto text = model()->data(index).to_string(); 207 int row_width = icon_spacing() + icon_size() + icon_spacing() + font().width(text) + icon_spacing() + s_arrow_bitmap_width + icon_spacing(); 208 if (row_width > column.width) 209 column.width = row_width; 210 } 211 total_width += column.width + 1; 212 } 213 214 set_content_size({ total_width, total_height }); 215} 216 217ModelIndex ColumnsView::index_at_event_position(const Gfx::Point& a_position) const 218{ 219 if (!model()) 220 return {}; 221 222 auto position = a_position.translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness()); 223 224 int column_x = 0; 225 226 for (auto& column : m_columns) { 227 if (position.x() < column_x) 228 break; 229 if (position.x() > column_x + column.width) { 230 column_x += column.width; 231 continue; 232 } 233 234 int row = position.y() / item_height(); 235 int row_count = model()->row_count(column.parent_index); 236 if (row >= row_count) 237 return {}; 238 239 return model()->index(row, m_model_column, column.parent_index); 240 } 241 242 return {}; 243} 244 245void ColumnsView::mousedown_event(MouseEvent& event) 246{ 247 AbstractView::mousedown_event(event); 248 249 if (!model()) 250 return; 251 252 if (event.button() != MouseButton::Left) 253 return; 254 255 auto index = index_at_event_position(event.position()); 256 if (index.is_valid() && !(event.modifiers() & Mod_Ctrl)) { 257 if (model()->row_count(index)) 258 push_column(index); 259 } 260} 261 262void ColumnsView::did_update_model() 263{ 264 AbstractView::did_update_model(); 265 266 // FIXME: Don't drop the columns on minor updates. 267 dbg() << "Model was updated; dropping columns :("; 268 m_columns.clear(); 269 m_columns.append({ {}, 0 }); 270 271 update_column_sizes(); 272 update(); 273} 274 275void ColumnsView::keydown_event(KeyEvent& event) 276{ 277 if (!model()) 278 return; 279 auto& model = *this->model(); 280 281 if (event.key() == KeyCode::Key_Return) { 282 activate_selected(); 283 return; 284 } 285 286 if (event.key() == KeyCode::Key_Up) { 287 ModelIndex new_index; 288 if (!selection().is_empty()) { 289 auto old_index = selection().first(); 290 auto parent_index = model.parent_index(old_index); 291 int row = old_index.row() > 0 ? old_index.row() - 1 : 0; 292 new_index = model.sibling(row, old_index.column(), parent_index); 293 } else { 294 new_index = model.index(0, m_model_column, {}); 295 } 296 if (model.is_valid(new_index)) { 297 selection().set(new_index); 298 update(); 299 } 300 return; 301 } 302 303 if (event.key() == KeyCode::Key_Down) { 304 ModelIndex new_index; 305 if (!selection().is_empty()) { 306 auto old_index = selection().first(); 307 auto parent_index = model.parent_index(old_index); 308 int row = old_index.row() + 1; 309 new_index = model.sibling(row, old_index.column(), parent_index); 310 } else { 311 new_index = model.index(0, m_model_column, {}); 312 } 313 if (model.is_valid(new_index)) { 314 selection().set(new_index); 315 update(); 316 } 317 return; 318 } 319 320 if (event.key() == KeyCode::Key_Left) { 321 ModelIndex new_index; 322 if (!selection().is_empty()) { 323 auto old_index = selection().first(); 324 new_index = model.parent_index(old_index); 325 } else { 326 new_index = model.index(0, m_model_column, {}); 327 } 328 if (model.is_valid(new_index)) { 329 selection().set(new_index); 330 update(); 331 } 332 return; 333 } 334 335 if (event.key() == KeyCode::Key_Right) { 336 ModelIndex old_index, new_index; 337 if (!selection().is_empty()) { 338 old_index = selection().first(); 339 new_index = model.index(0, m_model_column, old_index); 340 } else { 341 new_index = model.index(0, m_model_column, {}); 342 } 343 if (model.is_valid(new_index)) { 344 selection().set(new_index); 345 if (model.is_valid(old_index)) 346 push_column(old_index); 347 update(); 348 } 349 return; 350 } 351} 352 353}