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); + } +} |