gtk/gdk/x11/gdkgeometry-x11.c
Alexander Larsson eabac453e6 Initial client-side-windows work
The history before this was kind of twisted as several different
approaches were tested, so that was all squashed into this initial
commit to hide the uninteresting changes and files that were later
removed.
2009-04-02 10:14:01 +02:00

450 lines
13 KiB
C

/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* gdkgeometry-x11.c: emulation of 32 bit coordinates within the
* limits of X.
*
* 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 "config.h"
#include "gdk.h" /* For gdk_rectangle_intersect */
#include "gdkprivate-x11.h"
#include "gdkx.h"
#include "gdkregion.h"
#include "gdkinternals.h"
#include "gdkscreen-x11.h"
#include "gdkdisplay-x11.h"
#include "gdkwindow-x11.h"
#include "gdkalias.h"
typedef struct _GdkWindowQueueItem GdkWindowQueueItem;
typedef struct _GdkWindowParentPos GdkWindowParentPos;
typedef enum {
GDK_WINDOW_QUEUE_TRANSLATE,
GDK_WINDOW_QUEUE_ANTIEXPOSE
} GdkWindowQueueType;
struct _GdkWindowQueueItem
{
GdkWindow *window;
gulong serial;
GdkWindowQueueType type;
union {
struct {
GdkRegion *area;
gint dx;
gint dy;
} translate;
struct {
GdkRegion *area;
} antiexpose;
} u;
};
static void
move (GdkWindow *window, GdkRectangle *pos)
{
XMoveWindow (GDK_WINDOW_XDISPLAY (window),
GDK_WINDOW_XID (window), pos->x, pos->y);
}
static void
move_resize (GdkWindow *window, GdkRectangle *pos)
{
XMoveResizeWindow (GDK_WINDOW_XDISPLAY (window),
GDK_WINDOW_XID (window),
pos->x, pos->y, pos->width, pos->height);
}
void
_gdk_window_move_resize_child (GdkWindow *window,
gint x,
gint y,
gint width,
gint height)
{
GdkWindowImplX11 *impl;
GdkWindowObject *obj;
GdkRectangle new_info;
gint dx, dy;
gboolean is_move;
gboolean is_resize;
g_return_if_fail (window != NULL);
g_return_if_fail (GDK_IS_WINDOW (window));
impl = GDK_WINDOW_IMPL_X11 (GDK_WINDOW_OBJECT (window)->impl);
obj = GDK_WINDOW_OBJECT (window);
dx = x - obj->x;
dy = y - obj->y;
is_move = dx != 0 || dy != 0;
is_resize = obj->width != width || obj->height != height;
#if 0 /* Not right, doesn't take abs_x/y into consideration */
if (!is_move && !is_resize)
return;
#endif
obj->x = x;
obj->y = y;
obj->width = width;
obj->height = height;
new_info.x = obj->x + obj->parent->abs_x;
new_info.y = obj->y + obj->parent->abs_y;
new_info.width = obj->width;
new_info.height = obj->height;
if (is_resize)
move_resize (window, &new_info);
else
move (window, &new_info);
}
static 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)
{
if (item->window)
{
g_object_remove_weak_pointer (G_OBJECT (item->window),
(gpointer *)&(item->window));
}
if (item->type == GDK_WINDOW_QUEUE_ANTIEXPOSE)
gdk_region_destroy (item->u.antiexpose.area);
else
{
if (item->u.translate.area)
gdk_region_destroy (item->u.translate.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;
/* an overflow-safe (item->serial < serial) */
if (item->serial - serial > (gulong) G_MAXLONG)
{
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;
}
}
item->window = window;
item->serial = NextRequest (GDK_WINDOW_XDISPLAY (window));
g_object_add_weak_pointer (G_OBJECT (window),
(gpointer *)&(item->window));
g_queue_push_tail (display_x11->translate_queue, item);
}
void
_gdk_windowing_window_queue_translation (GdkWindow *window,
GdkRegion *area,
gint dx,
gint dy)
{
GdkWindowQueueItem *item = g_new (GdkWindowQueueItem, 1);
item->type = GDK_WINDOW_QUEUE_TRANSLATE;
item->u.translate.area = area ? gdk_region_copy (area) : NULL;
item->u.translate.dx = dx;
item->u.translate.dy = dy;
gdk_window_queue (window, item);
}
gboolean
_gdk_x11_window_queue_antiexpose (GdkWindow *window,
GdkRegion *area)
{
GdkWindowQueueItem *item = g_new (GdkWindowQueueItem, 1);
item->type = GDK_WINDOW_QUEUE_ANTIEXPOSE;
item->u.antiexpose.area = area;
gdk_window_queue (window, item);
return TRUE;
}
void
_gdk_window_process_expose (GdkWindow *window,
gulong serial,
GdkRectangle *area)
{
GdkWindowImplX11 *impl;
GdkRegion *invalidate_region = gdk_region_rectangle (area);
GdkDisplayX11 *display_x11 = GDK_DISPLAY_X11 (GDK_WINDOW_DISPLAY (window));
impl = GDK_WINDOW_IMPL_X11 (GDK_WINDOW_OBJECT (window)->impl);
if (display_x11->translate_queue)
{
GList *tmp_list = display_x11->translate_queue->head;
while (tmp_list)
{
GdkWindowQueueItem *item = tmp_list->data;
GList *next = tmp_list->next;
/* an overflow-safe (serial < item->serial) */
if (serial - item->serial > (gulong) G_MAXLONG)
{
if (item->window == window)
{
if (item->type == GDK_WINDOW_QUEUE_TRANSLATE)
{
if (item->u.translate.area)
{
GdkRegion *intersection;
intersection = gdk_region_copy (invalidate_region);
gdk_region_intersect (intersection, item->u.translate.area);
gdk_region_subtract (invalidate_region, intersection);
gdk_region_offset (intersection, item->u.translate.dx, item->u.translate.dy);
gdk_region_union (invalidate_region, intersection);
gdk_region_destroy (intersection);
}
else
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, tmp_list);
queue_item_free (item);
}
tmp_list = next;
}
}
if (!gdk_region_empty (invalidate_region))
_gdk_window_invalidate_for_expose (window, invalidate_region);
gdk_region_destroy (invalidate_region);
}
#define __GDK_GEOMETRY_X11_C__
#include "gdkaliasdef.c"