Serenity Operating System
at portability 267 lines 9.1 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@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 <Kernel/KeyCode.h> 28#include <LibGUI/ListView.h> 29#include <LibGUI/Model.h> 30#include <LibGUI/Painter.h> 31#include <LibGUI/ScrollBar.h> 32#include <LibGfx/Palette.h> 33 34namespace GUI { 35 36ListView::ListView() 37{ 38 set_background_role(ColorRole::Base); 39 set_foreground_role(ColorRole::BaseText); 40} 41 42ListView::~ListView() 43{ 44} 45 46void ListView::select_all() 47{ 48 selection().clear(); 49 for (int item_index = 0; item_index < item_count(); ++item_index) { 50 auto index = model()->index(item_index, m_model_column); 51 selection().add(index); 52 } 53} 54 55void ListView::update_content_size() 56{ 57 if (!model()) 58 return set_content_size({}); 59 60 int content_width = 0; 61 for (int row = 0, row_count = model()->row_count(); row < row_count; ++row) { 62 auto text = model()->data(model()->index(row, m_model_column), Model::Role::Display); 63 content_width = max(content_width, font().width(text.to_string())); 64 } 65 66 content_width = max(content_width, widget_inner_rect().width()); 67 68 int content_height = item_count() * item_height(); 69 set_content_size({ content_width, content_height }); 70} 71 72void ListView::resize_event(ResizeEvent& event) 73{ 74 update_content_size(); 75 AbstractView::resize_event(event); 76} 77 78void ListView::did_update_model() 79{ 80 AbstractView::did_update_model(); 81 update_content_size(); 82 update(); 83} 84 85Gfx::Rect ListView::content_rect(int row) const 86{ 87 return { 0, row * item_height(), content_width(), item_height() }; 88} 89 90Gfx::Rect ListView::content_rect(const ModelIndex& index) const 91{ 92 return content_rect(index.row()); 93} 94 95ModelIndex ListView::index_at_event_position(const Gfx::Point& point) const 96{ 97 ASSERT(model()); 98 99 auto adjusted_position = this->adjusted_position(point); 100 for (int row = 0, row_count = model()->row_count(); row < row_count; ++row) { 101 if (!content_rect(row).contains(adjusted_position)) 102 continue; 103 return model()->index(row, m_model_column); 104 } 105 return {}; 106} 107 108Gfx::Point ListView::adjusted_position(const Gfx::Point& position) const 109{ 110 return position.translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness()); 111} 112 113void ListView::paint_event(PaintEvent& event) 114{ 115 Frame::paint_event(event); 116 117 if (!model()) 118 return; 119 120 Painter painter(*this); 121 painter.add_clip_rect(frame_inner_rect()); 122 painter.add_clip_rect(event.rect()); 123 painter.translate(frame_thickness(), frame_thickness()); 124 painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); 125 126 int exposed_width = max(content_size().width(), width()); 127 int painted_item_index = 0; 128 129 for (int row_index = 0; row_index < model()->row_count(); ++row_index) { 130 bool is_selected_row = selection().contains_row(row_index); 131 int y = painted_item_index * item_height(); 132 133 Color background_color; 134 if (is_selected_row) { 135 background_color = is_focused() ? palette().selection() : palette().inactive_selection(); 136 } else { 137 Color row_fill_color = palette().color(background_role()); 138 if (alternating_row_colors() && (painted_item_index % 2)) { 139 background_color = row_fill_color.darkened(0.8f); 140 } else { 141 background_color = row_fill_color; 142 } 143 } 144 145 auto column_metadata = model()->column_metadata(m_model_column); 146 147 Gfx::Rect row_rect(0, y, content_width(), item_height()); 148 painter.fill_rect(row_rect, background_color); 149 auto index = model()->index(row_index, m_model_column); 150 auto data = model()->data(index); 151 auto font = font_for_index(index); 152 if (data.is_bitmap()) { 153 painter.blit(row_rect.location(), data.as_bitmap(), data.as_bitmap().rect()); 154 } else if (data.is_icon()) { 155 if (auto bitmap = data.as_icon().bitmap_for_size(16)) 156 painter.blit(row_rect.location(), *bitmap, bitmap->rect()); 157 } else { 158 Color text_color; 159 if (is_selected_row) 160 text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text(); 161 else 162 text_color = model()->data(index, Model::Role::ForegroundColor).to_color(palette().color(foreground_role())); 163 auto text_rect = row_rect; 164 text_rect.move_by(horizontal_padding(), 0); 165 text_rect.set_width(text_rect.width() - horizontal_padding() * 2); 166 painter.draw_text(text_rect, data.to_string(), font, column_metadata.text_alignment, text_color); 167 } 168 169 ++painted_item_index; 170 }; 171 172 Gfx::Rect unpainted_rect(0, painted_item_index * item_height(), exposed_width, height()); 173 painter.fill_rect(unpainted_rect, palette().color(background_role())); 174} 175 176int ListView::item_count() const 177{ 178 if (!model()) 179 return 0; 180 return model()->row_count(); 181} 182 183void ListView::keydown_event(KeyEvent& event) 184{ 185 if (!model()) 186 return; 187 auto& model = *this->model(); 188 if (event.key() == KeyCode::Key_Return) { 189 activate_selected(); 190 return; 191 } 192 if (event.key() == KeyCode::Key_Up) { 193 ModelIndex new_index; 194 if (!selection().is_empty()) { 195 auto old_index = selection().first(); 196 new_index = model.index(old_index.row() - 1, old_index.column()); 197 } else { 198 new_index = model.index(0, 0); 199 } 200 if (model.is_valid(new_index)) { 201 selection().set(new_index); 202 scroll_into_view(new_index, Orientation::Vertical); 203 update(); 204 } 205 return; 206 } 207 if (event.key() == KeyCode::Key_Down) { 208 ModelIndex new_index; 209 if (!selection().is_empty()) { 210 auto old_index = selection().first(); 211 new_index = model.index(old_index.row() + 1, old_index.column()); 212 } else { 213 new_index = model.index(0, 0); 214 } 215 if (model.is_valid(new_index)) { 216 selection().set(new_index); 217 scroll_into_view(new_index, Orientation::Vertical); 218 update(); 219 } 220 return; 221 } 222 if (event.key() == KeyCode::Key_PageUp) { 223 int items_per_page = visible_content_rect().height() / item_height(); 224 auto old_index = selection().first(); 225 auto new_index = model.index(max(0, old_index.row() - items_per_page), old_index.column()); 226 if (model.is_valid(new_index)) { 227 selection().set(new_index); 228 scroll_into_view(new_index, Orientation::Vertical); 229 update(); 230 } 231 return; 232 } 233 if (event.key() == KeyCode::Key_PageDown) { 234 int items_per_page = visible_content_rect().height() / item_height(); 235 auto old_index = selection().first(); 236 auto new_index = model.index(min(model.row_count() - 1, old_index.row() + items_per_page), old_index.column()); 237 if (model.is_valid(new_index)) { 238 selection().set(new_index); 239 scroll_into_view(new_index, Orientation::Vertical); 240 update(); 241 } 242 return; 243 } 244 return Widget::keydown_event(event); 245} 246 247void ListView::scroll_into_view(const ModelIndex& index, Orientation orientation) 248{ 249 auto rect = content_rect(index.row()); 250 ScrollableWidget::scroll_into_view(rect, orientation); 251} 252 253void ListView::doubleclick_event(MouseEvent& event) 254{ 255 if (!model()) 256 return; 257 if (event.button() == MouseButton::Left) { 258 if (!selection().is_empty()) { 259 if (is_editable()) 260 begin_editing(selection().first()); 261 else 262 activate_selected(); 263 } 264 } 265} 266 267}