Serenity Operating System
at hosted 213 lines 7.4 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 "Locator.h" 28#include "Project.h" 29#include <LibGUI/BoxLayout.h> 30#include <LibGUI/TableView.h> 31#include <LibGUI/TextBox.h> 32#include <LibGUI/Window.h> 33 34extern RefPtr<Project> g_project; 35extern void open_file(const String&); 36static RefPtr<Gfx::Bitmap> s_file_icon; 37static RefPtr<Gfx::Bitmap> s_cplusplus_icon; 38static RefPtr<Gfx::Bitmap> s_header_icon; 39 40class LocatorSuggestionModel final : public GUI::Model { 41public: 42 explicit LocatorSuggestionModel(Vector<String>&& suggestions) 43 : m_suggestions(move(suggestions)) 44 { 45 } 46 47 enum Column { 48 Icon, 49 Name, 50 __Column_Count, 51 }; 52 virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_suggestions.size(); } 53 virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Column_Count; } 54 virtual GUI::Variant data(const GUI::ModelIndex& index, Role role = Role::Display) const override 55 { 56 auto& suggestion = m_suggestions.at(index.row()); 57 if (role == Role::Display) { 58 if (index.column() == Column::Name) 59 return suggestion; 60 if (index.column() == Column::Icon) { 61 if (suggestion.ends_with(".cpp")) 62 return *s_cplusplus_icon; 63 if (suggestion.ends_with(".h")) 64 return *s_header_icon; 65 return *s_file_icon; 66 } 67 } 68 return {}; 69 } 70 virtual void update() override {}; 71 72private: 73 Vector<String> m_suggestions; 74}; 75 76class LocatorTextBox final : public GUI::TextBox { 77 C_OBJECT(LocatorTextBox) 78public: 79 virtual ~LocatorTextBox() override {} 80 81 Function<void()> on_up; 82 Function<void()> on_down; 83 84 virtual void keydown_event(GUI::KeyEvent& event) override 85 { 86 if (event.key() == Key_Up) 87 on_up(); 88 else if (event.key() == Key_Down) 89 on_down(); 90 91 GUI::TextBox::keydown_event(event); 92 } 93 94private: 95 LocatorTextBox() {} 96}; 97 98Locator::Locator() 99{ 100 if (!s_cplusplus_icon) { 101 s_file_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-unknown.png"); 102 s_cplusplus_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-cplusplus.png"); 103 s_header_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-header.png"); 104 } 105 106 set_layout<GUI::VerticalBoxLayout>(); 107 set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 108 set_preferred_size(0, 20); 109 m_textbox = add<LocatorTextBox>(); 110 m_textbox->on_change = [this] { 111 update_suggestions(); 112 }; 113 m_textbox->on_escape_pressed = [this] { 114 m_popup_window->hide(); 115 }; 116 m_textbox->on_up = [this] { 117 GUI::ModelIndex new_index = m_suggestion_view->selection().first(); 118 if (new_index.is_valid()) 119 new_index = m_suggestion_view->model()->index(new_index.row() - 1); 120 else 121 new_index = m_suggestion_view->model()->index(0); 122 123 if (m_suggestion_view->model()->is_valid(new_index)) { 124 m_suggestion_view->selection().set(new_index); 125 m_suggestion_view->scroll_into_view(new_index, Orientation::Vertical); 126 } 127 }; 128 m_textbox->on_down = [this] { 129 GUI::ModelIndex new_index = m_suggestion_view->selection().first(); 130 if (new_index.is_valid()) 131 new_index = m_suggestion_view->model()->index(new_index.row() + 1); 132 else 133 new_index = m_suggestion_view->model()->index(0); 134 135 if (m_suggestion_view->model()->is_valid(new_index)) { 136 m_suggestion_view->selection().set(new_index); 137 m_suggestion_view->scroll_into_view(new_index, Orientation::Vertical); 138 } 139 }; 140 141 m_textbox->on_return_pressed = [this] { 142 auto selected_index = m_suggestion_view->selection().first(); 143 if (!selected_index.is_valid()) 144 return; 145 open_suggestion(selected_index); 146 }; 147 148 m_popup_window = GUI::Window::construct(); 149 // FIXME: This is obviously not a tooltip window, but it's the closest thing to what we want atm. 150 m_popup_window->set_window_type(GUI::WindowType::Tooltip); 151 m_popup_window->set_rect(0, 0, 500, 200); 152 153 m_suggestion_view = m_popup_window->set_main_widget<GUI::TableView>(); 154 m_suggestion_view->set_size_columns_to_fit_content(true); 155 m_suggestion_view->set_headers_visible(false); 156 157 m_suggestion_view->on_activation = [this](auto& index) { 158 open_suggestion(index); 159 }; 160} 161 162Locator::~Locator() 163{ 164} 165 166void Locator::open_suggestion(const GUI::ModelIndex& index) 167{ 168 auto filename_index = m_suggestion_view->model()->index(index.row(), LocatorSuggestionModel::Column::Name); 169 auto filename = m_suggestion_view->model()->data(filename_index, GUI::Model::Role::Display).to_string(); 170 open_file(filename); 171 close(); 172} 173 174void Locator::open() 175{ 176 m_textbox->set_focus(true); 177 if (!m_textbox->text().is_empty()) { 178 m_textbox->select_all(); 179 m_popup_window->show(); 180 } 181} 182 183void Locator::close() 184{ 185 m_popup_window->hide(); 186} 187 188void Locator::update_suggestions() 189{ 190 auto typed_text = m_textbox->text(); 191 Vector<String> suggestions; 192 g_project->for_each_text_file([&](auto& file) { 193 if (file.name().contains(typed_text)) 194 suggestions.append(file.name()); 195 }); 196 dbg() << "I have " << suggestions.size() << " suggestion(s):"; 197 for (auto& s : suggestions) { 198 dbg() << " " << s; 199 } 200 201 bool has_suggestions = !suggestions.is_empty(); 202 203 m_suggestion_view->set_model(adopt(*new LocatorSuggestionModel(move(suggestions)))); 204 205 if (!has_suggestions) 206 m_suggestion_view->selection().clear(); 207 else 208 m_suggestion_view->selection().set(m_suggestion_view->model()->index(0)); 209 210 m_popup_window->move_to(screen_relative_rect().top_left().translated(0, -m_popup_window->height())); 211 dbg() << "Popup rect: " << m_popup_window->rect(); 212 m_popup_window->show(); 213}