edge-resistance.c

Go to the documentation of this file.
00001 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
00002 
00003 /* Edge resistance for move/resize operations */
00004 
00005 /* 
00006  * Copyright (C) 2005, 2006 Elijah Newren
00007  * 
00008  * This program is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU General Public License as
00010  * published by the Free Software Foundation; either version 2 of the
00011  * License, or (at your option) any later version.
00012  *
00013  * This program is distributed in the hope that it will be useful, but
00014  * WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016  * General Public License for more details.
00017  * 
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program; if not, write to the Free Software
00020  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
00021  * 02111-1307, USA.
00022  */
00023 
00024 #include <config.h>
00025 #include "edge-resistance.h"
00026 #include "boxes.h"
00027 #include "display-private.h"
00028 #include "workspace.h"
00029 
00030 /* A simple macro for whether a given window's edges are potentially
00031  * relevant for resistance/snapping during a move/resize operation
00032  */
00033 #define WINDOW_EDGES_RELEVANT(window, display) \
00034   meta_window_should_be_showing (window) &&    \
00035   window->screen == display->grab_screen &&    \
00036   window         != display->grab_window &&    \
00037   window->type   != META_WINDOW_DESKTOP &&     \
00038   window->type   != META_WINDOW_MENU    &&     \
00039   window->type   != META_WINDOW_SPLASHSCREEN
00040 
00041 struct ResistanceDataForAnEdge
00042 {
00043   gboolean     timeout_setup;
00044   guint        timeout_id;
00045   int          timeout_edge_pos;
00046   gboolean     timeout_over;
00047   GSourceFunc  timeout_func;
00048   MetaWindow  *window;
00049   int          keyboard_buildup;
00050 };
00051 typedef struct ResistanceDataForAnEdge ResistanceDataForAnEdge;
00052 
00053 struct MetaEdgeResistanceData
00054 {
00055   GArray *left_edges;
00056   GArray *right_edges;
00057   GArray *top_edges;
00058   GArray *bottom_edges;
00059 
00060   ResistanceDataForAnEdge left_data;
00061   ResistanceDataForAnEdge right_data;
00062   ResistanceDataForAnEdge top_data;
00063   ResistanceDataForAnEdge bottom_data;
00064 };
00065 
00066 /* !WARNING!: this function can return invalid indices (namely, either -1 or
00067  * edges->len); this is by design, but you need to remember this.
00068  */
00069 static int
00070 find_index_of_edge_near_position (const GArray *edges,
00071                                   int           position,
00072                                   gboolean      want_interval_min,
00073                                   gboolean      horizontal)
00074 {
00075   /* This is basically like a binary search, except that we're trying to
00076    * find a range instead of an exact value.  So, if we have in our array
00077    *   Value: 3  27 316 316 316 505 522 800 1213
00078    *   Index: 0   1   2   3   4   5   6   7    8
00079    * and we call this function with position=500 & want_interval_min=TRUE
00080    * then we should get 5 (because 505 is the first value bigger than 500).
00081    * If we call this function with position=805 and want_interval_min=FALSE
00082    * then we should get 7 (because 800 is the last value smaller than 800).
00083    * A couple more, to make things clear:
00084    *    position  want_interval_min  correct_answer
00085    *         316               TRUE               2
00086    *         316              FALSE               4
00087    *           2              FALSE              -1
00088    *        2000               TRUE               9
00089    */
00090   int low, high, mid;
00091   int compare;
00092   MetaEdge *edge;
00093 
00094   /* Initialize mid, edge, & compare in the off change that the array only
00095    * has one element.
00096    */
00097   mid  = 0;
00098   edge = g_array_index (edges, MetaEdge*, mid);
00099   compare = horizontal ? edge->rect.x : edge->rect.y;
00100 
00101   /* Begin the search... */
00102   low  = 0;
00103   high = edges->len - 1;
00104   while (low < high)
00105     {
00106       mid = low + (high - low)/2;
00107       edge = g_array_index (edges, MetaEdge*, mid);
00108       compare = horizontal ? edge->rect.x : edge->rect.y;
00109 
00110       if (compare == position)
00111         break;
00112 
00113       if (compare > position)
00114         high = mid - 1;
00115       else
00116         low = mid + 1;
00117     }
00118 
00119   /* mid should now be _really_ close to the index we want, so we start
00120    * linearly searching.  However, note that we don't know if mid is less
00121    * than or greater than what we need and it's possible that there are
00122    * several equal values equal to what we were searching for and we ended
00123    * up in the middle of them instead of at the end.  So we may need to
00124    * move mid multiple locations over.
00125    */
00126   if (want_interval_min)
00127     {
00128       while (compare >= position && mid > 0)
00129         {
00130           mid--;
00131           edge = g_array_index (edges, MetaEdge*, mid);
00132           compare = horizontal ? edge->rect.x : edge->rect.y;
00133         }
00134       while (compare < position && mid < (int)edges->len - 1)
00135         {
00136           mid++;
00137           edge = g_array_index (edges, MetaEdge*, mid);
00138           compare = horizontal ? edge->rect.x : edge->rect.y;
00139         }
00140 
00141       /* Special case for no values in array big enough */
00142       if (compare < position)
00143         return edges->len;
00144 
00145       /* Return the found value */
00146       return mid;
00147     }
00148   else
00149     {
00150       while (compare <= position && mid < (int)edges->len - 1)
00151         {
00152           mid++;
00153           edge = g_array_index (edges, MetaEdge*, mid);
00154           compare = horizontal ? edge->rect.x : edge->rect.y;
00155         }
00156       while (compare > position && mid > 0)
00157         {
00158           mid--;
00159           edge = g_array_index (edges, MetaEdge*, mid);
00160           compare = horizontal ? edge->rect.x : edge->rect.y;
00161         }
00162 
00163       /* Special case for no values in array small enough */
00164       if (compare > position)
00165         return -1;
00166 
00167       /* Return the found value */
00168       return mid;
00169     }
00170 }
00171 
00172 static gboolean
00173 points_on_same_side (int ref, int pt1, int pt2)
00174 {
00175   return (pt1 - ref) * (pt2 - ref) > 0;
00176 }
00177 
00178 static int
00179 find_nearest_position (const GArray        *edges,
00180                        int                  position,
00181                        int                  old_position,
00182                        const MetaRectangle *new_rect,
00183                        gboolean             horizontal,
00184                        gboolean             only_forward)
00185 {
00186   /* This is basically just a binary search except that we're looking
00187    * for the value closest to position, rather than finding that
00188    * actual value.  Also, we ignore any edges that aren't relevant
00189    * given the horizontal/vertical position of new_rect.
00190    */
00191   int low, high, mid;
00192   int compare;
00193   MetaEdge *edge;
00194   int best, best_dist, i;
00195   gboolean edges_align;
00196 
00197   /* Initialize mid, edge, & compare in the off change that the array only
00198    * has one element.
00199    */
00200   mid  = 0;
00201   edge = g_array_index (edges, MetaEdge*, mid);
00202   compare = horizontal ? edge->rect.x : edge->rect.y;
00203 
00204   /* Begin the search... */
00205   low  = 0;
00206   high = edges->len - 1;
00207   while (low < high)
00208     {
00209       mid = low + (high - low)/2;
00210       edge = g_array_index (edges, MetaEdge*, mid);
00211       compare = horizontal ? edge->rect.x : edge->rect.y;
00212 
00213       if (compare == position)
00214         break;
00215 
00216       if (compare > position)
00217         high = mid - 1;
00218       else
00219         low = mid + 1;
00220     }
00221 
00222   /* mid should now be _really_ close to the index we want, so we
00223    * start searching nearby for something that overlaps and is closer
00224    * than the original position.
00225    */
00226   best = old_position;
00227   best_dist = INT_MAX;
00228 
00229   /* Start the search at mid */
00230   edge = g_array_index (edges, MetaEdge*, mid);
00231   compare = horizontal ? edge->rect.x : edge->rect.y;
00232   edges_align = meta_rectangle_edge_aligns (new_rect, edge);
00233   if (edges_align &&
00234       (!only_forward || !points_on_same_side (position, compare, old_position)))
00235     {
00236       int dist = ABS (compare - position);
00237       if (dist < best_dist)
00238         {
00239           best = compare;
00240           best_dist = dist;
00241         }
00242     }
00243 
00244   /* Now start searching higher than mid */
00245   for (i = mid + 1; i < (int)edges->len; i++)
00246     {
00247       edge = g_array_index (edges, MetaEdge*, i);
00248       compare = horizontal ? edge->rect.x : edge->rect.y;
00249   
00250       edges_align = horizontal ? 
00251         meta_rectangle_vert_overlap (&edge->rect, new_rect) :
00252         meta_rectangle_horiz_overlap (&edge->rect, new_rect);
00253 
00254       if (edges_align &&
00255           (!only_forward ||
00256            !points_on_same_side (position, compare, old_position)))
00257         {
00258           int dist = ABS (compare - position);
00259           if (dist < best_dist)
00260             {
00261               best = compare;
00262               best_dist = dist;
00263             }
00264           break;
00265         }
00266     }
00267 
00268   /* Now start searching lower than mid */
00269   for (i = mid-1; i >= 0; i--)
00270     {
00271       edge = g_array_index (edges, MetaEdge*, i);
00272       compare = horizontal ? edge->rect.x : edge->rect.y;
00273   
00274       edges_align = horizontal ? 
00275         meta_rectangle_vert_overlap (&edge->rect, new_rect) :
00276         meta_rectangle_horiz_overlap (&edge->rect, new_rect);
00277 
00278       if (edges_align &&
00279           (!only_forward ||
00280            !points_on_same_side (position, compare, old_position)))
00281         {
00282           int dist = ABS (compare - position);
00283           if (dist < best_dist)
00284             {
00285               best = compare;
00286               best_dist = dist;
00287             }
00288           break;
00289         }
00290     }
00291 
00292   /* Return the best one found */
00293   return best;
00294 }
00295 
00296 static gboolean
00297 movement_towards_edge (MetaDirection side, int increment)
00298 {
00299   switch (side)
00300     {
00301     case META_DIRECTION_LEFT:
00302     case META_DIRECTION_TOP:
00303       return increment < 0;
00304     case META_DIRECTION_RIGHT:
00305     case META_DIRECTION_BOTTOM:
00306       return increment > 0;
00307     default:
00308       g_assert_not_reached ();
00309     }
00310 }
00311 
00312 static gboolean
00313 edge_resistance_timeout (gpointer data)
00314 {
00315   ResistanceDataForAnEdge *resistance_data = data;
00316 
00317   resistance_data->timeout_over = TRUE;
00318   resistance_data->timeout_id = 0;
00319   (*resistance_data->timeout_func)(resistance_data->window);
00320 
00321   return FALSE;
00322 }
00323 
00324 static int
00325 apply_edge_resistance (MetaWindow                *window,
00326                        int                        old_pos,
00327                        int                        new_pos,
00328                        const MetaRectangle       *old_rect,
00329                        const MetaRectangle       *new_rect,
00330                        GArray                    *edges,
00331                        ResistanceDataForAnEdge   *resistance_data,
00332                        GSourceFunc                timeout_func,
00333                        gboolean                   xdir,
00334                        gboolean                   keyboard_op)
00335 {
00336   int i, begin, end;
00337   int last_edge;
00338   gboolean increasing = new_pos > old_pos;
00339   int      increment = increasing ? 1 : -1;
00340 
00341   const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW    = 16;
00342   const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW   =  0;
00343   const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_XINERAMA  = 32;
00344   const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_XINERAMA =  0;
00345   const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN    = 32;
00346   const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN   =  0;
00347   const int TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW   =   0;
00348   const int TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA =   0;
00349   const int TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN   =   0;
00350 
00351   /* Quit if no movement was specified */
00352   if (old_pos == new_pos)
00353     return new_pos;
00354 
00355   /* Remove the old timeout if it's no longer relevant */
00356   if (resistance_data->timeout_setup &&
00357       ((resistance_data->timeout_edge_pos > old_pos &&
00358         resistance_data->timeout_edge_pos > new_pos)  ||
00359        (resistance_data->timeout_edge_pos < old_pos &&
00360         resistance_data->timeout_edge_pos < new_pos)))
00361     {
00362       resistance_data->timeout_setup = FALSE;
00363       if (resistance_data->timeout_id != 0)
00364         {
00365           g_source_remove (resistance_data->timeout_id);
00366           resistance_data->timeout_id = 0;
00367         }
00368     }
00369 
00370   /* Get the range of indices in the edge array that we move past/to. */
00371   begin = find_index_of_edge_near_position (edges, old_pos,  increasing, xdir);
00372   end   = find_index_of_edge_near_position (edges, new_pos, !increasing, xdir);
00373 
00374   /* begin and end can be outside the array index, if the window is partially
00375    * off the screen
00376    */
00377   last_edge = edges->len - 1;
00378   begin = CLAMP (begin, 0, last_edge);
00379   end   = CLAMP (end,   0, last_edge);
00380 
00381   /* Loop over all these edges we're moving past/to. */
00382   i = begin;
00383   while ((increasing  && i <= end) ||
00384          (!increasing && i >= end))
00385     {
00386       gboolean  edges_align;
00387       MetaEdge *edge = g_array_index (edges, MetaEdge*, i);
00388       int       compare = xdir ? edge->rect.x : edge->rect.y;
00389 
00390       /* Find out if this edge is relevant */
00391       edges_align = meta_rectangle_edge_aligns (new_rect, edge)  ||
00392                     meta_rectangle_edge_aligns (old_rect, edge);
00393 
00394       /* Nothing to do unless the edges align */
00395       if (!edges_align)
00396         {
00397           /* Go to the next edge in the range */
00398           i += increment;
00399           continue;
00400         }
00401 
00402       /* Rest is easier to read if we split on keyboard vs. mouse op */
00403       if (keyboard_op)
00404         {
00405           if ((old_pos < compare && compare < new_pos) ||
00406               (old_pos > compare && compare > new_pos))
00407             return compare;
00408         }
00409       else /* mouse op */
00410         {
00411           int threshold;
00412 
00413           /* TIMEOUT RESISTANCE: If the edge is relevant and we're moving
00414            * towards it, then we may want to have some kind of time delay
00415            * before the user can move past this edge.
00416            */
00417           if (movement_towards_edge (edge->side_type, increment))
00418             {
00419               /* First, determine the length of time for the resistance */
00420               int timeout_length_ms = 0;
00421               switch (edge->edge_type)
00422                 {
00423                 case META_EDGE_WINDOW:
00424                   timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW;
00425                   break;
00426                 case META_EDGE_XINERAMA:
00427                   timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA;
00428                   break;
00429                 case META_EDGE_SCREEN:
00430                   timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN;
00431                   break;
00432                 }
00433 
00434               if (!resistance_data->timeout_setup &&
00435                   timeout_length_ms != 0)
00436                 {
00437                   resistance_data->timeout_id = 
00438                     g_timeout_add (timeout_length_ms,
00439                                    edge_resistance_timeout,
00440                                    resistance_data);
00441                   resistance_data->timeout_setup = TRUE;
00442                   resistance_data->timeout_edge_pos = compare;
00443                   resistance_data->timeout_over = FALSE;
00444                   resistance_data->timeout_func = timeout_func;
00445                   resistance_data->window = window;
00446                 }
00447               if (!resistance_data->timeout_over &&
00448                   timeout_length_ms != 0)
00449                 return compare;
00450             }
00451 
00452           /* PIXEL DISTANCE MOUSE RESISTANCE: If the edge matters and the
00453            * user hasn't moved at least threshold pixels past this edge,
00454            * stop movement at this edge.  (Note that this is different from
00455            * keyboard resistance precisely because keyboard move ops are
00456            * relative to previous positions, whereas mouse move ops are
00457            * relative to differences in mouse position and mouse position
00458            * is an absolute quantity rather than a relative quantity)
00459            */
00460 
00461           /* First, determine the threshold */
00462           threshold = 0;
00463           switch (edge->edge_type)
00464             {
00465             case META_EDGE_WINDOW:
00466               if (movement_towards_edge (edge->side_type, increment))
00467                 threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW;
00468               else
00469                 threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW;
00470               break;
00471             case META_EDGE_XINERAMA:
00472               if (movement_towards_edge (edge->side_type, increment))
00473                 threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_XINERAMA;
00474               else
00475                 threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_XINERAMA;
00476               break;
00477             case META_EDGE_SCREEN:
00478               if (movement_towards_edge (edge->side_type, increment))
00479                 threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN;
00480               else
00481                 threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN;
00482               break;
00483             }
00484 
00485           if (ABS (compare - new_pos) < threshold)
00486             return compare;
00487         }
00488 
00489       /* Go to the next edge in the range */
00490       i += increment;
00491     }
00492 
00493   return new_pos;
00494 }
00495 
00496 static int
00497 apply_edge_snapping (int                  old_pos,
00498                      int                  new_pos,
00499                      const MetaRectangle *new_rect,
00500                      GArray              *edges,
00501                      gboolean             xdir,
00502                      gboolean             keyboard_op)
00503 {
00504   int snap_to;
00505 
00506   if (old_pos == new_pos)
00507     return new_pos;
00508 
00509   snap_to = find_nearest_position (edges,
00510                                    new_pos,
00511                                    old_pos,
00512                                    new_rect,
00513                                    xdir,
00514                                    keyboard_op);
00515 
00516   /* If mouse snap-moving, the user could easily accidentally move just a
00517    * couple pixels in a direction they didn't mean to move; so ignore snap
00518    * movement in those cases unless it's only a small number of pixels
00519    * anyway.
00520    */
00521   if (!keyboard_op &&
00522       ABS (snap_to - old_pos) >= 8 &&
00523       ABS (new_pos - old_pos) < 8)
00524     return old_pos;
00525   else
00526     /* Otherwise, return the snapping position found */
00527     return snap_to;
00528 }
00529 
00530 /* This function takes the position (including any frame) of the window and
00531  * a proposed new position (ignoring edge resistance/snapping), and then
00532  * applies edge resistance to EACH edge (separately) updating new_outer.
00533  * It returns true if new_outer is modified, false otherwise.
00534  *
00535  * display->grab_edge_resistance_data MUST already be setup or calling this
00536  * function will cause a crash.
00537  */
00538 static gboolean 
00539 apply_edge_resistance_to_each_side (MetaDisplay         *display,
00540                                     MetaWindow          *window,
00541                                     const MetaRectangle *old_outer,
00542                                     MetaRectangle       *new_outer,
00543                                     GSourceFunc          timeout_func,
00544                                     gboolean             auto_snap,
00545                                     gboolean             keyboard_op,
00546                                     gboolean             is_resize)
00547 {
00548   MetaEdgeResistanceData *edge_data;
00549   MetaRectangle           modified_rect;
00550   gboolean                modified;
00551   int new_left, new_right, new_top, new_bottom;
00552 
00553   g_assert (display->grab_edge_resistance_data != NULL);
00554   edge_data = display->grab_edge_resistance_data;
00555 
00556   if (auto_snap)
00557     {
00558       /* Do the auto snapping instead of normal edge resistance; in all
00559        * cases, we allow snapping to opposite kinds of edges (e.g. left
00560        * sides of windows to both left and right edges.
00561        */
00562 
00563       new_left   = apply_edge_snapping (BOX_LEFT (*old_outer),
00564                                         BOX_LEFT (*new_outer),
00565                                         new_outer,
00566                                         edge_data->left_edges,
00567                                         TRUE,
00568                                         keyboard_op);
00569 
00570       new_right  = apply_edge_snapping (BOX_RIGHT (*old_outer),
00571                                         BOX_RIGHT (*new_outer),
00572                                         new_outer,
00573                                         edge_data->right_edges,
00574                                         TRUE,
00575                                         keyboard_op);
00576 
00577       new_top    = apply_edge_snapping (BOX_TOP (*old_outer),
00578                                         BOX_TOP (*new_outer),
00579                                         new_outer,
00580                                         edge_data->top_edges,
00581                                         FALSE,
00582                                         keyboard_op);
00583 
00584       new_bottom = apply_edge_snapping (BOX_BOTTOM (*old_outer),
00585                                         BOX_BOTTOM (*new_outer),
00586                                         new_outer,
00587                                         edge_data->bottom_edges,
00588                                         FALSE,
00589                                         keyboard_op);
00590     }
00591   else
00592     {
00593       /* Disable edge resistance for resizes when windows have size
00594        * increment hints; see #346782.  For all other cases, apply
00595        * them.
00596        */
00597       if (!is_resize || window->size_hints.width_inc == 1)
00598         {
00599           /* Now, apply the normal horizontal edge resistance */
00600           new_left   = apply_edge_resistance (window,
00601                                               BOX_LEFT (*old_outer),
00602                                               BOX_LEFT (*new_outer),
00603                                               old_outer,
00604                                               new_outer,
00605                                               edge_data->left_edges,
00606                                               &edge_data->left_data,
00607                                               timeout_func,
00608                                               TRUE,
00609                                               keyboard_op);
00610           new_right  = apply_edge_resistance (window,
00611                                               BOX_RIGHT (*old_outer),
00612                                               BOX_RIGHT (*new_outer),
00613                                               old_outer,
00614                                               new_outer,
00615                                               edge_data->right_edges,
00616                                               &edge_data->right_data,
00617                                               timeout_func,
00618                                               TRUE,
00619                                               keyboard_op);
00620         }
00621       else
00622         {
00623           new_left  = new_outer->x;
00624           new_right = new_outer->x + new_outer->width;
00625         }
00626       /* Same for vertical resizes... */
00627       if (!is_resize || window->size_hints.height_inc == 1)
00628         {
00629           new_top    = apply_edge_resistance (window,
00630                                               BOX_TOP (*old_outer),
00631                                               BOX_TOP (*new_outer),
00632                                               old_outer,
00633                                               new_outer,
00634                                               edge_data->top_edges,
00635                                               &edge_data->top_data,
00636                                               timeout_func,
00637                                               FALSE,
00638                                               keyboard_op);
00639           new_bottom = apply_edge_resistance (window,
00640                                               BOX_BOTTOM (*old_outer),
00641                                               BOX_BOTTOM (*new_outer),
00642                                               old_outer,
00643                                               new_outer,
00644                                               edge_data->bottom_edges,
00645                                               &edge_data->bottom_data,
00646                                               timeout_func,
00647                                               FALSE,
00648                                               keyboard_op);
00649         }
00650       else
00651         {
00652           new_top    = new_outer->y;
00653           new_bottom = new_outer->y + new_outer->height;
00654         }
00655     }
00656 
00657   /* Determine whether anything changed, and save the changes */
00658   modified_rect = meta_rect (new_left, 
00659                              new_top,
00660                              new_right - new_left,
00661                              new_bottom - new_top);
00662   modified = !meta_rectangle_equal (new_outer, &modified_rect);
00663   *new_outer = modified_rect;
00664   return modified;
00665 }
00666 
00667 void
00668 meta_display_cleanup_edges (MetaDisplay *display)
00669 {
00670   guint i,j;
00671   MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data;
00672   GHashTable *edges_to_be_freed;
00673 
00674   g_assert (edge_data != NULL);
00675 
00676   /* We first need to clean out any window edges */
00677   edges_to_be_freed = g_hash_table_new_full (g_direct_hash, g_direct_equal,
00678                                              g_free, NULL);
00679   for (i = 0; i < 4; i++)
00680     {
00681       GArray *tmp = NULL;
00682       MetaDirection dir;
00683       switch (i)
00684         {
00685         case 0:
00686           tmp = edge_data->left_edges;
00687           dir = META_DIRECTION_LEFT;
00688           break;
00689         case 1:
00690           tmp = edge_data->right_edges;
00691           dir = META_DIRECTION_RIGHT;
00692           break;
00693         case 2:
00694           tmp = edge_data->top_edges;
00695           dir = META_DIRECTION_TOP;
00696           break;
00697         case 3:
00698           tmp = edge_data->bottom_edges;
00699           dir = META_DIRECTION_BOTTOM;
00700           break;
00701         default:
00702           g_assert_not_reached ();
00703         }
00704 
00705       for (j = 0; j < tmp->len; j++)
00706         {
00707           MetaEdge *edge = g_array_index (tmp, MetaEdge*, j);
00708           if (edge->edge_type == META_EDGE_WINDOW &&
00709               edge->side_type == dir)
00710             {
00711               /* The same edge will appear in two arrays, and we can't free
00712                * it yet we still need to compare edge->side_type for the other
00713                * array that it is in.  So store it in a hash table for later
00714                * freeing.  Could also do this in a simple linked list.
00715                */
00716               g_hash_table_insert (edges_to_be_freed, edge, edge);
00717             }
00718         }
00719     }
00720 
00721   /* Now free all the window edges (the key destroy function is g_free) */
00722   g_hash_table_destroy (edges_to_be_freed);
00723 
00724   /* Now free the arrays and data */
00725   g_array_free (edge_data->left_edges, TRUE);
00726   g_array_free (edge_data->right_edges, TRUE);
00727   g_array_free (edge_data->top_edges, TRUE);
00728   g_array_free (edge_data->bottom_edges, TRUE);
00729   edge_data->left_edges = NULL;
00730   edge_data->right_edges = NULL;
00731   edge_data->top_edges = NULL;
00732   edge_data->bottom_edges = NULL;
00733 
00734   /* Cleanup the timeouts */
00735   if (edge_data->left_data.timeout_setup   &&
00736       edge_data->left_data.timeout_id   != 0)
00737     g_source_remove (edge_data->left_data.timeout_id);
00738   if (edge_data->right_data.timeout_setup  &&
00739       edge_data->right_data.timeout_id  != 0)
00740     g_source_remove (edge_data->right_data.timeout_id);
00741   if (edge_data->top_data.timeout_setup    &&
00742       edge_data->top_data.timeout_id    != 0)
00743     g_source_remove (edge_data->top_data.timeout_id);
00744   if (edge_data->bottom_data.timeout_setup &&
00745       edge_data->bottom_data.timeout_id != 0)
00746     g_source_remove (edge_data->bottom_data.timeout_id);
00747 
00748   g_free (display->grab_edge_resistance_data);
00749   display->grab_edge_resistance_data = NULL;
00750 }
00751 
00752 static int
00753 stupid_sort_requiring_extra_pointer_dereference (gconstpointer a, 
00754                                                  gconstpointer b)
00755 {
00756   const MetaEdge * const *a_edge = a;
00757   const MetaEdge * const *b_edge = b;
00758   return meta_rectangle_edge_cmp_ignore_type (*a_edge, *b_edge);
00759 }
00760 
00761 static void
00762 cache_edges (MetaDisplay *display,
00763              GList *window_edges,
00764              GList *xinerama_edges,
00765              GList *screen_edges)
00766 {
00767   MetaEdgeResistanceData *edge_data;
00768   GList *tmp;
00769   int num_left, num_right, num_top, num_bottom;
00770   int i;
00771 
00772   /*
00773    * 0th: Print debugging information to the log about the edges
00774    */
00775 #ifdef WITH_VERBOSE_MODE
00776   if (meta_is_verbose())
00777     {
00778       int max_edges = MAX (MAX( g_list_length (window_edges), 
00779                                 g_list_length (xinerama_edges)),
00780                            g_list_length (screen_edges));
00781       char big_buffer[(EDGE_LENGTH+2)*max_edges];
00782 
00783       meta_rectangle_edge_list_to_string (window_edges, ", ", big_buffer);
00784       meta_topic (META_DEBUG_EDGE_RESISTANCE,
00785                   "Window edges for resistance  : %s\n", big_buffer);
00786 
00787       meta_rectangle_edge_list_to_string (xinerama_edges, ", ", big_buffer);
00788       meta_topic (META_DEBUG_EDGE_RESISTANCE,
00789                   "Xinerama edges for resistance: %s\n", big_buffer);
00790 
00791       meta_rectangle_edge_list_to_string (screen_edges, ", ", big_buffer);
00792       meta_topic (META_DEBUG_EDGE_RESISTANCE,
00793                   "Screen edges for resistance  : %s\n", big_buffer);
00794     }
00795 #endif
00796 
00797   /*
00798    * 1st: Get the total number of each kind of edge
00799    */
00800   num_left = num_right = num_top = num_bottom = 0;
00801   for (i = 0; i < 3; i++)
00802     {
00803       tmp = NULL;
00804       switch (i)
00805         {
00806         case 0:
00807           tmp = window_edges;
00808           break;
00809         case 1:
00810           tmp = xinerama_edges;
00811           break;
00812         case 2:
00813           tmp = screen_edges;
00814           break;
00815         default:
00816           g_assert_not_reached ();
00817         }
00818 
00819       while (tmp)
00820         {
00821           MetaEdge *edge = tmp->data;
00822           switch (edge->side_type)
00823             {
00824             case META_DIRECTION_LEFT:
00825               num_left++;
00826               break;
00827             case META_DIRECTION_RIGHT:
00828               num_right++;
00829               break;
00830             case META_DIRECTION_TOP:
00831               num_top++;
00832               break;
00833             case META_DIRECTION_BOTTOM:
00834               num_bottom++;
00835               break;
00836             default:
00837               g_assert_not_reached ();
00838             }
00839           tmp = tmp->next;
00840         }
00841     }
00842 
00843   /*
00844    * 2nd: Allocate the edges
00845    */
00846   g_assert (display->grab_edge_resistance_data == NULL);
00847   display->grab_edge_resistance_data = g_new (MetaEdgeResistanceData, 1);
00848   edge_data = display->grab_edge_resistance_data;
00849   edge_data->left_edges   = g_array_sized_new (FALSE,
00850                                                FALSE,
00851                                                sizeof(MetaEdge*),
00852                                                num_left + num_right);
00853   edge_data->right_edges  = g_array_sized_new (FALSE,
00854                                                FALSE,
00855                                                sizeof(MetaEdge*),
00856                                                num_left + num_right);
00857   edge_data->top_edges    = g_array_sized_new (FALSE,
00858                                                FALSE,
00859                                                sizeof(MetaEdge*),
00860                                                num_top + num_bottom);
00861   edge_data->bottom_edges = g_array_sized_new (FALSE,
00862                                                FALSE,
00863                                                sizeof(MetaEdge*),
00864                                                num_top + num_bottom);
00865 
00866   /*
00867    * 3rd: Add the edges to the arrays
00868    */
00869   for (i = 0; i < 3; i++)
00870     {
00871       tmp = NULL;
00872       switch (i)
00873         {
00874         case 0:
00875           tmp = window_edges;
00876           break;
00877         case 1:
00878           tmp = xinerama_edges;
00879           break;
00880         case 2:
00881           tmp = screen_edges;
00882           break;
00883         default:
00884           g_assert_not_reached ();
00885         }
00886 
00887       while (tmp)
00888         {
00889           MetaEdge *edge = tmp->data;
00890           switch (edge->side_type)
00891             {
00892             case META_DIRECTION_LEFT:
00893             case META_DIRECTION_RIGHT:
00894               g_array_append_val (edge_data->left_edges, edge);
00895               g_array_append_val (edge_data->right_edges, edge);
00896               break;
00897             case META_DIRECTION_TOP:
00898             case META_DIRECTION_BOTTOM:
00899               g_array_append_val (edge_data->top_edges, edge);
00900               g_array_append_val (edge_data->bottom_edges, edge);
00901               break;
00902             default:
00903               g_assert_not_reached ();
00904             }
00905           tmp = tmp->next;
00906         }
00907     }
00908 
00909   /*
00910    * 4th: Sort the arrays (FIXME: This is kinda dumb since the arrays were
00911    * individually sorted earlier and we could have done this faster and
00912    * avoided this sort by sticking them into the array with some simple
00913    * merging of the lists).
00914    */
00915   g_array_sort (display->grab_edge_resistance_data->left_edges, 
00916                 stupid_sort_requiring_extra_pointer_dereference);
00917   g_array_sort (display->grab_edge_resistance_data->right_edges, 
00918                 stupid_sort_requiring_extra_pointer_dereference);
00919   g_array_sort (display->grab_edge_resistance_data->top_edges, 
00920                 stupid_sort_requiring_extra_pointer_dereference);
00921   g_array_sort (display->grab_edge_resistance_data->bottom_edges, 
00922                 stupid_sort_requiring_extra_pointer_dereference);
00923 }
00924 
00925 static void
00926 initialize_grab_edge_resistance_data (MetaDisplay *display)
00927 {
00928   MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data;
00929 
00930   edge_data->left_data.timeout_setup   = FALSE;
00931   edge_data->right_data.timeout_setup  = FALSE;
00932   edge_data->top_data.timeout_setup    = FALSE;
00933   edge_data->bottom_data.timeout_setup = FALSE;
00934 
00935   edge_data->left_data.keyboard_buildup   = 0;
00936   edge_data->right_data.keyboard_buildup  = 0;
00937   edge_data->top_data.keyboard_buildup    = 0;
00938   edge_data->bottom_data.keyboard_buildup = 0;
00939 }
00940 
00941 void
00942 meta_display_compute_resistance_and_snapping_edges (MetaDisplay *display)
00943 {
00944   GList *stacked_windows;
00945   GList *cur_window_iter;
00946   GList *edges;
00947   /* Lists of window positions (rects) and their relative stacking positions */
00948   int stack_position;
00949   GSList *obscuring_windows, *window_stacking;
00950   /* The portions of the above lists that still remain at the stacking position
00951    * in the layer that we are working on
00952    */
00953   GSList *rem_windows, *rem_win_stacking;
00954 
00955   /*
00956    * 1st: Get the list of relevant windows, from bottom to top
00957    */
00958   stacked_windows = 
00959     meta_stack_list_windows (display->grab_screen->stack,
00960                              display->grab_screen->active_workspace);
00961 
00962   /*
00963    * 2nd: we need to separate that stacked list into a list of windows that
00964    * can obscure other edges.  To make sure we only have windows obscuring
00965    * those below it instead of going both ways, we also need to keep a
00966    * counter list.  Messy, I know.
00967    */
00968   obscuring_windows = window_stacking = NULL;
00969   cur_window_iter = stacked_windows;
00970   stack_position = 0;
00971   while (cur_window_iter != NULL)
00972     {
00973       MetaWindow *cur_window = cur_window_iter->data;
00974       if (WINDOW_EDGES_RELEVANT (cur_window, display))
00975         {
00976           MetaRectangle *new_rect;
00977           new_rect = g_new (MetaRectangle, 1);
00978           meta_window_get_outer_rect (cur_window, new_rect);
00979           obscuring_windows = g_slist_prepend (obscuring_windows, new_rect);
00980           window_stacking = 
00981             g_slist_prepend (window_stacking, GINT_TO_POINTER (stack_position));
00982         }
00983 
00984       stack_position++;
00985       cur_window_iter = cur_window_iter->next;
00986     }
00987   /* Put 'em in bottom to top order */
00988   rem_windows       = g_slist_reverse (obscuring_windows);
00989   rem_win_stacking  = g_slist_reverse (window_stacking);
00990 
00991   /*
00992    * 3rd: loop over the windows again, this time getting the edges from
00993    * them and removing intersections with the relevant obscuring_windows &
00994    * obscuring_docks.
00995    */
00996   edges = NULL;
00997   stack_position = 0;
00998   cur_window_iter = stacked_windows;
00999   while (cur_window_iter != NULL)
01000     {
01001       MetaRectangle  cur_rect;
01002       MetaWindow    *cur_window = cur_window_iter->data;
01003       meta_window_get_outer_rect (cur_window, &cur_rect);
01004 
01005       /* Check if we want to use this window's edges for edge
01006        * resistance (note that dock edges are considered screen edges
01007        * which are handled separately
01008        */
01009       if (WINDOW_EDGES_RELEVANT (cur_window, display) &&
01010           cur_window->type != META_WINDOW_DOCK)
01011         {
01012           GList *new_edges;
01013           MetaEdge *new_edge;
01014           MetaRectangle reduced;
01015 
01016           /* We don't care about snapping to any portion of the window that
01017            * is offscreen (we also don't care about parts of edges covered
01018            * by other windows or DOCKS, but that's handled below).
01019            */
01020           meta_rectangle_intersect (&cur_rect, 
01021                                     &display->grab_screen->rect,
01022                                     &reduced);
01023 
01024           new_edges = NULL;
01025 
01026           /* Left side of this window is resistance for the right edge of
01027            * the window being moved.
01028            */
01029           new_edge = g_new (MetaEdge, 1);
01030           new_edge->rect = reduced;
01031           new_edge->rect.width = 0;
01032           new_edge->side_type = META_DIRECTION_RIGHT;
01033           new_edge->edge_type = META_EDGE_WINDOW;
01034           new_edges = g_list_prepend (new_edges, new_edge);
01035 
01036           /* Right side of this window is resistance for the left edge of
01037            * the window being moved.
01038            */
01039           new_edge = g_new (MetaEdge, 1);
01040           new_edge->rect = reduced;
01041           new_edge->rect.x += new_edge->rect.width;
01042           new_edge->rect.width = 0;
01043           new_edge->side_type = META_DIRECTION_LEFT;
01044           new_edge->edge_type = META_EDGE_WINDOW;
01045           new_edges = g_list_prepend (new_edges, new_edge);
01046           
01047           /* Top side of this window is resistance for the bottom edge of
01048            * the window being moved.
01049            */
01050           new_edge = g_new (MetaEdge, 1);
01051           new_edge->rect = reduced;
01052           new_edge->rect.height = 0;
01053           new_edge->side_type = META_DIRECTION_BOTTOM;
01054           new_edge->edge_type = META_EDGE_WINDOW;
01055           new_edges = g_list_prepend (new_edges, new_edge);
01056 
01057           /* Top side of this window is resistance for the bottom edge of
01058            * the window being moved.
01059            */
01060           new_edge = g_new (MetaEdge, 1);
01061           new_edge->rect = reduced;
01062           new_edge->rect.y += new_edge->rect.height;
01063           new_edge->rect.height = 0;
01064           new_edge->side_type = META_DIRECTION_TOP;
01065           new_edge->edge_type = META_EDGE_WINDOW;
01066           new_edges = g_list_prepend (new_edges, new_edge);
01067 
01068           /* Update the remaining windows to only those at a higher
01069            * stacking position than this one.
01070            */
01071           while (rem_win_stacking && 
01072                  stack_position >= GPOINTER_TO_INT (rem_win_stacking->data))
01073             {
01074               rem_windows      = rem_windows->next;
01075               rem_win_stacking = rem_win_stacking->next;
01076             }
01077 
01078           /* Remove edge portions overlapped by rem_windows and rem_docks */
01079           new_edges = 
01080             meta_rectangle_remove_intersections_with_boxes_from_edges (
01081               new_edges,
01082               rem_windows);
01083 
01084           /* Save the new edges */
01085           edges = g_list_concat (new_edges, edges);
01086         }
01087 
01088       stack_position++;
01089       cur_window_iter = cur_window_iter->next;
01090     }
01091 
01092   /*
01093    * 4th: Free the extra memory not needed and sort the list
01094    */
01095   g_list_free (stacked_windows);
01096   /* Free the memory used by the obscuring windows/docks lists */
01097   g_slist_free (window_stacking);
01098   /* FIXME: Shouldn't there be a helper function to make this one line of code
01099    * to free a list instead of four ugly ones?
01100    */
01101   g_slist_foreach (obscuring_windows, 
01102                    (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */
01103                    NULL);
01104   g_slist_free (obscuring_windows);
01105 
01106   /* Sort the list.  FIXME: Should I bother with this sorting?  I just
01107    * sort again later in cache_edges() anyway...
01108    */
01109   edges = g_list_sort (edges, meta_rectangle_edge_cmp);
01110 
01111   /*
01112    * 5th: Cache the combination of these edges with the onscreen and
01113    * xinerama edges in an array for quick access.  Free the edges since
01114    * they've been cached elsewhere.
01115    */
01116   cache_edges (display,
01117                edges,
01118                display->grab_screen->active_workspace->xinerama_edges,
01119                display->grab_screen->active_workspace->screen_edges);
01120   g_list_free (edges);
01121 
01122   /*
01123    * 6th: Initialize the resistance timeouts and buildups
01124    */
01125   initialize_grab_edge_resistance_data (display);
01126 }
01127 
01128 /* Note that old_[xy] and new_[xy] are with respect to inner positions of
01129  * the window.
01130  */
01131 void
01132 meta_window_edge_resistance_for_move (MetaWindow  *window,
01133                                       int          old_x,
01134                                       int          old_y,
01135                                       int         *new_x,
01136                                       int         *new_y,
01137                                       GSourceFunc  timeout_func,
01138                                       gboolean     snap,
01139                                       gboolean     is_keyboard_op)
01140 {
01141   MetaRectangle old_outer, proposed_outer, new_outer;
01142   gboolean is_resize;
01143 
01144   if (window == window->display->grab_window &&
01145       window->display->grab_wireframe_active)
01146     {
01147       meta_window_get_xor_rect (window,
01148                                 &window->display->grab_wireframe_rect,
01149                                 &old_outer);
01150     }
01151   else
01152     {
01153       meta_window_get_outer_rect (window, &old_outer);
01154     }
01155   proposed_outer = old_outer;
01156   proposed_outer.x += (*new_x - old_x);
01157   proposed_outer.y += (*new_y - old_y);
01158   new_outer = proposed_outer;
01159 
01160   window->display->grab_last_user_action_was_snap = snap;
01161   is_resize = FALSE;
01162   if (apply_edge_resistance_to_each_side (window->display,
01163                                           window,
01164                                           &old_outer,
01165                                           &new_outer,
01166                                           timeout_func,
01167                                           snap,
01168                                           is_keyboard_op,
01169                                           is_resize))
01170     {
01171       /* apply_edge_resistance_to_each_side independently applies
01172        * resistance to both the right and left edges of new_outer as both
01173        * could meet areas of resistance.  But we don't want a resize, so we
01174        * just have both edges move according to the stricter of the
01175        * resistances.  Same thing goes for top & bottom edges.
01176        */
01177       MetaRectangle *reference;
01178       int left_change, right_change, smaller_x_change;
01179       int top_change, bottom_change, smaller_y_change;
01180 
01181       if (snap && !is_keyboard_op)
01182         reference = &proposed_outer;
01183       else
01184         reference = &old_outer;
01185 
01186       left_change  = BOX_LEFT (new_outer)  - BOX_LEFT (*reference);
01187       right_change = BOX_RIGHT (new_outer) - BOX_RIGHT (*reference);
01188       if (     snap && is_keyboard_op && left_change == 0)
01189         smaller_x_change = right_change;
01190       else if (snap && is_keyboard_op && right_change == 0)
01191         smaller_x_change = left_change;
01192       else if (ABS (left_change) < ABS (right_change))
01193         smaller_x_change = left_change;
01194       else
01195         smaller_x_change = right_change;
01196 
01197       top_change    = BOX_TOP (new_outer)    - BOX_TOP (*reference);
01198       bottom_change = BOX_BOTTOM (new_outer) - BOX_BOTTOM (*reference);
01199       if (     snap && is_keyboard_op && top_change == 0)
01200         smaller_y_change = bottom_change;
01201       else if (snap && is_keyboard_op && bottom_change == 0)
01202         smaller_y_change = top_change;
01203       else if (ABS (top_change) < ABS (bottom_change))
01204         smaller_y_change = top_change;
01205       else
01206         smaller_y_change = bottom_change;
01207 
01208       *new_x = old_x + smaller_x_change + 
01209               (BOX_LEFT (*reference) - BOX_LEFT (old_outer));
01210       *new_y = old_y + smaller_y_change +
01211               (BOX_TOP (*reference) - BOX_TOP (old_outer));
01212 
01213       meta_topic (META_DEBUG_EDGE_RESISTANCE,
01214                   "outer x & y move-to coordinate changed from %d,%d to %d,%d\n",
01215                   proposed_outer.x, proposed_outer.y,
01216                   old_outer.x + (*new_x - old_x),
01217                   old_outer.y + (*new_y - old_y));
01218     }
01219 }
01220 
01221 /* Note that old_(width|height) and new_(width|height) are with respect to
01222  * sizes of the inner window.
01223  */
01224 void
01225 meta_window_edge_resistance_for_resize (MetaWindow  *window,
01226                                         int          old_width,
01227                                         int          old_height,
01228                                         int         *new_width,
01229                                         int         *new_height,
01230                                         int          gravity,
01231                                         GSourceFunc  timeout_func,
01232                                         gboolean     snap,
01233                                         gboolean     is_keyboard_op)
01234 {
01235   MetaRectangle old_outer, new_outer;
01236   int proposed_outer_width, proposed_outer_height;
01237   gboolean is_resize;
01238 
01239   if (window == window->display->grab_window &&
01240       window->display->grab_wireframe_active)
01241     {
01242       meta_window_get_xor_rect (window,
01243                                 &window->display->grab_wireframe_rect,
01244                                 &old_outer);
01245     }
01246   else
01247     {
01248       meta_window_get_outer_rect (window, &old_outer);
01249     }
01250   proposed_outer_width  = old_outer.width  + (*new_width  - old_width);
01251   proposed_outer_height = old_outer.height + (*new_height - old_height);
01252   meta_rectangle_resize_with_gravity (&old_outer, 
01253                                       &new_outer,
01254                                       gravity,
01255                                       proposed_outer_width,
01256                                       proposed_outer_height);
01257 
01258   window->display->grab_last_user_action_was_snap = snap;
01259   is_resize = TRUE;
01260   if (apply_edge_resistance_to_each_side (window->display,
01261                                           window,
01262                                           &old_outer,
01263                                           &new_outer,
01264                                           timeout_func,
01265                                           snap,
01266                                           is_keyboard_op,
01267                                           is_resize))
01268     {
01269       *new_width  = old_width  + (new_outer.width  - old_outer.width);
01270       *new_height = old_height + (new_outer.height - old_outer.height);
01271 
01272       meta_topic (META_DEBUG_EDGE_RESISTANCE,
01273                   "outer width & height got changed from %d,%d to %d,%d\n",
01274                   proposed_outer_width, proposed_outer_height,
01275                   new_outer.width, new_outer.height);
01276     }
01277 }

Generated on Sat Aug 23 22:04:16 2008 for metacity by  doxygen 1.5.5