[metacity/bug356715] Allow horizontally-maximised windows to be shaken loose between xineramas. Closes #356715.



commit 12cbf9c7306296a559a71b2ca9cc725465a96816
Author: Carlo Wood <carlo alinoe com>
Date:   Fri Jan 21 23:17:46 2011 -0500

    Allow horizontally-maximised windows to be shaken loose between xineramas. Closes #356715.

 src/core/constraints.c    |    2 +-
 src/core/display.c        |    3 -
 src/core/keybindings.c    |   91 ++++---
 src/core/screen-private.h |    2 +-
 src/core/screen.c         |   38 +++-
 src/core/window-private.h |    7 +-
 src/core/window.c         |  629 +++++++++++++++++++++++++++++++++++++--------
 7 files changed, 615 insertions(+), 157 deletions(-)
---
diff --git a/src/core/constraints.c b/src/core/constraints.c
index 5606b76..e2ee3ab 100644
--- a/src/core/constraints.c
+++ b/src/core/constraints.c
@@ -129,7 +129,7 @@ typedef struct
   FixedDirections      fixed_directions;
 
   /* work_area_xinerama - current xinerama region minus struts
-   * entire_xinerama    - current xienrama, including strut regions
+   * entire_xinerama    - current xinerama, including strut regions
    */
   MetaRectangle        work_area_xinerama;
   MetaRectangle        entire_xinerama;
