]> Pileus Git - ~andy/gtk/blobdiff - gdk/x11/gdkgeometry-x11.c
Fix wrong sign for queued translation. (Found by Chris Blizzard, #100274)
[~andy/gtk] / gdk / x11 / gdkgeometry-x11.c
index b479ab5e753293c27b8f522b796f9df291ec6df2..693a0cc51b324f15e044dbddd59cc2bb143de04e 100644 (file)
  *
  * By Owen Taylor <otaylor@redhat.com>
  * Copyright Red Hat, Inc. 2000
+ *
+ * The algorithms implemented in this file are an extension of the
+ * idea of guffaw scrolling, a technique (and name) taken from the classic
+ * Netscape source code. The basic idea of guffaw scrolling is a trick
+ * to get around a limitation of X: there is no way of scrolling the
+ * contents of a window. Guffaw scrolling exploits the X concepts of
+ * window gravity and bit gravity:
+ *
+ *  window gravity: the window gravity of a window affects what happens
+ *   to a windows position when _its parent_ is resized, or
+ *   moved and resized simultaneously.
+ *
+ *  bit gravity: the bit gravity of a window affects what happens to
+ *  the pixels of a window when _it_ is is resized, or moved and
+ *  resized simultaneously.
+ *
+ * These were basically intended to do things like have right
+ * justified widgets in a window automatically stay right justified
+ * when the window was resized, but there is also the special
+ * "StaticGravity" which means "do nothing." We can exploit
+ * StaticGravity to scroll a window:
+ *
+ *     |  VISIBLE  |
+ * 
+ *     |abcdefghijk|
+ *     |abcdefghijk    |   (1) Resize bigger
+ * |    efghijk    |       (2) Move  
+ *     |efghijk    |       (3) Move-resize back to the original size
+ *
+ * Or, going the other way:
+
+ *     |abcdefghijk|
+ * |    abcdefghijk|       (1) Move-resize bigger
+ *     |    abcdefghijk|   (2) Move  
+ *     |    abcdefg|       (4) Resize back to the original size
+ *
+ * By using this technique, we can simulate scrolling around in a
+ * large virtual space without having to actually have windows that
+ * big; for the pixels of the window, this is all we have to do.  For
+ * subwindows, we have to take care of one other detail - since
+ * coordinates in X are limited to 16 bits, subwindows scrolled off
+ * will wrap around and come back eventually. So, we have to take care
+ * to unmap windows that go outside the 16-bit range and remap them as
+ * they come back in.
+ *
+ * Since we are temporarily making the window bigger, this only looks
+ * good if the edges of the window are obscured. Typically, we do
+ * this by making the window we are scrolling the immediate child
+ * of a "clip window".
+ *
+ * But, this isn't a perfect API for applications for several reasons:
+ *
+ *  - We have to use this inefficient technique even for small windows
+ *    if the window _could_ be big.
+ *  - Applications have to use a special scrolling API.
+ *
+ * What we'd like is to simply have windows with 32 bit coordinates
+ * so applications could scroll in the classic way - just move a big
+ * window around.
+ *
+ * It turns out that StaticGravity can also be used to achieve emulation
+ * of 32 bit coordinates with only 16 bit coordinates if we expand
+ * our horizons just a bit; what guffaw scrolling really is is a way
+ * to move the contents of a window a different amount than we move
+ * the borders of of the window. In the above example pictures we
+ * ended up with the borders of the window not moving at all, but
+ * that isn't necessary.
+ *
+ * So, what we do is set up a mapping from virtual 32 bit window position/size
+ * to:
+ *
+ *  - Real window position/size
+ *  - Offset between virtual coordinates and real coordinates for the window
+ *  - Map state (mapped or unmapped)
+ *
+ * By the following rules:
+ *
+ *  - If the window is less than 32767 pixels in width (resp. height), we use it's
+ *    virtual width and position.
+ *  - Otherwise, we use a width of 32767 and determine the position of the window
+ *    so that the portion of the real window [16384, 16383] in _toplevel window
+ *    coordinates_ is the same as the portion of the real window 
+ *
+ * This is implemented in gdk_window_compute_position(). Then the algorithm
+ * for a moving a window (_window_move_resize_child ()) is:
+ * 
+ *  - Compute the new window mappings for the window and all subwindows
+ *  - Expand out the boundary of the window and all subwindows by the amount
+ *    that the real/virtual offset changes for each window. 
+ *    (compute_intermediate_position() computes expanded boundary)
+ *  - Move the toplevel by the amount that it's contents need to translate.
+ *  - Move/resize the window and all subwindows to the newly computed
+ *    positions.
+ *
+ * If we just are scrolling (gdk_window_guffaw_scroll()), then things
+ * are similar, except that the final mappings for the toplevel are
+ * the same as the initial mappings, but we act as if it moved by the
+ * amount we are scrolling by.
+ *
+ * Note that we don't have to worry about a clip window in
+ * _gdk_window_move_resize() since we have set up our translation so
+ * that things in the range [16384,16383] in toplevel window
+ * coordinates look exactly as they would if we were simply moving the
+ * windows, and nothing outside this range is going to be visible
+ * unless the user has a _really_ huge screen.
  */
 
 #include "gdk.h"               /* For gdk_rectangle_intersect */
 #include "gdkx.h"
 #include "gdkregion.h"
 #include "gdkinternals.h"
+#include "gdkscreen-x11.h"
+#include "gdkdisplay-x11.h"
 
 typedef struct _GdkWindowQueueItem GdkWindowQueueItem;
 typedef struct _GdkWindowParentPos GdkWindowParentPos;
@@ -81,8 +188,6 @@ static void gdk_window_clip_changed       (GdkWindow          *window,
                                           GdkRectangle       *old_clip,
                                           GdkRectangle       *new_clip);
 
-static GSList *translate_queue = NULL;
-
 void
 _gdk_windowing_window_get_offsets (GdkWindow *window,
                                   gint      *x_offset,
@@ -226,8 +331,8 @@ gdk_window_guffaw_scroll (GdkWindow    *window,
 
   gdk_window_tmp_unset_bg (window);
 
-  if (d_xoffset < 0 || d_yoffset < 0)
-    gdk_window_queue_translation (window, MIN (d_xoffset, 0), MIN (d_yoffset, 0));
+  if (dx > 0 || dy > 0)
+    gdk_window_queue_translation (window, MAX (dx, 0), MAX (dy, 0));
        
   gdk_window_set_static_gravities (window, TRUE);
 
@@ -252,8 +357,8 @@ gdk_window_guffaw_scroll (GdkWindow    *window,
               GDK_WINDOW_XID (window),
               new_position.x - d_xoffset, new_position.y - d_yoffset);
   
-  if (d_xoffset > 0 || d_yoffset > 0)
-    gdk_window_queue_translation (window, MAX (d_xoffset, 0), MAX (d_yoffset, 0));
+  if (dx < 0 || dy < 0)
+    gdk_window_queue_translation (window, MIN (dx, 0), MIN (dy, 0));
   
   XMoveResizeWindow (GDK_WINDOW_XDISPLAY (window),
                     GDK_WINDOW_XID (window),
@@ -521,7 +626,7 @@ gdk_window_compute_position (GdkWindowImplX11   *window,
          if (parent_pos->x + wrapper->x + window->width < 16384)
            info->x = parent_pos->x + wrapper->x + window->width - info->width - parent_pos->x11_x;
          else
-           info->x = -16384 - parent_pos->x11_y;
+           info->x = -16384 - parent_pos->x11_x;
        }
       else
        info->x = parent_pos->x + wrapper->x - parent_pos->x11_x;
@@ -755,20 +860,134 @@ gdk_window_postmove (GdkWindow          *window,
     }
 }
 
+Bool
+expose_serial_predicate (Display *xdisplay,
+                        XEvent  *xev,
+                        XPointer arg)
+{
+  gulong *serial = (gulong *)arg;
+
+  if (xev->xany.type == Expose)
+    *serial = MIN (*serial, xev->xany.serial);
+
+  return False;
+}
+
+/* Find oldest possible serial for an outstanding expose event
+ */
+static gulong
+find_current_serial (Display *xdisplay)
+{
+  XEvent xev;
+  gulong serial = NextRequest (xdisplay);
+  
+  XSync (xdisplay, False);
+
+  XCheckIfEvent (xdisplay, &xev, expose_serial_predicate, (XPointer)&serial);
+
+  return serial;
+}
+
+static void
+queue_delete_link (GQueue *queue,
+                  GList  *link)
+{
+  if (queue->tail == link)
+    queue->tail = link->prev;
+  
+  queue->head = g_list_remove_link (queue->head, link);
+  g_list_free_1 (link);
+  queue->length--;
+}
+
+static void
+queue_item_free (GdkWindowQueueItem *item)
+{
+  g_object_unref (item->window);
+  
+  if (item->type == GDK_WINDOW_QUEUE_ANTIEXPOSE)
+    gdk_region_destroy (item->u.antiexpose.area);
+  
+  g_free (item);
+}
+
+static void
+gdk_window_queue (GdkWindow          *window,
+                 GdkWindowQueueItem *item)
+{
+  GdkDisplayX11 *display_x11 = GDK_DISPLAY_X11 (GDK_WINDOW_DISPLAY (window));
+  
+  if (!display_x11->translate_queue)
+    display_x11->translate_queue = g_queue_new ();
+
+  /* Keep length of queue finite by, if it grows too long,
+   * figuring out the latest relevant serial and discarding
+   * irrelevant queue items.
+   */
+  if (display_x11->translate_queue->length >= 64)
+    {
+      gulong serial = find_current_serial (GDK_WINDOW_XDISPLAY (window));
+      GList *tmp_list = display_x11->translate_queue->head;
+      
+      while (tmp_list)
+       {
+         GdkWindowQueueItem *item = tmp_list->data;
+         GList *next = tmp_list->next;
+         
+         if (serial > item->serial)
+           {
+             queue_delete_link (display_x11->translate_queue, tmp_list);
+             queue_item_free (item);
+           }
+
+         tmp_list = next;
+       }
+    }
+
+  /* Catch the case where someone isn't processing events and there
+   * is an event stuck in the event queue with an old serial:
+   * If we can't reduce the queue length by the above method,
+   * discard anti-expose items. (We can't discard translate
+   * items 
+   */
+  if (display_x11->translate_queue->length >= 64)
+    {
+      GList *tmp_list = display_x11->translate_queue->head;
+      
+      while (tmp_list)
+       {
+         GdkWindowQueueItem *item = tmp_list->data;
+         GList *next = tmp_list->next;
+         
+         if (item->type == GDK_WINDOW_QUEUE_ANTIEXPOSE)
+           {
+             queue_delete_link (display_x11->translate_queue, tmp_list);
+             queue_item_free (item);
+           }
+
+         tmp_list = next;
+       }
+    }
+      
+  g_object_ref (window);
+
+  item->window = window;
+  item->serial = NextRequest (GDK_WINDOW_XDISPLAY (window));
+  
+  g_queue_push_tail (display_x11->translate_queue, item);
+}
+
 static void
 gdk_window_queue_translation (GdkWindow *window,
                              gint       dx,
                              gint       dy)
 {
   GdkWindowQueueItem *item = g_new (GdkWindowQueueItem, 1);
-  item->window = window;
-  item->serial = NextRequest (GDK_WINDOW_XDISPLAY (window));
   item->type = GDK_WINDOW_QUEUE_TRANSLATE;
   item->u.translate.dx = dx;
   item->u.translate.dy = dy;
 
-  gdk_drawable_ref (window);
-  translate_queue = g_slist_append (translate_queue, item);
+  gdk_window_queue (window, item);
 }
 
 gboolean
@@ -776,13 +995,10 @@ _gdk_windowing_window_queue_antiexpose (GdkWindow *window,
                                        GdkRegion *area)
 {
   GdkWindowQueueItem *item = g_new (GdkWindowQueueItem, 1);
-  item->window = window;
-  item->serial = NextRequest (GDK_WINDOW_XDISPLAY (window));
   item->type = GDK_WINDOW_QUEUE_ANTIEXPOSE;
   item->u.antiexpose.area = area;
 
-  gdk_drawable_ref (window);
-  translate_queue = g_slist_append (translate_queue, item);
+  gdk_window_queue (window, item);
 
   return TRUE;
 }
@@ -795,37 +1011,34 @@ _gdk_window_process_expose (GdkWindow    *window,
   GdkWindowImplX11 *impl;
   GdkRegion *invalidate_region = gdk_region_rectangle (area);
   GdkRegion *clip_region;
-  GSList *tmp_list = translate_queue;
-  
+  GdkDisplayX11 *display_x11 = GDK_DISPLAY_X11 (GDK_WINDOW_DISPLAY (window));
   impl = GDK_WINDOW_IMPL_X11 (GDK_WINDOW_OBJECT (window)->impl);
-  
-  while (tmp_list)
+      
+  if (display_x11->translate_queue)
     {
-      GdkWindowQueueItem *item = tmp_list->data;
-      tmp_list = tmp_list->next;
-
-      if (serial < item->serial)
+      GList *tmp_list = display_x11->translate_queue->head;
+      
+      while (tmp_list)
        {
-         if (item->window == window)
+         GdkWindowQueueItem *item = tmp_list->data;
+         tmp_list = tmp_list->next;
+         
+         if (serial < item->serial)
            {
-             if (item->type == GDK_WINDOW_QUEUE_TRANSLATE)
-               gdk_region_offset (invalidate_region, item->u.translate.dx, item->u.translate.dy);
-             else              /* anti-expose */
-               gdk_region_subtract (invalidate_region, item->u.antiexpose.area);
+             if (item->window == window)
+               {
+                 if (item->type == GDK_WINDOW_QUEUE_TRANSLATE)
+                   gdk_region_offset (invalidate_region, item->u.translate.dx, item->u.translate.dy);
+                 else          /* anti-expose */
+                   gdk_region_subtract (invalidate_region, item->u.antiexpose.area);
+               }
+           }
+         else
+           {
+             queue_delete_link (display_x11->translate_queue, 
+                                display_x11->translate_queue->head);
+             queue_item_free (item);
            }
-       }
-      else
-       {
-         GSList *tmp_link = translate_queue;
-         
-         translate_queue = g_slist_remove_link (translate_queue, translate_queue);
-         gdk_drawable_unref (item->window);
-
-         if (item->type == GDK_WINDOW_QUEUE_ANTIEXPOSE)
-           gdk_region_destroy (item->u.antiexpose.area);
-         
-         g_free (item);
-         g_slist_free_1 (tmp_link);
        }
     }