blob: 36253d2eceaf3016aa3625efc15f58dc34c5a598 [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"
#ifndef NO_SwtFixed
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 void swt_fixed_get_preferred_width(GtkWidget *widget, gint *minimum,
gint *natural);
static void swt_fixed_get_preferred_height(GtkWidget *widget, gint *minimum,
gint *natural);
static void swt_fixed_size_allocate(GtkWidget *widget,
GtkAllocation *allocation);
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,
gboolean include_internals, 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->get_preferred_width = swt_fixed_get_preferred_width;
widget_class->get_preferred_height = swt_fixed_get_preferred_height;
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;
}
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) {
GtkAllocation allocation;
GdkWindow *window;
GdkWindowAttr attributes;
gint attributes_mask;
if (!gtk_widget_get_has_window(widget)) {
GTK_WIDGET_CLASS(swt_fixed_parent_class)->realize(widget);
return;
}
gtk_widget_set_realized(widget, TRUE);
gtk_widget_get_allocation(widget, &allocation);
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = allocation.x;
attributes.y = allocation.y;
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual(widget);
attributes.event_mask = GDK_EXPOSURE_MASK | GDK_SCROLL_MASK | 1 << 23 /*GDK_SMOOTH_SCROLL_MASK*/
| gtk_widget_get_events(widget);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes,
attributes_mask);
gtk_widget_set_window(widget, window);
gdk_window_set_user_data(window, widget);
if (NULL != gtk_check_version(3, 18, 0)) {
gtk_style_context_set_background(gtk_widget_get_style_context(widget),
window);
}
}
static void swt_fixed_map(GtkWidget *widget) {
SwtFixed *fixed = SWT_FIXED(widget);
SwtFixedPrivate *priv = fixed->priv;
GList *list;
gtk_widget_set_mapped(widget, TRUE);
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_window(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_window_show_unraised(gtk_widget_get_window(widget));
}
}
static void swt_fixed_get_preferred_width(GtkWidget *widget, gint *minimum,
gint *natural) {
if (minimum)
*minimum = 0;
if (natural)
*natural = 0;
}
static void swt_fixed_get_preferred_height(GtkWidget *widget, gint *minimum,
gint *natural) {
if (minimum)
*minimum = 0;
if (natural)
*natural = 0;
}
static void swt_fixed_size_allocate(GtkWidget *widget,
GtkAllocation *allocation) {
SwtFixed *fixed = SWT_FIXED(widget);
SwtFixedPrivate *priv = fixed->priv;
GList *list;
GtkAllocation child_allocation;
GtkRequisition requisition;
gint w, h;
gtk_widget_set_allocation(widget, allocation);
if (gtk_widget_get_has_window(widget)) {
if (gtk_widget_get_realized(widget)) {
gdk_window_move_resize(gtk_widget_get_window(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_window(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);
}
}
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);
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,
gboolean include_internals, 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.
if (include_internals)
list = g_list_last(list);
while (list) {
SwtFixedChild *child_data = list->data;
GtkWidget *child = child_data->widget;
if (include_internals)
list = list->prev;
else
list = list->next;
(*callback)(child, callback_data);
}
}
#endif