Serenity Operating System
1/*
2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Debug.h>
8#include <AK/StringBuilder.h>
9#include <AK/Utf8View.h>
10#include <LibCore/Object.h>
11#include <LibCore/Timer.h>
12#include <LibGUI/AbstractView.h>
13#include <LibGUI/DragOperation.h>
14#include <LibGUI/Model.h>
15#include <LibGUI/ModelEditingDelegate.h>
16#include <LibGUI/Painter.h>
17#include <LibGUI/Scrollbar.h>
18#include <LibGUI/TextBox.h>
19#include <LibGfx/Palette.h>
20
21namespace GUI {
22
23AbstractView::AbstractView()
24 : m_sort_order(SortOrder::Ascending)
25 , m_selection(*this)
26{
27 REGISTER_BOOL_PROPERTY("activates_on_selection", activates_on_selection, set_activates_on_selection);
28 REGISTER_BOOL_PROPERTY("editable", is_editable, set_editable);
29 REGISTER_BOOL_PROPERTY("searchable", is_searchable, set_searchable);
30 REGISTER_ENUM_PROPERTY("selection_behavior", selection_behavior, set_selection_behavior, SelectionBehavior,
31 { SelectionBehavior::SelectItems, "SelectItems" },
32 { SelectionBehavior::SelectRows, "SelectRows" });
33 REGISTER_ENUM_PROPERTY("selection_mode", selection_mode, set_selection_mode, SelectionMode,
34 { SelectionMode::SingleSelection, "SingleSelection" },
35 { SelectionMode::MultiSelection, "MultiSeleciton" },
36 { SelectionMode::NoSelection, "NoSelection" });
37 REGISTER_INT_PROPERTY("key_column", key_column, set_key_column);
38 REGISTER_ENUM_PROPERTY("sort_order", sort_order, set_sort_order, SortOrder,
39 { SortOrder::Ascending, "Ascending" },
40 { SortOrder::Descending, "Descending" });
41 REGISTER_BOOL_PROPERTY("tab_key_navigation_enabled", is_tab_key_navigation_enabled, set_tab_key_navigation_enabled);
42 REGISTER_BOOL_PROPERTY("draw_item_text_with_shadow", does_draw_item_text_with_shadow, set_draw_item_text_with_shadow);
43
44 set_focus_policy(GUI::FocusPolicy::StrongFocus);
45}
46
47AbstractView::~AbstractView()
48{
49 if (m_highlighted_search_timer)
50 m_highlighted_search_timer->stop();
51 if (m_model)
52 m_model->unregister_view({}, *this);
53}
54
55void AbstractView::set_model(RefPtr<Model> model)
56{
57 if (model == m_model)
58 return;
59 if (m_model)
60 m_model->unregister_view({}, *this);
61 m_model = move(model);
62 if (m_model)
63 m_model->register_view({}, *this);
64 model_did_update(GUI::Model::InvalidateAllIndices);
65 scroll_to_top();
66}
67
68void AbstractView::model_did_update(unsigned int flags)
69{
70 if (!model() || (flags & GUI::Model::InvalidateAllIndices)) {
71 stop_editing();
72 m_edit_index = {};
73 m_hovered_index = {};
74 m_cursor_index = {};
75 m_drop_candidate_index = {};
76 clear_selection();
77 } else {
78 // FIXME: These may no longer point to whatever they did before,
79 // but let's be optimistic until we can be sure about it.
80 if (!model()->is_within_range(m_edit_index)) {
81 stop_editing();
82 m_edit_index = {};
83 }
84 if (!model()->is_within_range(m_hovered_index))
85 m_hovered_index = {};
86 if (!model()->is_within_range(m_cursor_index))
87 m_cursor_index = {};
88 if (!model()->is_within_range(m_drop_candidate_index))
89 m_drop_candidate_index = {};
90 selection().remove_all_matching([this](auto& index) { return !model()->is_within_range(index); });
91
92 auto index = find_next_search_match(m_highlighted_search.view());
93 if (index.is_valid())
94 highlight_search(index);
95 }
96 m_selection_start_index = {};
97}
98
99void AbstractView::clear_selection()
100{
101 m_selection.clear();
102}
103
104void AbstractView::set_selection(ModelIndex const& new_index)
105{
106 m_selection.set(new_index);
107}
108
109void AbstractView::set_selection_start_index(ModelIndex const& new_index)
110{
111 m_selection_start_index = new_index;
112}
113
114void AbstractView::add_selection(ModelIndex const& new_index)
115{
116 m_selection.add(new_index);
117}
118
119void AbstractView::remove_selection(ModelIndex const& new_index)
120{
121 m_selection.remove(new_index);
122}
123
124void AbstractView::toggle_selection(ModelIndex const& new_index)
125{
126 m_selection.toggle(new_index);
127}
128
129void AbstractView::did_update_selection()
130{
131 if (!model() || selection().first() != m_edit_index)
132 stop_editing();
133 if (model() && on_selection_change)
134 on_selection_change();
135}
136
137void AbstractView::did_scroll()
138{
139 update_edit_widget_position();
140}
141
142void AbstractView::update_edit_widget_position()
143{
144 if (!m_edit_widget)
145 return;
146 m_edit_widget->set_relative_rect(m_edit_widget_content_rect.translated(-horizontal_scrollbar().value(), -vertical_scrollbar().value()));
147}
148
149void AbstractView::begin_editing(ModelIndex const& index)
150{
151 VERIFY(is_editable());
152 VERIFY(model());
153 if (m_edit_index == index)
154 return;
155 if (!model()->is_editable(index))
156 return;
157 if (m_edit_widget) {
158 remove_child(*m_edit_widget);
159 m_edit_widget = nullptr;
160 }
161 m_edit_index = index;
162
163 VERIFY(aid_create_editing_delegate);
164 m_editing_delegate = aid_create_editing_delegate(index);
165 m_editing_delegate->bind(*model(), index);
166 m_editing_delegate->set_value(index.data());
167 m_edit_widget = m_editing_delegate->widget();
168 add_child(*m_edit_widget);
169 m_edit_widget->move_to_back();
170 m_edit_widget_content_rect = editing_rect(index).translated(frame_thickness(), frame_thickness());
171 update_edit_widget_position();
172 m_edit_widget->set_focus(true);
173 m_editing_delegate->will_begin_editing();
174 m_editing_delegate->on_commit = [this] {
175 VERIFY(model());
176 model()->set_data(m_edit_index, m_editing_delegate->value());
177 stop_editing();
178 };
179 m_editing_delegate->on_rollback = [this] {
180 VERIFY(model());
181 stop_editing();
182 };
183 m_editing_delegate->on_change = [this, index] {
184 editing_widget_did_change(index);
185 };
186}
187
188void AbstractView::stop_editing()
189{
190 bool take_back_focus = false;
191 m_edit_index = {};
192 if (m_edit_widget) {
193 take_back_focus = m_edit_widget->is_focused();
194 remove_child(*m_edit_widget);
195 m_edit_widget = nullptr;
196 }
197 if (take_back_focus)
198 set_focus(true);
199}
200
201void AbstractView::activate(ModelIndex const& index)
202{
203 if (on_activation)
204 on_activation(index);
205}
206
207void AbstractView::activate_selected()
208{
209 if (!on_activation)
210 return;
211
212 selection().for_each_index([this](auto& index) {
213 on_activation(index);
214 });
215}
216
217void AbstractView::notify_selection_changed(Badge<ModelSelection>)
218{
219 did_update_selection();
220 if (!m_suppress_update_on_selection_change)
221 update();
222}
223
224NonnullRefPtr<Gfx::Font const> AbstractView::font_for_index(ModelIndex const& index) const
225{
226 if (!model())
227 return font();
228
229 auto font_data = index.data(ModelRole::Font);
230 if (font_data.is_font())
231 return font_data.as_font();
232
233 return font();
234}
235
236void AbstractView::mousedown_event(MouseEvent& event)
237{
238 AbstractScrollableWidget::mousedown_event(event);
239
240 if (!model())
241 return;
242
243 if (event.button() == MouseButton::Primary)
244 m_left_mousedown_position = event.position();
245
246 auto index = index_at_event_position(event.position());
247 m_might_drag = false;
248
249 if (!index.is_valid()) {
250 clear_selection();
251 } else if (event.modifiers() & Mod_Ctrl) {
252 set_cursor(index, SelectionUpdate::Ctrl);
253 } else if (event.modifiers() & Mod_Shift) {
254 set_cursor(index, SelectionUpdate::Shift);
255 } else if (event.button() == MouseButton::Primary && m_selection.contains(index) && !m_model->drag_data_type().is_null()) {
256 // We might be starting a drag, so don't throw away other selected items yet.
257 m_might_drag = true;
258 } else if (event.button() == MouseButton::Secondary) {
259 set_cursor(index, SelectionUpdate::ClearIfNotSelected);
260 } else {
261 set_cursor(index, SelectionUpdate::Set);
262 m_might_drag = true;
263 }
264
265 update();
266}
267
268void AbstractView::set_hovered_index(ModelIndex const& index)
269{
270 if (m_hovered_index == index)
271 return;
272 auto old_index = m_hovered_index;
273 m_hovered_index = index;
274 did_change_hovered_index(old_index, index);
275
276 if (old_index.is_valid())
277 update(to_widget_rect(paint_invalidation_rect(old_index)));
278
279 if (index.is_valid())
280 update(to_widget_rect(paint_invalidation_rect(index)));
281}
282
283void AbstractView::leave_event(Core::Event& event)
284{
285 AbstractScrollableWidget::leave_event(event);
286 set_hovered_index({});
287}
288
289void AbstractView::mousemove_event(MouseEvent& event)
290{
291 if (!model())
292 return AbstractScrollableWidget::mousemove_event(event);
293
294 auto hovered_index = index_at_event_position(event.position());
295 set_hovered_index(hovered_index);
296
297 auto data_type = m_model->drag_data_type();
298 if (data_type.is_null())
299 return AbstractScrollableWidget::mousemove_event(event);
300
301 if (!m_might_drag)
302 return AbstractScrollableWidget::mousemove_event(event);
303
304 if (!(event.buttons() & MouseButton::Primary) || m_selection.is_empty()) {
305 m_might_drag = false;
306 return AbstractScrollableWidget::mousemove_event(event);
307 }
308
309 auto diff = event.position() - m_left_mousedown_position;
310 auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y();
311 constexpr int drag_distance_threshold = 5;
312
313 if (distance_travelled_squared <= drag_distance_threshold)
314 return AbstractScrollableWidget::mousemove_event(event);
315
316 VERIFY(!data_type.is_null());
317
318 if (m_is_dragging)
319 return;
320
321 // An event might sneak in between us constructing the drag operation and the
322 // event loop exec at the end of `drag_operation->exec()' if the user is fast enough.
323 // Prevent this by just ignoring later drag initiations (until the current drag operation ends).
324 TemporaryChange dragging { m_is_dragging, true };
325
326 dbgln_if(DRAG_DEBUG, "Initiate drag!");
327 auto drag_operation = DragOperation::construct();
328
329 drag_operation->set_mime_data(m_model->mime_data(m_selection));
330
331 auto outcome = drag_operation->exec();
332
333 switch (outcome) {
334 case DragOperation::Outcome::Accepted:
335 dbgln_if(DRAG_DEBUG, "Drag was accepted!");
336 break;
337 case DragOperation::Outcome::Cancelled:
338 dbgln_if(DRAG_DEBUG, "Drag was cancelled!");
339 m_might_drag = false;
340 break;
341 default:
342 VERIFY_NOT_REACHED();
343 break;
344 }
345}
346
347void AbstractView::mouseup_event(MouseEvent& event)
348{
349 AbstractScrollableWidget::mouseup_event(event);
350
351 if (!model())
352 return;
353
354 set_automatic_scrolling_timer_active(false);
355
356 if (m_might_drag) {
357 // We were unsure about unselecting items other than the current one
358 // in mousedown_event(), because we could be seeing a start of a drag.
359 // Since we're here, it was not that; so fix up the selection now.
360 auto index = index_at_event_position(event.position());
361 if (index.is_valid()) {
362 set_cursor(index, SelectionUpdate::Set, true);
363 } else
364 clear_selection();
365 m_might_drag = false;
366 update();
367 }
368
369 if (activates_on_selection())
370 activate_selected();
371}
372
373void AbstractView::doubleclick_event(MouseEvent& event)
374{
375 if (!model())
376 return;
377
378 if (event.button() != MouseButton::Primary)
379 return;
380
381 m_might_drag = false;
382
383 auto index = index_at_event_position(event.position());
384
385 if (!index.is_valid()) {
386 clear_selection();
387 return;
388 }
389
390 if (!m_selection.contains(index))
391 set_selection(index);
392
393 if (is_editable() && edit_triggers() & EditTrigger::DoubleClicked)
394 begin_editing(cursor_index());
395 else
396 activate(cursor_index());
397}
398
399void AbstractView::context_menu_event(ContextMenuEvent& event)
400{
401 if (!model())
402 return;
403
404 auto index = index_at_event_position(event.position());
405
406 if (index.is_valid())
407 add_selection(index);
408 else
409 clear_selection();
410
411 if (on_context_menu_request)
412 on_context_menu_request(index, event);
413}
414
415void AbstractView::drop_event(DropEvent& event)
416{
417 event.accept();
418
419 if (!model())
420 return;
421
422 auto index = index_at_event_position(event.position());
423 if (on_drop)
424 on_drop(index, event);
425}
426
427void AbstractView::set_selection_mode(SelectionMode selection_mode)
428{
429 if (m_selection_mode == selection_mode)
430 return;
431 m_selection_mode = selection_mode;
432
433 if (m_selection_mode == SelectionMode::NoSelection)
434 m_selection.clear();
435 else if (m_selection_mode != SelectionMode::SingleSelection && m_selection.size() > 1) {
436 auto first_selected = m_selection.first();
437 m_selection.clear();
438 m_selection.set(first_selected);
439 }
440
441 update();
442}
443
444void AbstractView::set_key_column_and_sort_order(int column, SortOrder sort_order)
445{
446 m_key_column = column;
447 m_sort_order = sort_order;
448
449 if (model())
450 model()->sort(column, sort_order);
451
452 update();
453}
454
455void AbstractView::select_range(ModelIndex const& index)
456{
457 auto min_row = min(selection_start_index().row(), index.row());
458 auto max_row = max(selection_start_index().row(), index.row());
459 auto min_column = min(selection_start_index().column(), index.column());
460 auto max_column = max(selection_start_index().column(), index.column());
461
462 clear_selection();
463 for (auto row = min_row; row <= max_row; ++row) {
464 for (auto column = min_column; column <= max_column; ++column) {
465 auto new_index = model()->index(row, column);
466 if (new_index.is_valid())
467 toggle_selection(new_index);
468 }
469 }
470}
471
472void AbstractView::set_cursor(ModelIndex index, SelectionUpdate selection_update, bool scroll_cursor_into_view)
473{
474 if (!model() || !index.is_valid() || selection_mode() == SelectionMode::NoSelection) {
475 m_cursor_index = {};
476 stop_highlighted_search_timer();
477 return;
478 }
479
480 if (!m_cursor_index.is_valid() || model()->parent_index(m_cursor_index) != model()->parent_index(index))
481 stop_highlighted_search_timer();
482
483 if (selection_mode() == SelectionMode::SingleSelection && (selection_update == SelectionUpdate::Ctrl || selection_update == SelectionUpdate::Shift))
484 selection_update = SelectionUpdate::Set;
485
486 if (model()->is_within_range(index)) {
487 if (selection_update == SelectionUpdate::Set) {
488 set_selection(index);
489 set_selection_start_index(index);
490 } else if (selection_update == SelectionUpdate::Ctrl) {
491 toggle_selection(index);
492 } else if (selection_update == SelectionUpdate::ClearIfNotSelected) {
493 if (!m_selection.contains(index))
494 clear_selection();
495 } else if (selection_update == SelectionUpdate::Shift) {
496 if (!selection_start_index().is_valid())
497 set_selection_start_index(index);
498 select_range(index);
499 }
500
501 // FIXME: Support the other SelectionUpdate types
502
503 auto old_cursor_index = m_cursor_index;
504 m_cursor_index = index;
505 did_change_cursor_index(old_cursor_index, m_cursor_index);
506
507 if (scroll_cursor_into_view)
508 scroll_into_view(index, true, true);
509 update();
510 }
511}
512
513void AbstractView::set_edit_triggers(unsigned triggers)
514{
515 m_edit_triggers = triggers;
516}
517
518void AbstractView::hide_event(HideEvent& event)
519{
520 stop_editing();
521 AbstractScrollableWidget::hide_event(event);
522}
523
524void AbstractView::keydown_event(KeyEvent& event)
525{
526 if (event.alt()) {
527 event.ignore();
528 return;
529 }
530
531 if (event.key() == KeyCode::Key_F2) {
532 if (is_editable() && edit_triggers() & EditTrigger::EditKeyPressed) {
533 begin_editing(cursor_index());
534 event.accept();
535 return;
536 }
537 }
538
539 if (event.key() == KeyCode::Key_Return) {
540 activate_selected();
541 event.accept();
542 return;
543 }
544
545 SelectionUpdate selection_update = SelectionUpdate::Set;
546 if (event.modifiers() == KeyModifier::Mod_Shift) {
547 selection_update = SelectionUpdate::Shift;
548 }
549
550 if (event.key() == KeyCode::Key_Left) {
551 move_cursor(CursorMovement::Left, selection_update);
552 event.accept();
553 return;
554 }
555 if (event.key() == KeyCode::Key_Right) {
556 move_cursor(CursorMovement::Right, selection_update);
557 event.accept();
558 return;
559 }
560 if (event.key() == KeyCode::Key_Up) {
561 move_cursor(CursorMovement::Up, selection_update);
562 event.accept();
563 return;
564 }
565 if (event.key() == KeyCode::Key_Down) {
566 move_cursor(CursorMovement::Down, selection_update);
567 event.accept();
568 return;
569 }
570 if (event.key() == KeyCode::Key_Home) {
571 move_cursor(CursorMovement::Home, selection_update);
572 event.accept();
573 return;
574 }
575 if (event.key() == KeyCode::Key_End) {
576 move_cursor(CursorMovement::End, selection_update);
577 event.accept();
578 return;
579 }
580 if (event.key() == KeyCode::Key_PageUp) {
581 move_cursor(CursorMovement::PageUp, selection_update);
582 event.accept();
583 return;
584 }
585 if (event.key() == KeyCode::Key_PageDown) {
586 move_cursor(CursorMovement::PageDown, selection_update);
587 event.accept();
588 return;
589 }
590
591 if (is_searchable()) {
592 if (event.key() == KeyCode::Key_Backspace) {
593 if (!m_highlighted_search.is_null()) {
594 // if (event.modifiers() == Mod_Ctrl) {
595 // TODO: delete last word
596 // }
597 Utf8View view(m_highlighted_search);
598 size_t n_code_points = view.length();
599 if (n_code_points > 1) {
600 n_code_points--;
601 StringBuilder sb;
602 for (auto it = view.begin(); it != view.end(); ++it) {
603 if (n_code_points == 0)
604 break;
605 n_code_points--;
606 sb.append_code_point(*it);
607 }
608 auto index = find_next_search_match(sb.string_view());
609 if (index.is_valid()) {
610 m_highlighted_search = sb.to_deprecated_string();
611 highlight_search(index);
612 start_highlighted_search_timer();
613 }
614 } else {
615 stop_highlighted_search_timer();
616 }
617
618 event.accept();
619 return;
620 }
621 } else if (event.key() == KeyCode::Key_Escape) {
622 if (!m_highlighted_search.is_null()) {
623 stop_highlighted_search_timer();
624
625 event.accept();
626 return;
627 }
628 } else if (event.key() != KeyCode::Key_Tab && !event.ctrl() && !event.alt() && event.code_point() != 0) {
629 StringBuilder sb;
630 sb.append(m_highlighted_search);
631 sb.append_code_point(event.code_point());
632
633 auto index = find_next_search_match(sb.string_view());
634 if (index.is_valid()) {
635 m_highlighted_search = sb.to_deprecated_string();
636 highlight_search(index);
637 start_highlighted_search_timer();
638 set_cursor(index, SelectionUpdate::None, true);
639 }
640
641 event.accept();
642 return;
643 }
644 }
645
646 AbstractScrollableWidget::keydown_event(event);
647}
648
649void AbstractView::stop_highlighted_search_timer()
650{
651 m_highlighted_search = nullptr;
652 if (m_highlighted_search_timer)
653 m_highlighted_search_timer->stop();
654 if (m_highlighted_search_index.is_valid()) {
655 m_highlighted_search_index = {};
656 update();
657 }
658}
659
660void AbstractView::start_highlighted_search_timer()
661{
662 if (!m_highlighted_search_timer) {
663 m_highlighted_search_timer = add<Core::Timer>();
664 m_highlighted_search_timer->set_single_shot(true);
665 m_highlighted_search_timer->on_timeout = [this] {
666 stop_highlighted_search_timer();
667 };
668 }
669 m_highlighted_search_timer->set_interval(5 * 1000);
670 m_highlighted_search_timer->restart();
671}
672
673ModelIndex AbstractView::find_next_search_match(StringView const search)
674{
675 if (search.is_empty())
676 return {};
677
678 auto found_indices = model()->matches(search, Model::MatchesFlag::FirstMatchOnly | Model::MatchesFlag::MatchAtStart | Model::MatchesFlag::CaseInsensitive, model()->parent_index(cursor_index()));
679
680 if (found_indices.is_empty())
681 return {};
682
683 return found_indices[0];
684}
685
686void AbstractView::highlight_search(ModelIndex const index)
687{
688 m_highlighted_search_index = index;
689 set_selection(index);
690 scroll_into_view(index);
691 update();
692}
693
694bool AbstractView::is_searchable() const
695{
696 if (!m_searchable || !model())
697 return false;
698 return model()->is_searchable();
699}
700
701void AbstractView::set_searchable(bool searchable)
702{
703 if (m_searchable == searchable)
704 return;
705 m_searchable = searchable;
706 if (!m_searchable)
707 stop_highlighted_search_timer();
708}
709
710void AbstractView::draw_item_text(Gfx::Painter& painter, ModelIndex const& index, bool is_selected, Gfx::IntRect const& text_rect, StringView item_text, Gfx::Font const& font, Gfx::TextAlignment alignment, Gfx::TextElision elision, size_t search_highlighting_offset)
711{
712 if (m_edit_index == index)
713 return;
714
715 Color text_color;
716 if (is_selected)
717 text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
718 else
719 text_color = index.data(ModelRole::ForegroundColor).to_color(palette().color(foreground_role()));
720 if (index == m_highlighted_search_index) {
721 Utf8View searching_text(m_highlighted_search);
722 auto searching_length = searching_text.length();
723 if (searching_length > search_highlighting_offset)
724 searching_length -= search_highlighting_offset;
725 else if (search_highlighting_offset > 0)
726 searching_length = 0;
727
728 // Highlight the text background first
729 auto background_searching_length = searching_length;
730 painter.draw_text([&](Gfx::FloatRect const& rect, Utf8CodePointIterator&) {
731 if (background_searching_length > 0) {
732 background_searching_length--;
733 painter.fill_rect(rect.to_type<int>().inflated(0, 2), palette().highlight_searching());
734 }
735 },
736 text_rect, item_text, font, alignment, elision);
737
738 // Then draw the text
739 auto text_searching_length = searching_length;
740 auto highlight_text_color = palette().highlight_searching_text();
741 searching_length = searching_text.length();
742 painter.draw_text([&](auto const& rect, Utf8CodePointIterator& it) {
743 if (text_searching_length > 0) {
744 text_searching_length--;
745 painter.draw_glyph_or_emoji(rect.location(), it, font, highlight_text_color);
746 } else {
747 painter.draw_glyph_or_emoji(rect.location(), it, font, text_color);
748 }
749 },
750 text_rect, item_text, font, alignment, elision);
751 } else {
752 if (m_draw_item_text_with_shadow) {
753 painter.draw_text(text_rect.translated(1, 1), item_text, font, alignment, Color::Black, elision);
754 painter.draw_text(text_rect, item_text, font, alignment, Color::White, elision);
755 } else {
756 painter.draw_text(text_rect, item_text, font, alignment, text_color, elision);
757 }
758 }
759}
760
761void AbstractView::focusin_event(FocusEvent& event)
762{
763 AbstractScrollableWidget::focusin_event(event);
764
765 if (model() && !cursor_index().is_valid()) {
766 move_cursor(CursorMovement::Home, SelectionUpdate::None);
767 clear_selection();
768 }
769}
770
771void AbstractView::drag_enter_event(DragEvent& event)
772{
773 if (!model())
774 return;
775
776 if (!is_editable())
777 return;
778
779 // NOTE: Right now, AbstractView accepts drags since we won't get "drag move" events
780 // unless we accept the "drag enter" event.
781 // We might be able to reduce event traffic by communicating the set of drag-accepting
782 // rects in this widget to the windowing system somehow.
783 event.accept();
784 dbgln_if(DRAG_DEBUG, "accepting drag of {}", event.mime_types().first());
785}
786
787void AbstractView::drag_move_event(DragEvent& event)
788{
789 if (!model())
790 return;
791
792 auto index = index_at_event_position(event.position());
793 ModelIndex new_drop_candidate_index;
794 bool acceptable = model()->accepts_drag(index, event.mime_types());
795
796 if (acceptable && index.is_valid())
797 new_drop_candidate_index = index;
798
799 if (acceptable) {
800 m_automatic_scroll_delta = automatic_scroll_delta_from_position(event.position());
801 set_automatic_scrolling_timer_active(!m_automatic_scroll_delta.is_zero());
802 }
803
804 if (m_drop_candidate_index != new_drop_candidate_index) {
805 m_drop_candidate_index = new_drop_candidate_index;
806 update();
807 }
808 if (m_drop_candidate_index.is_valid())
809 event.accept();
810}
811
812void AbstractView::drag_leave_event(Event&)
813{
814 if (m_drop_candidate_index.is_valid()) {
815 m_drop_candidate_index = {};
816 update();
817 }
818
819 set_automatic_scrolling_timer_active(false);
820}
821
822void AbstractView::automatic_scrolling_timer_did_fire()
823{
824 if (m_automatic_scroll_delta.is_zero())
825 return;
826
827 vertical_scrollbar().increase_slider_by(m_automatic_scroll_delta.y());
828 horizontal_scrollbar().increase_slider_by(m_automatic_scroll_delta.x());
829}
830
831}