diff options
author | Tom <tomut@yahoo.com> | 2020-07-07 12:09:18 -0600 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-07-11 11:45:49 +0200 |
commit | fc4e01a3c9719113e9ae2e17e36777507b8edd52 (patch) | |
tree | 1d005f1b7395931d1cd7af1261eef3fd32fee894 | |
parent | 684b04e02a208cd6c3fa1f67e772228762769291 (diff) | |
download | serenity-fc4e01a3c9719113e9ae2e17e36777507b8edd52.zip |
WindowServer: Add support for default MenuItem
This allows marking a MenuItem as a default action, e.g. in a
context menu for an action that reflects what e.g. a double click
would perform.
Also enhance the window menu to mark the close action as the
default, and when double clicked perform that action.
Fixes #1289
-rw-r--r-- | Services/WindowServer/ClientConnection.cpp | 2 | ||||
-rw-r--r-- | Services/WindowServer/Menu.cpp | 22 | ||||
-rw-r--r-- | Services/WindowServer/Menu.h | 2 | ||||
-rw-r--r-- | Services/WindowServer/MenuItem.cpp | 8 | ||||
-rw-r--r-- | Services/WindowServer/MenuItem.h | 4 | ||||
-rw-r--r-- | Services/WindowServer/Window.cpp | 29 | ||||
-rw-r--r-- | Services/WindowServer/Window.h | 15 | ||||
-rw-r--r-- | Services/WindowServer/WindowFrame.cpp | 34 | ||||
-rw-r--r-- | Services/WindowServer/WindowManager.cpp | 103 | ||||
-rw-r--r-- | Services/WindowServer/WindowManager.h | 7 |
10 files changed, 194 insertions, 32 deletions
diff --git a/Services/WindowServer/ClientConnection.cpp b/Services/WindowServer/ClientConnection.cpp index c1da4bc0cb..4934d6a03d 100644 --- a/Services/WindowServer/ClientConnection.cpp +++ b/Services/WindowServer/ClientConnection.cpp @@ -648,7 +648,7 @@ void ClientConnection::handle(const Messages::WindowServer::WM_PopupWindowMenu& return; } auto& window = *(*it).value; - window.popup_window_menu(message.screen_position()); + window.popup_window_menu(message.screen_position(), WindowMenuDefaultAction::BasedOnWindowState); } void ClientConnection::handle(const Messages::WindowServer::WM_StartWindowResize& request) diff --git a/Services/WindowServer/Menu.cpp b/Services/WindowServer/Menu.cpp index 5370f19f16..b3efd1f1d2 100644 --- a/Services/WindowServer/Menu.cpp +++ b/Services/WindowServer/Menu.cpp @@ -110,9 +110,10 @@ int Menu::content_width() const for (auto& item : m_items) { if (item.type() != MenuItem::Text) continue; - int text_width = font().width(item.text()); + auto& use_font = item.is_default() ? Gfx::Font::default_bold_font() : font(); + int text_width = use_font.width(item.text()); if (!item.shortcut_text().is_empty()) { - int shortcut_width = font().width(item.shortcut_text()); + int shortcut_width = use_font.width(item.shortcut_text()); widest_shortcut = max(shortcut_width, widest_shortcut); } widest_text = max(widest_text, text_width); @@ -248,10 +249,14 @@ void Menu::draw() icon_rect.center_vertically_within(text_rect); painter.blit(icon_rect.location(), *item.icon(), item.icon()->rect()); } + auto& previous_font = painter.font(); + if (item.is_default()) + painter.set_font(Gfx::Font::default_bold_font()); painter.draw_text(text_rect, item.text(), Gfx::TextAlignment::CenterLeft, text_color); if (!item.shortcut_text().is_empty()) { painter.draw_text(item.rect().translated(-right_padding(), 0), item.shortcut_text(), Gfx::TextAlignment::CenterRight, text_color); } + painter.set_font(previous_font); if (item.is_submenu()) { static auto& submenu_arrow_bitmap = Gfx::CharacterBitmap::create_from_ascii(s_submenu_arrow_bitmap_data, s_submenu_arrow_bitmap_width, s_submenu_arrow_bitmap_height).leak_ref(); Gfx::IntRect submenu_arrow_rect { @@ -458,6 +463,19 @@ void Menu::did_activate(MenuItem& item) m_client->post_message(Messages::WindowClient::MenuItemActivated(m_menu_id, item.identifier())); } +bool Menu::activate_default() +{ + for (auto& item : m_items) { + if (item.type() == MenuItem::Type::Separator) + continue; + if (item.is_enabled() && item.is_default()) { + did_activate(item); + return true; + } + } + return false; +} + MenuItem* Menu::item_with_identifier(unsigned identifer) { for (auto& item : m_items) { diff --git a/Services/WindowServer/Menu.h b/Services/WindowServer/Menu.h index 1aa053e20d..2656febac2 100644 --- a/Services/WindowServer/Menu.h +++ b/Services/WindowServer/Menu.h @@ -87,6 +87,8 @@ public: bool is_window_menu_open() { return m_is_window_menu_open; } void set_window_menu_open(bool is_open) { m_is_window_menu_open = is_open; } + bool activate_default(); + int content_width() const; int item_height() const { return 20; } diff --git a/Services/WindowServer/MenuItem.cpp b/Services/WindowServer/MenuItem.cpp index 6d005be429..324724a893 100644 --- a/Services/WindowServer/MenuItem.cpp +++ b/Services/WindowServer/MenuItem.cpp @@ -71,6 +71,14 @@ void MenuItem::set_checked(bool checked) m_menu.redraw(); } +void MenuItem::set_default(bool is_default) +{ + if (m_default == is_default) + return; + m_default = is_default; + m_menu.redraw(); +} + Menu* MenuItem::submenu() { ASSERT(is_submenu()); diff --git a/Services/WindowServer/MenuItem.h b/Services/WindowServer/MenuItem.h index 08d8d502ac..6bb1c203f1 100644 --- a/Services/WindowServer/MenuItem.h +++ b/Services/WindowServer/MenuItem.h @@ -58,6 +58,9 @@ public: bool is_checked() const { return m_checked; } void set_checked(bool); + bool is_default() const { return m_default; } + void set_default(bool); + String text() const { return m_text; } void set_text(const String& text) { m_text = text; } @@ -88,6 +91,7 @@ private: bool m_enabled { true }; bool m_checkable { false }; bool m_checked { false }; + bool m_default { false }; unsigned m_identifier { 0 }; String m_text; String m_shortcut_text; diff --git a/Services/WindowServer/Window.cpp b/Services/WindowServer/Window.cpp index b99b906a35..8f16da8d93 100644 --- a/Services/WindowServer/Window.cpp +++ b/Services/WindowServer/Window.cpp @@ -405,7 +405,7 @@ void Window::request_update(const Gfx::IntRect& rect, bool ignore_occlusion) m_pending_paint_rects.add(rect); } -void Window::popup_window_menu(const Gfx::IntPoint& position) +void Window::ensure_window_menu() { if (!m_window_menu) { m_window_menu = Menu::construct(nullptr, -1, "(Window Menu)"); @@ -422,7 +422,9 @@ void Window::popup_window_menu(const Gfx::IntPoint& position) m_window_menu->add_item(make<MenuItem>(*m_window_menu, MenuItem::Type::Separator)); auto close_item = make<MenuItem>(*m_window_menu, 3, "Close"); - close_item->set_icon(&close_icon()); + m_window_menu_close_item = close_item.ptr(); + m_window_menu_close_item->set_icon(&close_icon()); + m_window_menu_close_item->set_default(true); m_window_menu->add_item(move(close_item)); m_window_menu->item((int)PopupMenuItem::Minimize).set_enabled(m_minimizable); @@ -447,12 +449,35 @@ void Window::popup_window_menu(const Gfx::IntPoint& position) } }; } +} + +void Window::popup_window_menu(const Gfx::IntPoint& position, WindowMenuDefaultAction default_action) +{ + ensure_window_menu(); + if (default_action == WindowMenuDefaultAction::BasedOnWindowState) { + // When clicked on the task bar, determine the default action + if (!is_active() && !is_minimized()) + default_action = WindowMenuDefaultAction::None; + else if (is_minimized()) + default_action = WindowMenuDefaultAction::Unminimize; + else + default_action = WindowMenuDefaultAction::Minimize; + } + m_window_menu_minimize_item->set_default(default_action == WindowMenuDefaultAction::Minimize || default_action == WindowMenuDefaultAction::Unminimize); m_window_menu_minimize_item->set_icon(m_minimized ? nullptr : &minimize_icon()); + m_window_menu_maximize_item->set_default(default_action == WindowMenuDefaultAction::Maximize || default_action == WindowMenuDefaultAction::Restore); m_window_menu_maximize_item->set_icon(m_maximized ? &restore_icon() : &maximize_icon()); + m_window_menu_close_item->set_default(default_action == WindowMenuDefaultAction::Close); m_window_menu->popup(position); } +void Window::window_menu_activate_default() +{ + ensure_window_menu(); + m_window_menu->activate_default(); +} + void Window::request_close() { Event close_request(Event::WindowCloseRequest); diff --git a/Services/WindowServer/Window.h b/Services/WindowServer/Window.h index 38e4ef0ee3..fec098aaf6 100644 --- a/Services/WindowServer/Window.h +++ b/Services/WindowServer/Window.h @@ -62,6 +62,16 @@ enum class PopupMenuItem { Maximize, }; +enum class WindowMenuDefaultAction { + None = 0, + BasedOnWindowState, + Close, + Minimize, + Unminimize, + Maximize, + Restore +}; + class Window final : public Core::Object , public InlineLinkedListNode<Window> { C_OBJECT(Window) @@ -70,7 +80,8 @@ public: Window(Core::Object&, WindowType); virtual ~Window() override; - void popup_window_menu(const Gfx::IntPoint&); + void popup_window_menu(const Gfx::IntPoint&, WindowMenuDefaultAction); + void window_menu_activate_default(); void request_close(); unsigned wm_event_mask() const { return m_wm_event_mask; } @@ -240,6 +251,7 @@ private: void update_menu_item_text(PopupMenuItem item); void update_menu_item_enabled(PopupMenuItem item); void add_child_window(Window&); + void ensure_window_menu(); ClientConnection* m_client { nullptr }; @@ -283,6 +295,7 @@ private: RefPtr<Menu> m_window_menu; MenuItem* m_window_menu_minimize_item { nullptr }; MenuItem* m_window_menu_maximize_item { nullptr }; + MenuItem* m_window_menu_close_item { nullptr }; int m_minimize_animation_step { -1 }; int m_progress { -1 }; }; diff --git a/Services/WindowServer/WindowFrame.cpp b/Services/WindowServer/WindowFrame.cpp index 3a69f78e73..7e1d9fa589 100644 --- a/Services/WindowServer/WindowFrame.cpp +++ b/Services/WindowServer/WindowFrame.cpp @@ -370,10 +370,35 @@ void WindowFrame::on_mouse_event(const MouseEvent& event) if (m_window.type() != WindowType::Normal && m_window.type() != WindowType::Notification) return; - if (m_window.type() == WindowType::Normal && event.type() == Event::MouseDown && (event.button() == MouseButton::Left || event.button() == MouseButton::Right) && title_bar_icon_rect().contains(event.position())) { + if (m_window.type() == WindowType::Normal && title_bar_icon_rect().contains(event.position())) { wm.move_to_front_and_make_active(m_window); - m_window.popup_window_menu(title_bar_rect().bottom_left().translated(rect().location())); - return; + if (event.type() == Event::MouseDown && (event.button() == MouseButton::Left || event.button() == MouseButton::Right)) { + // Manually start a potential double click. Since we're opening + // a menu, we will only receive the MouseDown event, so we + // need to record that fact. If the user subsequently clicks + // on the same area, the menu will get closed, and we will + // receive a MouseUp event, but because windows have changed + // we don't get a MouseDoubleClick event. We can however record + // this click, and when we receive the MouseUp event check if + // it would have been considered a double click, if it weren't + // for the fact that we opened and closed a window in the meanwhile + auto& wm = WindowManager::the(); + wm.start_menu_doubleclick(m_window, event); + + m_window.popup_window_menu(title_bar_rect().bottom_left().translated(rect().location()), WindowMenuDefaultAction::Close); + return; + } else if (event.type() == Event::MouseUp && event.button() == MouseButton::Left) { + // Since the MouseDown event opened a menu, another MouseUp + // from the second click outside the menu wouldn't be considered + // a double click, so let's manually check if it would otherwise + // have been be considered to be one + auto& wm = WindowManager::the(); + if (wm.is_menu_doubleclick(m_window, event)) { + // It is a double click, so perform activate the default item + m_window.window_menu_activate_default(); + } + return; + } } // This is slightly hackish, but expand the title bar rect by two pixels downwards, @@ -394,7 +419,8 @@ void WindowFrame::on_mouse_event(const MouseEvent& event) } if (event.type() == Event::MouseDown) { if (m_window.type() == WindowType::Normal && event.button() == MouseButton::Right) { - m_window.popup_window_menu(event.position().translated(rect().location())); + auto default_action = m_window.is_maximized() ? WindowMenuDefaultAction::Restore : WindowMenuDefaultAction::Maximize; + m_window.popup_window_menu(event.position().translated(rect().location()), default_action); return; } if (m_window.is_movable() && event.button() == MouseButton::Left) diff --git a/Services/WindowServer/WindowManager.cpp b/Services/WindowServer/WindowManager.cpp index 3ab981117f..9addfd4a23 100644 --- a/Services/WindowServer/WindowManager.cpp +++ b/Services/WindowServer/WindowManager.cpp @@ -670,6 +670,24 @@ void WindowManager::set_cursor_tracking_button(Button* button) m_cursor_tracking_button = button ? button->make_weak_ptr() : nullptr; } +auto WindowManager::DoubleClickInfo::metadata_for_button(MouseButton button) const -> const ClickMetadata& +{ + switch (button) { + case MouseButton::Left: + return m_left; + case MouseButton::Right: + return m_right; + case MouseButton::Middle: + return m_middle; + case MouseButton::Back: + return m_back; + case MouseButton::Forward: + return m_forward; + default: + ASSERT_NOT_REACHED(); + } +} + auto WindowManager::DoubleClickInfo::metadata_for_button(MouseButton button) -> ClickMetadata& { switch (button) { @@ -690,6 +708,59 @@ auto WindowManager::DoubleClickInfo::metadata_for_button(MouseButton button) -> // #define DOUBLECLICK_DEBUG +bool WindowManager::is_considered_doubleclick(const MouseEvent& event, const DoubleClickInfo::ClickMetadata& metadata) const +{ + int elapsed_since_last_click = metadata.clock.elapsed(); + if (elapsed_since_last_click < m_double_click_speed) { + auto diff = event.position() - metadata.last_position; + auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y(); + if (distance_travelled_squared <= (m_max_distance_for_double_click * m_max_distance_for_double_click)) + return true; + } + return false; +} + +void WindowManager::start_menu_doubleclick(Window& window, const MouseEvent& event) +{ + // This is a special case. Basically, we're trying to determine whether + // double clicking on the window menu icon happened. In this case, the + // WindowFrame only receives a MouseDown event, and since the window + // menu popus up, it does not see the MouseUp event. But, if they subsequently + // click there again, the menu is closed and we receive a MouseUp event. + // So, in order to be able to detect a double click when a menu is being + // opened by the MouseDown event, we need to consider the MouseDown event + // as a potential double-click trigger + ASSERT(event.type() == Event::MouseDown); + + auto& metadata = m_double_click_info.metadata_for_button(event.button()); + if (&window != m_double_click_info.m_clicked_window) { + // we either haven't clicked anywhere, or we haven't clicked on this + // window. set the current click window, and reset the timers. +#if defined(DOUBLECLICK_DEBUG) + dbg() << "Initial mousedown on window " << &window << " for menu (previous was " << m_double_click_info.m_clicked_window << ')'; +#endif + m_double_click_info.m_clicked_window = window.make_weak_ptr(); + m_double_click_info.reset(); + } + + metadata.last_position = event.position(); + metadata.clock.start(); +} + +bool WindowManager::is_menu_doubleclick(Window& window, const MouseEvent& event) const +{ + ASSERT(event.type() == Event::MouseUp); + + if (&window != m_double_click_info.m_clicked_window) + return false; + + auto& metadata = m_double_click_info.metadata_for_button(event.button()); + if (!metadata.clock.is_valid()) + return false; + + return is_considered_doubleclick(event, metadata); +} + void WindowManager::process_event_for_doubleclick(Window& window, MouseEvent& event) { // We only care about button presses (because otherwise it's not a doubleclick, duh!) @@ -707,32 +778,20 @@ void WindowManager::process_event_for_doubleclick(Window& window, MouseEvent& ev auto& metadata = m_double_click_info.metadata_for_button(event.button()); - // if the clock is invalid, we haven't clicked with this button on this - // window yet, so there's nothing to do. - if (!metadata.clock.is_valid()) { + if (!metadata.clock.is_valid() || !is_considered_doubleclick(event, metadata)) { + // either the clock is invalid because we haven't clicked on this + // button on this window yet, so there's nothing to do, or this + // isn't considered to be a double click. either way, restart the + // clock metadata.clock.start(); } else { - int elapsed_since_last_click = metadata.clock.elapsed(); - metadata.clock.start(); - if (elapsed_since_last_click < m_double_click_speed) { - auto diff = event.position() - metadata.last_position; - auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y(); - if (distance_travelled_squared > (m_max_distance_for_double_click * m_max_distance_for_double_click)) { - // too far; try again - metadata.clock.start(); - } else { #if defined(DOUBLECLICK_DEBUG) - dbg() << "Transforming MouseUp to MouseDoubleClick (" << elapsed_since_last_click << " < " << m_double_click_speed << ")!"; + dbg() << "Transforming MouseUp to MouseDoubleClick (" << elapsed_since_last_click << " < " << m_double_click_speed << ")!"; #endif - event = MouseEvent(Event::MouseDoubleClick, event.position(), event.buttons(), event.button(), event.modifiers(), event.wheel_delta()); - // invalidate this now we've delivered a doubleclick, otherwise - // tripleclick will deliver two doubleclick events (incorrectly). - metadata.clock = {}; - } - } else { - // too slow; try again - metadata.clock.start(); - } + event = MouseEvent(Event::MouseDoubleClick, event.position(), event.buttons(), event.button(), event.modifiers(), event.wheel_delta()); + // invalidate this now we've delivered a doubleclick, otherwise + // tripleclick will deliver two doubleclick events (incorrectly). + metadata.clock = {}; } metadata.last_position = event.position(); diff --git a/Services/WindowServer/WindowManager.h b/Services/WindowServer/WindowManager.h index 96588e6a2a..640a719349 100644 --- a/Services/WindowServer/WindowManager.h +++ b/Services/WindowServer/WindowManager.h @@ -172,6 +172,9 @@ public: void did_popup_a_menu(Badge<Menu>); + void start_menu_doubleclick(Window& window, const MouseEvent& event); + bool is_menu_doubleclick(Window& window, const MouseEvent& event) const; + private: NonnullRefPtr<Cursor> get_cursor(const String& name); NonnullRefPtr<Cursor> get_cursor(const String& name, const Gfx::IntPoint& hotspot); @@ -227,6 +230,7 @@ private: Gfx::IntPoint last_position; }; + const ClickMetadata& metadata_for_button(MouseButton) const; ClickMetadata& metadata_for_button(MouseButton); void reset() @@ -247,6 +251,9 @@ private: ClickMetadata m_back; ClickMetadata m_forward; }; + + bool is_considered_doubleclick(const MouseEvent& event, const DoubleClickInfo::ClickMetadata& metadata) const; + DoubleClickInfo m_double_click_info; int m_double_click_speed { 0 }; int m_max_distance_for_double_click { 4 }; |