Serenity Operating System
at master 398 lines 12 kB view raw
1/* 2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/NeverDestroyed.h> 8#include <LibConfig/Client.h> 9#include <LibCore/EventLoop.h> 10#include <LibGUI/Action.h> 11#include <LibGUI/Application.h> 12#include <LibGUI/Clipboard.h> 13#include <LibGUI/ConnectionToWindowServer.h> 14#include <LibGUI/Desktop.h> 15#include <LibGUI/Label.h> 16#include <LibGUI/Menubar.h> 17#include <LibGUI/Painter.h> 18#include <LibGUI/Window.h> 19#include <LibGfx/Font/Font.h> 20#include <LibGfx/Palette.h> 21 22namespace GUI { 23 24class Application::TooltipWindow final : public Window { 25 C_OBJECT(TooltipWindow); 26 27public: 28 void set_tooltip(DeprecatedString const& tooltip) 29 { 30 m_label->set_text(Gfx::parse_ampersand_string(tooltip)); 31 int tooltip_width = m_label->effective_min_size().width().as_int() + 10; 32 int line_count = m_label->text().count("\n"sv); 33 int font_size = m_label->font().pixel_size_rounded_up(); 34 int tooltip_height = font_size * (1 + line_count) + ((font_size + 1) / 2) * line_count + 8; 35 36 Gfx::IntRect desktop_rect = Desktop::the().rect(); 37 if (tooltip_width > desktop_rect.width()) 38 tooltip_width = desktop_rect.width(); 39 40 set_rect(rect().x(), rect().y(), tooltip_width, tooltip_height); 41 } 42 43private: 44 TooltipWindow() 45 { 46 set_window_type(WindowType::Tooltip); 47 set_obey_widget_min_size(false); 48 m_label = set_main_widget<Label>().release_value_but_fixme_should_propagate_errors(); 49 m_label->set_background_role(Gfx::ColorRole::Tooltip); 50 m_label->set_foreground_role(Gfx::ColorRole::TooltipText); 51 m_label->set_fill_with_background_color(true); 52 m_label->set_frame_thickness(1); 53 m_label->set_frame_shape(Gfx::FrameShape::Container); 54 m_label->set_frame_shadow(Gfx::FrameShadow::Plain); 55 m_label->set_autosize(true); 56 } 57 58 RefPtr<Label> m_label; 59}; 60 61static NeverDestroyed<WeakPtr<Application>> s_the; 62 63Application* Application::the() 64{ 65 // NOTE: If we don't explicitly call revoke_weak_ptrs() in the 66 // ~Application destructor, we would have to change this to 67 // return s_the->strong_ref().ptr(); 68 // This is because this is using the unsafe operator*/operator-> 69 // that do not have the ability to check the ref count! 70 return *s_the; 71} 72 73Application::Application(int argc, char** argv, Core::EventLoop::MakeInspectable make_inspectable) 74{ 75 VERIFY(!*s_the); 76 *s_the = *this; 77 m_event_loop = make<Core::EventLoop>(make_inspectable); 78 ConnectionToWindowServer::the(); 79 Clipboard::initialize({}); 80 if (argc > 0) 81 m_invoked_as = argv[0]; 82 83 if (getenv("GUI_FOCUS_DEBUG")) 84 m_focus_debugging_enabled = true; 85 86 if (getenv("GUI_HOVER_DEBUG")) 87 m_hover_debugging_enabled = true; 88 89 if (getenv("GUI_DND_DEBUG")) 90 m_dnd_debugging_enabled = true; 91 92 for (int i = 1; i < argc; i++) { 93 DeprecatedString arg(argv[i]); 94 m_args.append(move(arg)); 95 } 96 97 m_tooltip_show_timer = Core::Timer::create_single_shot(700, [this] { 98 request_tooltip_show(); 99 }).release_value_but_fixme_should_propagate_errors(); 100 101 m_tooltip_hide_timer = Core::Timer::create_single_shot(50, [this] { 102 tooltip_hide_timer_did_fire(); 103 }).release_value_but_fixme_should_propagate_errors(); 104} 105 106static bool s_in_teardown; 107 108bool Application::in_teardown() 109{ 110 return s_in_teardown; 111} 112 113Application::~Application() 114{ 115 s_in_teardown = true; 116 revoke_weak_ptrs(); 117} 118 119int Application::exec() 120{ 121 return m_event_loop->exec(); 122} 123 124void Application::quit(int exit_code) 125{ 126 m_event_loop->quit(exit_code); 127} 128 129void Application::register_global_shortcut_action(Badge<Action>, Action& action) 130{ 131 m_global_shortcut_actions.set(action.shortcut(), &action); 132 m_global_shortcut_actions.set(action.alternate_shortcut(), &action); 133} 134 135void Application::unregister_global_shortcut_action(Badge<Action>, Action& action) 136{ 137 m_global_shortcut_actions.remove(action.shortcut()); 138 m_global_shortcut_actions.remove(action.alternate_shortcut()); 139} 140 141Action* Application::action_for_shortcut(Shortcut const& shortcut) const 142{ 143 auto it = m_global_shortcut_actions.find(shortcut); 144 if (it == m_global_shortcut_actions.end()) 145 return nullptr; 146 return (*it).value; 147} 148 149void Application::show_tooltip(DeprecatedString tooltip, Widget const* tooltip_source_widget) 150{ 151 if (!Desktop::the().system_effects().tooltips()) 152 return; 153 m_tooltip_source_widget = tooltip_source_widget; 154 if (!m_tooltip_window) { 155 m_tooltip_window = TooltipWindow::construct(); 156 m_tooltip_window->set_double_buffering_enabled(false); 157 } 158 m_tooltip_window->set_tooltip(move(tooltip)); 159 160 if (m_tooltip_window->is_visible()) { 161 request_tooltip_show(); 162 m_tooltip_show_timer->stop(); 163 m_tooltip_hide_timer->stop(); 164 } else { 165 m_tooltip_show_timer->restart(); 166 m_tooltip_hide_timer->stop(); 167 } 168} 169 170void Application::show_tooltip_immediately(DeprecatedString tooltip, Widget const* tooltip_source_widget) 171{ 172 if (!Desktop::the().system_effects().tooltips()) 173 return; 174 m_tooltip_source_widget = tooltip_source_widget; 175 if (!m_tooltip_window) { 176 m_tooltip_window = TooltipWindow::construct(); 177 m_tooltip_window->set_double_buffering_enabled(false); 178 } 179 m_tooltip_window->set_tooltip(move(tooltip)); 180 181 request_tooltip_show(); 182 m_tooltip_show_timer->stop(); 183 m_tooltip_hide_timer->stop(); 184} 185 186void Application::hide_tooltip() 187{ 188 m_tooltip_show_timer->stop(); 189 m_tooltip_hide_timer->start(); 190} 191 192void Application::did_create_window(Badge<Window>) 193{ 194 if (m_event_loop->was_exit_requested()) 195 m_event_loop->unquit(); 196} 197 198void Application::did_delete_last_window(Badge<Window>) 199{ 200 if (m_quit_when_last_window_deleted) 201 m_event_loop->quit(0); 202} 203 204void Application::set_system_palette(Core::AnonymousBuffer& buffer) 205{ 206 if (!m_system_palette) 207 m_system_palette = Gfx::PaletteImpl::create_with_anonymous_buffer(buffer); 208 else 209 m_system_palette->replace_internal_buffer({}, buffer); 210 211 if (!m_palette) 212 m_palette = m_system_palette; 213} 214 215void Application::set_palette(Palette& palette) 216{ 217 m_palette = palette.impl(); 218} 219 220Gfx::Palette Application::palette() const 221{ 222 return Palette(*m_palette); 223} 224 225void Application::request_tooltip_show() 226{ 227 VERIFY(m_tooltip_window); 228 Gfx::IntRect desktop_rect = Desktop::the().rect(); 229 230 int const margin = 30; 231 Gfx::IntPoint adjusted_pos = ConnectionToWindowServer::the().get_global_cursor_position(); 232 233 adjusted_pos.translate_by(0, 14); 234 235 if (adjusted_pos.x() + m_tooltip_window->width() >= desktop_rect.width() - margin) { 236 adjusted_pos = adjusted_pos.translated(-m_tooltip_window->width(), 0); 237 } 238 if (adjusted_pos.y() + m_tooltip_window->height() >= desktop_rect.height() - margin) { 239 adjusted_pos = adjusted_pos.translated(0, -(m_tooltip_window->height() * 2)); 240 } 241 if (adjusted_pos.x() < 0) 242 adjusted_pos.set_x(0); 243 244 m_tooltip_window->move_to(adjusted_pos); 245 m_tooltip_window->show(); 246} 247 248void Application::tooltip_hide_timer_did_fire() 249{ 250 m_tooltip_source_widget = nullptr; 251 if (m_tooltip_window) 252 m_tooltip_window->hide(); 253} 254 255void Application::window_did_become_active(Badge<Window>, Window& window) 256{ 257 m_active_window = window.make_weak_ptr<Window>(); 258 window.update(); 259} 260 261void Application::window_did_become_inactive(Badge<Window>, Window& window) 262{ 263 if (m_active_window.ptr() != &window) 264 return; 265 window.update(); 266 m_active_window = nullptr; 267} 268 269void Application::set_pending_drop_widget(Widget* widget) 270{ 271 if (m_pending_drop_widget == widget) 272 return; 273 if (m_pending_drop_widget) 274 m_pending_drop_widget->update(); 275 m_pending_drop_widget = widget; 276 if (m_pending_drop_widget) 277 m_pending_drop_widget->update(); 278} 279 280void Application::set_drag_hovered_widget_impl(Widget* widget, Gfx::IntPoint position, Vector<DeprecatedString> mime_types) 281{ 282 if (widget == m_drag_hovered_widget) 283 return; 284 285 if (m_drag_hovered_widget) { 286 Event leave_event(Event::DragLeave); 287 m_drag_hovered_widget->dispatch_event(leave_event, m_drag_hovered_widget->window()); 288 } 289 290 set_pending_drop_widget(nullptr); 291 m_drag_hovered_widget = widget; 292 293 if (m_drag_hovered_widget) { 294 DragEvent enter_event(Event::DragEnter, position, move(mime_types)); 295 enter_event.ignore(); 296 m_drag_hovered_widget->dispatch_event(enter_event, m_drag_hovered_widget->window()); 297 if (enter_event.is_accepted()) 298 set_pending_drop_widget(m_drag_hovered_widget); 299 ConnectionToWindowServer::the().async_set_accepts_drag(enter_event.is_accepted()); 300 } 301} 302 303void Application::notify_drag_cancelled(Badge<ConnectionToWindowServer>) 304{ 305 set_drag_hovered_widget_impl(nullptr); 306} 307 308void Application::event(Core::Event& event) 309{ 310 if (event.type() == GUI::Event::ActionEnter || event.type() == GUI::Event::ActionLeave) { 311 auto& action_event = static_cast<ActionEvent&>(event); 312 auto& action = action_event.action(); 313 if (action_event.type() == GUI::Event::ActionEnter) { 314 if (on_action_enter) 315 on_action_enter(action); 316 } else { 317 if (on_action_leave) 318 on_action_leave(action); 319 } 320 } 321 if (event.type() == GUI::Event::ThemeChange) { 322 if (on_theme_change) 323 on_theme_change(); 324 } 325 Object::event(event); 326} 327 328void Application::set_config_domain(String config_domain) 329{ 330 m_config_domain = move(config_domain); 331} 332 333void Application::register_recent_file_actions(Badge<GUI::Menu>, Vector<NonnullRefPtr<GUI::Action>> actions) 334{ 335 m_recent_file_actions = move(actions); 336 update_recent_file_actions(); 337} 338 339void Application::update_recent_file_actions() 340{ 341 VERIFY(!m_config_domain.is_empty()); 342 343 size_t number_of_recently_open_files = 0; 344 auto update_action = [&](size_t index) { 345 auto& action = m_recent_file_actions[index]; 346 char buffer = static_cast<char>('0' + index); 347 auto key = StringView(&buffer, 1); 348 auto path = Config::read_string(m_config_domain, "RecentFiles"sv, key); 349 350 if (path.is_empty()) { 351 action->set_visible(false); 352 action->set_enabled(false); 353 } else { 354 action->set_visible(true); 355 action->set_enabled(true); 356 action->set_text(path); 357 ++number_of_recently_open_files; 358 } 359 }; 360 for (size_t i = 0; i < max_recently_open_files(); ++i) 361 update_action(i); 362 363 // Hide or show the "(No recently open files)" placeholder. 364 m_recent_file_actions.last()->set_visible(number_of_recently_open_files == 0); 365} 366 367void Application::set_most_recently_open_file(String new_path) 368{ 369 Vector<DeprecatedString> new_recent_files_list; 370 371 for (size_t i = 0; i < max_recently_open_files(); ++i) { 372 static_assert(max_recently_open_files() < 10); 373 char buffer = static_cast<char>('0' + i); 374 auto key = StringView(&buffer, 1); 375 new_recent_files_list.append(Config::read_string(m_config_domain, "RecentFiles"sv, key)); 376 } 377 378 new_recent_files_list.remove_all_matching([&](auto& existing_path) { 379 return existing_path.view() == new_path; 380 }); 381 382 new_recent_files_list.prepend(new_path.to_deprecated_string()); 383 384 for (size_t i = 0; i < max_recently_open_files(); ++i) { 385 auto& path = new_recent_files_list[i]; 386 char buffer = static_cast<char>('0' + i); 387 auto key = StringView(&buffer, 1); 388 Config::write_string( 389 m_config_domain, 390 "RecentFiles"sv, 391 key, 392 path); 393 } 394 395 update_recent_file_actions(); 396} 397 398}