Blob Blame History Raw
From 00d291927a59b6bca164fc5cd2d4729604b40d1c Mon Sep 17 00:00:00 2001
From: "Owen W. Taylor" <otaylor@fishsoup.net>
Date: Wed, 23 Jun 2010 15:08:38 -0400
Subject: [PATCH] Allow breaking out from maximization during a mouse
 resize

A maximized window can't be resized from the screen edges (preserves
Fitts law goodness for the application), but it's still possible
to start a resize drag with alt-middle-button. Currently we just
don't let the user resize the window, while showing drag feedback;
it's more useful to let the user "break" out from the resize.

This provides a fast way to get a window partially aligned with
the screen edges - maximize, then alt-drag it out from one edge.

Behavior choices in this patch:

 - You can drag out a window out of maximization in both directions -
   smaller and larger. This can be potentilaly useful in multihead.

 - Dragging a window in only one direction unmaximizes the window
   fully, rather than leaving it in a horizontally/vertically
   maximized state. This is done because the horizontally/vertically
   maximzed states don't have clear visual representation and can
   be confusing to the user.

 - If you drag back to the maximized state after breaking out,
   maximization is restored, but you can't maximize a window by
   dragging to the full size if it didn't start out that way.

A new internal function meta_window_unmaximize_with_gravity() is
added for implementing this; it's a hybrid of
meta_window_unmaximize() and meta_window_resize_with_gravity().

https://bugzilla.gnome.org/show_bug.cgi?id=622517
---
 src/core/display-private.h |    3 +
 src/core/display.c         |   20 +++-
 src/core/window-private.h  |    5 +
 src/core/window.c          |  224 ++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 228 insertions(+), 24 deletions(-)

diff --git a/src/core/display-private.h b/src/core/display-private.h
index 7f779fd..bb6ba8a 100644
--- a/src/core/display-private.h
+++ b/src/core/display-private.h
@@ -178,6 +178,9 @@ struct _MetaDisplay
   guint       grab_wireframe_active : 1;
   guint       grab_was_cancelled : 1;    /* Only used in wireframe mode */
   guint       grab_frame_action : 1;
+  /* During a resize operation, the directions in which we've broken
+   * out of the initial maximization state */
+  guint       grab_resize_unmaximize : 2; /* MetaMaximizeFlags */
   MetaRectangle grab_wireframe_rect;
   MetaRectangle grab_wireframe_last_xor_rect;
   MetaRectangle grab_initial_window_pos;
diff --git a/src/core/display.c b/src/core/display.c
index 0c5f61d..25cf857 100644
--- a/src/core/display.c
+++ b/src/core/display.c
@@ -3470,6 +3470,7 @@ meta_display_begin_grab_op (MetaDisplay *display,
 #endif
   display->grab_was_cancelled = FALSE;
   display->grab_frame_action = frame_action;
+  display->grab_resize_unmaximize = 0;
 
   if (display->grab_resize_timeout_id)
     {
@@ -3700,11 +3701,20 @@ meta_display_end_grab_op (MetaDisplay *display,
                               display->grab_wireframe_rect.x,
                               display->grab_wireframe_rect.y);
           if (meta_grab_op_is_resizing (display->grab_op))
-            meta_window_resize_with_gravity (display->grab_window,
-                                             TRUE,
-                                             display->grab_wireframe_rect.width,
-                                             display->grab_wireframe_rect.height,
-                                             meta_resize_gravity_from_grab_op (display->grab_op));
+            {
+              if (display->grab_resize_unmaximize != 0)
+                meta_window_unmaximize_with_gravity (display->grab_window,
+                                                     display->grab_resize_unmaximize,
+                                                     display->grab_wireframe_rect.width,
+                                                     display->grab_wireframe_rect.height,
+                                                     meta_resize_gravity_from_grab_op (display->grab_op));
+              else
+                meta_window_resize_with_gravity (display->grab_window,
+                                                 TRUE,
+                                                 display->grab_wireframe_rect.width,
+                                                 display->grab_wireframe_rect.height,
+                                                 meta_resize_gravity_from_grab_op (display->grab_op));
+            }
         }
       meta_window_calc_showing (display->grab_window);
     }
