summaryrefslogtreecommitdiffstats
path: root/subprojects/libhandy/src/hdy-window-handle-controller.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--subprojects/libhandy/src/hdy-window-handle-controller.c515
1 files changed, 515 insertions, 0 deletions
diff --git a/subprojects/libhandy/src/hdy-window-handle-controller.c b/subprojects/libhandy/src/hdy-window-handle-controller.c
new file mode 100644
index 0000000..d668745
--- /dev/null
+++ b/subprojects/libhandy/src/hdy-window-handle-controller.c
@@ -0,0 +1,515 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+/* Most of the file is based on bits of code from GtkWindow */
+
+#include "config.h"
+
+#include "gtk-window-private.h"
+#include "hdy-window-handle-controller-private.h"
+
+#include <glib/gi18n-lib.h>
+
+/**
+ * PRIVATE:hdy-window-handle-controller
+ * @short_description: An oblect that makes widgets behave like titlebars.
+ * @Title: HdyWindowHandleController
+ * @See_also: #HdyHeaderBar, #HdyWindowHandle
+ * @stability: Private
+ *
+ * When HdyWindowHandleController is added to the widget, dragging that widget
+ * will move the window, and right click, double click and middle click will be
+ * handled as if that widget was a titlebar. Currently it's used to implement
+ * these properties in #HdyWindowHandle and #HdyHeaderBar
+ *
+ * Since: 1.0
+ */
+
+struct _HdyWindowHandleController
+{
+ GObject parent;
+
+ GtkWidget *widget;
+ GtkGesture *multipress_gesture;
+ GtkWidget *fallback_menu;
+ gboolean keep_above;
+};
+
+G_DEFINE_TYPE (HdyWindowHandleController, hdy_window_handle_controller, G_TYPE_OBJECT);
+
+static GtkWindow *
+get_window (HdyWindowHandleController *self)
+{
+ GtkWidget *toplevel = gtk_widget_get_toplevel (self->widget);
+
+ if (GTK_IS_WINDOW (toplevel))
+ return GTK_WINDOW (toplevel);
+
+ return NULL;
+}
+
+static void
+popup_menu_detach (GtkWidget *widget,
+ GtkMenu *menu)
+{
+ HdyWindowHandleController *self;
+
+ self = g_object_steal_data (G_OBJECT (menu), "hdywindowhandlecontroller");
+
+ self->fallback_menu = NULL;
+}
+
+static void
+restore_window_cb (GtkMenuItem *menuitem,
+ HdyWindowHandleController *self)
+{
+ GtkWindow *window = get_window (self);
+ GdkWindowState state;
+
+ if (!window)
+ return;
+
+ if (gtk_window_is_maximized (window)) {
+ gtk_window_unmaximize (window);
+ return;
+ }
+
+ state = hdy_gtk_window_get_state (window);
+
+ if (state & GDK_WINDOW_STATE_ICONIFIED)
+ gtk_window_deiconify (window);
+}
+
+static void
+move_window_cb (GtkMenuItem *menuitem,
+ HdyWindowHandleController *self)
+{
+ GtkWindow *window = get_window (self);
+
+ if (!window)
+ return;
+
+ gtk_window_begin_move_drag (window,
+ 0, /* 0 means "use keyboard" */
+ 0, 0,
+ GDK_CURRENT_TIME);
+}
+
+static void
+resize_window_cb (GtkMenuItem *menuitem,
+ HdyWindowHandleController *self)
+{
+ GtkWindow *window = get_window (self);
+
+ if (!window)
+ return;
+
+ gtk_window_begin_resize_drag (window,
+ 0,
+ 0, /* 0 means "use keyboard" */
+ 0, 0,
+ GDK_CURRENT_TIME);
+}
+
+static void
+minimize_window_cb (GtkMenuItem *menuitem,
+ HdyWindowHandleController *self)
+{
+ GtkWindow *window = get_window (self);
+
+ if (!window)
+ return;
+
+ /* Turns out, we can't iconify a maximized window */
+ if (gtk_window_is_maximized (window))
+ gtk_window_unmaximize (window);
+
+ gtk_window_iconify (window);
+}
+
+static void
+maximize_window_cb (GtkMenuItem *menuitem,
+ HdyWindowHandleController *self)
+{
+ GtkWindow *window = get_window (self);
+ GdkWindowState state;
+
+ if (!window)
+ return;
+
+ state = hdy_gtk_window_get_state (window);
+
+ if (state & GDK_WINDOW_STATE_ICONIFIED)
+ gtk_window_deiconify (window);
+
+ gtk_window_maximize (window);
+}
+
+static void
+ontop_window_cb (GtkMenuItem *menuitem,
+ HdyWindowHandleController *self)
+{
+ GtkWindow *window = get_window (self);
+
+ if (!window)
+ return;
+
+ /*
+ * FIXME: It will go out of sync if something else calls
+ * gtk_window_set_keep_above(), so we need to actually track it.
+ * For some reason this doesn't seem to be reflected in the
+ * window state.
+ */
+ self->keep_above = !self->keep_above;
+ gtk_window_set_keep_above (window, self->keep_above);
+}
+
+static void
+close_window_cb (GtkMenuItem *menuitem,
+ HdyWindowHandleController *self)
+{
+ GtkWindow *window = get_window (self);
+
+ if (!window)
+ return;
+
+ gtk_window_close (window);
+}
+
+static void
+do_popup (HdyWindowHandleController *self,
+ GdkEventButton *event)
+{
+ GtkWindow *window = get_window (self);
+ GtkWidget *menuitem;
+ GdkWindowState state;
+ gboolean maximized, iconified, resizable;
+ GdkWindowTypeHint type_hint;
+
+ if (!window)
+ return;
+
+ if (gdk_window_show_window_menu (gtk_widget_get_window (GTK_WIDGET (window)),
+ (GdkEvent *) event))
+ return;
+
+ if (self->fallback_menu)
+ gtk_widget_destroy (self->fallback_menu);
+
+ state = hdy_gtk_window_get_state (window);
+
+ iconified = (state & GDK_WINDOW_STATE_ICONIFIED) == GDK_WINDOW_STATE_ICONIFIED;
+ maximized = gtk_window_is_maximized (window) && !iconified;
+ resizable = gtk_window_get_resizable (window);
+ type_hint = gtk_window_get_type_hint (window);
+
+ self->fallback_menu = gtk_menu_new ();
+ gtk_style_context_add_class (gtk_widget_get_style_context (self->fallback_menu),
+ GTK_STYLE_CLASS_CONTEXT_MENU);
+
+ /* We can't pass self to popup_menu_detach, so will have to use custom data */
+ g_object_set_data (G_OBJECT (self->fallback_menu),
+ "hdywindowhandlecontroller", self);
+
+ gtk_menu_attach_to_widget (GTK_MENU (self->fallback_menu),
+ self->widget,
+ popup_menu_detach);
+
+ menuitem = gtk_menu_item_new_with_label (_("Restore"));
+ gtk_widget_show (menuitem);
+ /* "Restore" means "Unmaximize" or "Unminimize"
+ * (yes, some WMs allow window menu to be shown for minimized windows).
+ * Not restorable:
+ * - visible windows that are not maximized or minimized
+ * - non-resizable windows that are not minimized
+ * - non-normal windows
+ */
+ if ((gtk_widget_is_visible (GTK_WIDGET (window)) &&
+ !(maximized || iconified)) ||
+ (!iconified && !resizable) ||
+ type_hint != GDK_WINDOW_TYPE_HINT_NORMAL)
+ gtk_widget_set_sensitive (menuitem, FALSE);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (restore_window_cb), self);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->fallback_menu), menuitem);
+
+ menuitem = gtk_menu_item_new_with_label (_("Move"));
+ gtk_widget_show (menuitem);
+ if (maximized || iconified)
+ gtk_widget_set_sensitive (menuitem, FALSE);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (move_window_cb), self);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->fallback_menu), menuitem);
+
+ menuitem = gtk_menu_item_new_with_label (_("Resize"));
+ gtk_widget_show (menuitem);
+ if (!resizable || maximized || iconified)
+ gtk_widget_set_sensitive (menuitem, FALSE);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (resize_window_cb), self);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->fallback_menu), menuitem);
+
+ menuitem = gtk_menu_item_new_with_label (_("Minimize"));
+ gtk_widget_show (menuitem);
+ if (iconified ||
+ type_hint != GDK_WINDOW_TYPE_HINT_NORMAL)
+ gtk_widget_set_sensitive (menuitem, FALSE);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (minimize_window_cb), self);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->fallback_menu), menuitem);
+
+ menuitem = gtk_menu_item_new_with_label (_("Maximize"));
+ gtk_widget_show (menuitem);
+ if (maximized ||
+ !resizable ||
+ type_hint != GDK_WINDOW_TYPE_HINT_NORMAL)
+ gtk_widget_set_sensitive (menuitem, FALSE);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (maximize_window_cb), self);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->fallback_menu), menuitem);
+
+ menuitem = gtk_separator_menu_item_new ();
+ gtk_widget_show (menuitem);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->fallback_menu), menuitem);
+
+ menuitem = gtk_check_menu_item_new_with_label (_("Always on Top"));
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menuitem), self->keep_above);
+ if (maximized)
+ gtk_widget_set_sensitive (menuitem, FALSE);
+ gtk_widget_show (menuitem);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (ontop_window_cb), self);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->fallback_menu), menuitem);
+
+ menuitem = gtk_separator_menu_item_new ();
+ gtk_widget_show (menuitem);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->fallback_menu), menuitem);
+
+ menuitem = gtk_menu_item_new_with_label (_("Close"));
+ gtk_widget_show (menuitem);
+ if (!gtk_window_get_deletable (window))
+ gtk_widget_set_sensitive (menuitem, FALSE);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (close_window_cb), self);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->fallback_menu), menuitem);
+ gtk_menu_popup_at_pointer (GTK_MENU (self->fallback_menu), (GdkEvent *) event);
+}
+
+static gboolean
+titlebar_action (HdyWindowHandleController *self,
+ const GdkEvent *event,
+ guint button)
+{
+ GtkSettings *settings;
+ g_autofree gchar *action = NULL;
+ GtkWindow *window = get_window (self);
+
+ if (!window)
+ return FALSE;
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (window));
+
+ switch (button) {
+ case GDK_BUTTON_PRIMARY:
+ g_object_get (settings, "gtk-titlebar-double-click", &action, NULL);
+ break;
+
+ case GDK_BUTTON_MIDDLE:
+ g_object_get (settings, "gtk-titlebar-middle-click", &action, NULL);
+ break;
+
+ case GDK_BUTTON_SECONDARY:
+ g_object_get (settings, "gtk-titlebar-right-click", &action, NULL);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (action == NULL)
+ return FALSE;
+
+ if (g_str_equal (action, "none"))
+ return FALSE;
+
+ if (g_str_has_prefix (action, "toggle-maximize")) {
+ /*
+ * gtk header bar won't show the maximize button if the following
+ * properties are not met, apply the same to title bar actions for
+ * consistency.
+ */
+ if (gtk_window_get_resizable (window) &&
+ gtk_window_get_type_hint (window) == GDK_WINDOW_TYPE_HINT_NORMAL)
+ hdy_gtk_window_toggle_maximized (window);
+
+ return TRUE;
+ }
+
+ if (g_str_equal (action, "lower")) {
+ gdk_window_lower (gtk_widget_get_window (GTK_WIDGET (window)));
+
+ return TRUE;
+ }
+
+ if (g_str_equal (action, "minimize")) {
+ gdk_window_iconify (gtk_widget_get_window (GTK_WIDGET (window)));
+
+ return TRUE;
+ }
+
+ if (g_str_equal (action, "menu")) {
+ do_popup (self, (GdkEventButton*) event);
+
+ return TRUE;
+ }
+
+ g_warning ("Unsupported titlebar action %s", action);
+
+ return FALSE;
+}
+
+static void
+pressed_cb (GtkGestureMultiPress *gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ HdyWindowHandleController *self)
+{
+ GtkWidget *window = gtk_widget_get_toplevel (self->widget);
+ GdkEventSequence *sequence =
+ gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ const GdkEvent *event =
+ gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+ guint button =
+ gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+
+ if (!event)
+ return;
+
+ if (gdk_display_device_is_grabbed (gtk_widget_get_display (window),
+ gtk_gesture_get_device (GTK_GESTURE (gesture))))
+ return;
+
+ switch (button) {
+ case GDK_BUTTON_PRIMARY:
+ gdk_window_raise (gtk_widget_get_window (window));
+
+ if (n_press == 2)
+ titlebar_action (self, event, button);
+
+ if (gtk_widget_has_grab (window))
+ gtk_gesture_set_sequence_state (GTK_GESTURE (gesture),
+ sequence, GTK_EVENT_SEQUENCE_CLAIMED);
+
+ break;
+
+ case GDK_BUTTON_SECONDARY:
+ if (titlebar_action (self, event, button))
+ gtk_gesture_set_sequence_state (GTK_GESTURE (gesture),
+ sequence, GTK_EVENT_SEQUENCE_CLAIMED);
+
+ gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
+ break;
+
+ case GDK_BUTTON_MIDDLE:
+ if (titlebar_action (self, event, button))
+ gtk_gesture_set_sequence_state (GTK_GESTURE (gesture),
+ sequence, GTK_EVENT_SEQUENCE_CLAIMED);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+hdy_window_handle_controller_finalize (GObject *object)
+{
+ HdyWindowHandleController *self = (HdyWindowHandleController *)object;
+
+ self->widget = NULL;
+ g_clear_object (&self->multipress_gesture);
+ g_clear_object (&self->fallback_menu);
+
+ G_OBJECT_CLASS (hdy_window_handle_controller_parent_class)->finalize (object);
+}
+
+static void
+hdy_window_handle_controller_class_init (HdyWindowHandleControllerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = hdy_window_handle_controller_finalize;
+}
+
+static void
+hdy_window_handle_controller_init (HdyWindowHandleController *self)
+{
+}
+
+/**
+ * hdy_window_handle_controller_new:
+ * @widget: The widget to create a controller for
+ *
+ * Creates a new #HdyWindowHandleController for @widget.
+ *
+ * Returns: (transfer full): a newly created #HdyWindowHandleController
+ *
+ * Since: 1.0
+ */
+HdyWindowHandleController *
+hdy_window_handle_controller_new (GtkWidget *widget)
+{
+ HdyWindowHandleController *self;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+
+ self = g_object_new (HDY_TYPE_WINDOW_HANDLE_CONTROLLER, NULL);
+
+ /* The object is intended to have the same life cycle as the widget,
+ * so we don't ref it. */
+ self->widget = widget;
+ self->multipress_gesture = g_object_new (GTK_TYPE_GESTURE_MULTI_PRESS,
+ "widget", widget,
+ "button", 0,
+ NULL);
+ g_signal_connect_object (self->multipress_gesture,
+ "pressed",
+ G_CALLBACK (pressed_cb),
+ self,
+ 0);
+
+ gtk_widget_add_events (widget,
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON_MOTION_MASK |
+ GDK_TOUCH_MASK);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (widget),
+ "windowhandle");
+
+ return self;
+}