Serenity Operating System
1/*
2 * Copyright (c) 2022, Itamar S. <itamar8910@gmail.com>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <LibGUI/Button.h>
9#include <LibGUI/IncrementalSearchBanner.h>
10#include <LibGUI/IncrementalSearchBannerGML.h>
11#include <LibGUI/Label.h>
12#include <LibGUI/Layout.h>
13#include <LibGUI/Painter.h>
14#include <LibGUI/TextBox.h>
15#include <LibGfx/Palette.h>
16
17namespace GUI {
18
19IncrementalSearchBanner::IncrementalSearchBanner(TextEditor& editor)
20 : m_editor(editor)
21{
22 load_from_gml(incremental_search_banner_gml).release_value_but_fixme_should_propagate_errors();
23 m_index_label = find_descendant_of_type_named<Label>("incremental_search_banner_index_label");
24
25 m_wrap_search_button = find_descendant_of_type_named<Button>("incremental_search_banner_wrap_search_button");
26 m_wrap_search_button->on_checked = [this](auto is_checked) {
27 m_wrap_search = is_checked
28 ? TextDocument::SearchShouldWrap::Yes
29 : TextDocument::SearchShouldWrap::No;
30 };
31
32 m_match_case_button = find_descendant_of_type_named<Button>("incremental_search_banner_match_case_button");
33 m_match_case_button->on_checked = [this](auto is_checked) {
34 m_match_case = is_checked;
35 m_editor->reset_search_results();
36 search(TextEditor::SearchDirection::Forward);
37 };
38
39 m_close_button = find_descendant_of_type_named<Button>("incremental_search_banner_close_button");
40 m_close_button->set_text("\xE2\x9D\x8C"_short_string);
41 m_close_button->on_click = [this](auto) {
42 hide();
43 };
44
45 m_next_button = find_descendant_of_type_named<Button>("incremental_search_banner_next_button");
46 m_next_button->on_click = [this](auto) {
47 search(TextEditor::SearchDirection::Forward);
48 };
49
50 m_previous_button = find_descendant_of_type_named<Button>("incremental_search_banner_previous_button");
51 m_previous_button->on_click = [this](auto) {
52 search(TextEditor::SearchDirection::Backward);
53 };
54
55 m_search_textbox = find_descendant_of_type_named<TextBox>("incremental_search_banner_search_textbox");
56 m_search_textbox->on_change = [this]() {
57 m_editor->reset_search_results();
58 search(TextEditor::SearchDirection::Forward);
59 };
60
61 m_search_textbox->on_return_pressed = [this]() {
62 search(TextEditor::SearchDirection::Forward);
63 };
64
65 m_search_textbox->on_shift_return_pressed = [this]() {
66 search(TextEditor::SearchDirection::Backward);
67 };
68
69 m_search_textbox->on_escape_pressed = [this]() {
70 hide();
71 };
72}
73
74void IncrementalSearchBanner::show()
75{
76 set_visible(true);
77 m_editor->do_layout();
78 m_editor->update_scrollbar_ranges();
79 m_search_textbox->set_focus(true);
80}
81
82void IncrementalSearchBanner::hide()
83{
84 set_visible(false);
85 m_editor->do_layout();
86 m_editor->update_scrollbar_ranges();
87 m_editor->reset_search_results();
88 m_editor->set_focus(true);
89}
90
91void IncrementalSearchBanner::search(TextEditor::SearchDirection direction)
92{
93 auto needle = m_search_textbox->text();
94 if (needle.is_empty()) {
95 m_editor->reset_search_results();
96 m_index_label->set_text(DeprecatedString::empty());
97 return;
98 }
99
100 auto index = m_editor->search_result_index().value_or(0) + 1;
101 if (m_wrap_search == TextDocument::SearchShouldWrap::No) {
102 auto forward = direction == TextEditor::SearchDirection::Forward;
103 if ((index == m_editor->search_results().size() && forward) || (index == 1 && !forward))
104 return;
105 }
106
107 auto result = m_editor->find_text(needle, direction, m_wrap_search, false, m_match_case);
108 index = m_editor->search_result_index().value_or(0) + 1;
109 if (result.is_valid())
110 m_index_label->set_text(DeprecatedString::formatted("{} of {}", index, m_editor->search_results().size()));
111 else
112 m_index_label->set_text(DeprecatedString::empty());
113}
114
115void IncrementalSearchBanner::paint_event(PaintEvent& event)
116{
117 Widget::paint_event(event);
118
119 Painter painter(*this);
120 painter.add_clip_rect(event.rect());
121 painter.draw_line({ 0, rect().bottom() - 1 }, { width(), rect().bottom() - 1 }, palette().threed_shadow1());
122 painter.draw_line({ 0, rect().bottom() }, { width(), rect().bottom() }, palette().threed_shadow2());
123}
124
125Optional<UISize> IncrementalSearchBanner::calculated_min_size() const
126{
127 auto textbox_width = m_search_textbox->effective_min_size().width().as_int();
128 auto textbox_height = m_search_textbox->effective_min_size().height().as_int();
129 auto button_width = m_next_button->effective_min_size().width().as_int();
130 VERIFY(layout());
131 auto margins = layout()->margins();
132 auto spacing = layout()->spacing();
133 return { { margins.left() + textbox_width + spacing + button_width * 2 + margins.right(), textbox_height + margins.top() + margins.bottom() } };
134}
135
136}