diff --git a/src/core/display.c b/src/core/display.c
index e816823..10d1ecc 100644
--- a/src/core/display.c
+++ b/src/core/display.c
@@ -3483,9 +3483,6 @@ meta_display_end_grab_op (MetaDisplay *display,
   if (display->grab_op == META_GRAB_OP_NONE)
     return;
 
-  if (display->grab_window != NULL)
-    display->grab_window->shaken_loose = FALSE;
-  
   if (display->grab_window != NULL &&
       !meta_prefs_get_raise_on_click () &&
       (meta_grab_op_is_moving (display->grab_op) ||
diff --git a/src/core/keybindings.c b/src/core/keybindings.c
index 8ea6b41..8670701 100644
--- a/src/core/keybindings.c
+++ b/src/core/keybindings.c
@@ -1425,6 +1425,51 @@ meta_display_process_key_event (MetaDisplay *display,
                  !all_keys_grabbed && window);
 }
 
+static void
+process_cancel_grab(MetaDisplay *display,
+                    MetaWindow  *window)
+{
+  /* End move or resize and restore to original state.
+   * In normal cases, we need to do a moveresize now to get the
+   * position back to the original.  In wireframe mode, we just
+   * need to set grab_was_cancelled to true to avoid avoid
+   * moveresizing to the position of the wireframe.
+   */
+  if (!display->grab_wireframe_active)
+    {
+      /* If the window was a maximized window that had been
+       * "shaken loose" we need to remaximize it.
+       *
+       * This needs to be done BEFORE calling meta_window_move_resize
+       * because that function calls constrain_size_increments
+       * which uses window->maximized_horizontally/vertically.
+       * Trying to set a window to grab_initial_window_pos.width
+       * without having maximized_horizontally set, for an originally
+       * horizontally maximized window, can easily fail the
+       * size-increments contrain, causing it to resize the window
+       * based on the current frame size, overwriting the y coordinate
+       * in the process.
+       */
+      if (window->shaken_loose_horizontally ||
+          window->shaken_loose_vertically)
+        meta_window_maximize (window,
+                              (window->shaken_loose_horizontally ?
+                                        META_MAXIMIZE_HORIZONTAL : 0) |
+                              (window->shaken_loose_vertically ?
+                                        META_MAXIMIZE_VERTICAL : 0));
+
+      /* Now it is safe to move it back to where it came from */
+      meta_window_move_resize (display->grab_window,
+                               TRUE,
+                               display->grab_initial_window_pos.x,
+                               display->grab_initial_window_pos.y,
+                               display->grab_initial_window_pos.width,
+                               display->grab_initial_window_pos.height);
+    }
+  else
+    display->grab_was_cancelled = TRUE;
+}
+
 static gboolean
 process_mouse_move_resize_grab (MetaDisplay *display,
                                 MetaScreen  *screen,
@@ -1438,26 +1483,7 @@ process_mouse_move_resize_grab (MetaDisplay *display,
 
   if (keysym == XK_Escape)
     {
-      /* End move or resize and restore to original state.  If the
-       * window was a maximized window that had been "shaken loose" we
-       * need to remaximize it.  In normal cases, we need to do a
-       * moveresize now to get the position back to the original.  In
-       * wireframe mode, we just need to set grab_was_cancelled to tru
-       * to avoid avoid moveresizing to the position of the wireframe.
-       */
-      if (window->shaken_loose)
-        meta_window_maximize (window,
-                              META_MAXIMIZE_HORIZONTAL |
-                              META_MAXIMIZE_VERTICAL);
-      else if (!display->grab_wireframe_active)
-        meta_window_move_resize (display->grab_window,
-                                 TRUE,
-                                 display->grab_initial_window_pos.x,
-                                 display->grab_initial_window_pos.y,
-                                 display->grab_initial_window_pos.width,
-                                 display->grab_initial_window_pos.height);
-      else
-        display->grab_was_cancelled = TRUE;
+      process_cancel_grab (display, window);
 
       /* End grab */
       return FALSE;
@@ -1511,29 +1537,8 @@ process_keyboard_move_grab (MetaDisplay *display,
     incr = NORMAL_INCREMENT;
 
   if (keysym == XK_Escape)
-    {
-      /* End move and restore to original state.  If the window was a
-       * maximized window that had been "shaken loose" we need to
-       * remaximize it.  In normal cases, we need to do a moveresize
-       * now to get the position back to the original.  In wireframe
-       * mode, we just need to set grab_was_cancelled to tru to avoid
-       * avoid moveresizing to the position of the wireframe.
-       */
-      if (window->shaken_loose)
-        meta_window_maximize (window,
-                              META_MAXIMIZE_HORIZONTAL |
-                              META_MAXIMIZE_VERTICAL);
-      else if (!display->grab_wireframe_active)
-        meta_window_move_resize (display->grab_window,
-                                 TRUE,
-                                 display->grab_initial_window_pos.x,
-                                 display->grab_initial_window_pos.y,
-                                 display->grab_initial_window_pos.width,
-                                 display->grab_initial_window_pos.height);
-      else
-        display->grab_was_cancelled = TRUE;
-    }
-  
+    process_cancel_grab (display, window);
+
   /* When moving by increments, we still snap to edges if the move
    * to the edge is smaller than the increment. This is because
    * Shift + arrow to snap is sort of a hidden feature. This way
diff --git a/src/core/screen-private.h b/src/core/screen-private.h
index 8430f19..c8aacf0 100644
--- a/src/core/screen-private.h
+++ b/src/core/screen-private.h
@@ -166,7 +166,7 @@ MetaWindow*   meta_screen_get_mouse_window     (MetaScreen                 *scre
 
 const MetaXineramaScreenInfo* meta_screen_get_current_xinerama    (MetaScreen    *screen);
 const MetaXineramaScreenInfo* meta_screen_get_xinerama_for_rect   (MetaScreen    *screen,
-                                                                   MetaRectangle *rect);
+                                                                   const MetaRectangle *rect);
 const MetaXineramaScreenInfo* meta_screen_get_xinerama_for_window (MetaScreen    *screen,
                                                                    MetaWindow    *window);
 
diff --git a/src/core/screen.c b/src/core/screen.c
index f202947..fcc0a39 100644
--- a/src/core/screen.c
+++ b/src/core/screen.c
@@ -1481,8 +1481,8 @@ meta_screen_get_mouse_window (MetaScreen  *screen,
 }
 
 const MetaXineramaScreenInfo*
-meta_screen_get_xinerama_for_rect (MetaScreen    *screen,
-				   MetaRectangle *rect)
+meta_screen_get_xinerama_for_rect (MetaScreen          *screen,
+                                   const MetaRectangle *rect)
 {
   int i;
   int best_xinerama, xinerama_score;
@@ -1509,6 +1509,40 @@ meta_screen_get_xinerama_for_rect (MetaScreen    *screen,
         }
     }
 
+  if (xinerama_score == 0)
+    {
+      /* If no overlapping area is found at all, returning xinerama 0
+       * isn't always the best. For example, if we are on xinerama 1
+       * and we move the window all the way down, then the inner window
+       * shifts completely off the monitor (resulting it getting here
+       * with xinerama_score == 0), yet we want to return xinerama 1
+       * of course and not 0, because that would cause the (still visible)
+       * title bar to jump to a different monitor.
+       *
+       * Try to make a reasonable guess that will make sense in most
+       * practical cases by returning the xinerama with the shortest
+       * distance (using the "Manhattan" metric) between the centers
+       * of rect and the xinerama.
+       */
+      int min_distance = 1000000;
+      int rect_center_x = rect->x + rect->width / 2;
+      int rect_center_y = rect->y + rect->height / 2;
+      for (i = 0; i < screen->n_xinerama_infos; i++)
+        {
+          int xinerama_center_x = screen->xinerama_infos[i].rect.x +
+              screen->xinerama_infos[i].rect.width / 2;
+          int xinerama_center_y = screen->xinerama_infos[i].rect.y +
+              screen->xinerama_infos[i].rect.height / 2;
+          int distance = ABS(rect_center_x - xinerama_center_x) +
+                         ABS(rect_center_y - xinerama_center_y);
+          if (distance < min_distance)
+            {
+              min_distance = distance;
+              best_xinerama = i;
+            }
+        }
+    }
+
   return &screen->xinerama_infos[best_xinerama];
 }
 
diff --git a/src/core/window-private.h b/src/core/window-private.h
index 7cf5b04..c0c831e 100644
--- a/src/core/window-private.h
+++ b/src/core/window-private.h
@@ -298,8 +298,11 @@ struct _MetaWindow
   /* icon props have changed */
   guint need_reread_icon : 1;
   
-  /* if TRUE, window was maximized at start of current grab op */
-  guint shaken_loose : 1;
+  /* if TRUE, window was maximized, or vertically maximized, at start of current grab op */
+  guint shaken_loose_vertically : 1;
+
+  /* if TRUE, window was horizontally maximized, at start of current grab op */
+  guint shaken_loose_horizontally : 1;
 
   /* if TRUE we have a grab on the focus click buttons */
   guint have_focus_click_grab : 1;
diff --git a/src/core/window.c b/src/core/window.c
index 11994d5..dddb7fd 100644
--- a/src/core/window.c
+++ b/src/core/window.c
@@ -494,7 +494,8 @@ meta_window_new_with_attrs (MetaDisplay       *display,
   window->net_wm_user_time_set = FALSE;
   window->user_time_window = None;
   window->calc_placement = FALSE;
-  window->shaken_loose = FALSE;
+  window->shaken_loose_vertically = FALSE;
+  window->shaken_loose_horizontally = FALSE;
   window->have_focus_click_grab = FALSE;
   window->disable_sync = FALSE;
   
@@ -2488,14 +2489,16 @@ meta_window_save_rect (MetaWindow *window)
   if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen))
     {
       /* save size/pos as appropriate args for move_resize */
-      if (!window->maximized_horizontally)
+      if (!window->maximized_horizontally &&
+          !window->shaken_loose_horizontally)
         {
           window->saved_rect.x      = window->rect.x;
           window->saved_rect.width  = window->rect.width;
           if (window->frame)
             window->saved_rect.x   += window->frame->rect.x;
         }
-      if (!window->maximized_vertically)
+      if (!window->maximized_vertically &&
+          !window->shaken_loose_vertically)
         {
           window->saved_rect.y      = window->rect.y;
           window->saved_rect.height = window->rect.height;
@@ -2533,12 +2536,14 @@ save_user_window_placement (MetaWindow *window)
 
       meta_window_get_client_root_coords (window, &user_rect);
 
-      if (!window->maximized_horizontally)
+      if (!window->maximized_horizontally &&
+          !window->shaken_loose_horizontally)
 	{
 	  window->user_rect.x     = user_rect.x;
 	  window->user_rect.width = user_rect.width;
 	}
-      if (!window->maximized_vertically)
+      if (!window->maximized_vertically &&
+          !window->shaken_loose_vertically)
 	{
 	  window->user_rect.y      = user_rect.y;
 	  window->user_rect.height = user_rect.height;
@@ -2576,6 +2581,10 @@ meta_window_maximize_internal (MetaWindow        *window,
   if (maximize_horizontally || maximize_vertically)
     window->force_save_user_rect = FALSE;
 
+  /* Never have shaken_loose_* set while maximized in any direction. */
+  window->shaken_loose_horizontally = FALSE;
+  window->shaken_loose_vertically = FALSE;
+
   /* Fix for #336850: If the frame shape isn't reapplied, it is
    * possible that the frame will retains its rounded corners. That
    * happens if the client's size when maximized equals the unmaximized
@@ -2730,8 +2739,17 @@ meta_window_unmaximize (MetaWindow        *window,
           window->display->grab_anchor_window_pos = target_rect;
         }
 
+      /* An unmaximize is a 'user action' (we pass TRUE here) because it
+       * needs to set user_rect to the new position (being saved_rect).
+       * The reason for that is that we want that a subsequential partial
+       * maximize should react exactly as if the window was brand new:
+       * simply expand from its current position. Without updating
+       * user_rect, it would jump back to where it was before the
+       * unmaximize, which could easily be at coordinate 0 when the last
+       * maximize was in the other direction (or full).
+       */
       meta_window_move_resize (window,
-                               FALSE,
+                               TRUE,
                                target_rect.x,
                                target_rect.y,
                                target_rect.width,
@@ -6892,6 +6910,483 @@ update_move_timeout (gpointer data)
   return FALSE;
 }
 
+/* Transform 'x' into a normalized coordinate relative to the outer
+ * window of 'rect', including its frame (if any).
+ * If x is on the left frame border, this function returns 0,
+ * if x is on the right frame border, 1.0 is returned.
+ */
+static double
+normalized_x (MetaRectangle *rect,
+              MetaFrame     *frame,
+              int           x)
+{
+  return
+      (double)(x - rect->x + (frame ? frame->child_x : 0)) /
+      (rect->width + (frame ? frame->child_x + frame->right_width : 0));
+}
+
+/* Transform 'y' into a normalized coordinate relative to the outer
+ * window of 'rect', including its frame (if any).
+ * If y is on the top frame border, this function returns 0,
+ * if y is on the bottom frame border, 1.0 is returned.
+ */
+static double
+normalized_y (MetaRectangle *rect,
+              MetaFrame     *frame,
+              int           y)
+{
+  return
+      (double)(y - rect->y + (frame ? frame->child_y : 0)) /
+      (rect->height + (frame ? frame->child_y + frame->bottom_height : 0));
+}
+
+static int const NONE = -1;     /* Not equal to an possible monitor number */
+
+/* Check if a window at position new_x, new_y, while the pointer
+ * is at position x, y would maximize to the respective relevant
+ * screen border(s), using a threshold 'snap_threshold'. If so,
+ * set *target_monitor to the monitor that it will be maximized
+ * on, set *target_work_area to the work area of that monitor,
+ * and set *far_side to true if we are against a far right and/or
+ * bottom monitor taking relevant directions into account.
+ * Set *target_monitor to NONE otherwise.
+ * Returns the current monitor.
+ */
+static int
+check_threshold (MetaWindow    *window,
+                 int           snap_threshold,
+                 int           x,
+                 int           y,
+                 const MetaRectangle *new_rect,
+                 gboolean      relevant_x,
+                 gboolean      relevant_y,
+                 int           *target_monitor,
+                 MetaRectangle *target_work_area,
+                 gboolean      *far_side)
+{
+  const MetaXineramaScreenInfo *wxinerama;
+  const MetaXineramaScreenInfo *xinerama_info;
+
+  gboolean target_at_bottom, target_at_right;
+
+  wxinerama = meta_screen_get_xinerama_for_window (window->screen, window);
+
+  /* Where does the window go if we call meta_window_maximize? */
+  xinerama_info = meta_screen_get_xinerama_for_rect (window->screen, new_rect);
+  *target_monitor = xinerama_info->number;
+  meta_window_get_work_area_for_xinerama (window, *target_monitor, target_work_area);
+
+  /* Is the target monitor on the far bottom/right side? */
+  target_at_right =
+      meta_screen_get_xinerama_neighbor(window->screen,
+          *target_monitor, META_SCREEN_LEFT) != NULL &&
+      meta_screen_get_xinerama_neighbor(window->screen,
+          *target_monitor, META_SCREEN_RIGHT) == NULL;
+  target_at_bottom =
+      meta_screen_get_xinerama_neighbor(window->screen,
+          *target_monitor, META_SCREEN_UP) != NULL &&
+      meta_screen_get_xinerama_neighbor(window->screen,
+          *target_monitor, META_SCREEN_DOWN) == NULL;
+
+  if (far_side)
+    /* This means we'll be checking the opposite side of the window */
+    *far_side = (relevant_x && target_at_right) ||
+                (relevant_y && target_at_bottom);
+
+  /* Check that pointer is inside the target work area
+   * and that the window is near the relevant boundary
+   * of that work area. The former to make sure that
+   * after maximization, the pointer is still inside
+   * the window. This check is therefore not necessary
+   * when no maximization takes place in the tested
+   * direction.
+   * If the target monitor is at the bottom or right
+   * then compare the bottom and right edge respecitively,
+   * instead of the top and left side of the window.
+   */
+  if ((window->shaken_loose_horizontally &&
+       !(x >= target_work_area->x &&
+         x < target_work_area->x + target_work_area->width)) ||
+      (window->shaken_loose_vertically &&
+       !(y >= target_work_area->y &&
+         y < target_work_area->y + target_work_area->height)) ||
+      (relevant_x &&
+        (target_at_right ?
+         ABS(new_rect->x + window->saved_rect.width +
+             (window->frame ? window->frame->right_width : 0) -
+             target_work_area->x - target_work_area->width) :
+         ABS(new_rect->x -
+             (window->frame ? window->frame->child_x : 0) -
+             target_work_area->x)) >= snap_threshold) ||
+      (relevant_y &&
+        (target_at_bottom ?
+         ABS(new_rect->y + window->saved_rect.height +
+             (window->frame ? window->frame->bottom_height : 0) -
+             target_work_area->y - target_work_area->height) :
+         ABS(new_rect->y -
+             (window->frame ? window->frame->child_y : 0) -
+             target_work_area->y)) >= snap_threshold))
+    /* Not close to a relevant edge: no maximization. */
+    *target_monitor = NONE;
+
+  /* Return current monitor. */
+  return wxinerama->number;
+}
+
+/* Check if a window, when translated over (dx,dy) from its current
+ * position and with the grab pointer at location (x,y) would
+ * shake loose from being maximized, or would re-maximize when
+ * already being shaken loose. If so, handle it.
+ *
+ * Returns true when this was the case and the move has been handled,
+ * returns false otherwise, in which case nothing has been done.
+ */
+static gboolean
+do_shake_loose(MetaWindow *window,
+               int const x, int const y,
+               int const dx, int const dy)
+{
+  MetaRectangle new_rect;
+  MetaDisplay *display = window->display;
+  int snap_threshold, shake_threshold;
+  gboolean relevant_x, relevant_y;
+  int relevant_delta;
+
+  /* Calculate the new rectangle, if we'd just normally move */
+  new_rect = display->grab_anchor_window_pos;
+  new_rect.x += dx;
+  new_rect.y += dy;
+
+  /* Shake loose (unmaximize) maximized window if dragged beyond the threshold
+   * in the Y direction (or X direction if only maximized horizontally).
+   */
+
+#define DRAG_THRESHOLD_TO_SNAP_THRESHOLD_FACTOR 4
+#define SHAKE_THRESHOLD 256
+  snap_threshold =
+      DRAG_THRESHOLD_TO_SNAP_THRESHOLD_FACTOR *
+      meta_ui_get_drag_threshold (window->screen->ui);
+  shake_threshold = SHAKE_THRESHOLD;
+    
+  /* Keep track of which directions are relevant (sensitive to the threshold) */
+  relevant_y = window->maximized_vertically || window->shaken_loose_vertically;
+  /* When shaking loose a fully maximized window, we demand that the
+   * vertical threshold is reached (and only the vertical threshold).
+   */
+  relevant_x = !relevant_y &&
+      (window->maximized_horizontally || window->shaken_loose_horizontally);
+  /* At this point at most one of relevant_x or relevant_y is set. */
+
+  relevant_delta = relevant_y ? dy : dx;
+
+  /* If the window is maximized, see if we need to shake it loose. */
+  if ((window->maximized_vertically || window->maximized_horizontally) &&
+      ABS(relevant_delta) >= shake_threshold)
+    {
+      MetaRectangle new_saved_rect = window->saved_rect;
+
+      /* 'saved_rect' should contain the rectangle that we unmaximize to.
+       * Overwrite it here so that the subsequent call to
+       * meta_window_unmaximize will put the window in a sane postion
+       * relative to the current pointer.
+       *
+       * "Sane" meaning the following:
+       * 1) The pointer should remain inside the window (including frame).
+       * 2) The result should be such that the window is not within or close
+       *    to the threshold (causing it rapidly to be maximized again).
+       * 3) If possible, it should be as close as possible to a point
+       *    proportionally equivalent to where it was initially at the
+       *    beginning of the grab.
+       *
+       * 1)
+       * The initial grab position should always be inside, or on
+       * the frame of a window -- otherwise it cannot be grabbed.
+       * After (un)maximization, the grab positions are updated;
+       * Point 1 guarantees that also then the grab position remains
+       * inside or on the frame of the window.
+       *
+       * 2)
+       * Stability is achieved as follows: if the pointer is moving
+       * to the right - then no special measures are needed in practise
+       * because the threshold sensitive left-border of a window will
+       * only move FURTHER away from the left edge of a monitor when
+       * unmaximizing (shrinking). However, when moving a horizontally
+       * maximized window to the left, it can easily happen moving it
+       * just a few pixels further it (un)maximizes again, not a pretty
+       * sight. Therefore, unmaximizing is prohibited if moving the
+       * mouse further in the same direction will cause it to be
+       * maximized again on the same monitor.
+       *
+       * 3)
+       * Great care has been taken to keep the pointer at the SAME
+       * place inside the unmaximized window, every time it is un-
+       * maximized again. This is done by putting the pointer
+       * proportionally at the same place inside the window when
+       * maximizing, and moving the window here such that again
+       * that same proportionality is maintained. The proportionality
+       * is thus a constant factor -- this is also nice for the eye
+       * when jumping from unmaximized to maximized or back.
+       */
+
+      /* Move the window to the pointer: this block sets new_rect_x and
+       * new_rect_y, candidate values for saved_rect before unmazimizing.
+       */
+      double prop_x_factor =
+          normalized_x(&display->grab_anchor_window_pos,
+                       window->frame,
+                       display->grab_anchor_root_x);
+      double prop_y_factor =
+          normalized_y(&display->grab_anchor_window_pos,
+                       window->frame,
+                       display->grab_anchor_root_y);
+      int frame_left = window->frame ? window->frame->child_x : 0;
+      int frame_top = window->frame ? window->frame->child_y : 0;
+      int frame_dx = window->frame ?
+          (window->frame->child_x + window->frame->right_width) : 0;
+      int frame_dy = window->frame ?
+          (window->frame->child_y + window->frame->bottom_height) : 0;
+      int saved_full_width = window->saved_rect.width + frame_dx;
+      int saved_full_height = window->saved_rect.height + frame_dy;
+
+      /* Horizontal placement proportionally equivalent to grab position */
+      new_saved_rect.x = x - saved_full_width * prop_x_factor + frame_left;
+
+      /* Vertical placement proportionally equivalent to grab position */
+      new_saved_rect.y = y - saved_full_height * prop_y_factor + frame_top;
+
+      /* These two variables are initialized to avoid a compiler warning.
+       * They are assigned a value by the call to check_threshold.
+       */
+      int target_monitor = 0;
+      MetaRectangle target_work_area = { 0 };
+      int current_monitor;
+      gboolean far_side;
+
+      /* Remember which directions were shaken loose.
+       * These values are used by check_threshold.
+       */
+      window->shaken_loose_vertically = window->maximized_vertically;
+      window->shaken_loose_horizontally = window->maximized_horizontally;
+
+      current_monitor =
+          check_threshold(window,
+                          snap_threshold,
+                          x, y,
+                          &new_saved_rect,
+                          relevant_x, relevant_y,
+                          &target_monitor, &target_work_area, &far_side);
+
+      /* Now check if we fulfill point 2. */
+      if (/* Inhibit maximization to the same monitor as we are already on */
+          target_monitor != current_monitor &&
+          /* Allow immediate maximization to a different monitor */
+          (target_monitor != NONE ||
+           /* Do not unmaximize the window if that means that it will
+            * maximize again on the same monitor if we continue to move
+            * in that same direction. */
+           /* Target is not on the far side? */
+           (!far_side &&
+            (relevant_delta > 0 ||      /* Moving away from threshold? */
+             /* New position is beyond threshold? */
+             ((!relevant_x ||
+               new_saved_rect.x < display->grab_anchor_window_pos.x) &&
+              (!relevant_y ||
+               new_saved_rect.y < display->grab_anchor_window_pos.y)))) ||
+           /* Target is on the far side? */
+           (far_side &&
+            (relevant_delta < 0 ||      /* Moving away from threshold? */
+             /* New position is beyond threshold? */
+             ((!relevant_x ||
+               new_saved_rect.x + window->saved_rect.width >
+                   display->grab_anchor_window_pos.x +
+                   display->grab_anchor_window_pos.width) &&
+              (!relevant_y ||
+               new_saved_rect.y + window->saved_rect.height >
+                   display->grab_anchor_window_pos.y +
+                   display->grab_anchor_window_pos.height))))
+          ))
+        {
+          /* Keep a copy of saved_rect. */
+          MetaRectangle restore_saved_rect = window->saved_rect;
+
+          /* Reinitialize grab positions to new (unmaximized) position. */
+          display->grab_anchor_root_x = x;
+          display->grab_anchor_root_y = y;
+          display->grab_anchor_window_pos.x = new_saved_rect.x;
+          display->grab_anchor_window_pos.y = new_saved_rect.y;
+
+          /* Unmaximize the window to new_saved_rect */
+          window->saved_rect = new_saved_rect;
+          meta_window_unmaximize (window,
+                                  META_MAXIMIZE_HORIZONTAL |
+                                  META_MAXIMIZE_VERTICAL);
+          window->saved_rect = restore_saved_rect;
+
+          /* At this point, shaken_loose_* is set and maximized_* both unset. */
+          return TRUE;
+        }
+
+      /* Abort shaking loose */
+      window->shaken_loose_vertically = FALSE;
+      window->shaken_loose_horizontally = FALSE;
+    }
+  /* If the window has been shaken loose, see if we need to remaximize
+   * it again, possibly on an other xinerama monitor.
+   */
+  else if (window->shaken_loose_vertically || window->shaken_loose_horizontally)
+    {
+      /* These two variables are initialized to avoid a compiler warning.
+       * They are assigned a value by the call to check_threshold.
+       */
+      int target_monitor = 0;
+      MetaRectangle target_work_area = { 0 };
+
+      check_threshold(window,
+                      snap_threshold,
+                      x, y,
+                      &new_rect,
+                      relevant_x, relevant_y,
+                      &target_monitor, &target_work_area, NULL);
+
+      if (target_monitor != NONE)
+        {
+          int new_ptr_x, new_ptr_y;
+          MetaRectangle* rect_ptr = &window->saved_rect;
+
+          /* Move the saved_rect and user_rect to the target monitor that
+           * we maximize on, so the user isn't surprised on a later unmaximize.
+           * Also see the note below point 9 of comment #4 of bug #356715.
+           */
+          for(;;)
+          {
+            const MetaXineramaScreenInfo *xinerama_info_rect;
+
+            xinerama_info_rect =
+                meta_screen_get_xinerama_for_rect (window->screen,
+                                                   rect_ptr);
+
+            if (xinerama_info_rect->number != target_monitor)
+              {
+                /* Move rect_ptr to the target monitor, because we demand
+                 * that unmaximizing a window never switches monitor.
+                 */
+                rect_ptr->x +=
+                    window->screen->xinerama_infos[target_monitor].rect.x -
+                    xinerama_info_rect->rect.x;
+                rect_ptr->y +=
+                    window->screen->xinerama_infos[target_monitor].rect.y -
+                    xinerama_info_rect->rect.y;
+
+                /* Make sure that the rect will be visible on the new
+                 * monitor (it might not, because the target monitor is
+                 * smaller.
+                 */
+                if (!meta_rectangle_overlap(rect_ptr,
+                                            &target_work_area))
+                  {
+                    /* If not visible - move it to the top-left position. */
+                    rect_ptr->x = target_work_area.x;
+                    rect_ptr->y = target_work_area.y;
+                    if (window->frame)
+                      {
+                        rect_ptr->x += window->frame->child_x;
+                        rect_ptr->y += window->frame->child_y;
+                      }
+                  }
+              }
+
+            if (rect_ptr == &window->user_rect)
+              break;
+            rect_ptr = &window->user_rect;
+          }
+
+          /* Reinitialize initial grab positions for newly maximized windows */
+          new_ptr_x = x;
+          if (window->shaken_loose_horizontally)
+            {
+              double prop_x_factor =
+                  normalized_x(&display->grab_anchor_window_pos,
+                               window->frame,
+                               display->grab_anchor_root_x);
+              display->grab_anchor_window_pos.x = target_work_area.x;
+              display->grab_anchor_window_pos.width = target_work_area.width;
+              if (window->frame)
+                {
+                  display->grab_anchor_window_pos.x += window->frame->child_x;
+                  display->grab_anchor_window_pos.width -=
+                      window->frame->child_x + window->frame->right_width;
+                }
+
+              /* Move the pointer to keep the same proportional
+               * position inside the window. */
+              new_ptr_x = target_work_area.x + prop_x_factor * target_work_area.width;
+              display->grab_last_user_action_was_snap = TRUE;
+            }
+          else
+            display->grab_anchor_window_pos.x = new_rect.x;
+          new_ptr_y = y;
+          if (window->shaken_loose_vertically)
+            {
+              double prop_y_factor =
+                  normalized_y(&display->grab_anchor_window_pos,
+                               window->frame,
+                               display->grab_anchor_root_y);
+              display->grab_anchor_window_pos.y = target_work_area.y;
+              display->grab_anchor_window_pos.height = target_work_area.height;
+              if (window->frame)
+                {
+                  display->grab_anchor_window_pos.y += window->frame->child_y;
+                  display->grab_anchor_window_pos.height -=
+                      window->frame->child_y + window->frame->bottom_height;
+                }
+              /* Move the pointer to keep the same proportional
+               * position inside the window. */
+              new_ptr_y = target_work_area.y + prop_y_factor * target_work_area.height;
+              display->grab_last_user_action_was_snap = TRUE;
+            }
+          else
+            display->grab_anchor_window_pos.y = new_rect.y;
+
+          /* Update state. This resets shaken_loose_*. */
+          meta_window_maximize_internal (window, 
+                                         (window->shaken_loose_horizontally ?
+                                          META_MAXIMIZE_HORIZONTAL : 0) |
+                                         (window->shaken_loose_vertically ?
+                                          META_MAXIMIZE_VERTICAL : 0),
+                                         &window->saved_rect);
+          meta_window_queue(window, META_QUEUE_MOVE_RESIZE);
+
+          display->grab_anchor_root_x = new_ptr_x;
+          display->grab_anchor_root_y = new_ptr_y;
+
+          /* Move the pointer so that we start fresh with a 'pull' of 0 */
+          if (new_ptr_x != x || new_ptr_y != y)
+            {
+              meta_error_trap_push (display);
+
+              meta_topic (META_DEBUG_WINDOW_OPS,
+                          "Warping pointer to %d,%d\n", new_ptr_x, new_ptr_y);
+
+              display->grab_latest_motion_x = new_ptr_x;
+              display->grab_latest_motion_y = new_ptr_y;
+
+              XWarpPointer (display->xdisplay,
+                            None,
+                            window->screen->xroot,
+                            0, 0, 0, 0, 
+                            new_ptr_x, new_ptr_y);
+
+              meta_error_trap_pop (display, FALSE);
+            }
+
+          /* At this point maximize_* is set and shaken_loose* both unset. */
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
 static void
 update_move (MetaWindow  *window,
              gboolean     snap,
@@ -6901,18 +7396,21 @@ update_move (MetaWindow  *window,
   int dx, dy;
   int new_x, new_y;
   MetaRectangle old;
-  int shake_threshold;
   MetaDisplay *display = window->display;
   
+  /* x,y is the current position of the pointer */
   display->grab_latest_motion_x = x;
   display->grab_latest_motion_y = y;
-  
+
+  /* grab_anchor_root_x,grab_anchor_root_y was the position
+   * of pointer at the time the grab operation began (mouse
+   * button pressed), or since the last (un)maximize (shake
+   * loose and subsequential remaximize).
+   * dx,dy is the movement since.
+   */
   dx = x - display->grab_anchor_root_x;
   dy = y - display->grab_anchor_root_y;
 
-  new_x = display->grab_anchor_window_pos.x + dx;
-  new_y = display->grab_anchor_window_pos.y + dy;
-
   meta_verbose ("x,y = %d,%d anchor ptr %d,%d anchor pos %d,%d dx,dy %d,%d\n",
                 x, y,
                 display->grab_anchor_root_x,
@@ -6928,102 +7426,17 @@ update_move (MetaWindow  *window,
   if (dx == 0 && dy == 0)
     return;
 
-  /* shake loose (unmaximize) maximized window if dragged beyond the threshold
-   * in the Y direction. You can't pull a window loose via X motion.
-   */
-
-#define DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR 6
-  shake_threshold = meta_ui_get_drag_threshold (window->screen->ui) *
-    DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR;
-    
-  if (META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold)
-    {
-      double prop;
-
-      /* Shake loose */
-      window->shaken_loose = TRUE;
-                  
-      /* move the unmaximized window to the cursor */
-      prop = 
-        ((double)(x - display->grab_initial_window_pos.x)) / 
-        ((double)display->grab_initial_window_pos.width);
-
-      display->grab_initial_window_pos.x = 
-        x - window->saved_rect.width * prop;
-      display->grab_initial_window_pos.y = y;
-
-      if (window->frame)
-        {
-          display->grab_initial_window_pos.y += window->frame->child_y / 2;
-        }
-
-      window->saved_rect.x = display->grab_initial_window_pos.x;
-      window->saved_rect.y = display->grab_initial_window_pos.y;
-      display->grab_anchor_root_x = x;
-      display->grab_anchor_root_y = y;
-
-      meta_window_unmaximize (window,
-                              META_MAXIMIZE_HORIZONTAL |
-                              META_MAXIMIZE_VERTICAL);
+  /* Handle (un)maximization thresholds */
+  if (do_shake_loose(window, x, y, dx, dy))
+    return;
 
-      return;
-    }
-  /* remaximize window on an other xinerama monitor if window has
-   * been shaken loose or it is still maximized (then move straight)
+  /* grab_anchor_window_pos contains the coordinates of the top-left
+   * of the client window (the inner window) of when the grab began,
+   * or since the last (un)maximize.
+   * Set new_x,new_y to the new position.
    */
-  else if (window->shaken_loose || META_WINDOW_MAXIMIZED (window))
-    {
-      const MetaXineramaScreenInfo *wxinerama;
-      MetaRectangle work_area;
-      int monitor;
-
-      wxinerama = meta_screen_get_xinerama_for_window (window->screen, window);
-
-      for (monitor = 0; monitor < window->screen->n_xinerama_infos; monitor++)
-        {
-          meta_window_get_work_area_for_xinerama (window, monitor, &work_area);
-
-          /* check if cursor is near the top of a xinerama work area */ 
-          if (x >= work_area.x &&
-              x < (work_area.x + work_area.width) &&
-              y >= work_area.y &&
-              y < (work_area.y + shake_threshold))
-            {
-              /* move the saved rect if window will become maximized on an
-               * other monitor so user isn't surprised on a later unmaximize
-               */
-              if (wxinerama->number != monitor)
-                {
-                  window->saved_rect.x = work_area.x;
-                  window->saved_rect.y = work_area.y;
-                  
-                  if (window->frame) 
-                    {
-                      window->saved_rect.x += window->frame->child_x;
-                      window->saved_rect.y += window->frame->child_y;
-                    }
-
-                  window->user_rect.x = window->saved_rect.x;
-                  window->user_rect.y = window->saved_rect.y;
-
-                  meta_window_unmaximize (window,
-                                          META_MAXIMIZE_HORIZONTAL |
-                                          META_MAXIMIZE_VERTICAL);
-                }
-
-              display->grab_initial_window_pos = work_area;
-              display->grab_anchor_root_x = x;
-              display->grab_anchor_root_y = y;
-              window->shaken_loose = FALSE;
-              
-              meta_window_maximize (window,
-                                    META_MAXIMIZE_HORIZONTAL |
-                                    META_MAXIMIZE_VERTICAL);
-
-              return;
-            }
-        }
-    }
+  new_x = display->grab_anchor_window_pos.x + dx;
+  new_y = display->grab_anchor_window_pos.y + dy;
 
   if (display->grab_wireframe_active)
     old = display->grab_wireframe_rect;
@@ -7464,6 +7877,12 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window,
             }
         }
 
+      if (window->shaken_loose_vertically || window->shaken_loose_horizontally)
+      {
+        window->shaken_loose_vertically = FALSE;
+        window->shaken_loose_horizontally = FALSE;
+        save_user_window_placement(window);
+      }
       meta_display_end_grab_op (window->display, event->xbutton.time);
       break;    
 



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