diff --git a/src/core/window-private.h b/src/core/window-private.h
index 7cf5b04..2c07e85 100644
--- a/src/core/window-private.h
+++ b/src/core/window-private.h
@@ -412,6 +412,11 @@ void        meta_window_maximize_internal  (MetaWindow        *window,
                                             MetaRectangle     *saved_rect);
 void        meta_window_unmaximize         (MetaWindow        *window,
                                             MetaMaximizeFlags  directions);
+void        meta_window_unmaximize_with_gravity (MetaWindow        *window,
+                                                 MetaMaximizeFlags  directions,
+                                                 int                new_width,
+                                                 int                new_height,
+                                                 int                gravity);
 void        meta_window_make_above         (MetaWindow  *window);
 void        meta_window_unmake_above       (MetaWindow  *window);
 void        meta_window_shade              (MetaWindow  *window,
diff --git a/src/core/window.c b/src/core/window.c
index 897161e..427f91f 100644
--- a/src/core/window.c
+++ b/src/core/window.c
@@ -2693,9 +2693,11 @@ unmaximize_window_before_freeing (MetaWindow        *window)
     }
 }
 
-void
-meta_window_unmaximize (MetaWindow        *window,
-                        MetaMaximizeFlags  directions)
+static void
+meta_window_unmaximize_internal (MetaWindow        *window,
+                                 MetaMaximizeFlags  directions,
+                                 MetaRectangle     *desired_rect,
+                                 int                gravity)
 {
   /* At least one of the two directions ought to be set */
   gboolean unmaximize_horizontally, unmaximize_vertically;
@@ -2729,13 +2731,13 @@ meta_window_unmaximize (MetaWindow        *window,
       meta_window_get_client_root_coords (window, &target_rect);
       if (unmaximize_horizontally)
         {
-          target_rect.x     = window->saved_rect.x;
-          target_rect.width = window->saved_rect.width;
+          target_rect.x     = desired_rect->x;
+          target_rect.width = desired_rect->width;
         }
       if (unmaximize_vertically)
         {
-          target_rect.y      = window->saved_rect.y;
-          target_rect.height = window->saved_rect.height;
+          target_rect.y      = desired_rect->y;
+          target_rect.height = desired_rect->height;
         }
 
       /* Window's size hints may have changed while maximized, making
@@ -2754,12 +2756,13 @@ meta_window_unmaximize (MetaWindow        *window,
           window->display->grab_anchor_window_pos = target_rect;
         }
 
-      meta_window_move_resize (window,
-                               FALSE,
-                               target_rect.x,
-                               target_rect.y,
-                               target_rect.width,
-                               target_rect.height);
+      meta_window_move_resize_internal (window,
+                                        META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION,
+                                        gravity,
+                                        target_rect.x,
+                                        target_rect.y,
+                                        target_rect.width,
+                                        target_rect.height);
 
       /* Make sure user_rect is current.
        */
@@ -2776,6 +2779,36 @@ meta_window_unmaximize (MetaWindow        *window,
 }
 
 void
+meta_window_unmaximize (MetaWindow        *window,
+                        MetaMaximizeFlags  directions)
+{
+  meta_window_unmaximize_internal (window, directions, &window->saved_rect,
+                                   NorthWestGravity);
+}
+
+/* Like meta_window_unmaximize(), but instead of unmaximizing to the
+ * saved position, we give the new desired size, and the gravity that
+ * determines the positioning relationship between the area occupied
+ * maximized and the new are. The arguments are similar to
+ * meta_window_resize_with_gravity().
+ */
+void
+meta_window_unmaximize_with_gravity (MetaWindow        *window,
+                                     MetaMaximizeFlags  directions,
+                                     int                new_width,
+                                     int                new_height,
+                                     int                gravity)
+{
+  MetaRectangle desired_rect;
+
+  meta_window_get_position (window, &desired_rect.x, &desired_rect.y);
+  desired_rect.width = new_width;
+  desired_rect.height = new_height;
+
+  meta_window_unmaximize_internal (window, directions, &desired_rect, gravity);
+}
+
+void
 meta_window_make_above (MetaWindow  *window)
 {
   window->wm_state_above = TRUE;
@@ -7124,6 +7157,112 @@ update_resize_timeout (gpointer data)
   return FALSE;
 }
 
+/* When resizing a maximized window by using alt-middle-drag (resizing
+ * with the grips or the menu for a maximized window is not enabled),
+ * the user can "break" out of the maximized state. This checks for
+ * that possibility. During such a break-out resize the user can also
+ * return to the previous maximization state by resizing back to near
+ * the original size.
+ */
+static MetaMaximizeFlags
+check_resize_unmaximize(MetaWindow *window,
+                        int         dx,
+                        int         dy)
+{
+  int threshold;
+  MetaMaximizeFlags new_unmaximize;
+
+#define DRAG_THRESHOLD_TO_RESIZE_THRESHOLD_FACTOR 3
+
+  threshold = meta_ui_get_drag_threshold (window->screen->ui) *
+    DRAG_THRESHOLD_TO_RESIZE_THRESHOLD_FACTOR;
+  new_unmaximize = 0;
+
+  if (window->maximized_horizontally ||
+      (window->display->grab_resize_unmaximize & META_MAXIMIZE_HORIZONTAL) != 0)
+    {
+      int x_amount;
+
+      /* We allow breaking out of maximization in either direction, to make
+       * the window larger than the monitor as well as smaller than the
+       * monitor. If we wanted to only allow resizing smaller than the
+       * monitor, we'd use - dx for NE/E/SE and dx for SW/W/NW.
+       */
+      switch (window->display->grab_op)
+        {
+        case META_GRAB_OP_RESIZING_NE:
+        case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+        case META_GRAB_OP_RESIZING_E:
+        case META_GRAB_OP_KEYBOARD_RESIZING_E:
+        case META_GRAB_OP_RESIZING_SE:
+        case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+        case META_GRAB_OP_RESIZING_SW:
+        case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+        case META_GRAB_OP_RESIZING_W:
+        case META_GRAB_OP_KEYBOARD_RESIZING_W:
+        case META_GRAB_OP_RESIZING_NW:
+        case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+          x_amount = dx < 0 ? - dx : dx;
+          break;
+        default:
+          x_amount = 0;
+          break;
+        }
+
+      if (x_amount > threshold)
+        new_unmaximize |= META_MAXIMIZE_HORIZONTAL;
+    }
+
+  if (window->maximized_vertically ||
+      (window->display->grab_resize_unmaximize & META_MAXIMIZE_VERTICAL) != 0)
+    {
+      int y_amount;
+
+      switch (window->display->grab_op)
+        {
+        case META_GRAB_OP_RESIZING_N:
+        case META_GRAB_OP_KEYBOARD_RESIZING_N:
+        case META_GRAB_OP_RESIZING_NE:
+        case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+        case META_GRAB_OP_RESIZING_NW:
+        case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+        case META_GRAB_OP_RESIZING_SE:
+        case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+        case META_GRAB_OP_RESIZING_S:
+        case META_GRAB_OP_KEYBOARD_RESIZING_S:
+        case META_GRAB_OP_RESIZING_SW:
+        case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+          y_amount = dy < 0 ? - dy : dy;
+          break;
+        default:
+          y_amount = 0;
+          break;
+        }
+
+      if (y_amount > threshold)
+        new_unmaximize |= META_MAXIMIZE_VERTICAL;
+    }
+
+  /* Metacity doesn't have a full user interface for only horizontally or
+   * vertically maximized, so while only unmaximizing in the direction drags
+   * has some advantages, it will also confuse the user. So, we always
+   * unmaximize both ways if possible.
+   */
+  if (new_unmaximize != 0)
+    {
+      new_unmaximize = 0;
+
+      if (window->maximized_horizontally ||
+          (window->display->grab_resize_unmaximize & META_MAXIMIZE_HORIZONTAL) != 0)
+        new_unmaximize |= META_MAXIMIZE_HORIZONTAL;
+      if (window->maximized_vertically ||
+          (window->display->grab_resize_unmaximize & META_MAXIMIZE_VERTICAL) != 0)
+        new_unmaximize |= META_MAXIMIZE_VERTICAL;
+    }
+
+  return new_unmaximize;
+}
+
 static void
 update_resize (MetaWindow *window,
                gboolean    snap,
@@ -7136,6 +7275,7 @@ update_resize (MetaWindow *window,
   MetaRectangle old;
   int new_x, new_y;
   double remaining;
+  MetaMaximizeFlags new_unmaximize;
   
   window->display->grab_latest_motion_x = x;
   window->display->grab_latest_motion_y = y;
@@ -7200,7 +7340,9 @@ update_resize (MetaWindow *window,
           meta_window_update_keyboard_resize (window, TRUE);
         }
     }
-  
+
+  new_unmaximize = check_resize_unmaximize (window, dx, dy);
+
   /* FIXME: This stupidity only needed because of wireframe mode and
    * the fact that wireframe isn't making use of
    * meta_rectangle_resize_with_gravity().  If we were to use that, we
@@ -7324,6 +7466,8 @@ update_resize (MetaWindow *window,
 
   if (window->display->grab_wireframe_active)
     {
+      MetaRectangle root_coords;
+
       if ((new_x + new_w <= new_x) || (new_y + new_h <= new_y))
         return;
 
@@ -7333,18 +7477,60 @@ update_resize (MetaWindow *window,
        * wireframe, but still resize it; however, that probably
        * confuses broken clients that have problems with opaque
        * resize, they probably don't track their visibility.
+       *
+       * We handle the basic constraints on maximized windows here
+       * to give the user feedback.
        */
+
+      if (window->maximized_horizontally && (new_unmaximize & META_MAXIMIZE_HORIZONTAL) == 0)
+        {
+          meta_window_get_client_root_coords (window, &root_coords);
+          new_x = root_coords.x;
+          new_w = root_coords.width;
+        }
+      if (window->maximized_vertically && (new_unmaximize & META_MAXIMIZE_VERTICAL) == 0)
+        {
+          meta_window_get_client_root_coords (window, &root_coords);
+          new_y = root_coords.y;
+          new_h = root_coords.height;
+        }
+
       meta_window_update_wireframe (window, new_x, new_y, new_w, new_h);
     }
   else
     {
-      /* We don't need to update unless the specified width and height
-       * are actually different from what we had before.
-       */
-      if (old.width != new_w || old.height != new_h)
-        meta_window_resize_with_gravity (window, TRUE, new_w, new_h, gravity);
+      if (new_unmaximize == window->display->grab_resize_unmaximize)
+        {
+          /* We don't need to update unless the specified width and height
+           * are actually different from what we had before.
+           */
+          if (old.width != new_w || old.height != new_h)
+            {
+              if ((window->display->grab_resize_unmaximize == new_unmaximize))
+                meta_window_resize_with_gravity (window, TRUE, new_w, new_h, gravity);
+            }
+        }
+      else
+        {
+          if ((new_unmaximize & ~window->display->grab_resize_unmaximize) != 0)
+            {
+              meta_window_unmaximize_with_gravity (window,
+                                                   (new_unmaximize & ~window->display->grab_resize_unmaximize),
+                                                   new_w, new_h, gravity);
+            }
+
+          if ((window->display->grab_resize_unmaximize & ~new_unmaximize))
+            {
+              MetaRectangle saved_rect = window->saved_rect;
+              meta_window_maximize (window,
+                                    (window->display->grab_resize_unmaximize & ~new_unmaximize));
+              window->saved_rect = saved_rect;
+            }
+        }
     }
 
+  window->display->grab_resize_unmaximize = new_unmaximize;
+
   /* Store the latest resize time, if we actually resized. */
   if (window->rect.width != old.width || window->rect.height != old.height)
     g_get_current_time (&window->display->grab_last_moveresize_time);
-- 
1.7.9