blob: c04f73f08fe4a8f52488155b7ab609686fd89abc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Red Hat and others. All rights reserved.
* The contents of this file are made available under the terms
* of the GNU Lesser General Public License (LGPL) Version 2.1 that
* accompanies this distribution (lgpl-v21.txt). The LGPL is also
* available at http://www.gnu.org/licenses/lgpl.html. If the version
* of the LGPL at http://www.gnu.org is different to the version of
* the LGPL accompanying this distribution and there is any conflict
* between the two license versions, the terms of the LGPL accompanying
* this distribution shall govern.
*
* Contributors:
* Red Hat - initial API and implementation
*******************************************************************************/
#include "os_custom.h"
struct _SwtFixedPrivate {
GtkAdjustment *hadjustment;
GtkAdjustment *vadjustment;
guint hscroll_policy : 1;
guint vscroll_policy : 1;
GList *children;
};
struct _SwtFixedChild
{
GtkWidget *widget;
gint x;
gint y;
gint width;
gint height;
};
typedef struct _SwtFixedChild SwtFixedChild;
enum {
PROP_0,
PROP_HADJUSTMENT,
PROP_VADJUSTMENT,
PROP_HSCROLL_POLICY,
PROP_VSCROLL_POLICY,
};
static void swt_fixed_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
static void swt_fixed_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
static void swt_fixed_finalize (GObject *object);
static void swt_fixed_realize (GtkWidget *widget);
static void swt_fixed_map (GtkWidget *widget);
static AtkObject *swt_fixed_get_accessible (GtkWidget *widget);
static void swt_fixed_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum,
int *natural, int *minimum_baseline, int *natural_baseline);
static void swt_fixed_size_allocate (GtkWidget *widget, const GtkAllocation *allocation, int baseline);
static void swt_fixed_add (GtkContainer *container, GtkWidget *widget);
static void swt_fixed_remove (GtkContainer *container, GtkWidget *widget);
static void swt_fixed_forall (GtkContainer *container, GtkCallback callback, gpointer callback_data);
G_DEFINE_TYPE_WITH_CODE (SwtFixed, swt_fixed, GTK_TYPE_CONTAINER, G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
static void swt_fixed_class_init (SwtFixedClass *class) {
GObjectClass *gobject_class = (GObjectClass*) class;
GtkWidgetClass *widget_class = (GtkWidgetClass*) class;
GtkContainerClass *container_class = (GtkContainerClass*) class;
/* GOject implementation */
gobject_class->set_property = swt_fixed_set_property;
gobject_class->get_property = swt_fixed_get_property;
gobject_class->finalize = swt_fixed_finalize;
/* Scrollable implemetation */
g_object_class_override_property (gobject_class, PROP_HADJUSTMENT, "hadjustment");
g_object_class_override_property (gobject_class, PROP_VADJUSTMENT, "vadjustment");
g_object_class_override_property (gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
g_object_class_override_property (gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
/* Widget implementation */
widget_class->realize = swt_fixed_realize;
widget_class->map = swt_fixed_map;
widget_class->measure = swt_fixed_measure;
widget_class->size_allocate = swt_fixed_size_allocate;
/* Container implementation */
container_class->add = swt_fixed_add;
container_class->remove = swt_fixed_remove;
container_class->forall = swt_fixed_forall;
g_type_class_add_private (class, sizeof (SwtFixedPrivate));
}
void swt_fixed_restack (SwtFixed *fixed, GtkWidget *widget, GtkWidget *sibling, gboolean above) {
SwtFixedPrivate *priv = fixed->priv;
GList *list;
SwtFixedChild *child, *sibling_child;
list = priv->children;
while (list) {
child = list->data;
if (child->widget == widget) break;
list = list->next;
}
if (!list) return;
priv->children = g_list_remove_link (priv->children, list);
g_list_free_1 (list);
list = NULL;
if (sibling) {
list = priv->children;
while (list) {
sibling_child = list->data;
if (sibling_child->widget == sibling) {
break;
}
list = list->next;
}
if (list) {
if (!above) list = list->next;
}
}
if (!list) {
list = above ? priv->children : NULL;
}
priv->children = g_list_insert_before (priv->children, list, child);
/*
{
GdkWindow *sibling_window = NULL;
if (list) {
child = list->data;
sibling_window = gtk_widget_get_window (child);
}
gdk_window_restack (gtk_widget_get_window (widget), sibling_window, above);
}
*/
}
GtkWidget *swt_fixed_new(void) {
return GTK_WIDGET(g_object_new(SWT_TYPE_FIXED, NULL));
}
static void swt_fixed_init (SwtFixed *widget) {
SwtFixedPrivate *priv;
priv = widget->priv = G_TYPE_INSTANCE_GET_PRIVATE (widget, SWT_TYPE_FIXED, SwtFixedPrivate);
priv->children = NULL;
priv->hadjustment = NULL;
priv->vadjustment = NULL;
gtk_widget_set_has_surface(GTK_WIDGET(widget), TRUE);
}
static void swt_fixed_finalize (GObject *object) {
SwtFixed *widget = SWT_FIXED (object);
SwtFixedPrivate *priv = widget->priv;
g_object_unref (priv->hadjustment);
g_object_unref (priv->vadjustment);
G_OBJECT_CLASS (swt_fixed_parent_class)->finalize (object);
}
static void swt_fixed_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
SwtFixed *widget = SWT_FIXED (object);
SwtFixedPrivate *priv = widget->priv;
switch (prop_id) {
case PROP_HADJUSTMENT:
g_value_set_object (value, priv->hadjustment);
break;
case PROP_VADJUSTMENT:
g_value_set_object (value, priv->vadjustment);
break;
case PROP_HSCROLL_POLICY:
g_value_set_enum (value, priv->hscroll_policy);
break;
case PROP_VSCROLL_POLICY:
g_value_set_enum (value, priv->vscroll_policy);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void swt_fixed_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
SwtFixed *widget = SWT_FIXED (object);
SwtFixedPrivate *priv = widget->priv;
GtkAdjustment *adjustment;
switch (prop_id) {
case PROP_HADJUSTMENT:
adjustment = g_value_get_object (value);
if (adjustment && priv->hadjustment == adjustment) return;
if (priv->hadjustment != NULL) g_object_unref (priv->hadjustment);
if (adjustment == NULL) adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
priv->hadjustment = g_object_ref_sink (adjustment);
g_object_notify (G_OBJECT (widget), "hadjustment");
break;
case PROP_VADJUSTMENT:
adjustment = g_value_get_object (value);
if (adjustment && priv->vadjustment == adjustment) return;
if (priv->vadjustment != NULL) g_object_unref (priv->vadjustment);
if (adjustment == NULL) adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
priv->vadjustment = g_object_ref_sink (adjustment);
g_object_notify (G_OBJECT (widget), "vadjustment");
break;
case PROP_HSCROLL_POLICY:
priv->hscroll_policy = g_value_get_enum (value);
break;
case PROP_VSCROLL_POLICY:
priv->vscroll_policy = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void swt_fixed_realize (GtkWidget *widget) {
SwtFixed *fixed = SWT_FIXED (widget);
SwtFixedPrivate *priv = fixed->priv;
GtkAllocation allocation;
GdkSurface *surface;
if (!gtk_widget_get_has_surface (widget)) {
GTK_WIDGET_CLASS (swt_fixed_parent_class)->realize (widget);
return;
}
gtk_widget_get_allocation (widget, &allocation);
surface = gdk_surface_new_child (gtk_widget_get_parent_surface (widget), &allocation);
gtk_widget_set_surface(widget, surface);
gdk_surface_set_user_data (surface, widget);
return GTK_WIDGET_CLASS (swt_fixed_parent_class)->realize (widget);
}
static void swt_fixed_map (GtkWidget *widget) {
SwtFixed *fixed = SWT_FIXED (widget);
SwtFixedPrivate *priv = fixed->priv;
GList *list;
list = priv->children;
while (list) {
SwtFixedChild *child_data = list->data;
GtkWidget *child = child_data->widget;
list = list->next;
if (gtk_widget_get_visible (child)) {
if (!gtk_widget_get_mapped (child)) gtk_widget_map (child);
}
}
if (gtk_widget_get_has_surface (widget)) {
//NOTE: contrary to most of GTK, swt_fixed_* container does not raise windows upon showing them.
//This has the effect that widgets are drawn *beneath* the previous one.
//E.g if this line is changed to gdk_window_show (..) then widgets are drawn on top of the previous one.
//This affects mostly only the absolute layout with overlapping widgets, e.g minimizied panels that
//pop-out in Eclipse (aka fast-view).
//As such, be attentive to swt_fixed_forall(..); traversing children may need to be done in reverse in some
//cases.
gdk_surface_show_unraised (gtk_widget_get_surface (widget));
}
return GTK_WIDGET_CLASS (swt_fixed_parent_class)->map (widget);
}
static void swt_fixed_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum,
int *natural, int *minimum_baseline, int *natural_baseline) {
natural = 0;
natural_baseline = 0;
minimum = 0;
minimum_baseline = 0;
return;
}
static void swt_fixed_size_allocate (GtkWidget *widget, const GtkAllocation *allocation, int baseline) {
SwtFixed *fixed = SWT_FIXED (widget);
SwtFixedPrivate *priv = fixed->priv;
GList *list;
GtkAllocation child_allocation;
GtkRequisition requisition;
gint w, h;
if (gtk_widget_get_has_surface (widget)) {
if (gtk_widget_get_realized (widget)) {
gdk_surface_move_resize (gtk_widget_get_surface (widget), allocation->x, allocation->y, allocation->width, allocation->height);
}
}
list = priv->children;
while (list) {
SwtFixedChild *child_data = list->data;
GtkWidget *child = child_data->widget;
list = list->next;
child_allocation.x = child_data->x;
child_allocation.y = child_data->y;
if (!gtk_widget_get_has_surface (widget)) {
child_allocation.x += allocation->x;
child_allocation.y += allocation->y;
}
w = child_data->width;
h = child_data->height;
if (w == -1 || h == -1) {
gtk_widget_get_preferred_size (child, &requisition, NULL);
if (w == -1) w = requisition.width;
if (h == -1) h = requisition.height;
}
// Feature in GTK: gtk_widget_preferred_size() has to be called before
// gtk_widget_size_allocate otherwise a warning is thrown. See Bug 486068.
gtk_widget_get_preferred_size (child, &requisition, NULL);
child_allocation.width = w;
child_allocation.height = h;
gtk_widget_size_allocate (child, &child_allocation, -1);
}
}
void swt_fixed_move (SwtFixed *fixed, GtkWidget *widget, gint x, gint y) {
SwtFixedPrivate *priv = fixed->priv;
GList *list;
list = priv->children;
while (list) {
SwtFixedChild *child_data = list->data;
GtkWidget *child = child_data->widget;
if (child == widget) {
child_data->x = x;
child_data->y = y;
break;
}
list = list->next;
}
}
void swt_fixed_resize (SwtFixed *fixed, GtkWidget *widget, gint width, gint height) {
SwtFixedPrivate *priv = fixed->priv;
GList *list;
list = priv->children;
while (list) {
SwtFixedChild *child_data = list->data;
GtkWidget *child = child_data->widget;
if (child == widget) {
child_data->width = width;
child_data->height = height;
/*
* Feature in GTK: sometimes the sizing of child SwtFixed widgets
* does not happen quickly enough, causing miscalculations in SWT.
* Allocate the size of the child directly when swt_fixed_resize()
* is called. See bug 487160.
*/
GtkAllocation allocation, to_allocate;
GtkRequisition req;
gtk_widget_get_allocation(child, &allocation);
// Keep x and y values the same to prevent misplaced containers
to_allocate.x = allocation.x;
to_allocate.y = allocation.y;
to_allocate.width = width;
to_allocate.height = height;
// Call gtk_widget_get_preferred_size() and finish the allocation.
gtk_widget_get_preferred_size (child, &req, NULL);
gtk_widget_size_allocate(child, &to_allocate, -1);
break;
}
list = list->next;
}
}
static void swt_fixed_add (GtkContainer *container, GtkWidget *child) {
GtkWidget *widget = GTK_WIDGET (container);
SwtFixed *fixed = SWT_FIXED (container);
SwtFixedPrivate *priv = fixed->priv;
SwtFixedChild *child_data;
child_data = g_new (SwtFixedChild, 1);
child_data->widget = child;
child_data->x = child_data->y = 0;
child_data->width = child_data->height = -1;
priv->children = g_list_append (priv->children, child_data);
gtk_widget_set_parent (child, widget);
}
static void swt_fixed_remove (GtkContainer *container, GtkWidget *widget) {
SwtFixed *fixed = SWT_FIXED (container);
SwtFixedPrivate *priv = fixed->priv;
GList *list;
list = priv->children;
while (list) {
SwtFixedChild *child_data = list->data;
GtkWidget *child = child_data->widget;
if (child == widget) {
gtk_widget_unparent (widget);
priv->children = g_list_remove_link (priv->children, list);
g_list_free_1 (list);
g_free (child_data);
break;
}
list = list->next;
}
}
static void swt_fixed_forall (GtkContainer *container, GtkCallback callback, gpointer callback_data) {
SwtFixed *fixed = SWT_FIXED (container);
SwtFixedPrivate *priv = fixed->priv;
GList *list;
list = priv->children;
// NOTE: The direction of the list traversal is conditional.
//
// 1) When we do a *_foreach() traversal (i.e, include_internals==FALSE), we traverse the list as normal
// from front to back.
// This is used to layout higher level widgets inside containers (e.g row/grid etc..) in the expected way.
// If for a non-internal traversal we were to go in reverse, then widgets would get laid out in inverse order.
// 2) When we do a *_forall() traversal (i.e, include_internals==TRUE), we traverse the list in *reverse* order.
// This is an internal traversal of the internals of a widget. Reverse traversal is necessary for things like
// DnD Drop and DnD Motion events to find the correct widget in the case of overlapping widgets on an absolute layout.
// Reversal is required because in swt_fixed_map(..) we do not raise the widget when we show it, as a result
// the stack is in reverse.
while (list) {
SwtFixedChild *child_data = list->data;
GtkWidget *child = child_data->widget;
list = list->next;
(* callback) (child, callback_data);
}
}