]> Pileus Git - ~andy/gtk/blob - gdk/x11/gdkgeometry-x11.c
Various fixes for native windows:
[~andy/gtk] / gdk / x11 / gdkgeometry-x11.c
1 /* GDK - The GIMP Drawing Kit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /* gdkgeometry-x11.c: emulation of 32 bit coordinates within the
21  * limits of X. 
22  *
23  * By Owen Taylor <otaylor@redhat.com>
24  * Copyright Red Hat, Inc. 2000
25  *
26  * The algorithms implemented in this file are an extension of the
27  * idea of guffaw scrolling, a technique (and name) taken from the classic
28  * Netscape source code. The basic idea of guffaw scrolling is a trick
29  * to get around a limitation of X: there is no way of scrolling the
30  * contents of a window. Guffaw scrolling exploits the X concepts of
31  * window gravity and bit gravity:
32  *
33  *  window gravity: the window gravity of a window affects what happens
34  *   to a windows position when _its parent_ is resized, or
35  *   moved and resized simultaneously.
36  *
37  *  bit gravity: the bit gravity of a window affects what happens to
38  *  the pixels of a window when _it_ is is resized, or moved and
39  *  resized simultaneously.
40  *
41  * These were basically intended to do things like have right
42  * justified widgets in a window automatically stay right justified
43  * when the window was resized, but there is also the special
44  * "StaticGravity" which means "do nothing." We can exploit
45  * StaticGravity to scroll a window:
46  *
47  *     |  VISIBLE  |
48  * 
49  *     |abcdefghijk|
50  *     |abcdefghijk    |   (1) Resize bigger
51  * |    efghijk    |       (2) Move  
52  *     |efghijk    |       (3) Move-resize back to the original size
53  *
54  * Or, going the other way:
55
56  *     |abcdefghijk|
57  * |    abcdefghijk|       (1) Move-resize bigger
58  *     |    abcdefghijk|   (2) Move  
59  *     |    abcdefg|       (4) Resize back to the original size
60  *
61  * By using this technique, we can simulate scrolling around in a
62  * large virtual space without having to actually have windows that
63  * big; for the pixels of the window, this is all we have to do.  For
64  * subwindows, we have to take care of one other detail - since
65  * coordinates in X are limited to 16 bits, subwindows scrolled off
66  * will wrap around and come back eventually. So, we have to take care
67  * to unmap windows that go outside the 16-bit range and remap them as
68  * they come back in.
69  *
70  * Since we are temporarily making the window bigger, this only looks
71  * good if the edges of the window are obscured. Typically, we do
72  * this by making the window we are scrolling the immediate child
73  * of a "clip window".
74  *
75  * But, this isn't a perfect API for applications for several reasons:
76  *
77  *  - We have to use this inefficient technique even for small windows
78  *    if the window _could_ be big.
79  *  - Applications have to use a special scrolling API.
80  *
81  * What we'd like is to simply have windows with 32 bit coordinates
82  * so applications could scroll in the classic way - just move a big
83  * window around.
84  *
85  * It turns out that StaticGravity can also be used to achieve emulation
86  * of 32 bit coordinates with only 16 bit coordinates if we expand
87  * our horizons just a bit; what guffaw scrolling really is is a way
88  * to move the contents of a window a different amount than we move
89  * the borders of of the window. In the above example pictures we
90  * ended up with the borders of the window not moving at all, but
91  * that isn't necessary.
92  *
93  * So, what we do is set up a mapping from virtual 32 bit window position/size
94  * to:
95  *
96  *  - Real window position/size
97  *  - Offset between virtual coordinates and real coordinates for the window
98  *  - Map state (mapped or unmapped)
99  *
100  * By the following rules:
101  *
102  *  - If the window is less than 32767 pixels in width (resp. height), we use it's
103  *    virtual width and position.
104  *  - Otherwise, we use a width of 32767 and determine the position of the window
105  *    so that the portion of the real window [16384, 16383] in _toplevel window
106  *    coordinates_ is the same as the portion of the real window 
107  *
108  * This is implemented in gdk_window_compute_position(). Then the algorithm
109  * for a moving a window (_window_move_resize_child ()) is:
110  * 
111  *  - Compute the new window mappings for the window and all subwindows
112  *  - Expand out the boundary of the window and all subwindows by the amount
113  *    that the real/virtual offset changes for each window. 
114  *    (compute_intermediate_position() computes expanded boundary)
115  *  - Move the toplevel by the amount that it's contents need to translate.
116  *  - Move/resize the window and all subwindows to the newly computed
117  *    positions.
118  *
119  * If we just are scrolling (gdk_window_guffaw_scroll()), then things
120  * are similar, except that the final mappings for the toplevel are
121  * the same as the initial mappings, but we act as if it moved by the
122  * amount we are scrolling by.
123  *
124  * Note that we don't have to worry about a clip window in
125  * _gdk_window_move_resize() since we have set up our translation so
126  * that things in the range [16384,16383] in toplevel window
127  * coordinates look exactly as they would if we were simply moving the
128  * windows, and nothing outside this range is going to be visible
129  * unless the user has a _really_ huge screen.
130  */
131
132 #include "config.h"
133 #include "gdk.h"                /* For gdk_rectangle_intersect */
134 #include "gdkprivate-x11.h"
135 #include "gdkx.h"
136 #include "gdkregion.h"
137 #include "gdkinternals.h"
138 #include "gdkscreen-x11.h"
139 #include "gdkdisplay-x11.h"
140 #include "gdkwindow-x11.h"
141 #include "gdkalias.h"
142
143 typedef struct _GdkWindowQueueItem GdkWindowQueueItem;
144 typedef struct _GdkWindowParentPos GdkWindowParentPos;
145
146 typedef enum {
147   GDK_WINDOW_QUEUE_TRANSLATE,
148   GDK_WINDOW_QUEUE_ANTIEXPOSE
149 } GdkWindowQueueType;
150
151 struct _GdkWindowQueueItem
152 {
153   GdkWindow *window;
154   gulong serial;
155   GdkWindowQueueType type;
156   union {
157     struct {
158       GdkRegion *area;
159       gint dx;
160       gint dy;
161     } translate;
162     struct {
163       GdkRegion *area;
164     } antiexpose;
165   } u;
166 };
167
168 static void
169 move (GdkWindow *window, GdkRectangle *pos)
170 {
171   XMoveWindow (GDK_WINDOW_XDISPLAY (window),
172                GDK_WINDOW_XID (window), pos->x, pos->y);
173 }
174
175 static void
176 move_resize (GdkWindow *window, GdkRectangle *pos)
177 {
178   XMoveResizeWindow (GDK_WINDOW_XDISPLAY (window),
179                      GDK_WINDOW_XID (window),
180                      pos->x, pos->y, pos->width, pos->height);
181 }
182
183 void
184 _gdk_window_move_resize_child (GdkWindow *window,
185                                gint       x,
186                                gint       y,
187                                gint       width,
188                                gint       height)
189 {
190   GdkWindowImplX11 *impl;
191   GdkWindowObject *obj;
192   GdkRectangle new_info;
193   gboolean is_resize;
194   
195   g_return_if_fail (window != NULL);
196   g_return_if_fail (GDK_IS_WINDOW (window)); 
197
198   impl = GDK_WINDOW_IMPL_X11 (GDK_WINDOW_OBJECT (window)->impl);
199   obj = GDK_WINDOW_OBJECT (window);
200
201   is_resize =
202     width != obj->width ||
203     height != obj->height;
204     
205   obj->x = x;
206   obj->y = y;
207   obj->width = width;
208   obj->height = height;
209
210   new_info.x = obj->x + obj->parent->abs_x;
211   new_info.y = obj->y + obj->parent->abs_y;
212   new_info.width = obj->width;
213   new_info.height = obj->height;
214
215   _gdk_x11_window_tmp_unset_bg (window, TRUE);
216   _gdk_x11_window_tmp_unset_bg (obj->parent, FALSE);
217   if (is_resize)
218     move_resize (window, &new_info);
219   else
220     move (window, &new_info);
221   _gdk_x11_window_tmp_reset_bg (obj->parent, FALSE);
222   _gdk_x11_window_tmp_reset_bg (window, TRUE);
223 }
224
225 static Bool
226 expose_serial_predicate (Display *xdisplay,
227                          XEvent  *xev,
228                          XPointer arg)
229 {
230   gulong *serial = (gulong *)arg;
231
232   if (xev->xany.type == Expose)
233     *serial = MIN (*serial, xev->xany.serial);
234
235   return False;
236 }
237
238 /* Find oldest possible serial for an outstanding expose event
239  */
240 static gulong
241 find_current_serial (Display *xdisplay)
242 {
243   XEvent xev;
244   gulong serial = NextRequest (xdisplay);
245   
246   XSync (xdisplay, False);
247
248   XCheckIfEvent (xdisplay, &xev, expose_serial_predicate, (XPointer)&serial);
249
250   return serial;
251 }
252
253 static void
254 queue_delete_link (GQueue *queue,
255                    GList  *link)
256 {
257   if (queue->tail == link)
258     queue->tail = link->prev;
259   
260   queue->head = g_list_remove_link (queue->head, link);
261   g_list_free_1 (link);
262   queue->length--;
263 }
264
265 static void
266 queue_item_free (GdkWindowQueueItem *item)
267 {
268   if (item->window)
269     {
270       g_object_remove_weak_pointer (G_OBJECT (item->window),
271                                     (gpointer *)&(item->window));
272     }
273   
274   if (item->type == GDK_WINDOW_QUEUE_ANTIEXPOSE)
275     gdk_region_destroy (item->u.antiexpose.area);
276   else
277     {
278       if (item->u.translate.area)
279         gdk_region_destroy (item->u.translate.area);
280     }
281   
282   g_free (item);
283 }
284
285 static void
286 gdk_window_queue (GdkWindow          *window,
287                   GdkWindowQueueItem *item)
288 {
289   GdkDisplayX11 *display_x11 = GDK_DISPLAY_X11 (GDK_WINDOW_DISPLAY (window));
290   
291   if (!display_x11->translate_queue)
292     display_x11->translate_queue = g_queue_new ();
293
294   /* Keep length of queue finite by, if it grows too long,
295    * figuring out the latest relevant serial and discarding
296    * irrelevant queue items.
297    */
298   if (display_x11->translate_queue->length >= 64)
299     {
300       gulong serial = find_current_serial (GDK_WINDOW_XDISPLAY (window));
301       GList *tmp_list = display_x11->translate_queue->head;
302       
303       while (tmp_list)
304         {
305           GdkWindowQueueItem *item = tmp_list->data;
306           GList *next = tmp_list->next;
307           
308           /* an overflow-safe (item->serial < serial) */
309           if (item->serial - serial > (gulong) G_MAXLONG)
310             {
311               queue_delete_link (display_x11->translate_queue, tmp_list);
312               queue_item_free (item);
313             }
314
315           tmp_list = next;
316         }
317     }
318
319   /* Catch the case where someone isn't processing events and there
320    * is an event stuck in the event queue with an old serial:
321    * If we can't reduce the queue length by the above method,
322    * discard anti-expose items. (We can't discard translate
323    * items 
324    */
325   if (display_x11->translate_queue->length >= 64)
326     {
327       GList *tmp_list = display_x11->translate_queue->head;
328       
329       while (tmp_list)
330         {
331           GdkWindowQueueItem *item = tmp_list->data;
332           GList *next = tmp_list->next;
333           
334           if (item->type == GDK_WINDOW_QUEUE_ANTIEXPOSE)
335             {
336               queue_delete_link (display_x11->translate_queue, tmp_list);
337               queue_item_free (item);
338             }
339
340           tmp_list = next;
341         }
342     }
343
344   item->window = window;
345   item->serial = NextRequest (GDK_WINDOW_XDISPLAY (window));
346   
347   g_object_add_weak_pointer (G_OBJECT (window),
348                              (gpointer *)&(item->window));
349
350   g_queue_push_tail (display_x11->translate_queue, item);
351 }
352
353 void
354 _gdk_x11_window_queue_translation (GdkWindow *window,
355                                    GdkRegion *area,
356                                    gint       dx,
357                                    gint       dy)
358 {
359   GdkWindowQueueItem *item = g_new (GdkWindowQueueItem, 1);
360   item->type = GDK_WINDOW_QUEUE_TRANSLATE;
361   item->u.translate.area = area ? gdk_region_copy (area) : NULL;
362   item->u.translate.dx = dx;
363   item->u.translate.dy = dy;
364
365   gdk_window_queue (window, item);
366 }
367
368 gboolean
369 _gdk_x11_window_queue_antiexpose (GdkWindow *window,
370                                   GdkRegion *area)
371 {
372   GdkWindowQueueItem *item = g_new (GdkWindowQueueItem, 1);
373   item->type = GDK_WINDOW_QUEUE_ANTIEXPOSE;
374   item->u.antiexpose.area = area;
375
376   gdk_window_queue (window, item);
377
378   return TRUE;
379 }
380
381 void
382 _gdk_window_process_expose (GdkWindow    *window,
383                             gulong        serial,
384                             GdkRectangle *area)
385 {
386   GdkWindowImplX11 *impl;
387   GdkRegion *invalidate_region = gdk_region_rectangle (area);
388   GdkDisplayX11 *display_x11 = GDK_DISPLAY_X11 (GDK_WINDOW_DISPLAY (window));
389   impl = GDK_WINDOW_IMPL_X11 (GDK_WINDOW_OBJECT (window)->impl);
390
391   if (display_x11->translate_queue)
392     {
393       GList *tmp_list = display_x11->translate_queue->head;
394       
395       while (tmp_list)
396         {
397           GdkWindowQueueItem *item = tmp_list->data;
398           GList *next = tmp_list->next;
399           
400           /* an overflow-safe (serial < item->serial) */
401           if (serial - item->serial > (gulong) G_MAXLONG)
402             {
403               if (item->window == window)
404                 {
405                   if (item->type == GDK_WINDOW_QUEUE_TRANSLATE)
406                     {
407                       if (item->u.translate.area)
408                         {
409                           GdkRegion *intersection;
410                           
411                           intersection = gdk_region_copy (invalidate_region);
412                           gdk_region_intersect (intersection, item->u.translate.area);
413                           gdk_region_subtract (invalidate_region, intersection);
414                           gdk_region_offset (intersection, item->u.translate.dx, item->u.translate.dy);
415                           gdk_region_union (invalidate_region, intersection);
416                           gdk_region_destroy (intersection);
417                         }
418                       else
419                         gdk_region_offset (invalidate_region, item->u.translate.dx, item->u.translate.dy);
420                     }
421                   else          /* anti-expose */
422                     {
423                       gdk_region_subtract (invalidate_region, item->u.antiexpose.area);
424                     }
425                 }
426             }
427           else
428             {
429               queue_delete_link (display_x11->translate_queue, tmp_list);
430               queue_item_free (item);
431             }
432           tmp_list = next;
433         }
434     }
435
436   if (!gdk_region_empty (invalidate_region))
437     _gdk_window_invalidate_for_expose (window, invalidate_region);
438   
439   gdk_region_destroy (invalidate_region);
440 }
441
442 #define __GDK_GEOMETRY_X11_C__
443 #include "gdkaliasdef.c"