Serenity Operating System
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}