diff options
Diffstat (limited to 'src/core/edge-resistance.c')
| -rw-r--r-- | src/core/edge-resistance.c | 1277 | 
1 files changed, 1277 insertions, 0 deletions
| diff --git a/src/core/edge-resistance.c b/src/core/edge-resistance.c new file mode 100644 index 00000000..fd3f2d44 --- /dev/null +++ b/src/core/edge-resistance.c @@ -0,0 +1,1277 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Edge resistance for move/resize operations */ + +/*  + * Copyright (C) 2005, 2006 Elijah Newren + *  + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + *  + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <config.h> +#include "edge-resistance.h" +#include "boxes.h" +#include "display-private.h" +#include "workspace.h" + +/* A simple macro for whether a given window's edges are potentially + * relevant for resistance/snapping during a move/resize operation + */ +#define WINDOW_EDGES_RELEVANT(window, display) \ +  meta_window_should_be_showing (window) &&    \ +  window->screen == display->grab_screen &&    \ +  window         != display->grab_window &&    \ +  window->type   != META_WINDOW_DESKTOP &&     \ +  window->type   != META_WINDOW_MENU    &&     \ +  window->type   != META_WINDOW_SPLASHSCREEN + +struct ResistanceDataForAnEdge +{ +  gboolean     timeout_setup; +  guint        timeout_id; +  int          timeout_edge_pos; +  gboolean     timeout_over; +  GSourceFunc  timeout_func; +  MetaWindow  *window; +  int          keyboard_buildup; +}; +typedef struct ResistanceDataForAnEdge ResistanceDataForAnEdge; + +struct MetaEdgeResistanceData +{ +  GArray *left_edges; +  GArray *right_edges; +  GArray *top_edges; +  GArray *bottom_edges; + +  ResistanceDataForAnEdge left_data; +  ResistanceDataForAnEdge right_data; +  ResistanceDataForAnEdge top_data; +  ResistanceDataForAnEdge bottom_data; +}; + +/* !WARNING!: this function can return invalid indices (namely, either -1 or + * edges->len); this is by design, but you need to remember this. + */ +static int +find_index_of_edge_near_position (const GArray *edges, +                                  int           position, +                                  gboolean      want_interval_min, +                                  gboolean      horizontal) +{ +  /* This is basically like a binary search, except that we're trying to +   * find a range instead of an exact value.  So, if we have in our array +   *   Value: 3  27 316 316 316 505 522 800 1213 +   *   Index: 0   1   2   3   4   5   6   7    8 +   * and we call this function with position=500 & want_interval_min=TRUE +   * then we should get 5 (because 505 is the first value bigger than 500). +   * If we call this function with position=805 and want_interval_min=FALSE +   * then we should get 7 (because 800 is the last value smaller than 800). +   * A couple more, to make things clear: +   *    position  want_interval_min  correct_answer +   *         316               TRUE               2 +   *         316              FALSE               4 +   *           2              FALSE              -1 +   *        2000               TRUE               9 +   */ +  int low, high, mid; +  int compare; +  MetaEdge *edge; + +  /* Initialize mid, edge, & compare in the off change that the array only +   * has one element. +   */ +  mid  = 0; +  edge = g_array_index (edges, MetaEdge*, mid); +  compare = horizontal ? edge->rect.x : edge->rect.y; + +  /* Begin the search... */ +  low  = 0; +  high = edges->len - 1; +  while (low < high) +    { +      mid = low + (high - low)/2; +      edge = g_array_index (edges, MetaEdge*, mid); +      compare = horizontal ? edge->rect.x : edge->rect.y; + +      if (compare == position) +        break; + +      if (compare > position) +        high = mid - 1; +      else +        low = mid + 1; +    } + +  /* mid should now be _really_ close to the index we want, so we start +   * linearly searching.  However, note that we don't know if mid is less +   * than or greater than what we need and it's possible that there are +   * several equal values equal to what we were searching for and we ended +   * up in the middle of them instead of at the end.  So we may need to +   * move mid multiple locations over. +   */ +  if (want_interval_min) +    { +      while (compare >= position && mid > 0) +        { +          mid--; +          edge = g_array_index (edges, MetaEdge*, mid); +          compare = horizontal ? edge->rect.x : edge->rect.y; +        } +      while (compare < position && mid < (int)edges->len - 1) +        { +          mid++; +          edge = g_array_index (edges, MetaEdge*, mid); +          compare = horizontal ? edge->rect.x : edge->rect.y; +        } + +      /* Special case for no values in array big enough */ +      if (compare < position) +        return edges->len; + +      /* Return the found value */ +      return mid; +    } +  else +    { +      while (compare <= position && mid < (int)edges->len - 1) +        { +          mid++; +          edge = g_array_index (edges, MetaEdge*, mid); +          compare = horizontal ? edge->rect.x : edge->rect.y; +        } +      while (compare > position && mid > 0) +        { +          mid--; +          edge = g_array_index (edges, MetaEdge*, mid); +          compare = horizontal ? edge->rect.x : edge->rect.y; +        } + +      /* Special case for no values in array small enough */ +      if (compare > position) +        return -1; + +      /* Return the found value */ +      return mid; +    } +} + +static gboolean +points_on_same_side (int ref, int pt1, int pt2) +{ +  return (pt1 - ref) * (pt2 - ref) > 0; +} + +static int +find_nearest_position (const GArray        *edges, +                       int                  position, +                       int                  old_position, +                       const MetaRectangle *new_rect, +                       gboolean             horizontal, +                       gboolean             only_forward) +{ +  /* This is basically just a binary search except that we're looking +   * for the value closest to position, rather than finding that +   * actual value.  Also, we ignore any edges that aren't relevant +   * given the horizontal/vertical position of new_rect. +   */ +  int low, high, mid; +  int compare; +  MetaEdge *edge; +  int best, best_dist, i; +  gboolean edges_align; + +  /* Initialize mid, edge, & compare in the off change that the array only +   * has one element. +   */ +  mid  = 0; +  edge = g_array_index (edges, MetaEdge*, mid); +  compare = horizontal ? edge->rect.x : edge->rect.y; + +  /* Begin the search... */ +  low  = 0; +  high = edges->len - 1; +  while (low < high) +    { +      mid = low + (high - low)/2; +      edge = g_array_index (edges, MetaEdge*, mid); +      compare = horizontal ? edge->rect.x : edge->rect.y; + +      if (compare == position) +        break; + +      if (compare > position) +        high = mid - 1; +      else +        low = mid + 1; +    } + +  /* mid should now be _really_ close to the index we want, so we +   * start searching nearby for something that overlaps and is closer +   * than the original position. +   */ +  best = old_position; +  best_dist = INT_MAX; + +  /* Start the search at mid */ +  edge = g_array_index (edges, MetaEdge*, mid); +  compare = horizontal ? edge->rect.x : edge->rect.y; +  edges_align = meta_rectangle_edge_aligns (new_rect, edge); +  if (edges_align && +      (!only_forward || !points_on_same_side (position, compare, old_position))) +    { +      int dist = ABS (compare - position); +      if (dist < best_dist) +        { +          best = compare; +          best_dist = dist; +        } +    } + +  /* Now start searching higher than mid */ +  for (i = mid + 1; i < (int)edges->len; i++) +    { +      edge = g_array_index (edges, MetaEdge*, i); +      compare = horizontal ? edge->rect.x : edge->rect.y; +   +      edges_align = horizontal ?  +        meta_rectangle_vert_overlap (&edge->rect, new_rect) : +        meta_rectangle_horiz_overlap (&edge->rect, new_rect); + +      if (edges_align && +          (!only_forward || +           !points_on_same_side (position, compare, old_position))) +        { +          int dist = ABS (compare - position); +          if (dist < best_dist) +            { +              best = compare; +              best_dist = dist; +            } +          break; +        } +    } + +  /* Now start searching lower than mid */ +  for (i = mid-1; i >= 0; i--) +    { +      edge = g_array_index (edges, MetaEdge*, i); +      compare = horizontal ? edge->rect.x : edge->rect.y; +   +      edges_align = horizontal ?  +        meta_rectangle_vert_overlap (&edge->rect, new_rect) : +        meta_rectangle_horiz_overlap (&edge->rect, new_rect); + +      if (edges_align && +          (!only_forward || +           !points_on_same_side (position, compare, old_position))) +        { +          int dist = ABS (compare - position); +          if (dist < best_dist) +            { +              best = compare; +              best_dist = dist; +            } +          break; +        } +    } + +  /* Return the best one found */ +  return best; +} + +static gboolean +movement_towards_edge (MetaSide side, int increment) +{ +  switch (side) +    { +    case META_SIDE_LEFT: +    case META_SIDE_TOP: +      return increment < 0; +    case META_SIDE_RIGHT: +    case META_SIDE_BOTTOM: +      return increment > 0; +    default: +      g_assert_not_reached (); +    } +} + +static gboolean +edge_resistance_timeout (gpointer data) +{ +  ResistanceDataForAnEdge *resistance_data = data; + +  resistance_data->timeout_over = TRUE; +  resistance_data->timeout_id = 0; +  (*resistance_data->timeout_func)(resistance_data->window); + +  return FALSE; +} + +static int +apply_edge_resistance (MetaWindow                *window, +                       int                        old_pos, +                       int                        new_pos, +                       const MetaRectangle       *old_rect, +                       const MetaRectangle       *new_rect, +                       GArray                    *edges, +                       ResistanceDataForAnEdge   *resistance_data, +                       GSourceFunc                timeout_func, +                       gboolean                   xdir, +                       gboolean                   keyboard_op) +{ +  int i, begin, end; +  int last_edge; +  gboolean increasing = new_pos > old_pos; +  int      increment = increasing ? 1 : -1; + +  const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW    = 16; +  const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW   =  0; +  const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_XINERAMA  = 32; +  const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_XINERAMA =  0; +  const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN    = 32; +  const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN   =  0; +  const int TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW   =   0; +  const int TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA =   0; +  const int TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN   =   0; + +  /* Quit if no movement was specified */ +  if (old_pos == new_pos) +    return new_pos; + +  /* Remove the old timeout if it's no longer relevant */ +  if (resistance_data->timeout_setup && +      ((resistance_data->timeout_edge_pos > old_pos && +        resistance_data->timeout_edge_pos > new_pos)  || +       (resistance_data->timeout_edge_pos < old_pos && +        resistance_data->timeout_edge_pos < new_pos))) +    { +      resistance_data->timeout_setup = FALSE; +      if (resistance_data->timeout_id != 0) +        { +          g_source_remove (resistance_data->timeout_id); +          resistance_data->timeout_id = 0; +        } +    } + +  /* Get the range of indices in the edge array that we move past/to. */ +  begin = find_index_of_edge_near_position (edges, old_pos,  increasing, xdir); +  end   = find_index_of_edge_near_position (edges, new_pos, !increasing, xdir); + +  /* begin and end can be outside the array index, if the window is partially +   * off the screen +   */ +  last_edge = edges->len - 1; +  begin = CLAMP (begin, 0, last_edge); +  end   = CLAMP (end,   0, last_edge); + +  /* Loop over all these edges we're moving past/to. */ +  i = begin; +  while ((increasing  && i <= end) || +         (!increasing && i >= end)) +    { +      gboolean  edges_align; +      MetaEdge *edge = g_array_index (edges, MetaEdge*, i); +      int       compare = xdir ? edge->rect.x : edge->rect.y; + +      /* Find out if this edge is relevant */ +      edges_align = meta_rectangle_edge_aligns (new_rect, edge)  || +                    meta_rectangle_edge_aligns (old_rect, edge); + +      /* Nothing to do unless the edges align */ +      if (!edges_align) +        { +          /* Go to the next edge in the range */ +          i += increment; +          continue; +        } + +      /* Rest is easier to read if we split on keyboard vs. mouse op */ +      if (keyboard_op) +        { +          if ((old_pos < compare && compare < new_pos) || +              (old_pos > compare && compare > new_pos)) +            return compare; +        } +      else /* mouse op */ +        { +          int threshold; + +          /* TIMEOUT RESISTANCE: If the edge is relevant and we're moving +           * towards it, then we may want to have some kind of time delay +           * before the user can move past this edge. +           */ +          if (movement_towards_edge (edge->side_type, increment)) +            { +              /* First, determine the length of time for the resistance */ +              int timeout_length_ms = 0; +              switch (edge->edge_type) +                { +                case META_EDGE_WINDOW: +                  timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW; +                  break; +                case META_EDGE_XINERAMA: +                  timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA; +                  break; +                case META_EDGE_SCREEN: +                  timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN; +                  break; +                } + +              if (!resistance_data->timeout_setup && +                  timeout_length_ms != 0) +                { +                  resistance_data->timeout_id =  +                    g_timeout_add (timeout_length_ms, +                                   edge_resistance_timeout, +                                   resistance_data); +                  resistance_data->timeout_setup = TRUE; +                  resistance_data->timeout_edge_pos = compare; +                  resistance_data->timeout_over = FALSE; +                  resistance_data->timeout_func = timeout_func; +                  resistance_data->window = window; +                } +              if (!resistance_data->timeout_over && +                  timeout_length_ms != 0) +                return compare; +            } + +          /* PIXEL DISTANCE MOUSE RESISTANCE: If the edge matters and the +           * user hasn't moved at least threshold pixels past this edge, +           * stop movement at this edge.  (Note that this is different from +           * keyboard resistance precisely because keyboard move ops are +           * relative to previous positions, whereas mouse move ops are +           * relative to differences in mouse position and mouse position +           * is an absolute quantity rather than a relative quantity) +           */ + +          /* First, determine the threshold */ +          threshold = 0; +          switch (edge->edge_type) +            { +            case META_EDGE_WINDOW: +              if (movement_towards_edge (edge->side_type, increment)) +                threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW; +              else +                threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW; +              break; +            case META_EDGE_XINERAMA: +              if (movement_towards_edge (edge->side_type, increment)) +                threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_XINERAMA; +              else +                threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_XINERAMA; +              break; +            case META_EDGE_SCREEN: +              if (movement_towards_edge (edge->side_type, increment)) +                threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN; +              else +                threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN; +              break; +            } + +          if (ABS (compare - new_pos) < threshold) +            return compare; +        } + +      /* Go to the next edge in the range */ +      i += increment; +    } + +  return new_pos; +} + +static int +apply_edge_snapping (int                  old_pos, +                     int                  new_pos, +                     const MetaRectangle *new_rect, +                     GArray              *edges, +                     gboolean             xdir, +                     gboolean             keyboard_op) +{ +  int snap_to; + +  if (old_pos == new_pos) +    return new_pos; + +  snap_to = find_nearest_position (edges, +                                   new_pos, +                                   old_pos, +                                   new_rect, +                                   xdir, +                                   keyboard_op); + +  /* If mouse snap-moving, the user could easily accidentally move just a +   * couple pixels in a direction they didn't mean to move; so ignore snap +   * movement in those cases unless it's only a small number of pixels +   * anyway. +   */ +  if (!keyboard_op && +      ABS (snap_to - old_pos) >= 8 && +      ABS (new_pos - old_pos) < 8) +    return old_pos; +  else +    /* Otherwise, return the snapping position found */ +    return snap_to; +} + +/* This function takes the position (including any frame) of the window and + * a proposed new position (ignoring edge resistance/snapping), and then + * applies edge resistance to EACH edge (separately) updating new_outer. + * It returns true if new_outer is modified, false otherwise. + * + * display->grab_edge_resistance_data MUST already be setup or calling this + * function will cause a crash. + */ +static gboolean  +apply_edge_resistance_to_each_side (MetaDisplay         *display, +                                    MetaWindow          *window, +                                    const MetaRectangle *old_outer, +                                    MetaRectangle       *new_outer, +                                    GSourceFunc          timeout_func, +                                    gboolean             auto_snap, +                                    gboolean             keyboard_op, +                                    gboolean             is_resize) +{ +  MetaEdgeResistanceData *edge_data; +  MetaRectangle           modified_rect; +  gboolean                modified; +  int new_left, new_right, new_top, new_bottom; + +  g_assert (display->grab_edge_resistance_data != NULL); +  edge_data = display->grab_edge_resistance_data; + +  if (auto_snap) +    { +      /* Do the auto snapping instead of normal edge resistance; in all +       * cases, we allow snapping to opposite kinds of edges (e.g. left +       * sides of windows to both left and right edges. +       */ + +      new_left   = apply_edge_snapping (BOX_LEFT (*old_outer), +                                        BOX_LEFT (*new_outer), +                                        new_outer, +                                        edge_data->left_edges, +                                        TRUE, +                                        keyboard_op); + +      new_right  = apply_edge_snapping (BOX_RIGHT (*old_outer), +                                        BOX_RIGHT (*new_outer), +                                        new_outer, +                                        edge_data->right_edges, +                                        TRUE, +                                        keyboard_op); + +      new_top    = apply_edge_snapping (BOX_TOP (*old_outer), +                                        BOX_TOP (*new_outer), +                                        new_outer, +                                        edge_data->top_edges, +                                        FALSE, +                                        keyboard_op); + +      new_bottom = apply_edge_snapping (BOX_BOTTOM (*old_outer), +                                        BOX_BOTTOM (*new_outer), +                                        new_outer, +                                        edge_data->bottom_edges, +                                        FALSE, +                                        keyboard_op); +    } +  else +    { +      /* Disable edge resistance for resizes when windows have size +       * increment hints; see #346782.  For all other cases, apply +       * them. +       */ +      if (!is_resize || window->size_hints.width_inc == 1) +        { +          /* Now, apply the normal horizontal edge resistance */ +          new_left   = apply_edge_resistance (window, +                                              BOX_LEFT (*old_outer), +                                              BOX_LEFT (*new_outer), +                                              old_outer, +                                              new_outer, +                                              edge_data->left_edges, +                                              &edge_data->left_data, +                                              timeout_func, +                                              TRUE, +                                              keyboard_op); +          new_right  = apply_edge_resistance (window, +                                              BOX_RIGHT (*old_outer), +                                              BOX_RIGHT (*new_outer), +                                              old_outer, +                                              new_outer, +                                              edge_data->right_edges, +                                              &edge_data->right_data, +                                              timeout_func, +                                              TRUE, +                                              keyboard_op); +        } +      else +        { +          new_left  = new_outer->x; +          new_right = new_outer->x + new_outer->width; +        } +      /* Same for vertical resizes... */ +      if (!is_resize || window->size_hints.height_inc == 1) +        { +          new_top    = apply_edge_resistance (window, +                                              BOX_TOP (*old_outer), +                                              BOX_TOP (*new_outer), +                                              old_outer, +                                              new_outer, +                                              edge_data->top_edges, +                                              &edge_data->top_data, +                                              timeout_func, +                                              FALSE, +                                              keyboard_op); +          new_bottom = apply_edge_resistance (window, +                                              BOX_BOTTOM (*old_outer), +                                              BOX_BOTTOM (*new_outer), +                                              old_outer, +                                              new_outer, +                                              edge_data->bottom_edges, +                                              &edge_data->bottom_data, +                                              timeout_func, +                                              FALSE, +                                              keyboard_op); +        } +      else +        { +          new_top    = new_outer->y; +          new_bottom = new_outer->y + new_outer->height; +        } +    } + +  /* Determine whether anything changed, and save the changes */ +  modified_rect = meta_rect (new_left,  +                             new_top, +                             new_right - new_left, +                             new_bottom - new_top); +  modified = !meta_rectangle_equal (new_outer, &modified_rect); +  *new_outer = modified_rect; +  return modified; +} + +void +meta_display_cleanup_edges (MetaDisplay *display) +{ +  guint i,j; +  MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data; +  GHashTable *edges_to_be_freed; + +  g_assert (edge_data != NULL); + +  /* We first need to clean out any window edges */ +  edges_to_be_freed = g_hash_table_new_full (g_direct_hash, g_direct_equal, +                                             g_free, NULL); +  for (i = 0; i < 4; i++) +    { +      GArray *tmp = NULL; +      MetaSide side; +      switch (i) +        { +        case 0: +          tmp = edge_data->left_edges; +          side = META_SIDE_LEFT; +          break; +        case 1: +          tmp = edge_data->right_edges; +          side = META_SIDE_RIGHT; +          break; +        case 2: +          tmp = edge_data->top_edges; +          side = META_SIDE_TOP; +          break; +        case 3: +          tmp = edge_data->bottom_edges; +          side = META_SIDE_BOTTOM; +          break; +        default: +          g_assert_not_reached (); +        } + +      for (j = 0; j < tmp->len; j++) +        { +          MetaEdge *edge = g_array_index (tmp, MetaEdge*, j); +          if (edge->edge_type == META_EDGE_WINDOW && +              edge->side_type == side) +            { +              /* The same edge will appear in two arrays, and we can't free +               * it yet we still need to compare edge->side_type for the other +               * array that it is in.  So store it in a hash table for later +               * freeing.  Could also do this in a simple linked list. +               */ +              g_hash_table_insert (edges_to_be_freed, edge, edge); +            } +        } +    } + +  /* Now free all the window edges (the key destroy function is g_free) */ +  g_hash_table_destroy (edges_to_be_freed); + +  /* Now free the arrays and data */ +  g_array_free (edge_data->left_edges, TRUE); +  g_array_free (edge_data->right_edges, TRUE); +  g_array_free (edge_data->top_edges, TRUE); +  g_array_free (edge_data->bottom_edges, TRUE); +  edge_data->left_edges = NULL; +  edge_data->right_edges = NULL; +  edge_data->top_edges = NULL; +  edge_data->bottom_edges = NULL; + +  /* Cleanup the timeouts */ +  if (edge_data->left_data.timeout_setup   && +      edge_data->left_data.timeout_id   != 0) +    g_source_remove (edge_data->left_data.timeout_id); +  if (edge_data->right_data.timeout_setup  && +      edge_data->right_data.timeout_id  != 0) +    g_source_remove (edge_data->right_data.timeout_id); +  if (edge_data->top_data.timeout_setup    && +      edge_data->top_data.timeout_id    != 0) +    g_source_remove (edge_data->top_data.timeout_id); +  if (edge_data->bottom_data.timeout_setup && +      edge_data->bottom_data.timeout_id != 0) +    g_source_remove (edge_data->bottom_data.timeout_id); + +  g_free (display->grab_edge_resistance_data); +  display->grab_edge_resistance_data = NULL; +} + +static int +stupid_sort_requiring_extra_pointer_dereference (gconstpointer a,  +                                                 gconstpointer b) +{ +  const MetaEdge * const *a_edge = a; +  const MetaEdge * const *b_edge = b; +  return meta_rectangle_edge_cmp_ignore_type (*a_edge, *b_edge); +} + +static void +cache_edges (MetaDisplay *display, +             GList *window_edges, +             GList *xinerama_edges, +             GList *screen_edges) +{ +  MetaEdgeResistanceData *edge_data; +  GList *tmp; +  int num_left, num_right, num_top, num_bottom; +  int i; + +  /* +   * 0th: Print debugging information to the log about the edges +   */ +#ifdef WITH_VERBOSE_MODE +  if (meta_is_verbose()) +    { +      int max_edges = MAX (MAX( g_list_length (window_edges),  +                                g_list_length (xinerama_edges)), +                           g_list_length (screen_edges)); +      char big_buffer[(EDGE_LENGTH+2)*max_edges]; + +      meta_rectangle_edge_list_to_string (window_edges, ", ", big_buffer); +      meta_topic (META_DEBUG_EDGE_RESISTANCE, +                  "Window edges for resistance  : %s\n", big_buffer); + +      meta_rectangle_edge_list_to_string (xinerama_edges, ", ", big_buffer); +      meta_topic (META_DEBUG_EDGE_RESISTANCE, +                  "Xinerama edges for resistance: %s\n", big_buffer); + +      meta_rectangle_edge_list_to_string (screen_edges, ", ", big_buffer); +      meta_topic (META_DEBUG_EDGE_RESISTANCE, +                  "Screen edges for resistance  : %s\n", big_buffer); +    } +#endif + +  /* +   * 1st: Get the total number of each kind of edge +   */ +  num_left = num_right = num_top = num_bottom = 0; +  for (i = 0; i < 3; i++) +    { +      tmp = NULL; +      switch (i) +        { +        case 0: +          tmp = window_edges; +          break; +        case 1: +          tmp = xinerama_edges; +          break; +        case 2: +          tmp = screen_edges; +          break; +        default: +          g_assert_not_reached (); +        } + +      while (tmp) +        { +          MetaEdge *edge = tmp->data; +          switch (edge->side_type) +            { +            case META_SIDE_LEFT: +              num_left++; +              break; +            case META_SIDE_RIGHT: +              num_right++; +              break; +            case META_SIDE_TOP: +              num_top++; +              break; +            case META_SIDE_BOTTOM: +              num_bottom++; +              break; +            default: +              g_assert_not_reached (); +            } +          tmp = tmp->next; +        } +    } + +  /* +   * 2nd: Allocate the edges +   */ +  g_assert (display->grab_edge_resistance_data == NULL); +  display->grab_edge_resistance_data = g_new (MetaEdgeResistanceData, 1); +  edge_data = display->grab_edge_resistance_data; +  edge_data->left_edges   = g_array_sized_new (FALSE, +                                               FALSE, +                                               sizeof(MetaEdge*), +                                               num_left + num_right); +  edge_data->right_edges  = g_array_sized_new (FALSE, +                                               FALSE, +                                               sizeof(MetaEdge*), +                                               num_left + num_right); +  edge_data->top_edges    = g_array_sized_new (FALSE, +                                               FALSE, +                                               sizeof(MetaEdge*), +                                               num_top + num_bottom); +  edge_data->bottom_edges = g_array_sized_new (FALSE, +                                               FALSE, +                                               sizeof(MetaEdge*), +                                               num_top + num_bottom); + +  /* +   * 3rd: Add the edges to the arrays +   */ +  for (i = 0; i < 3; i++) +    { +      tmp = NULL; +      switch (i) +        { +        case 0: +          tmp = window_edges; +          break; +        case 1: +          tmp = xinerama_edges; +          break; +        case 2: +          tmp = screen_edges; +          break; +        default: +          g_assert_not_reached (); +        } + +      while (tmp) +        { +          MetaEdge *edge = tmp->data; +          switch (edge->side_type) +            { +            case META_SIDE_LEFT: +            case META_SIDE_RIGHT: +              g_array_append_val (edge_data->left_edges, edge); +              g_array_append_val (edge_data->right_edges, edge); +              break; +            case META_SIDE_TOP: +            case META_SIDE_BOTTOM: +              g_array_append_val (edge_data->top_edges, edge); +              g_array_append_val (edge_data->bottom_edges, edge); +              break; +            default: +              g_assert_not_reached (); +            } +          tmp = tmp->next; +        } +    } + +  /* +   * 4th: Sort the arrays (FIXME: This is kinda dumb since the arrays were +   * individually sorted earlier and we could have done this faster and +   * avoided this sort by sticking them into the array with some simple +   * merging of the lists). +   */ +  g_array_sort (display->grab_edge_resistance_data->left_edges,  +                stupid_sort_requiring_extra_pointer_dereference); +  g_array_sort (display->grab_edge_resistance_data->right_edges,  +                stupid_sort_requiring_extra_pointer_dereference); +  g_array_sort (display->grab_edge_resistance_data->top_edges,  +                stupid_sort_requiring_extra_pointer_dereference); +  g_array_sort (display->grab_edge_resistance_data->bottom_edges,  +                stupid_sort_requiring_extra_pointer_dereference); +} + +static void +initialize_grab_edge_resistance_data (MetaDisplay *display) +{ +  MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data; + +  edge_data->left_data.timeout_setup   = FALSE; +  edge_data->right_data.timeout_setup  = FALSE; +  edge_data->top_data.timeout_setup    = FALSE; +  edge_data->bottom_data.timeout_setup = FALSE; + +  edge_data->left_data.keyboard_buildup   = 0; +  edge_data->right_data.keyboard_buildup  = 0; +  edge_data->top_data.keyboard_buildup    = 0; +  edge_data->bottom_data.keyboard_buildup = 0; +} + +void +meta_display_compute_resistance_and_snapping_edges (MetaDisplay *display) +{ +  GList *stacked_windows; +  GList *cur_window_iter; +  GList *edges; +  /* Lists of window positions (rects) and their relative stacking positions */ +  int stack_position; +  GSList *obscuring_windows, *window_stacking; +  /* The portions of the above lists that still remain at the stacking position +   * in the layer that we are working on +   */ +  GSList *rem_windows, *rem_win_stacking; + +  /* +   * 1st: Get the list of relevant windows, from bottom to top +   */ +  stacked_windows =  +    meta_stack_list_windows (display->grab_screen->stack, +                             display->grab_screen->active_workspace); + +  /* +   * 2nd: we need to separate that stacked list into a list of windows that +   * can obscure other edges.  To make sure we only have windows obscuring +   * those below it instead of going both ways, we also need to keep a +   * counter list.  Messy, I know. +   */ +  obscuring_windows = window_stacking = NULL; +  cur_window_iter = stacked_windows; +  stack_position = 0; +  while (cur_window_iter != NULL) +    { +      MetaWindow *cur_window = cur_window_iter->data; +      if (WINDOW_EDGES_RELEVANT (cur_window, display)) +        { +          MetaRectangle *new_rect; +          new_rect = g_new (MetaRectangle, 1); +          meta_window_get_outer_rect (cur_window, new_rect); +          obscuring_windows = g_slist_prepend (obscuring_windows, new_rect); +          window_stacking =  +            g_slist_prepend (window_stacking, GINT_TO_POINTER (stack_position)); +        } + +      stack_position++; +      cur_window_iter = cur_window_iter->next; +    } +  /* Put 'em in bottom to top order */ +  rem_windows = obscuring_windows = g_slist_reverse (obscuring_windows); +  rem_win_stacking = window_stacking = g_slist_reverse (window_stacking); + +  /* +   * 3rd: loop over the windows again, this time getting the edges from +   * them and removing intersections with the relevant obscuring_windows & +   * obscuring_docks. +   */ +  edges = NULL; +  stack_position = 0; +  cur_window_iter = stacked_windows; +  while (cur_window_iter != NULL) +    { +      MetaRectangle  cur_rect; +      MetaWindow    *cur_window = cur_window_iter->data; +      meta_window_get_outer_rect (cur_window, &cur_rect); + +      /* Check if we want to use this window's edges for edge +       * resistance (note that dock edges are considered screen edges +       * which are handled separately +       */ +      if (WINDOW_EDGES_RELEVANT (cur_window, display) && +          cur_window->type != META_WINDOW_DOCK) +        { +          GList *new_edges; +          MetaEdge *new_edge; +          MetaRectangle reduced; + +          /* We don't care about snapping to any portion of the window that +           * is offscreen (we also don't care about parts of edges covered +           * by other windows or DOCKS, but that's handled below). +           */ +          meta_rectangle_intersect (&cur_rect,  +                                    &display->grab_screen->rect, +                                    &reduced); + +          new_edges = NULL; + +          /* Left side of this window is resistance for the right edge of +           * the window being moved. +           */ +          new_edge = g_new (MetaEdge, 1); +          new_edge->rect = reduced; +          new_edge->rect.width = 0; +          new_edge->side_type = META_SIDE_RIGHT; +          new_edge->edge_type = META_EDGE_WINDOW; +          new_edges = g_list_prepend (new_edges, new_edge); + +          /* Right side of this window is resistance for the left edge of +           * the window being moved. +           */ +          new_edge = g_new (MetaEdge, 1); +          new_edge->rect = reduced; +          new_edge->rect.x += new_edge->rect.width; +          new_edge->rect.width = 0; +          new_edge->side_type = META_SIDE_LEFT; +          new_edge->edge_type = META_EDGE_WINDOW; +          new_edges = g_list_prepend (new_edges, new_edge); +           +          /* Top side of this window is resistance for the bottom edge of +           * the window being moved. +           */ +          new_edge = g_new (MetaEdge, 1); +          new_edge->rect = reduced; +          new_edge->rect.height = 0; +          new_edge->side_type = META_SIDE_BOTTOM; +          new_edge->edge_type = META_EDGE_WINDOW; +          new_edges = g_list_prepend (new_edges, new_edge); + +          /* Top side of this window is resistance for the bottom edge of +           * the window being moved. +           */ +          new_edge = g_new (MetaEdge, 1); +          new_edge->rect = reduced; +          new_edge->rect.y += new_edge->rect.height; +          new_edge->rect.height = 0; +          new_edge->side_type = META_SIDE_TOP; +          new_edge->edge_type = META_EDGE_WINDOW; +          new_edges = g_list_prepend (new_edges, new_edge); + +          /* Update the remaining windows to only those at a higher +           * stacking position than this one. +           */ +          while (rem_win_stacking &&  +                 stack_position >= GPOINTER_TO_INT (rem_win_stacking->data)) +            { +              rem_windows      = rem_windows->next; +              rem_win_stacking = rem_win_stacking->next; +            } + +          /* Remove edge portions overlapped by rem_windows and rem_docks */ +          new_edges =  +            meta_rectangle_remove_intersections_with_boxes_from_edges ( +              new_edges, +              rem_windows); + +          /* Save the new edges */ +          edges = g_list_concat (new_edges, edges); +        } + +      stack_position++; +      cur_window_iter = cur_window_iter->next; +    } + +  /* +   * 4th: Free the extra memory not needed and sort the list +   */ +  g_list_free (stacked_windows); +  /* Free the memory used by the obscuring windows/docks lists */ +  g_slist_free (window_stacking); +  /* FIXME: Shouldn't there be a helper function to make this one line of code +   * to free a list instead of four ugly ones? +   */ +  g_slist_foreach (obscuring_windows,  +                   (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */ +                   NULL); +  g_slist_free (obscuring_windows); + +  /* Sort the list.  FIXME: Should I bother with this sorting?  I just +   * sort again later in cache_edges() anyway... +   */ +  edges = g_list_sort (edges, meta_rectangle_edge_cmp); + +  /* +   * 5th: Cache the combination of these edges with the onscreen and +   * xinerama edges in an array for quick access.  Free the edges since +   * they've been cached elsewhere. +   */ +  cache_edges (display, +               edges, +               display->grab_screen->active_workspace->xinerama_edges, +               display->grab_screen->active_workspace->screen_edges); +  g_list_free (edges); + +  /* +   * 6th: Initialize the resistance timeouts and buildups +   */ +  initialize_grab_edge_resistance_data (display); +} + +/* Note that old_[xy] and new_[xy] are with respect to inner positions of + * the window. + */ +void +meta_window_edge_resistance_for_move (MetaWindow  *window, +                                      int          old_x, +                                      int          old_y, +                                      int         *new_x, +                                      int         *new_y, +                                      GSourceFunc  timeout_func, +                                      gboolean     snap, +                                      gboolean     is_keyboard_op) +{ +  MetaRectangle old_outer, proposed_outer, new_outer; +  gboolean is_resize; + +  if (window == window->display->grab_window && +      window->display->grab_wireframe_active) +    { +      meta_window_get_xor_rect (window, +                                &window->display->grab_wireframe_rect, +                                &old_outer); +    } +  else +    { +      meta_window_get_outer_rect (window, &old_outer); +    } +  proposed_outer = old_outer; +  proposed_outer.x += (*new_x - old_x); +  proposed_outer.y += (*new_y - old_y); +  new_outer = proposed_outer; + +  window->display->grab_last_user_action_was_snap = snap; +  is_resize = FALSE; +  if (apply_edge_resistance_to_each_side (window->display, +                                          window, +                                          &old_outer, +                                          &new_outer, +                                          timeout_func, +                                          snap, +                                          is_keyboard_op, +                                          is_resize)) +    { +      /* apply_edge_resistance_to_each_side independently applies +       * resistance to both the right and left edges of new_outer as both +       * could meet areas of resistance.  But we don't want a resize, so we +       * just have both edges move according to the stricter of the +       * resistances.  Same thing goes for top & bottom edges. +       */ +      MetaRectangle *reference; +      int left_change, right_change, smaller_x_change; +      int top_change, bottom_change, smaller_y_change; + +      if (snap && !is_keyboard_op) +        reference = &proposed_outer; +      else +        reference = &old_outer; + +      left_change  = BOX_LEFT (new_outer)  - BOX_LEFT (*reference); +      right_change = BOX_RIGHT (new_outer) - BOX_RIGHT (*reference); +      if (     snap && is_keyboard_op && left_change == 0) +        smaller_x_change = right_change; +      else if (snap && is_keyboard_op && right_change == 0) +        smaller_x_change = left_change; +      else if (ABS (left_change) < ABS (right_change)) +        smaller_x_change = left_change; +      else +        smaller_x_change = right_change; + +      top_change    = BOX_TOP (new_outer)    - BOX_TOP (*reference); +      bottom_change = BOX_BOTTOM (new_outer) - BOX_BOTTOM (*reference); +      if (     snap && is_keyboard_op && top_change == 0) +        smaller_y_change = bottom_change; +      else if (snap && is_keyboard_op && bottom_change == 0) +        smaller_y_change = top_change; +      else if (ABS (top_change) < ABS (bottom_change)) +        smaller_y_change = top_change; +      else +        smaller_y_change = bottom_change; + +      *new_x = old_x + smaller_x_change +  +              (BOX_LEFT (*reference) - BOX_LEFT (old_outer)); +      *new_y = old_y + smaller_y_change + +              (BOX_TOP (*reference) - BOX_TOP (old_outer)); + +      meta_topic (META_DEBUG_EDGE_RESISTANCE, +                  "outer x & y move-to coordinate changed from %d,%d to %d,%d\n", +                  proposed_outer.x, proposed_outer.y, +                  old_outer.x + (*new_x - old_x), +                  old_outer.y + (*new_y - old_y)); +    } +} + +/* Note that old_(width|height) and new_(width|height) are with respect to + * sizes of the inner window. + */ +void +meta_window_edge_resistance_for_resize (MetaWindow  *window, +                                        int          old_width, +                                        int          old_height, +                                        int         *new_width, +                                        int         *new_height, +                                        int          gravity, +                                        GSourceFunc  timeout_func, +                                        gboolean     snap, +                                        gboolean     is_keyboard_op) +{ +  MetaRectangle old_outer, new_outer; +  int proposed_outer_width, proposed_outer_height; +  gboolean is_resize; + +  if (window == window->display->grab_window && +      window->display->grab_wireframe_active) +    { +      meta_window_get_xor_rect (window, +                                &window->display->grab_wireframe_rect, +                                &old_outer); +    } +  else +    { +      meta_window_get_outer_rect (window, &old_outer); +    } +  proposed_outer_width  = old_outer.width  + (*new_width  - old_width); +  proposed_outer_height = old_outer.height + (*new_height - old_height); +  meta_rectangle_resize_with_gravity (&old_outer,  +                                      &new_outer, +                                      gravity, +                                      proposed_outer_width, +                                      proposed_outer_height); + +  window->display->grab_last_user_action_was_snap = snap; +  is_resize = TRUE; +  if (apply_edge_resistance_to_each_side (window->display, +                                          window, +                                          &old_outer, +                                          &new_outer, +                                          timeout_func, +                                          snap, +                                          is_keyboard_op, +                                          is_resize)) +    { +      *new_width  = old_width  + (new_outer.width  - old_outer.width); +      *new_height = old_height + (new_outer.height - old_outer.height); + +      meta_topic (META_DEBUG_EDGE_RESISTANCE, +                  "outer width & height got changed from %d,%d to %d,%d\n", +                  proposed_outer_width, proposed_outer_height, +                  new_outer.width, new_outer.height); +    } +} | 
