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 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}