1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2
3 /* Edge resistance for move/resize operations */
4
5 /*
6 * Copyright (C) 2005, 2006 Elijah Newren
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA
21 * 02110-1335, USA.
22 */
23
24 #include <config.h>
25 #include "edge-resistance.h"
26 #include "boxes-private.h"
27 #include "display-private.h"
28 #include "workspace-private.h"
29
30 /* A simple macro for whether a given window's edges are potentially
31 * relevant for resistance/snapping during a move/resize operation
32 */
33 #define WINDOW_EDGES_RELEVANT(window, display) \
34 meta_window_should_be_showing (window) && \
35 window->screen == display->grab_screen && \
36 window != display->grab_window && \
37 window->type != META_WINDOW_DESKTOP && \
38 window->type != META_WINDOW_MENU && \
39 window->type != META_WINDOW_SPLASHSCREEN
40
41 struct ResistanceDataForAnEdge
42 {
43 gboolean timeout_setup;
44 guint timeout_id;
45 int timeout_edge_pos;
46 gboolean timeout_over;
47 GSourceFunc timeout_func;
48 MetaWindow *window;
49 int keyboard_buildup;
50 };
51 typedef struct ResistanceDataForAnEdge ResistanceDataForAnEdge;
52
53 struct MetaEdgeResistanceData
54 {
55 GArray *left_edges;
56 GArray *right_edges;
57 GArray *top_edges;
58 GArray *bottom_edges;
59
60 ResistanceDataForAnEdge left_data;
61 ResistanceDataForAnEdge right_data;
62 ResistanceDataForAnEdge top_data;
63 ResistanceDataForAnEdge bottom_data;
64 };
65
66 static void compute_resistance_and_snapping_edges (MetaDisplay *display);
67
68 /* !WARNING!: this function can return invalid indices (namely, either -1 or
69 * edges->len); this is by design, but you need to remember this.
70 */
71 static int
find_index_of_edge_near_position(const GArray * edges,int position,gboolean want_interval_min,gboolean horizontal)72 find_index_of_edge_near_position (const GArray *edges,
73 int position,
74 gboolean want_interval_min,
75 gboolean horizontal)
76 {
77 /* This is basically like a binary search, except that we're trying to
78 * find a range instead of an exact value. So, if we have in our array
79 * Value: 3 27 316 316 316 505 522 800 1213
80 * Index: 0 1 2 3 4 5 6 7 8
81 * and we call this function with position=500 & want_interval_min=TRUE
82 * then we should get 5 (because 505 is the first value bigger than 500).
83 * If we call this function with position=805 and want_interval_min=FALSE
84 * then we should get 7 (because 800 is the last value smaller than 800).
85 * A couple more, to make things clear:
86 * position want_interval_min correct_answer
87 * 316 TRUE 2
88 * 316 FALSE 4
89 * 2 FALSE -1
90 * 2000 TRUE 9
91 */
92 int low, high, mid;
93 int compare;
94 MetaEdge *edge;
95
96 /* Initialize mid, edge, & compare in the off change that the array only
97 * has one element.
98 */
99 mid = 0;
100 edge = g_array_index (edges, MetaEdge*, mid);
101 compare = horizontal ? edge->rect.x : edge->rect.y;
102
103 /* Begin the search... */
104 low = 0;
105 high = edges->len - 1;
106 while (low < high)
107 {
108 mid = low + (high - low)/2;
109 edge = g_array_index (edges, MetaEdge*, mid);
110 compare = horizontal ? edge->rect.x : edge->rect.y;
111
112 if (compare == position)
113 break;
114
115 if (compare > position)
116 high = mid - 1;
117 else
118 low = mid + 1;
119 }
120
121 /* mid should now be _really_ close to the index we want, so we start
122 * linearly searching. However, note that we don't know if mid is less
123 * than or greater than what we need and it's possible that there are
124 * several equal values equal to what we were searching for and we ended
125 * up in the middle of them instead of at the end. So we may need to
126 * move mid multiple locations over.
127 */
128 if (want_interval_min)
129 {
130 while (compare >= position && mid > 0)
131 {
132 mid--;
133 edge = g_array_index (edges, MetaEdge*, mid);
134 compare = horizontal ? edge->rect.x : edge->rect.y;
135 }
136 while (compare < position && mid < (int)edges->len - 1)
137 {
138 mid++;
139 edge = g_array_index (edges, MetaEdge*, mid);
140 compare = horizontal ? edge->rect.x : edge->rect.y;
141 }
142
143 /* Special case for no values in array big enough */
144 if (compare < position)
145 return edges->len;
146
147 /* Return the found value */
148 return mid;
149 }
150 else
151 {
152 while (compare <= position && mid < (int)edges->len - 1)
153 {
154 mid++;
155 edge = g_array_index (edges, MetaEdge*, mid);
156 compare = horizontal ? edge->rect.x : edge->rect.y;
157 }
158 while (compare > position && mid > 0)
159 {
160 mid--;
161 edge = g_array_index (edges, MetaEdge*, mid);
162 compare = horizontal ? edge->rect.x : edge->rect.y;
163 }
164
165 /* Special case for no values in array small enough */
166 if (compare > position)
167 return -1;
168
169 /* Return the found value */
170 return mid;
171 }
172 }
173
174 static gboolean
points_on_same_side(int ref,int pt1,int pt2)175 points_on_same_side (int ref, int pt1, int pt2)
176 {
177 return (pt1 - ref) * (pt2 - ref) > 0;
178 }
179
180 static int
find_nearest_position(const GArray * edges,int position,int old_position,const MetaRectangle * new_rect,gboolean horizontal,gboolean only_forward)181 find_nearest_position (const GArray *edges,
182 int position,
183 int old_position,
184 const MetaRectangle *new_rect,
185 gboolean horizontal,
186 gboolean only_forward)
187 {
188 /* This is basically just a binary search except that we're looking
189 * for the value closest to position, rather than finding that
190 * actual value. Also, we ignore any edges that aren't relevant
191 * given the horizontal/vertical position of new_rect.
192 */
193 int low, high, mid;
194 int compare;
195 MetaEdge *edge;
196 int best, best_dist, i;
197 gboolean edges_align;
198
199 /* Initialize mid in the off chance that the array only
200 * has one element.
201 */
202 mid = 0;
203
204 /* Begin the search... */
205 low = 0;
206 high = edges->len - 1;
207 while (low < high)
208 {
209 mid = low + (high - low)/2;
210 edge = g_array_index (edges, MetaEdge*, mid);
211 compare = horizontal ? edge->rect.x : edge->rect.y;
212
213 if (compare == position)
214 break;
215
216 if (compare > position)
217 high = mid - 1;
218 else
219 low = mid + 1;
220 }
221
222 /* mid should now be _really_ close to the index we want, so we
223 * start searching nearby for something that overlaps and is closer
224 * than the original position.
225 */
226 best = old_position;
227 best_dist = INT_MAX;
228
229 /* Start the search at mid */
230 edge = g_array_index (edges, MetaEdge*, mid);
231 compare = horizontal ? edge->rect.x : edge->rect.y;
232 edges_align = meta_rectangle_edge_aligns (new_rect, edge);
233 if (edges_align &&
234 (!only_forward || !points_on_same_side (position, compare, old_position)))
235 {
236 int dist = ABS (compare - position);
237 if (dist < best_dist)
238 {
239 best = compare;
240 best_dist = dist;
241 }
242 }
243
244 /* Now start searching higher than mid */
245 for (i = mid + 1; i < (int)edges->len; i++)
246 {
247 edge = g_array_index (edges, MetaEdge*, i);
248 compare = horizontal ? edge->rect.x : edge->rect.y;
249
250 edges_align = horizontal ?
251 meta_rectangle_vert_overlap (&edge->rect, new_rect) :
252 meta_rectangle_horiz_overlap (&edge->rect, new_rect);
253
254 if (edges_align &&
255 (!only_forward ||
256 !points_on_same_side (position, compare, old_position)))
257 {
258 int dist = ABS (compare - position);
259 if (dist < best_dist)
260 {
261 best = compare;
262 best_dist = dist;
263 }
264 break;
265 }
266 }
267
268 /* Now start searching lower than mid */
269 for (i = mid-1; i >= 0; i--)
270 {
271 edge = g_array_index (edges, MetaEdge*, i);
272 compare = horizontal ? edge->rect.x : edge->rect.y;
273
274 edges_align = horizontal ?
275 meta_rectangle_vert_overlap (&edge->rect, new_rect) :
276 meta_rectangle_horiz_overlap (&edge->rect, new_rect);
277
278 if (edges_align &&
279 (!only_forward ||
280 !points_on_same_side (position, compare, old_position)))
281 {
282 int dist = ABS (compare - position);
283 if (dist < best_dist)
284 {
285 best = compare;
286 }
287 break;
288 }
289 }
290
291 /* Return the best one found */
292 return best;
293 }
294
295 static gboolean
movement_towards_edge(MetaSide side,int increment)296 movement_towards_edge (MetaSide side, int increment)
297 {
298 switch (side)
299 {
300 case META_SIDE_LEFT:
301 case META_SIDE_TOP:
302 return increment < 0;
303 case META_SIDE_RIGHT:
304 case META_SIDE_BOTTOM:
305 return increment > 0;
306 default:
307 g_assert_not_reached ();
308 }
309 }
310
311 static gboolean
edge_resistance_timeout(gpointer data)312 edge_resistance_timeout (gpointer data)
313 {
314 ResistanceDataForAnEdge *resistance_data = data;
315
316 resistance_data->timeout_over = TRUE;
317 resistance_data->timeout_id = 0;
318 (*resistance_data->timeout_func)(resistance_data->window);
319
320 return FALSE;
321 }
322
323 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)324 apply_edge_resistance (MetaWindow *window,
325 int old_pos,
326 int new_pos,
327 const MetaRectangle *old_rect,
328 const MetaRectangle *new_rect,
329 GArray *edges,
330 ResistanceDataForAnEdge *resistance_data,
331 GSourceFunc timeout_func,
332 gboolean xdir,
333 gboolean keyboard_op)
334 {
335 int i, begin, end;
336 int last_edge;
337 gboolean increasing = new_pos > old_pos;
338 int increment = increasing ? 1 : -1;
339
340 const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW = 16;
341 const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW = 0;
342 const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_MONITOR = 32;
343 const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_MONITOR = 0;
344 const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN = 32;
345 const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN = 0;
346 const int TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW = 0;
347 const int TIMEOUT_RESISTANCE_LENGTH_MS_MONITOR = 0;
348 const int TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN = 0;
349
350 /* Edge resistance can be disabled in gettings. */
351 if (!meta_prefs_get_edge_resistance_window ())
352 return new_pos;
353
354 /* Quit if no movement was specified */
355 if (old_pos == new_pos)
356 return new_pos;
357
358 /* Remove the old timeout if it's no longer relevant */
359 if (resistance_data->timeout_setup &&
360 ((resistance_data->timeout_edge_pos > old_pos &&
361 resistance_data->timeout_edge_pos > new_pos) ||
362 (resistance_data->timeout_edge_pos < old_pos &&
363 resistance_data->timeout_edge_pos < new_pos)))
364 {
365 resistance_data->timeout_setup = FALSE;
366 if (resistance_data->timeout_id != 0)
367 {
368 g_source_remove (resistance_data->timeout_id);
369 resistance_data->timeout_id = 0;
370 }
371 }
372
373 /* Get the range of indices in the edge array that we move past/to. */
374 begin = find_index_of_edge_near_position (edges, old_pos, increasing, xdir);
375 end = find_index_of_edge_near_position (edges, new_pos, !increasing, xdir);
376
377 /* begin and end can be outside the array index, if the window is partially
378 * off the screen
379 */
380 last_edge = edges->len - 1;
381 begin = CLAMP (begin, 0, last_edge);
382 end = CLAMP (end, 0, last_edge);
383
384 /* Loop over all these edges we're moving past/to. */
385 i = begin;
386 while ((increasing && i <= end) ||
387 (!increasing && i >= end))
388 {
389 gboolean edges_align;
390 MetaEdge *edge = g_array_index (edges, MetaEdge*, i);
391 int compare = xdir ? edge->rect.x : edge->rect.y;
392
393 /* Find out if this edge is relevant */
394 edges_align = meta_rectangle_edge_aligns (new_rect, edge) ||
395 meta_rectangle_edge_aligns (old_rect, edge);
396
397 /* Nothing to do unless the edges align */
398 if (!edges_align)
399 {
400 /* Go to the next edge in the range */
401 i += increment;
402 continue;
403 }
404
405 /* Rest is easier to read if we split on keyboard vs. mouse op */
406 if (keyboard_op)
407 {
408 if ((old_pos < compare && compare < new_pos) ||
409 (old_pos > compare && compare > new_pos))
410 return compare;
411 }
412 else /* mouse op */
413 {
414 int threshold;
415
416 /* TIMEOUT RESISTANCE: If the edge is relevant and we're moving
417 * towards it, then we may want to have some kind of time delay
418 * before the user can move past this edge.
419 */
420 if (movement_towards_edge (edge->side_type, increment))
421 {
422 /* First, determine the length of time for the resistance */
423 int timeout_length_ms = 0;
424 switch (edge->edge_type)
425 {
426 case META_EDGE_WINDOW:
427 timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW;
428 break;
429 case META_EDGE_MONITOR:
430 timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_MONITOR;
431 break;
432 case META_EDGE_SCREEN:
433 timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN;
434 break;
435 }
436
437 if (!resistance_data->timeout_setup &&
438 timeout_length_ms != 0)
439 {
440 resistance_data->timeout_id =
441 g_timeout_add (timeout_length_ms,
442 edge_resistance_timeout,
443 resistance_data);
444 resistance_data->timeout_setup = TRUE;
445 resistance_data->timeout_edge_pos = compare;
446 resistance_data->timeout_over = FALSE;
447 resistance_data->timeout_func = timeout_func;
448 resistance_data->window = window;
449 }
450 if (!resistance_data->timeout_over &&
451 timeout_length_ms != 0)
452 return compare;
453 }
454
455 /* PIXEL DISTANCE MOUSE RESISTANCE: If the edge matters and the
456 * user hasn't moved at least threshold pixels past this edge,
457 * stop movement at this edge. (Note that this is different from
458 * keyboard resistance precisely because keyboard move ops are
459 * relative to previous positions, whereas mouse move ops are
460 * relative to differences in mouse position and mouse position
461 * is an absolute quantity rather than a relative quantity)
462 */
463
464 /* First, determine the threshold */
465 threshold = 0;
466 switch (edge->edge_type)
467 {
468 case META_EDGE_WINDOW:
469 if (movement_towards_edge (edge->side_type, increment))
470 threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW;
471 else
472 threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW;
473 break;
474 case META_EDGE_MONITOR:
475 if (movement_towards_edge (edge->side_type, increment))
476 threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_MONITOR;
477 else
478 threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_MONITOR;
479 break;
480 case META_EDGE_SCREEN:
481 if (movement_towards_edge (edge->side_type, increment))
482 threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN;
483 else
484 threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN;
485 break;
486 }
487
488 if (ABS (compare - new_pos) < threshold)
489 return compare;
490 }
491
492 /* Go to the next edge in the range */
493 i += increment;
494 }
495
496 return new_pos;
497 }
498
499 static int
apply_edge_snapping(int old_pos,int new_pos,const MetaRectangle * new_rect,GArray * edges,gboolean xdir,gboolean keyboard_op)500 apply_edge_snapping (int old_pos,
501 int new_pos,
502 const MetaRectangle *new_rect,
503 GArray *edges,
504 gboolean xdir,
505 gboolean keyboard_op)
506 {
507 int snap_to;
508
509 if (old_pos == new_pos)
510 return new_pos;
511
512 snap_to = find_nearest_position (edges,
513 new_pos,
514 old_pos,
515 new_rect,
516 xdir,
517 keyboard_op);
518
519 /* If mouse snap-moving, the user could easily accidentally move just a
520 * couple pixels in a direction they didn't mean to move; so ignore snap
521 * movement in those cases unless it's only a small number of pixels
522 * anyway.
523 */
524 if (!keyboard_op &&
525 ABS (snap_to - old_pos) >= 8 &&
526 ABS (new_pos - old_pos) < 8)
527 return old_pos;
528 else
529 /* Otherwise, return the snapping position found */
530 return snap_to;
531 }
532
533 /* This function takes the position (including any frame) of the window and
534 * a proposed new position (ignoring edge resistance/snapping), and then
535 * applies edge resistance to EACH edge (separately) updating new_outer.
536 * It returns true if new_outer is modified, false otherwise.
537 *
538 * display->grab_edge_resistance_data MUST already be setup or calling this
539 * function will cause a crash.
540 */
541 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)542 apply_edge_resistance_to_each_side (MetaDisplay *display,
543 MetaWindow *window,
544 const MetaRectangle *old_outer,
545 MetaRectangle *new_outer,
546 GSourceFunc timeout_func,
547 gboolean auto_snap,
548 gboolean keyboard_op,
549 gboolean is_resize)
550 {
551 MetaEdgeResistanceData *edge_data;
552 MetaRectangle modified_rect;
553 gboolean modified;
554 int new_left, new_right, new_top, new_bottom;
555
556 if (display->grab_edge_resistance_data == NULL)
557 compute_resistance_and_snapping_edges (display);
558
559 edge_data = display->grab_edge_resistance_data;
560
561 if (auto_snap)
562 {
563 /* Do the auto snapping instead of normal edge resistance; in all
564 * cases, we allow snapping to opposite kinds of edges (e.g. left
565 * sides of windows to both left and right edges.
566 */
567
568 new_left = apply_edge_snapping (BOX_LEFT (*old_outer),
569 BOX_LEFT (*new_outer),
570 new_outer,
571 edge_data->left_edges,
572 TRUE,
573 keyboard_op);
574
575 new_right = apply_edge_snapping (BOX_RIGHT (*old_outer),
576 BOX_RIGHT (*new_outer),
577 new_outer,
578 edge_data->right_edges,
579 TRUE,
580 keyboard_op);
581
582 new_top = apply_edge_snapping (BOX_TOP (*old_outer),
583 BOX_TOP (*new_outer),
584 new_outer,
585 edge_data->top_edges,
586 FALSE,
587 keyboard_op);
588
589 new_bottom = apply_edge_snapping (BOX_BOTTOM (*old_outer),
590 BOX_BOTTOM (*new_outer),
591 new_outer,
592 edge_data->bottom_edges,
593 FALSE,
594 keyboard_op);
595 }
596 else
597 {
598 /* Disable edge resistance for resizes when windows have size
599 * increment hints; see #346782. For all other cases, apply
600 * them.
601 */
602 if (!is_resize || window->size_hints.width_inc == 1)
603 {
604 /* Now, apply the normal horizontal edge resistance */
605 new_left = apply_edge_resistance (window,
606 BOX_LEFT (*old_outer),
607 BOX_LEFT (*new_outer),
608 old_outer,
609 new_outer,
610 edge_data->left_edges,
611 &edge_data->left_data,
612 timeout_func,
613 TRUE,
614 keyboard_op);
615 new_right = apply_edge_resistance (window,
616 BOX_RIGHT (*old_outer),
617 BOX_RIGHT (*new_outer),
618 old_outer,
619 new_outer,
620 edge_data->right_edges,
621 &edge_data->right_data,
622 timeout_func,
623 TRUE,
624 keyboard_op);
625 }
626 else
627 {
628 new_left = new_outer->x;
629 new_right = new_outer->x + new_outer->width;
630 }
631 /* Same for vertical resizes... */
632 if (!is_resize || window->size_hints.height_inc == 1)
633 {
634 new_top = apply_edge_resistance (window,
635 BOX_TOP (*old_outer),
636 BOX_TOP (*new_outer),
637 old_outer,
638 new_outer,
639 edge_data->top_edges,
640 &edge_data->top_data,
641 timeout_func,
642 FALSE,
643 keyboard_op);
644 new_bottom = apply_edge_resistance (window,
645 BOX_BOTTOM (*old_outer),
646 BOX_BOTTOM (*new_outer),
647 old_outer,
648 new_outer,
649 edge_data->bottom_edges,
650 &edge_data->bottom_data,
651 timeout_func,
652 FALSE,
653 keyboard_op);
654 }
655 else
656 {
657 new_top = new_outer->y;
658 new_bottom = new_outer->y + new_outer->height;
659 }
660 }
661
662 /* Determine whether anything changed, and save the changes */
663 modified_rect = meta_rect (new_left,
664 new_top,
665 new_right - new_left,
666 new_bottom - new_top);
667 modified = !meta_rectangle_equal (new_outer, &modified_rect);
668 *new_outer = modified_rect;
669 return modified;
670 }
671
672 LOCAL_SYMBOL void
meta_display_cleanup_edges(MetaDisplay * display)673 meta_display_cleanup_edges (MetaDisplay *display)
674 {
675 guint i,j;
676 MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data;
677 GHashTable *edges_to_be_freed;
678
679 if (edge_data == NULL) /* Not currently cached */
680 return;
681
682 /* We first need to clean out any window edges */
683 edges_to_be_freed = g_hash_table_new_full (g_direct_hash, g_direct_equal,
684 free, NULL);
685 for (i = 0; i < 4; i++)
686 {
687 GArray *tmp = NULL;
688 MetaSide side;
689 switch (i)
690 {
691 case 0:
692 tmp = edge_data->left_edges;
693 side = META_SIDE_LEFT;
694 break;
695 case 1:
696 tmp = edge_data->right_edges;
697 side = META_SIDE_RIGHT;
698 break;
699 case 2:
700 tmp = edge_data->top_edges;
701 side = META_SIDE_TOP;
702 break;
703 case 3:
704 tmp = edge_data->bottom_edges;
705 side = META_SIDE_BOTTOM;
706 break;
707 default:
708 g_assert_not_reached ();
709 }
710
711 for (j = 0; j < tmp->len; j++)
712 {
713 MetaEdge *edge = g_array_index (tmp, MetaEdge*, j);
714 if (edge->edge_type == META_EDGE_WINDOW &&
715 edge->side_type == side)
716 {
717 /* The same edge will appear in two arrays, and we can't free
718 * it yet we still need to compare edge->side_type for the other
719 * array that it is in. So store it in a hash table for later
720 * freeing. Could also do this in a simple linked list.
721 */
722 g_hash_table_insert (edges_to_be_freed, edge, edge);
723 }
724 }
725 }
726
727 /* Now free all the window edges (the key destroy function is free) */
728 g_hash_table_destroy (edges_to_be_freed);
729
730 /* Now free the arrays and data */
731 g_array_free (edge_data->left_edges, TRUE);
732 g_array_free (edge_data->right_edges, TRUE);
733 g_array_free (edge_data->top_edges, TRUE);
734 g_array_free (edge_data->bottom_edges, TRUE);
735 edge_data->left_edges = NULL;
736 edge_data->right_edges = NULL;
737 edge_data->top_edges = NULL;
738 edge_data->bottom_edges = NULL;
739
740 /* Cleanup the timeouts */
741 if (edge_data->left_data.timeout_setup && edge_data->left_data.timeout_id != 0) {
742 g_source_remove (edge_data->left_data.timeout_id);
743 edge_data->left_data.timeout_id = 0;
744 }
745 if (edge_data->right_data.timeout_setup && edge_data->right_data.timeout_id != 0) {
746 g_source_remove (edge_data->right_data.timeout_id);
747 edge_data->right_data.timeout_id = 0;
748 }
749 if (edge_data->top_data.timeout_setup && edge_data->top_data.timeout_id != 0) {
750 g_source_remove (edge_data->top_data.timeout_id);
751 edge_data->top_data.timeout_id = 0;
752 }
753 if (edge_data->bottom_data.timeout_setup && edge_data->bottom_data.timeout_id != 0) {
754 g_source_remove (edge_data->bottom_data.timeout_id);
755 edge_data->bottom_data.timeout_id = 0;
756 }
757
758 free (display->grab_edge_resistance_data);
759 display->grab_edge_resistance_data = NULL;
760 }
761
762 static int
stupid_sort_requiring_extra_pointer_dereference(gconstpointer a,gconstpointer b)763 stupid_sort_requiring_extra_pointer_dereference (gconstpointer a,
764 gconstpointer b)
765 {
766 const MetaEdge * const *a_edge = a;
767 const MetaEdge * const *b_edge = b;
768 return meta_rectangle_edge_cmp_ignore_type (*a_edge, *b_edge);
769 }
770
771 static void
cache_edges(MetaDisplay * display,GList * window_edges,GList * monitor_edges,GList * screen_edges)772 cache_edges (MetaDisplay *display,
773 GList *window_edges,
774 GList *monitor_edges,
775 GList *screen_edges)
776 {
777 MetaEdgeResistanceData *edge_data;
778 GList *tmp;
779 int num_left, num_right, num_top, num_bottom;
780 int i;
781
782 /*
783 * 0th: Print debugging information to the log about the edges
784 */
785 #ifdef WITH_VERBOSE_MODE
786 if (meta_is_verbose())
787 {
788 int max_edges = MAX (MAX( g_list_length (window_edges),
789 g_list_length (monitor_edges)),
790 g_list_length (screen_edges));
791 char big_buffer[(EDGE_LENGTH+2)*max_edges];
792
793 meta_rectangle_edge_list_to_string (window_edges, ", ", big_buffer);
794 meta_topic (META_DEBUG_EDGE_RESISTANCE,
795 "Window edges for resistance : %s\n", big_buffer);
796
797 meta_rectangle_edge_list_to_string (monitor_edges, ", ", big_buffer);
798 meta_topic (META_DEBUG_EDGE_RESISTANCE,
799 "Monitor edges for resistance: %s\n", big_buffer);
800
801 meta_rectangle_edge_list_to_string (screen_edges, ", ", big_buffer);
802 meta_topic (META_DEBUG_EDGE_RESISTANCE,
803 "Screen edges for resistance : %s\n", big_buffer);
804 }
805 #endif
806
807 /*
808 * 1st: Get the total number of each kind of edge
809 */
810 num_left = num_right = num_top = num_bottom = 0;
811 for (i = 0; i < 3; i++)
812 {
813 tmp = NULL;
814 switch (i)
815 {
816 case 0:
817 tmp = window_edges;
818 break;
819 case 1:
820 tmp = monitor_edges;
821 break;
822 case 2:
823 tmp = screen_edges;
824 break;
825 default:
826 g_assert_not_reached ();
827 }
828
829 while (tmp)
830 {
831 MetaEdge *edge = tmp->data;
832 switch (edge->side_type)
833 {
834 case META_SIDE_LEFT:
835 num_left++;
836 break;
837 case META_SIDE_RIGHT:
838 num_right++;
839 break;
840 case META_SIDE_TOP:
841 num_top++;
842 break;
843 case META_SIDE_BOTTOM:
844 num_bottom++;
845 break;
846 default:
847 g_assert_not_reached ();
848 }
849 tmp = tmp->next;
850 }
851 }
852
853 /*
854 * 2nd: Allocate the edges
855 */
856 g_assert (display->grab_edge_resistance_data == NULL);
857 display->grab_edge_resistance_data = g_new0 (MetaEdgeResistanceData, 1);
858 edge_data = display->grab_edge_resistance_data;
859 edge_data->left_edges = g_array_sized_new (FALSE,
860 FALSE,
861 sizeof(MetaEdge*),
862 num_left + num_right);
863 edge_data->right_edges = g_array_sized_new (FALSE,
864 FALSE,
865 sizeof(MetaEdge*),
866 num_left + num_right);
867 edge_data->top_edges = g_array_sized_new (FALSE,
868 FALSE,
869 sizeof(MetaEdge*),
870 num_top + num_bottom);
871 edge_data->bottom_edges = g_array_sized_new (FALSE,
872 FALSE,
873 sizeof(MetaEdge*),
874 num_top + num_bottom);
875
876 /*
877 * 3rd: Add the edges to the arrays
878 */
879 for (i = 0; i < 3; i++)
880 {
881 tmp = NULL;
882 switch (i)
883 {
884 case 0:
885 tmp = window_edges;
886 break;
887 case 1:
888 tmp = monitor_edges;
889 break;
890 case 2:
891 tmp = screen_edges;
892 break;
893 default:
894 g_assert_not_reached ();
895 }
896
897 while (tmp)
898 {
899 MetaEdge *edge = tmp->data;
900 switch (edge->side_type)
901 {
902 case META_SIDE_LEFT:
903 case META_SIDE_RIGHT:
904 g_array_append_val (edge_data->left_edges, edge);
905 g_array_append_val (edge_data->right_edges, edge);
906 break;
907 case META_SIDE_TOP:
908 case META_SIDE_BOTTOM:
909 g_array_append_val (edge_data->top_edges, edge);
910 g_array_append_val (edge_data->bottom_edges, edge);
911 break;
912 default:
913 g_assert_not_reached ();
914 }
915 tmp = tmp->next;
916 }
917 }
918
919 /*
920 * 4th: Sort the arrays (FIXME: This is kinda dumb since the arrays were
921 * individually sorted earlier and we could have done this faster and
922 * avoided this sort by sticking them into the array with some simple
923 * merging of the lists).
924 */
925 g_array_sort (display->grab_edge_resistance_data->left_edges,
926 stupid_sort_requiring_extra_pointer_dereference);
927 g_array_sort (display->grab_edge_resistance_data->right_edges,
928 stupid_sort_requiring_extra_pointer_dereference);
929 g_array_sort (display->grab_edge_resistance_data->top_edges,
930 stupid_sort_requiring_extra_pointer_dereference);
931 g_array_sort (display->grab_edge_resistance_data->bottom_edges,
932 stupid_sort_requiring_extra_pointer_dereference);
933 }
934
935 static void
initialize_grab_edge_resistance_data(MetaDisplay * display)936 initialize_grab_edge_resistance_data (MetaDisplay *display)
937 {
938 MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data;
939
940 edge_data->left_data.timeout_setup = FALSE;
941 edge_data->right_data.timeout_setup = FALSE;
942 edge_data->top_data.timeout_setup = FALSE;
943 edge_data->bottom_data.timeout_setup = FALSE;
944
945 edge_data->left_data.keyboard_buildup = 0;
946 edge_data->right_data.keyboard_buildup = 0;
947 edge_data->top_data.keyboard_buildup = 0;
948 edge_data->bottom_data.keyboard_buildup = 0;
949 }
950
951 static void
compute_resistance_and_snapping_edges(MetaDisplay * display)952 compute_resistance_and_snapping_edges (MetaDisplay *display)
953 {
954 GList *stacked_windows;
955 GList *cur_window_iter;
956 GList *edges;
957 /* Lists of window positions (rects) and their relative stacking positions */
958 int stack_position;
959 GSList *obscuring_windows, *window_stacking;
960 /* The portions of the above lists that still remain at the stacking position
961 * in the layer that we are working on
962 */
963 GSList *rem_windows, *rem_win_stacking;
964
965 g_assert (display->grab_window != NULL);
966 meta_topic (META_DEBUG_WINDOW_OPS,
967 "Computing edges to resist-movement or snap-to for %s.\n",
968 display->grab_window->desc);
969
970 /*
971 * 1st: Get the list of relevant windows, from bottom to top
972 */
973 stacked_windows =
974 meta_stack_list_windows (display->grab_screen->stack,
975 display->grab_screen->active_workspace);
976
977 /*
978 * 2nd: we need to separate that stacked list into a list of windows that
979 * can obscure other edges. To make sure we only have windows obscuring
980 * those below it instead of going both ways, we also need to keep a
981 * counter list. Messy, I know.
982 */
983 obscuring_windows = window_stacking = NULL;
984 cur_window_iter = stacked_windows;
985 stack_position = 0;
986 while (cur_window_iter != NULL)
987 {
988 MetaWindow *cur_window = cur_window_iter->data;
989 if (WINDOW_EDGES_RELEVANT (cur_window, display))
990 {
991 MetaRectangle *new_rect;
992 new_rect = g_new (MetaRectangle, 1);
993 meta_window_get_outer_rect (cur_window, new_rect);
994 obscuring_windows = g_slist_prepend (obscuring_windows, new_rect);
995 window_stacking =
996 g_slist_prepend (window_stacking, GINT_TO_POINTER (stack_position));
997 }
998
999 stack_position++;
1000 cur_window_iter = cur_window_iter->next;
1001 }
1002 /* Put 'em in bottom to top order */
1003 rem_windows = obscuring_windows = g_slist_reverse (obscuring_windows);
1004 rem_win_stacking = window_stacking = g_slist_reverse (window_stacking);
1005
1006 /*
1007 * 3rd: loop over the windows again, this time getting the edges from
1008 * them and removing intersections with the relevant obscuring_windows &
1009 * obscuring_docks.
1010 */
1011 edges = NULL;
1012 stack_position = 0;
1013 cur_window_iter = stacked_windows;
1014 while (cur_window_iter != NULL)
1015 {
1016 MetaRectangle cur_rect;
1017 MetaWindow *cur_window = cur_window_iter->data;
1018 meta_window_get_outer_rect (cur_window, &cur_rect);
1019
1020 /* Check if we want to use this window's edges for edge
1021 * resistance (note that dock edges are considered screen edges
1022 * which are handled separately
1023 */
1024 if (WINDOW_EDGES_RELEVANT (cur_window, display) &&
1025 cur_window->type != META_WINDOW_DOCK)
1026 {
1027 GList *new_edges;
1028 MetaEdge *new_edge;
1029 MetaRectangle reduced;
1030
1031 /* We don't care about snapping to any portion of the window that
1032 * is offscreen (we also don't care about parts of edges covered
1033 * by other windows or DOCKS, but that's handled below).
1034 */
1035 meta_rectangle_intersect (&cur_rect,
1036 &display->grab_screen->rect,
1037 &reduced);
1038
1039 new_edges = NULL;
1040
1041 /* Left side of this window is resistance for the right edge of
1042 * the window being moved.
1043 */
1044 new_edge = g_new (MetaEdge, 1);
1045 new_edge->rect = reduced;
1046 new_edge->rect.width = 0;
1047 new_edge->side_type = META_SIDE_RIGHT;
1048 new_edge->edge_type = META_EDGE_WINDOW;
1049 new_edges = g_list_prepend (new_edges, new_edge);
1050
1051 /* Right side of this window is resistance for the left edge of
1052 * the window being moved.
1053 */
1054 new_edge = g_new (MetaEdge, 1);
1055 new_edge->rect = reduced;
1056 new_edge->rect.x += new_edge->rect.width;
1057 new_edge->rect.width = 0;
1058 new_edge->side_type = META_SIDE_LEFT;
1059 new_edge->edge_type = META_EDGE_WINDOW;
1060 new_edges = g_list_prepend (new_edges, new_edge);
1061
1062 /* Top side of this window is resistance for the bottom edge of
1063 * the window being moved.
1064 */
1065 new_edge = g_new (MetaEdge, 1);
1066 new_edge->rect = reduced;
1067 new_edge->rect.height = 0;
1068 new_edge->side_type = META_SIDE_BOTTOM;
1069 new_edge->edge_type = META_EDGE_WINDOW;
1070 new_edges = g_list_prepend (new_edges, new_edge);
1071
1072 /* Top side of this window is resistance for the bottom edge of
1073 * the window being moved.
1074 */
1075 new_edge = g_new (MetaEdge, 1);
1076 new_edge->rect = reduced;
1077 new_edge->rect.y += new_edge->rect.height;
1078 new_edge->rect.height = 0;
1079 new_edge->side_type = META_SIDE_TOP;
1080 new_edge->edge_type = META_EDGE_WINDOW;
1081 new_edges = g_list_prepend (new_edges, new_edge);
1082
1083 /* Update the remaining windows to only those at a higher
1084 * stacking position than this one.
1085 */
1086 while (rem_win_stacking &&
1087 stack_position >= GPOINTER_TO_INT (rem_win_stacking->data))
1088 {
1089 rem_windows = rem_windows->next;
1090 rem_win_stacking = rem_win_stacking->next;
1091 }
1092
1093 /* Remove edge portions overlapped by rem_windows and rem_docks */
1094 new_edges =
1095 meta_rectangle_remove_intersections_with_boxes_from_edges (
1096 new_edges,
1097 rem_windows);
1098
1099 /* Save the new edges */
1100 edges = g_list_concat (new_edges, edges);
1101 }
1102
1103 stack_position++;
1104 cur_window_iter = cur_window_iter->next;
1105 }
1106
1107 /*
1108 * 4th: Free the extra memory not needed and sort the list
1109 */
1110 g_list_free (stacked_windows);
1111 /* Free the memory used by the obscuring windows/docks lists */
1112 g_slist_free (window_stacking);
1113 /* FIXME: Shouldn't there be a helper function to make this one line of code
1114 * to free a list instead of four ugly ones?
1115 */
1116 g_slist_foreach (obscuring_windows,
1117 (void (*)(gpointer,gpointer))&free, /* ew, for ugly */
1118 NULL);
1119 g_slist_free (obscuring_windows);
1120
1121 /* Sort the list. FIXME: Should I bother with this sorting? I just
1122 * sort again later in cache_edges() anyway...
1123 */
1124 edges = g_list_sort (edges, meta_rectangle_edge_cmp);
1125
1126 /*
1127 * 5th: Cache the combination of these edges with the onscreen and
1128 * monitor edges in an array for quick access. Free the edges since
1129 * they've been cached elsewhere.
1130 */
1131 cache_edges (display,
1132 edges,
1133 display->grab_screen->active_workspace->monitor_edges,
1134 display->grab_screen->active_workspace->screen_edges);
1135 g_list_free (edges);
1136
1137 /*
1138 * 6th: Initialize the resistance timeouts and buildups
1139 */
1140 initialize_grab_edge_resistance_data (display);
1141 }
1142
1143 /* Note that old_[xy] and new_[xy] are with respect to inner positions of
1144 * the window.
1145 */
1146 LOCAL_SYMBOL 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)1147 meta_window_edge_resistance_for_move (MetaWindow *window,
1148 int old_x,
1149 int old_y,
1150 int *new_x,
1151 int *new_y,
1152 GSourceFunc timeout_func,
1153 gboolean snap,
1154 gboolean is_keyboard_op)
1155 {
1156 MetaRectangle old_outer, proposed_outer, new_outer;
1157 gboolean is_resize;
1158
1159 meta_window_get_outer_rect (window, &old_outer);
1160
1161 proposed_outer = old_outer;
1162 proposed_outer.x += (*new_x - old_x);
1163 proposed_outer.y += (*new_y - old_y);
1164 new_outer = proposed_outer;
1165
1166 window->display->grab_last_user_action_was_snap = snap;
1167 is_resize = FALSE;
1168 if (apply_edge_resistance_to_each_side (window->display,
1169 window,
1170 &old_outer,
1171 &new_outer,
1172 timeout_func,
1173 snap,
1174 is_keyboard_op,
1175 is_resize))
1176 {
1177 /* apply_edge_resistance_to_each_side independently applies
1178 * resistance to both the right and left edges of new_outer as both
1179 * could meet areas of resistance. But we don't want a resize, so we
1180 * just have both edges move according to the stricter of the
1181 * resistances. Same thing goes for top & bottom edges.
1182 */
1183 MetaRectangle *reference;
1184 int left_change, right_change, smaller_x_change;
1185 int top_change, bottom_change, smaller_y_change;
1186
1187 if (snap && !is_keyboard_op)
1188 reference = &proposed_outer;
1189 else
1190 reference = &old_outer;
1191
1192 left_change = BOX_LEFT (new_outer) - BOX_LEFT (*reference);
1193 right_change = BOX_RIGHT (new_outer) - BOX_RIGHT (*reference);
1194 if ( snap && is_keyboard_op && left_change == 0)
1195 smaller_x_change = right_change;
1196 else if (snap && is_keyboard_op && right_change == 0)
1197 smaller_x_change = left_change;
1198 else if (ABS (left_change) < ABS (right_change))
1199 smaller_x_change = left_change;
1200 else
1201 smaller_x_change = right_change;
1202
1203 top_change = BOX_TOP (new_outer) - BOX_TOP (*reference);
1204 bottom_change = BOX_BOTTOM (new_outer) - BOX_BOTTOM (*reference);
1205 if ( snap && is_keyboard_op && top_change == 0)
1206 smaller_y_change = bottom_change;
1207 else if (snap && is_keyboard_op && bottom_change == 0)
1208 smaller_y_change = top_change;
1209 else if (ABS (top_change) < ABS (bottom_change))
1210 smaller_y_change = top_change;
1211 else
1212 smaller_y_change = bottom_change;
1213
1214 *new_x = old_x + smaller_x_change +
1215 (BOX_LEFT (*reference) - BOX_LEFT (old_outer));
1216 *new_y = old_y + smaller_y_change +
1217 (BOX_TOP (*reference) - BOX_TOP (old_outer));
1218
1219 meta_topic (META_DEBUG_EDGE_RESISTANCE,
1220 "outer x & y move-to coordinate changed from %d,%d to %d,%d\n",
1221 proposed_outer.x, proposed_outer.y,
1222 old_outer.x + (*new_x - old_x),
1223 old_outer.y + (*new_y - old_y));
1224 }
1225 }
1226
1227 /* Note that old_(width|height) and new_(width|height) are with respect to
1228 * sizes of the inner window.
1229 */
1230 LOCAL_SYMBOL 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)1231 meta_window_edge_resistance_for_resize (MetaWindow *window,
1232 int old_width,
1233 int old_height,
1234 int *new_width,
1235 int *new_height,
1236 int gravity,
1237 GSourceFunc timeout_func,
1238 gboolean snap,
1239 gboolean is_keyboard_op)
1240 {
1241 MetaRectangle old_outer, new_outer;
1242 int proposed_outer_width, proposed_outer_height;
1243 gboolean is_resize;
1244
1245 meta_window_get_outer_rect (window, &old_outer);
1246 proposed_outer_width = old_outer.width + (*new_width - old_width);
1247 proposed_outer_height = old_outer.height + (*new_height - old_height);
1248 meta_rectangle_resize_with_gravity (&old_outer,
1249 &new_outer,
1250 gravity,
1251 proposed_outer_width,
1252 proposed_outer_height);
1253
1254 window->display->grab_last_user_action_was_snap = snap;
1255 is_resize = TRUE;
1256 if (apply_edge_resistance_to_each_side (window->display,
1257 window,
1258 &old_outer,
1259 &new_outer,
1260 timeout_func,
1261 snap,
1262 is_keyboard_op,
1263 is_resize))
1264 {
1265 *new_width = old_width + (new_outer.width - old_outer.width);
1266 *new_height = old_height + (new_outer.height - old_outer.height);
1267
1268 meta_topic (META_DEBUG_EDGE_RESISTANCE,
1269 "outer width & height got changed from %d,%d to %d,%d\n",
1270 proposed_outer_width, proposed_outer_height,
1271 new_outer.width, new_outer.height);
1272 }
1273 }
1274