summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom <tomut@yahoo.com>2020-07-07 12:09:18 -0600
committerAndreas Kling <kling@serenityos.org>2020-07-11 11:45:49 +0200
commitfc4e01a3c9719113e9ae2e17e36777507b8edd52 (patch)
tree1d005f1b7395931d1cd7af1261eef3fd32fee894
parent684b04e02a208cd6c3fa1f67e772228762769291 (diff)
downloadserenity-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.cpp2
-rw-r--r--Services/WindowServer/Menu.cpp22
-rw-r--r--Services/WindowServer/Menu.h2
-rw-r--r--Services/WindowServer/MenuItem.cpp8
-rw-r--r--Services/WindowServer/MenuItem.h4
-rw-r--r--Services/WindowServer/Window.cpp29
-rw-r--r--Services/WindowServer/Window.h15
-rw-r--r--Services/WindowServer/WindowFrame.cpp34
-rw-r--r--Services/WindowServer/WindowManager.cpp103
-rw-r--r--Services/WindowServer/WindowManager.h7
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 };