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