Serenity Operating System
at hosted 359 lines 10 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 <AK/StringBuilder.h> 28#include <AK/Vector.h> 29#include <Kernel/KeyCode.h> 30#include <LibGUI/AbstractView.h> 31#include <LibGUI/DragOperation.h> 32#include <LibGUI/Model.h> 33#include <LibGUI/ModelEditingDelegate.h> 34#include <LibGUI/Painter.h> 35#include <LibGUI/ScrollBar.h> 36#include <LibGUI/TextBox.h> 37 38namespace GUI { 39 40AbstractView::AbstractView() 41 : m_selection(*this) 42{ 43} 44 45AbstractView::~AbstractView() 46{ 47} 48 49void AbstractView::set_model(RefPtr<Model> model) 50{ 51 if (model == m_model) 52 return; 53 if (m_model) 54 m_model->unregister_view({}, *this); 55 m_model = move(model); 56 if (m_model) 57 m_model->register_view({}, *this); 58 did_update_model(); 59} 60 61void AbstractView::did_update_model() 62{ 63 // FIXME: It's unfortunate that we lose so much view state when the model updates in any way. 64 stop_editing(); 65 m_edit_index = {}; 66 m_hovered_index = {}; 67 if (model()) { 68 selection().remove_matching([this](auto& index) { return !model()->is_valid(index); }); 69 } else { 70 selection().clear(); 71 } 72} 73 74void AbstractView::did_update_selection() 75{ 76 if (!model() || selection().first() != m_edit_index) 77 stop_editing(); 78 if (model() && on_selection && selection().first().is_valid()) 79 on_selection(selection().first()); 80} 81 82void AbstractView::did_scroll() 83{ 84 update_edit_widget_position(); 85} 86 87void AbstractView::update_edit_widget_position() 88{ 89 if (!m_edit_widget) 90 return; 91 m_edit_widget->set_relative_rect(m_edit_widget_content_rect.translated(-horizontal_scrollbar().value(), -vertical_scrollbar().value())); 92} 93 94void AbstractView::begin_editing(const ModelIndex& index) 95{ 96 ASSERT(is_editable()); 97 ASSERT(model()); 98 if (m_edit_index == index) 99 return; 100 if (!model()->is_editable(index)) 101 return; 102 if (m_edit_widget) { 103 remove_child(*m_edit_widget); 104 m_edit_widget = nullptr; 105 } 106 m_edit_index = index; 107 108 ASSERT(aid_create_editing_delegate); 109 m_editing_delegate = aid_create_editing_delegate(index); 110 m_editing_delegate->bind(*model(), index); 111 m_editing_delegate->set_value(model()->data(index, Model::Role::Display)); 112 m_edit_widget = m_editing_delegate->widget(); 113 add_child(*m_edit_widget); 114 m_edit_widget->move_to_back(); 115 m_edit_widget_content_rect = content_rect(index).translated(frame_thickness(), frame_thickness()); 116 update_edit_widget_position(); 117 m_edit_widget->set_focus(true); 118 m_editing_delegate->will_begin_editing(); 119 m_editing_delegate->on_commit = [this] { 120 ASSERT(model()); 121 model()->set_data(m_edit_index, m_editing_delegate->value()); 122 stop_editing(); 123 }; 124} 125 126void AbstractView::stop_editing() 127{ 128 m_edit_index = {}; 129 if (m_edit_widget) { 130 remove_child(*m_edit_widget); 131 m_edit_widget = nullptr; 132 } 133} 134 135void AbstractView::activate(const ModelIndex& index) 136{ 137 if (on_activation) 138 on_activation(index); 139} 140 141void AbstractView::activate_selected() 142{ 143 if (!on_activation) 144 return; 145 146 selection().for_each_index([this](auto& index) { 147 on_activation(index); 148 }); 149} 150 151void AbstractView::notify_selection_changed(Badge<ModelSelection>) 152{ 153 did_update_selection(); 154 if (on_selection_change) 155 on_selection_change(); 156 update(); 157} 158 159NonnullRefPtr<Gfx::Font> AbstractView::font_for_index(const ModelIndex& index) const 160{ 161 if (!model()) 162 return font(); 163 164 auto font_data = model()->data(index, Model::Role::Font); 165 if (font_data.is_font()) 166 return font_data.as_font(); 167 168 auto column_metadata = model()->column_metadata(index.column()); 169 if (column_metadata.font) 170 return *column_metadata.font; 171 return font(); 172} 173 174void AbstractView::mousedown_event(MouseEvent& event) 175{ 176 ScrollableWidget::mousedown_event(event); 177 178 if (!model()) 179 return; 180 181 if (event.button() == MouseButton::Left) 182 m_left_mousedown_position = event.position(); 183 184 auto index = index_at_event_position(event.position()); 185 m_might_drag = false; 186 187 if (!index.is_valid()) { 188 m_selection.clear(); 189 } else if (event.modifiers() & Mod_Ctrl) { 190 m_selection.toggle(index); 191 } else if (event.button() == MouseButton::Left && m_selection.contains(index) && !m_model->drag_data_type().is_null()) { 192 // We might be starting a drag, so don't throw away other selected items yet. 193 m_might_drag = true; 194 } else { 195 m_selection.set(index); 196 } 197 198 update(); 199} 200 201void AbstractView::set_hovered_index(const ModelIndex& index) 202{ 203 if (m_hovered_index == index) 204 return; 205 m_hovered_index = index; 206 update(); 207} 208 209void AbstractView::leave_event(Core::Event& event) 210{ 211 ScrollableWidget::leave_event(event); 212 set_hovered_index({}); 213} 214 215void AbstractView::mousemove_event(MouseEvent& event) 216{ 217 if (!model()) 218 return ScrollableWidget::mousemove_event(event); 219 220 auto hovered_index = index_at_event_position(event.position()); 221 set_hovered_index(hovered_index); 222 223 if (!m_might_drag) 224 return ScrollableWidget::mousemove_event(event); 225 226 if (!(event.buttons() & MouseButton::Left) || m_selection.is_empty()) { 227 m_might_drag = false; 228 return ScrollableWidget::mousemove_event(event); 229 } 230 231 auto diff = event.position() - m_left_mousedown_position; 232 auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y(); 233 constexpr int drag_distance_threshold = 5; 234 235 if (distance_travelled_squared <= drag_distance_threshold) 236 return ScrollableWidget::mousemove_event(event); 237 238 auto data_type = m_model->drag_data_type(); 239 ASSERT(!data_type.is_null()); 240 241 dbg() << "Initiate drag!"; 242 auto drag_operation = DragOperation::construct(); 243 244 RefPtr<Gfx::Bitmap> bitmap; 245 246 StringBuilder text_builder; 247 StringBuilder data_builder; 248 bool first = true; 249 m_selection.for_each_index([&](auto& index) { 250 auto text_data = m_model->data(index); 251 if (!first) 252 text_builder.append(", "); 253 text_builder.append(text_data.to_string()); 254 255 auto drag_data = m_model->data(index, Model::Role::DragData); 256 data_builder.append(drag_data.to_string()); 257 data_builder.append('\n'); 258 259 first = false; 260 261 if (!bitmap) { 262 Variant icon_data = model()->data(index, Model::Role::Icon); 263 if (icon_data.is_icon()) 264 bitmap = icon_data.as_icon().bitmap_for_size(32); 265 } 266 }); 267 268 drag_operation->set_text(text_builder.to_string()); 269 drag_operation->set_bitmap(bitmap); 270 drag_operation->set_data(data_type, data_builder.to_string()); 271 272 auto outcome = drag_operation->exec(); 273 274 switch (outcome) { 275 case DragOperation::Outcome::Accepted: 276 dbg() << "Drag was accepted!"; 277 break; 278 case DragOperation::Outcome::Cancelled: 279 dbg() << "Drag was cancelled!"; 280 break; 281 default: 282 ASSERT_NOT_REACHED(); 283 break; 284 } 285} 286 287void AbstractView::mouseup_event(MouseEvent& event) 288{ 289 ScrollableWidget::mouseup_event(event); 290 291 if (!model()) 292 return; 293 294 if (m_might_drag) { 295 // We were unsure about unselecting items other than the current one 296 // in mousedown_event(), because we could be seeing a start of a drag. 297 // Since we're here, it was not that; so fix up the selection now. 298 auto index = index_at_event_position(event.position()); 299 if (index.is_valid()) 300 m_selection.set(index); 301 else 302 m_selection.clear(); 303 m_might_drag = false; 304 update(); 305 } 306} 307 308void AbstractView::doubleclick_event(MouseEvent& event) 309{ 310 if (!model()) 311 return; 312 313 if (event.button() != MouseButton::Left) 314 return; 315 316 m_might_drag = false; 317 318 auto index = index_at_event_position(event.position()); 319 320 if (!index.is_valid()) 321 m_selection.clear(); 322 else if (!m_selection.contains(index)) 323 m_selection.set(index); 324 325 activate_selected(); 326} 327 328void AbstractView::context_menu_event(ContextMenuEvent& event) 329{ 330 if (!model()) 331 return; 332 333 auto index = index_at_event_position(event.position()); 334 335 if (index.is_valid()) 336 m_selection.add(index); 337 else 338 selection().clear(); 339 340 if (on_context_menu_request) 341 on_context_menu_request(index, event); 342} 343 344void AbstractView::drop_event(DropEvent& event) 345{ 346 event.accept(); 347 348 if (!model()) 349 return; 350 351 auto index = index_at_event_position(event.position()); 352 if (!index.is_valid()) 353 return; 354 355 if (on_drop) 356 on_drop(index, event); 357} 358 359}