[gtk+] window: Allow resizing by clicking on the border



commit bf03c85e432d6c2439338e2bbb1fbc08843635f1
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Mar 10 01:11:18 2013 -0500

    window: Allow resizing by clicking on the border
    
    We also change cursors to indicate what interactions
    are possible in various window regions.
    Double clicking on the titlebar maximizes the window.

 gtk/gtkwindow.c |  400 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 376 insertions(+), 24 deletions(-)
---
diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c
index ade610d..ac81666 100644
--- a/gtk/gtkwindow.c
+++ b/gtk/gtkwindow.c
@@ -147,6 +147,8 @@ struct _GtkWindowPrivate
   GtkWidget *title_max_button;
   GtkWidget *title_close_button;
 
+  GdkCursor *default_cursor;
+
   /* The following flags are initially TRUE (before a window is mapped).
    * They cause us to compute a configure request that involves
    * default-only parameters. Once mapped, we set them to FALSE.
@@ -256,6 +258,22 @@ enum {
   LAST_ARG
 };
 
+/* Must be kept in sync with GdkWindowEdge ! */
+typedef enum
+{
+  GTK_WINDOW_REGION_EDGE_NW,
+  GTK_WINDOW_REGION_EDGE_N,
+  GTK_WINDOW_REGION_EDGE_NE,
+  GTK_WINDOW_REGION_EDGE_W,
+  GTK_WINDOW_REGION_EDGE_E,
+  GTK_WINDOW_REGION_EDGE_SW,
+  GTK_WINDOW_REGION_EDGE_S,
+  GTK_WINDOW_REGION_EDGE_SE,
+  GTK_WINDOW_REGION_CONTENT,
+  GTK_WINDOW_REGION_TITLE,
+  GTK_WINDOW_REGION_EDGE
+} GtkWindowRegion;
+
 typedef struct
 {
   GList     *icon_list;
@@ -355,6 +373,8 @@ static gint gtk_window_enter_notify_event (GtkWidget         *widget,
                                           GdkEventCrossing  *event);
 static gint gtk_window_leave_notify_event (GtkWidget         *widget,
                                           GdkEventCrossing  *event);
+static gint gtk_window_motion_notify_event (GtkWidget        *widget,
+                                           GdkEventMotion   *event);
 static gint gtk_window_focus_in_event     (GtkWidget         *widget,
                                           GdkEventFocus     *event);
 static gint gtk_window_focus_out_event    (GtkWidget         *widget,
@@ -436,6 +456,7 @@ static void     resize_grip_create_window             (GtkWindow    *window);
 static void     resize_grip_destroy_window            (GtkWindow    *window);
 static void     update_grip_visibility                (GtkWindow    *window);
 static void     update_window_buttons                 (GtkWindow    *window);
+static void     update_cursor_at_position             (GtkWidget *widget, gint x, gint y);
 
 static void        gtk_window_notify_keys_changed (GtkWindow   *window);
 static GtkKeyHash *gtk_window_get_key_hash        (GtkWindow   *window);
@@ -449,6 +470,9 @@ static void        gtk_window_on_theme_variant_changed (GtkSettings *settings,
 #endif
 static void        gtk_window_set_theme_variant         (GtkWindow  *window);
 
+static void        window_cursor_changed       (GdkWindow  *window,
+                                                GParamSpec *pspec,
+                                                GtkWidget  *widget);
 
 static void gtk_window_get_preferred_width (GtkWidget *widget,
                                             gint      *minimum_size,
@@ -616,6 +640,7 @@ gtk_window_class_init (GtkWindowClass *klass)
   widget_class->key_release_event = gtk_window_key_release_event;
   widget_class->enter_notify_event = gtk_window_enter_notify_event;
   widget_class->leave_notify_event = gtk_window_leave_notify_event;
+  widget_class->motion_notify_event = gtk_window_motion_notify_event;
   widget_class->focus_in_event = gtk_window_focus_in_event;
   widget_class->button_press_event = gtk_window_button_press_event;
   widget_class->focus_out_event = gtk_window_focus_out_event;
@@ -1033,6 +1058,13 @@ gtk_window_class_init (GtkWindowClass *klass)
                                                                 GTK_PARAM_READABLE));
 
   gtk_widget_class_install_style_property (widget_class,
+                                           g_param_spec_int ("decoration-resize-handle",
+                                                             P_("Decoration resize handle size"),
+                                                             P_("Decoration resize handle size"),
+                                                             0, G_MAXINT,
+                                                             20, GTK_PARAM_READWRITE));
+
+  gtk_widget_class_install_style_property (widget_class,
                                            g_param_spec_int ("resize-grip-width",
                                                              P_("Width of resize grip"),
                                                              P_("Width of resize grip"),
@@ -5589,6 +5621,10 @@ gtk_window_realize (GtkWidget *widget)
                            GDK_LEAVE_NOTIFY_MASK |
                            GDK_FOCUS_CHANGE_MASK |
                            GDK_STRUCTURE_MASK);
+
+  if (priv->decorated && priv->client_decorated)
+    attributes.event_mask |= GDK_POINTER_MOTION_MASK;
+
   attributes.type_hint = priv->type_hint;
 
   attributes_mask |= GDK_WA_VISUAL | GDK_WA_TYPE_HINT;
@@ -5666,9 +5702,14 @@ gtk_window_realize (GtkWidget *widget)
     }
 #endif
 
+  /* get the default cursor */
+  priv->default_cursor = gdk_window_get_cursor (gdk_window);
+  g_signal_connect (G_OBJECT (gdk_window), "notify::cursor",
+                    G_CALLBACK (window_cursor_changed), widget);
+
   /* Icons */
   gtk_window_realize_icon (window);
-  
+
   if (priv->has_resize_grip)
     resize_grip_create_window (window);
 }
@@ -5956,10 +5997,8 @@ _gtk_window_set_allocation (GtkWindow           *window,
                                        NULL,
                                        &title_height);
 
-      title_allocation.x = title_border.left +
-                           window_border.left;
-      title_allocation.y = title_border.top +
-                           window_border.top;
+      title_allocation.x = title_border.left + window_border.left;
+      title_allocation.y = title_border.top + window_border.top;
       title_allocation.width =
         MAX (1, (gint) allocation->width -
              title_border.left - title_border.right -
@@ -6465,13 +6504,12 @@ gboolean
 gtk_window_propagate_key_event (GtkWindow        *window,
                                 GdkEventKey      *event)
 {
-  GtkWindowPrivate *priv;
+  GtkWindowPrivate *priv = window->priv;
   gboolean handled = FALSE;
   GtkWidget *widget, *focus;
 
   g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
 
-  priv = window->priv;
   widget = GTK_WIDGET (window);
 
   focus = priv->focus_widget;
@@ -6542,11 +6580,174 @@ gtk_window_key_release_event (GtkWidget   *widget,
   return handled;
 }
 
+static GtkWindowRegion
+get_region_type (GtkWindow *window, gint x, gint y)
+{
+  GtkWindowPrivate *priv = window->priv;
+  GtkWidget *widget = GTK_WIDGET (window);
+  gint title_height = 0;
+  gint resize_handle = 0;
+  GtkBorder window_border;
+
+  if (priv->title_box)
+    title_height = gtk_widget_get_allocated_height (priv->title_box);
+  get_decoration_borders (widget, NULL, &window_border);
+  gtk_widget_style_get (widget,
+                        "decoration-resize-handle", &resize_handle,
+                        NULL);
+
+  if (x < window_border.left)
+    {
+      if (y < window_border.top + MAX (title_height, resize_handle))
+        return GTK_WINDOW_REGION_EDGE_NW;
+      else if (y > gtk_widget_get_allocated_height (widget) - window_border.bottom - resize_handle)
+        return GTK_WINDOW_REGION_EDGE_SW;
+      else
+        return GTK_WINDOW_REGION_EDGE_W;
+    }
+  else if (x > gtk_widget_get_allocated_width (widget) - window_border.right)
+    {
+      if (y < window_border.top + MAX (title_height, resize_handle))
+        return GTK_WINDOW_REGION_EDGE_NE;
+      else if (y > gtk_widget_get_allocated_height (widget) - window_border.bottom - resize_handle)
+        return GTK_WINDOW_REGION_EDGE_SE;
+      else
+        return GTK_WINDOW_REGION_EDGE_E;
+    }
+  else if (y < window_border.top)
+    {
+      if (x < window_border.left + resize_handle)
+        return GTK_WINDOW_REGION_EDGE_NW;
+      else if (x > gtk_widget_get_allocated_width (widget) - window_border.right - resize_handle)
+        return GTK_WINDOW_REGION_EDGE_NE;
+      else
+        return GTK_WINDOW_REGION_EDGE_N;
+    }
+  else if (y > gtk_widget_get_allocated_height (widget) - window_border.bottom)
+    {
+      if (x < window_border.left + resize_handle)
+        return GTK_WINDOW_REGION_EDGE_SW;
+      else if (x > gtk_widget_get_allocated_width (widget) - window_border.right - resize_handle)
+        return GTK_WINDOW_REGION_EDGE_SE;
+      else
+        return GTK_WINDOW_REGION_EDGE_S;
+    }
+  else
+    {
+      if (y < window_border.top + title_height)
+        return GTK_WINDOW_REGION_TITLE;
+      else
+        return GTK_WINDOW_REGION_CONTENT;
+    }
+}
+
+static GtkWindowRegion
+get_active_region_type (GtkWindow *window, gint x, gint y)
+{
+  GtkWindowPrivate *priv = window->priv;
+  GtkWidget *widget = GTK_WIDGET (window);
+  GtkWindowRegion region;
+  gboolean resize_h, resize_v;
+  gint state;
+  GtkBorder window_border;
+
+  region = get_region_type (window, x, y);
+  get_decoration_borders (widget, NULL, &window_border);
+
+  state = gdk_window_get_state (gtk_widget_get_window (widget));
+  if (!priv->resizable || (state & GDK_WINDOW_STATE_MAXIMIZED))
+    {
+      resize_h = resize_v = FALSE;
+    }
+  else
+    {
+      resize_h = resize_v = TRUE;
+      if (priv->geometry_info)
+        {
+          GdkGeometry *geometry = &priv->geometry_info->geometry;
+          GdkWindowHints flags = priv->geometry_info->mask;
+
+          if ((flags & GDK_HINT_MIN_SIZE) && (flags & GDK_HINT_MAX_SIZE))
+            {
+              resize_h = geometry->min_width != geometry->max_width;
+              resize_v = geometry->min_height != geometry->max_height;
+            }
+        }
+    }
+
+  switch (region)
+    {
+    case GTK_WINDOW_REGION_EDGE_N:
+    case GTK_WINDOW_REGION_EDGE_S:
+      if (resize_v)
+        return region;
+      else
+        return GTK_WINDOW_REGION_EDGE;
+      break;
+
+    case GTK_WINDOW_REGION_EDGE_W:
+    case GTK_WINDOW_REGION_EDGE_E:
+      if (resize_h)
+        return region;
+      else
+        return GTK_WINDOW_REGION_EDGE;
+      break;
+
+    case GTK_WINDOW_REGION_EDGE_NW:
+      if (resize_h && resize_v)
+        return region;
+      else if (resize_h && x < window_border.left)
+        return GTK_WINDOW_REGION_EDGE_W;
+      else if (resize_v && y < window_border.top)
+        return GTK_WINDOW_REGION_EDGE_N;
+      else
+        return GTK_WINDOW_REGION_EDGE;
+      break;
+
+    case GTK_WINDOW_REGION_EDGE_NE:
+      if (resize_h && resize_v)
+        return region;
+      else if (resize_h && x > gtk_widget_get_allocated_width (widget) - window_border.right)
+        return GTK_WINDOW_REGION_EDGE_E;
+      else if (resize_v && y < window_border.top)
+        return GTK_WINDOW_REGION_EDGE_N;
+      else
+        return GTK_WINDOW_REGION_EDGE;
+      break;
+
+    case GTK_WINDOW_REGION_EDGE_SW:
+      if (resize_h && resize_v)
+        return region;
+      else if (resize_h && x < window_border.left)
+        return GTK_WINDOW_REGION_EDGE_W;
+      else if (resize_v && y > gtk_widget_get_allocated_height (widget) - window_border.bottom)
+        return GTK_WINDOW_REGION_EDGE_N;
+      else
+        return GTK_WINDOW_REGION_EDGE;
+      break;
+
+    case GTK_WINDOW_REGION_EDGE_SE:
+      if (resize_h && resize_v)
+        return region;
+      else if (resize_h && x > gtk_widget_get_allocated_width (widget) - window_border.right)
+        return GTK_WINDOW_REGION_EDGE_E;
+      else if (resize_v && y > gtk_widget_get_allocated_height (widget) - window_border.bottom)
+        return GTK_WINDOW_REGION_EDGE_S;
+      else
+        return GTK_WINDOW_REGION_EDGE;
+      break;
+
+    default:
+      return region;
+    }
+}
+
 static gint
 gtk_window_button_press_event (GtkWidget *widget,
                                GdkEventButton *event)
 {
-  GtkWindowPrivate *priv = GTK_WINDOW (widget)->priv;
+  GtkWindow *window = GTK_WINDOW (widget);
+  GtkWindowPrivate *priv = window->priv;
   GdkWindowEdge edge;
 
   if (event->window == priv->grip_window)
@@ -6567,26 +6768,50 @@ gtk_window_button_press_event (GtkWidget *widget,
            priv->title_box != NULL &&
            !priv->fullscreen)
     {
-      GtkAllocation allocation;
-      int border_width;
+      gint x = event->x;
+      gint y = event->y;
+      GtkWindowRegion region = get_active_region_type (window, x, y);
 
-      gtk_widget_get_allocation (priv->title_box, &allocation);
-      border_width =
-        gtk_container_get_border_width (GTK_CONTAINER (priv->title_box));
+      if (event->type == GDK_BUTTON_PRESS)
+        {
+          if (event->button == GDK_BUTTON_PRIMARY)
+            {
+              switch (region)
+                {
+                case GTK_WINDOW_REGION_TITLE:
+                case GTK_WINDOW_REGION_CONTENT:
+                case GTK_WINDOW_REGION_EDGE:
+                  gdk_window_begin_move_drag_for_device (gtk_widget_get_window (widget),
+                                                         gdk_event_get_device ((GdkEvent *) event),
+                                                         event->button,
+                                                         event->x_root,
+                                                         event->y_root,
+                                                         event->time);
+                  break;
+
+                default:
+                  gdk_window_begin_resize_drag_for_device (gtk_widget_get_window (widget),
+                                                           (GdkWindowEdge)region,
+                                                           gdk_event_get_device ((GdkEvent *) event),
+
+                                                           event->button,
+                                                           event->x_root,
+                                                           event->y_root,
+                                                           event->time);
+                  break;
+                }
 
-      if (allocation.x - border_width <= event->x &&
-          event->x < allocation.x + border_width + allocation.width &&
-          allocation.y - border_width <= event->y &&
-          event->y < allocation.y + border_width + allocation.height)
+              return TRUE;
+            }
+        }
+      else if (event->type == GDK_2BUTTON_PRESS)
         {
-          gdk_window_begin_move_drag_for_device (gtk_widget_get_window(widget),
-                                                 gdk_event_get_device((GdkEvent *) event),
-                                                 event->button,
-                                                 event->x_root,
-                                                 event->y_root,
-                                                 event->time);
+          if (region == GTK_WINDOW_REGION_TITLE)
+            {
+              gtk_window_title_max_clicked (widget, widget);
 
-          return TRUE;
+              return TRUE;
+            }
         }
     }
 
@@ -6609,6 +6834,20 @@ static gint
 gtk_window_enter_notify_event (GtkWidget        *widget,
                               GdkEventCrossing *event)
 {
+  GtkWindowPrivate *priv = GTK_WINDOW (widget)->priv;
+
+  if (priv->client_decorated)
+    {
+      gint x, y;
+      GdkWindow *gdk_window;
+
+      gdk_window = gtk_widget_get_window (widget);
+      gdk_window_get_device_position (gdk_window,
+                                      gdk_event_get_device ((GdkEvent*)event),
+                                      &x, &y, NULL);
+      update_cursor_at_position (widget, x, y);
+    }
+
   return FALSE;
 }
 
@@ -6619,6 +6858,27 @@ gtk_window_leave_notify_event (GtkWidget        *widget,
   return FALSE;
 }
 
+static gboolean
+gtk_window_motion_notify_event (GtkWidget       *widget,
+                                GdkEventMotion  *event)
+{
+  GtkWindowPrivate *priv = GTK_WINDOW (widget)->priv;
+
+  if (priv->client_decorated)
+    {
+      gint x, y;
+      GdkWindow *gdk_window;
+
+      gdk_window = gtk_widget_get_window (widget);
+      gdk_window_get_device_position (gdk_window,
+                                      gdk_event_get_device ((GdkEvent*)event),
+                                      &x, &y, NULL);
+      update_cursor_at_position (widget, x, y);
+    }
+
+  return FALSE;
+}
+
 static void
 do_focus_change (GtkWidget *widget,
                 gboolean   in)
@@ -6951,6 +7211,98 @@ gtk_window_real_set_focus (GtkWindow *window,
 }
 
 static void
+window_cursor_changed (GdkWindow  *window,
+                       GParamSpec *pspec,
+                       GtkWidget  *widget)
+{
+  GTK_WINDOW (widget)->priv->default_cursor = gdk_window_get_cursor (window);
+}
+
+static void
+update_cursor_at_position (GtkWidget *widget, gint x, gint y)
+{
+  GtkWindow *window = GTK_WINDOW (widget);
+  GtkWindowPrivate *priv = window->priv;
+  GtkWindowRegion region;
+  GdkCursorType cursor_type;
+  GdkCursor *cursor;
+  GdkWindowState state;
+  gboolean use_default = FALSE;
+  GdkWindow *gdk_window;
+
+  region = get_active_region_type (window, x, y);
+
+  state = gdk_window_get_state (gtk_widget_get_window (widget));
+
+  if ((state & GDK_WINDOW_STATE_MAXIMIZED) || !priv->resizable || !priv->decorated)
+    {
+      use_default = TRUE;
+    }
+  else
+    {
+      switch (region)
+        {
+        case GTK_WINDOW_REGION_EDGE_NW:
+          cursor_type = GDK_TOP_LEFT_CORNER;
+          break;
+
+        case GTK_WINDOW_REGION_EDGE_N:
+          cursor_type = GDK_TOP_SIDE;
+          break;
+
+        case GTK_WINDOW_REGION_EDGE_NE:
+          cursor_type = GDK_TOP_RIGHT_CORNER;
+          break;
+
+        case GTK_WINDOW_REGION_EDGE_W:
+          cursor_type = GDK_LEFT_SIDE;
+          break;
+
+        case GTK_WINDOW_REGION_EDGE_E:
+          cursor_type = GDK_RIGHT_SIDE;
+          break;
+
+        case GTK_WINDOW_REGION_EDGE_SW:
+          cursor_type = GDK_BOTTOM_LEFT_CORNER;
+          break;
+
+        case GTK_WINDOW_REGION_EDGE_S:
+          cursor_type = GDK_BOTTOM_SIDE;
+          break;
+
+        case GTK_WINDOW_REGION_EDGE_SE:
+          cursor_type = GDK_BOTTOM_RIGHT_CORNER;
+          break;
+
+        default:
+          use_default = TRUE;
+          break;
+        }
+    }
+
+  gdk_window = gtk_widget_get_window (widget);
+  g_signal_handlers_disconnect_by_func (G_OBJECT (gdk_window),
+                                        G_CALLBACK (window_cursor_changed),
+                                        widget);
+
+  if (use_default)
+    {
+      gdk_window_set_cursor (gdk_window, priv->default_cursor);
+    }
+  else
+    {
+      cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), cursor_type);
+      gdk_window_set_cursor (gdk_window, cursor);
+      g_object_unref (cursor);
+    }
+
+  g_signal_connect (G_OBJECT (gdk_window), "notify::cursor",
+                    G_CALLBACK (window_cursor_changed), widget);
+}
+
+
+
+static void
 gtk_window_get_preferred_width (GtkWidget *widget,
                                 gint      *minimum_size,
                                 gint      *natural_size)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]