diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 2f05cbbdd7..c8bbf2d9e3 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -302,6 +302,8 @@ gtk_public_h_sources = \ gtkstylecontext.h \ gtkstyleprovider.h \ gtkswitch.h \ + gtktab.h \ + gtktabstrip.h \ gtktestutils.h \ gtktextattributes.h \ gtktextbuffer.h \ @@ -900,6 +902,8 @@ gtk_base_c_sources = \ gtkstyleprovider.c \ gtkstyleproviderprivate.c \ gtkswitch.c \ + gtktab.c \ + gtktabstrip.c \ gtktestutils.c \ gtktextattributes.c \ gtktextbtree.c \ diff --git a/gtk/gtk.h b/gtk/gtk.h index a7233dadd1..216eea52a1 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -203,6 +203,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/gtk/gtktab.c b/gtk/gtktab.c new file mode 100644 index 0000000000..6a0bf20797 --- /dev/null +++ b/gtk/gtktab.c @@ -0,0 +1,519 @@ +/* gtktab.c + * + * Copyright (C) 2016 Red Hat Inc. + * + * 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 program 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, see . + */ + +#include "config.h" + +#include "gtktab.h" +#include "gtkintl.h" +#include "gtkprivate.h" +#include "gtkenums.h" +#include "gtktypebuiltins.h" +#include "gtkboxgadgetprivate.h" +#include "gtkwidgetprivate.h" + +typedef struct _GtkTabPrivate GtkTabPrivate; + +struct _GtkTabPrivate +{ + gchar *title; + GtkWidget *widget; + + GtkWidget *child; + GtkCssGadget *gadget; + GdkWindow *event_window; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GtkTab, gtk_tab, GTK_TYPE_CONTAINER) + +enum { + PROP_0, + PROP_TITLE, + PROP_WIDGET, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +enum { + ACTIVATE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static void +gtk_tab_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkTab *self = GTK_TAB (object); + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, gtk_tab_get_title (self)); + break; + + case PROP_WIDGET: + g_value_set_object (value, gtk_tab_get_widget (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_tab_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkTab *self = GTK_TAB (object); + + switch (prop_id) + { + case PROP_TITLE: + gtk_tab_set_title (self, g_value_get_string (value)); + break; + + case PROP_WIDGET: + gtk_tab_set_widget (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_tab_finalize (GObject *object) +{ + GtkTab *self = GTK_TAB (object); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + g_free (priv->title); + + g_clear_object (&priv->gadget); + + G_OBJECT_CLASS (gtk_tab_parent_class)->finalize (object); +} + +static void +gtk_tab_destroy (GtkWidget *widget) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + if (priv->widget) + { + g_object_remove_weak_pointer (G_OBJECT (priv->widget), (gpointer *)&priv->widget); + priv->widget = NULL; + } + + GTK_WIDGET_CLASS (gtk_tab_parent_class)->destroy (widget); +} + +static void +gtk_tab_realize (GtkWidget *widget) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + GtkAllocation allocation; + + gtk_widget_set_realized (widget, TRUE); + + gtk_widget_get_allocation (widget, &allocation); + + window = gtk_widget_get_parent_window (widget); + gtk_widget_set_window (widget, window); + g_object_ref (window); + + 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_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= (GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | + GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + attributes_mask = GDK_WA_X | GDK_WA_Y; + + priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gtk_widget_register_window (widget, priv->event_window); +} + +static void +gtk_tab_unrealize (GtkWidget *widget) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + gtk_widget_unregister_window (widget, priv->event_window); + gdk_window_destroy (priv->event_window); + priv->event_window = NULL; + + GTK_WIDGET_CLASS (gtk_tab_parent_class)->unrealize (widget); +} + +static void +gtk_tab_map (GtkWidget *widget) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + GTK_WIDGET_CLASS (gtk_tab_parent_class)->map (widget); + + gdk_window_show_unraised (priv->event_window); +} + +static void +gtk_tab_unmap (GtkWidget *widget) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + gdk_window_hide (priv->event_window); + + GTK_WIDGET_CLASS (gtk_tab_parent_class)->unmap (widget); +} + +static gboolean +gtk_tab_enter (GtkWidget *widget, + GdkEventCrossing *event) +{ + gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE); + + return TRUE; +} + +static gboolean +gtk_tab_leave (GtkWidget *widget, + GdkEventCrossing *event) +{ + gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT); + + return TRUE; +} + +static gboolean +gtk_tab_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + if (event->button != GDK_BUTTON_PRIMARY) + return FALSE; + + g_signal_emit (widget, signals[ACTIVATE], 0); + + return TRUE; +} + +static void +gtk_tab_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + gtk_css_gadget_get_preferred_size (priv->gadget, + GTK_ORIENTATION_HORIZONTAL, + -1, + minimum, natural, + NULL, NULL); +} + +static void +gtk_tab_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + gtk_css_gadget_get_preferred_size (priv->gadget, + GTK_ORIENTATION_VERTICAL, + -1, + minimum, natural, + NULL, NULL); +} + +static void +gtk_tab_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum, + gint *natural) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + gtk_css_gadget_get_preferred_size (priv->gadget, + GTK_ORIENTATION_HORIZONTAL, + height, + minimum, natural, + NULL, NULL); +} + +static void +gtk_tab_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum, + gint *natural) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + gtk_css_gadget_get_preferred_size (priv->gadget, + GTK_ORIENTATION_VERTICAL, + width, + minimum, natural, + NULL, NULL); +} + +static void +gtk_tab_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + GtkAllocation clip; + + gtk_widget_set_allocation (widget, allocation); + + gtk_css_gadget_allocate (priv->gadget, + allocation, + gtk_widget_get_allocated_baseline (widget), + &clip); + + gtk_widget_set_clip (widget, &clip); + + if (gtk_widget_get_realized (widget)) + { + GtkAllocation border_allocation; + + gtk_css_gadget_get_border_allocation (priv->gadget, &border_allocation, NULL); + gdk_window_move_resize (priv->event_window, + border_allocation.x, border_allocation.y, + border_allocation.width, border_allocation.height); + if (gtk_widget_get_mapped (widget)) + gdk_window_show_unraised (priv->event_window); + } +} + +static gboolean +gtk_tab_draw (GtkWidget *widget, + cairo_t *cr) +{ + GtkTab *self = GTK_TAB (widget); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + gtk_css_gadget_draw (priv->gadget, cr); + + return FALSE; +} + +static void +gtk_tab_add (GtkContainer *container, + GtkWidget *child) +{ + GtkTab *self = GTK_TAB (container); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + if (priv->child) + { + g_warning ("GtkTab cannot have more than one child"); + return; + } + + priv->child = child; + gtk_widget_set_parent (child, GTK_WIDGET (container)); + gtk_box_gadget_insert_widget (GTK_BOX_GADGET (priv->gadget), 0, child); + gtk_box_gadget_set_gadget_expand (GTK_BOX_GADGET (priv->gadget), G_OBJECT (child), TRUE); +} + +static void +gtk_tab_remove (GtkContainer *container, + GtkWidget *child) +{ + GtkTab *self = GTK_TAB (container); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + if (priv->child == child) + { + gtk_box_gadget_remove_widget (GTK_BOX_GADGET (priv->gadget), child); + gtk_widget_unparent (child); + priv->child = NULL; + } +} + +static void +gtk_tab_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer data) +{ + GtkTab *self = GTK_TAB (container); + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + if (priv->child) + (*callback) (priv->child, data); +} + +static void +gtk_tab_class_init (GtkTabClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->get_property = gtk_tab_get_property; + object_class->set_property = gtk_tab_set_property; + object_class->finalize = gtk_tab_finalize; + + widget_class->destroy = gtk_tab_destroy; + widget_class->realize = gtk_tab_realize; + widget_class->unrealize = gtk_tab_unrealize; + widget_class->map = gtk_tab_map; + widget_class->unmap = gtk_tab_unmap; + widget_class->enter_notify_event = gtk_tab_enter; + widget_class->leave_notify_event = gtk_tab_leave; + widget_class->button_press_event = gtk_tab_button_press; + widget_class->get_preferred_width = gtk_tab_get_preferred_width; + widget_class->get_preferred_height = gtk_tab_get_preferred_height; + widget_class->get_preferred_width_for_height = gtk_tab_get_preferred_width_for_height; + widget_class->get_preferred_height_for_width = gtk_tab_get_preferred_height_for_width; + widget_class->size_allocate = gtk_tab_size_allocate; + widget_class->draw = gtk_tab_draw; + + container_class->add = gtk_tab_add; + container_class->remove = gtk_tab_remove; + container_class->forall = gtk_tab_forall; + + gtk_widget_class_set_css_name (widget_class, "tab"); + + properties[PROP_TITLE] = + g_param_spec_string ("title", P_("Title"), P_("Title"), + NULL, + GTK_PARAM_READWRITE); + + properties[PROP_WIDGET] = + g_param_spec_object ("widget", P_("Widget"), P_("The widget the tab represents"), + GTK_TYPE_WIDGET, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + signals[ACTIVATE] = + g_signal_new (I_("activate"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTabClass, activate), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + widget_class->activate_signal = signals[ACTIVATE]; + +} + +static void +gtk_tab_init (GtkTab *self) +{ + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + GtkCssNode *widget_node; + + gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE); + gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); + + widget_node = gtk_widget_get_css_node (GTK_WIDGET (self)); + priv->gadget = gtk_box_gadget_new_for_node (widget_node, GTK_WIDGET (self)); + gtk_box_gadget_set_draw_focus (GTK_BOX_GADGET (priv->gadget), TRUE); +} + +const gchar * +gtk_tab_get_title (GtkTab *self) +{ + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TAB (self), NULL); + + return priv->title; +} + +void +gtk_tab_set_title (GtkTab *self, + const gchar *title) +{ + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + g_return_if_fail (GTK_IS_TAB (self)); + + g_free (priv->title); + priv->title = g_strdup (title); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +GtkWidget * +gtk_tab_get_widget (GtkTab *self) +{ + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TAB (self), NULL); + + return priv->widget; +} + +void +gtk_tab_set_widget (GtkTab *self, + GtkWidget *widget) +{ + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + g_return_if_fail (GTK_IS_TAB (self)); + + if (priv->widget == widget) + return; + + if (priv->widget) + g_object_remove_weak_pointer (G_OBJECT (priv->widget), (gpointer *)&priv->widget); + + priv->widget = widget; + + if (widget) + g_object_add_weak_pointer (G_OBJECT (priv->widget), (gpointer *)&priv->widget); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WIDGET]); +} + +void +gtk_tab_set_child (GtkTab *self, + GtkWidget *child) +{ + g_return_if_fail (GTK_IS_TAB (self)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + gtk_tab_add (GTK_CONTAINER (self), child); +} diff --git a/gtk/gtktab.h b/gtk/gtktab.h new file mode 100644 index 0000000000..af5741e329 --- /dev/null +++ b/gtk/gtktab.h @@ -0,0 +1,75 @@ +/* gtktab.h + * + * Copyright (C) 2016 Red Hat Inc. + * + * 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 program 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, see . + */ + +#ifndef __GTK_TAB_H__ +#define __GTK_TAB_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TAB (gtk_tab_get_type ()) +#define GTK_TAB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TAB, GtkTab)) +#define GTK_IS_TAB(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TAB)) +#define GTK_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TAB, GtkTabClass)) +#define GTK_IS_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TAB)) +#define GTK_TAB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TAB, GtkTabClass)) + +typedef struct _GtkTab GtkTab; +typedef struct _GtkTabClass GtkTabClass; + +struct _GtkTab +{ + GtkContainer parent; +}; + +struct _GtkTabClass +{ + GtkContainerClass parent_class; + + void (* activate) (GtkTab *tab); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); +}; + +GDK_AVAILABLE_IN_3_22 +GType gtk_tab_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_3_22 +const gchar *gtk_tab_get_title (GtkTab *self); +GDK_AVAILABLE_IN_3_22 +void gtk_tab_set_title (GtkTab *self, + const gchar *title); +GDK_AVAILABLE_IN_3_22 +GtkWidget *gtk_tab_get_widget (GtkTab *self); +GDK_AVAILABLE_IN_3_22 +void gtk_tab_set_widget (GtkTab *self, + GtkWidget *widget); +GDK_AVAILABLE_IN_3_22 +void gtk_tab_set_child (GtkTab *self, + GtkWidget *child); + +G_END_DECLS + +#endif /* __GTK_TAB_H__ */ diff --git a/gtk/gtktabstrip.c b/gtk/gtktabstrip.c new file mode 100644 index 0000000000..db21a3afac --- /dev/null +++ b/gtk/gtktabstrip.c @@ -0,0 +1,546 @@ +/* gtktabstrip.c + * + * Copyright (C) 2016 Red Hat Inc. + * + * 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 program 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, see . + */ + +#include "config.h" + +#include "gtktabstrip.h" +#include "gtktab.h" +#include "gtksimpletab.h" +#include "gtkintl.h" +#include "gtkprivate.h" +#include "gtkorientable.h" + +typedef struct +{ + GtkStack *stack; + gboolean in_child_changed; + GdkWindow *event_window; +} GtkTabStripPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GtkTabStrip, gtk_tab_strip, GTK_TYPE_BOX) + +enum { + PROP_0, + PROP_STACK, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static void +gtk_tab_strip_add (GtkContainer *container, + GtkWidget *widget) +{ + GTK_CONTAINER_CLASS (gtk_tab_strip_parent_class)->add (container, widget); +} + +static void +gtk_tab_strip_destroy (GtkWidget *widget) +{ + GtkTabStrip *self = GTK_TAB_STRIP (widget); + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + gtk_tab_strip_set_stack (self, NULL); + + g_clear_object (&priv->stack); + + GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->destroy (widget); +} + +static void +gtk_tab_strip_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkTabStrip *self = GTK_TAB_STRIP (object); + + switch (prop_id) + { + case PROP_STACK: + g_value_set_object (value, gtk_tab_strip_get_stack (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_tab_strip_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkTabStrip *self = GTK_TAB_STRIP (object); + + switch (prop_id) + { + case PROP_STACK: + gtk_tab_strip_set_stack (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static gboolean +get_widget_coordinates (GtkWidget *widget, + GdkEvent *event, + gdouble *x, + gdouble *y) +{ + GdkWindow *window = ((GdkEventAny *)event)->window; + gdouble tx, ty; + + if (!gdk_event_get_coords (event, &tx, &ty)) + return FALSE; + + while (window && window != gtk_widget_get_window (widget)) + { + gint window_x, window_y; + + gdk_window_get_position (window, &window_x, &window_y); + tx += window_x; + ty += window_y; + + window = gdk_window_get_parent (window); + } + + if (window) + { + *x = tx; + *y = ty; + + return TRUE; + } + else + return FALSE; +} + +static GtkTab * +get_tab_at_pos (GtkTabStrip *self, + gdouble x, + gdouble y) +{ + GtkAllocation allocation; + GList *children, *l; + GtkTab *tab; + + children = gtk_container_get_children (GTK_CONTAINER (self)); + + tab = NULL; + for (l = children; l; l = l->next) + { + gtk_widget_get_allocation (GTK_WIDGET (l->data), &allocation); + if ((x >= allocation.x) && + (y >= allocation.y) && + (x <= (allocation.x + allocation.width)) && + (y <= (allocation.y + allocation.height))) + { + tab = l->data; + break; + } + } + + g_list_free (children); + + return tab; +} + +static gboolean +gtk_tab_strip_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkTabStrip *self = GTK_TAB_STRIP (widget); + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + GtkTab *tab; + GtkWidget *child; + gdouble x, y; + + if (!get_widget_coordinates (widget, (GdkEvent *)event, &x, &y)) + return FALSE; + + if (event->button != GDK_BUTTON_PRIMARY) + return FALSE; + + tab = get_tab_at_pos (self, x, y); + + if (tab == NULL) + return FALSE; + + child = gtk_tab_get_widget (tab); + if (child == NULL) + return FALSE; + + gtk_stack_set_visible_child (priv->stack, child); + + return TRUE; +} + +static void +gtk_tab_strip_map (GtkWidget *widget) +{ + GtkTabStrip *self = GTK_TAB_STRIP (widget); + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->map (widget); + + gdk_window_show_unraised (priv->event_window); +} + +static void +gtk_tab_strip_unmap (GtkWidget *widget) +{ + GtkTabStrip *self = GTK_TAB_STRIP (widget); + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + gdk_window_hide (priv->event_window); + + GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->unmap (widget); +} + +static void +gtk_tab_strip_realize (GtkWidget *widget) +{ + GtkTabStrip *self = GTK_TAB_STRIP (widget); + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + GtkAllocation allocation; + + gtk_widget_set_realized (widget, TRUE); + + gtk_widget_get_allocation (widget, &allocation); + + window = gtk_widget_get_parent_window (widget); + gtk_widget_set_window (widget, window); + g_object_ref (window); + + 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_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= (GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | + GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + attributes_mask = GDK_WA_X | GDK_WA_Y; + + priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gtk_widget_register_window (widget, priv->event_window); +} + +static void +gtk_tab_strip_unrealize (GtkWidget *widget) +{ + GtkTabStrip *self = GTK_TAB_STRIP (widget); + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + gtk_widget_unregister_window (widget, priv->event_window); + gdk_window_destroy (priv->event_window); + priv->event_window = NULL; + + GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->unrealize (widget); +} + +static void +gtk_tab_strip_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkTabStrip *self = GTK_TAB_STRIP (widget); + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->size_allocate (widget, allocation); + + if (gtk_widget_get_realized (widget)) + { + gdk_window_move_resize (priv->event_window, + allocation->x, allocation->y, + allocation->width, allocation->height); + if (gtk_widget_get_mapped (widget)) + gdk_window_show_unraised (priv->event_window); + } +} + +static void +gtk_tab_strip_class_init (GtkTabStripClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->get_property = gtk_tab_strip_get_property; + object_class->set_property = gtk_tab_strip_set_property; + + widget_class->destroy = gtk_tab_strip_destroy; + widget_class->map = gtk_tab_strip_map; + widget_class->unmap = gtk_tab_strip_unmap; + widget_class->realize = gtk_tab_strip_realize; + widget_class->unrealize = gtk_tab_strip_unrealize; + widget_class->size_allocate = gtk_tab_strip_size_allocate; + widget_class->button_press_event = gtk_tab_strip_button_press; + + container_class->add = gtk_tab_strip_add; + + properties[PROP_STACK] = + g_param_spec_object ("stack", P_("Stack"), P_("The stack of items to manage"), + GTK_TYPE_STACK, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, "tabs"); +} + +static void +gtk_tab_strip_init (GtkTabStrip *self) +{ +} + +static void +gtk_tab_strip_child_position_changed (GtkTabStrip *self, + GParamSpec *pspec, + GtkWidget *child) +{ + GtkWidget *parent; + GtkTab *tab; + guint position; + + tab = g_object_get_data (G_OBJECT (child), "GTK_TAB"); + + if (!tab || !GTK_IS_TAB (tab)) + return; + + parent = gtk_widget_get_parent (child); + + gtk_container_child_get (GTK_CONTAINER (parent), child, + "position", &position, + NULL); + + gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (tab), + "position", position, + NULL); +} + +static void +gtk_tab_strip_child_title_changed (GtkTabStrip *self, + GParamSpec *pspec, + GtkWidget *child) +{ + g_autofree gchar *title = NULL; + GtkWidget *parent; + GtkTab *tab; + + tab = g_object_get_data (G_OBJECT (child), "GTK_TAB"); + + if (!GTK_IS_TAB (tab)) + return; + + parent = gtk_widget_get_parent (child); + + gtk_container_child_get (GTK_CONTAINER (parent), child, + "title", &title, + NULL); + + gtk_tab_set_title (tab, title); +} + +static void +update_visible_child (GtkWidget *tab, + gpointer user_data) +{ + GtkWidget *visible_child = user_data; + + if (GTK_IS_TAB (tab)) + { + if (gtk_tab_get_widget (GTK_TAB (tab)) == visible_child) + gtk_widget_set_state_flags (tab, GTK_STATE_FLAG_CHECKED, FALSE); + else + gtk_widget_unset_state_flags (tab, GTK_STATE_FLAG_CHECKED); + } +} + +static void +gtk_tab_strip_stack_notify_visible_child (GtkTabStrip *self, + GParamSpec *pspec, + GtkStack *stack) +{ + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + GtkWidget *visible_child; + + visible_child = gtk_stack_get_visible_child (stack); + + priv->in_child_changed = TRUE; + gtk_container_foreach (GTK_CONTAINER (self), update_visible_child, visible_child); + priv->in_child_changed = FALSE; +} + +static void +tab_activated (GtkTab *tab, + GtkTabStrip *self) +{ + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + GtkWidget *widget; + + widget = gtk_tab_get_widget (tab); + if (widget) + gtk_stack_set_visible_child (priv->stack, widget); +} + +static void +gtk_tab_strip_stack_add (GtkTabStrip *self, + GtkWidget *widget, + GtkStack *stack) +{ + GtkTab *tab; + gint position = 0; + + gtk_container_child_get (GTK_CONTAINER (stack), widget, + "position", &position, + NULL); + + tab = g_object_new (GTK_TYPE_SIMPLE_TAB, + "widget", widget, + NULL); + + g_object_set_data (G_OBJECT (widget), "GTK_TAB", tab); + + g_signal_connect (tab, "activate", + G_CALLBACK (tab_activated), self); + + g_signal_connect_object (widget, "child-notify::position", + G_CALLBACK (gtk_tab_strip_child_position_changed), self, + G_CONNECT_SWAPPED); + + g_signal_connect_object (widget, "child-notify::title", + G_CALLBACK (gtk_tab_strip_child_title_changed), self, + G_CONNECT_SWAPPED); + + gtk_box_pack_start (GTK_BOX (self), GTK_WIDGET (tab), TRUE, TRUE, 0); + + g_object_bind_property (widget, "visible", tab, "visible", G_BINDING_SYNC_CREATE); + + gtk_tab_strip_child_title_changed (self, NULL, widget); + gtk_tab_strip_stack_notify_visible_child (self, NULL, stack); +} + +static void +gtk_tab_strip_stack_remove (GtkTabStrip *self, + GtkWidget *widget, + GtkStack *stack) +{ + GtkTab *tab; + + tab = g_object_get_data (G_OBJECT (widget), "GTK_TAB"); + + if (GTK_IS_TAB (tab)) + gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (tab)); +} + +GtkWidget * +gtk_tab_strip_new (void) +{ + return g_object_new (GTK_TYPE_TAB_STRIP, NULL); +} + +GtkStack * +gtk_tab_strip_get_stack (GtkTabStrip *self) +{ + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TAB_STRIP (self), NULL); + + return priv->stack; +} + +static void +gtk_tab_strip_cold_plug (GtkWidget *widget, + gpointer user_data) +{ + GtkTabStrip *self = user_data; + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + gtk_tab_strip_stack_add (self, widget, priv->stack); +} + +void +gtk_tab_strip_set_stack (GtkTabStrip *self, + GtkStack *stack) +{ + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + g_return_if_fail (GTK_IS_TAB_STRIP (self)); + g_return_if_fail (!stack || GTK_IS_STACK (stack)); + + if (priv->stack == stack) + return; + + if (priv->stack != NULL) + { + g_signal_handlers_disconnect_by_func (priv->stack, + G_CALLBACK (gtk_tab_strip_stack_notify_visible_child), + self); + + g_signal_handlers_disconnect_by_func (priv->stack, + G_CALLBACK (gtk_tab_strip_stack_add), + self); + + g_signal_handlers_disconnect_by_func (priv->stack, + G_CALLBACK (gtk_tab_strip_stack_remove), + self); + + gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback)gtk_widget_destroy, NULL); + + g_clear_object (&priv->stack); + } + + if (stack != NULL) + { + priv->stack = g_object_ref (stack); + + g_signal_connect_object (priv->stack, + "notify::visible-child", + G_CALLBACK (gtk_tab_strip_stack_notify_visible_child), + self, + G_CONNECT_SWAPPED); + + g_signal_connect_object (priv->stack, + "add", + G_CALLBACK (gtk_tab_strip_stack_add), + self, + G_CONNECT_SWAPPED); + + g_signal_connect_object (priv->stack, + "remove", + G_CALLBACK (gtk_tab_strip_stack_remove), + self, + G_CONNECT_SWAPPED); + + gtk_container_foreach (GTK_CONTAINER (priv->stack), + gtk_tab_strip_cold_plug, + self); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STACK]); +} diff --git a/gtk/gtktabstrip.h b/gtk/gtktabstrip.h new file mode 100644 index 0000000000..377ac565e6 --- /dev/null +++ b/gtk/gtktabstrip.h @@ -0,0 +1,65 @@ +/* gtktabstrip.h + * + * Copyright (C) 2016 Red Hat Inc. + * + * 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 program 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, see . + */ + +#if !defined(__GTK_H_INSIDE__) && !defined(GTK_COMPILATION) +# error "Only can be included directly." +#endif + +#ifndef __GTK_TAB_STRIP_H__ +#define __GTK_TAB_STRIP_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TAB_STRIP (gtk_tab_strip_get_type ()) +#define GTK_TAB_STRIP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TAB_STRIP, GtkTabStrip)) +#define GTK_IS_TAB_STRIP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TAB_STRIP)) +#define GTK_TAB_STRIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TAB_STRIP, GtkTabStripClass)) +#define GTK_IS_TAB_STRIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TAB_STRIP)) +#define GTK_TAB_STRIP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TAB_STRIP, GtkTabStripClass)) + +typedef struct _GtkTabStrip GtkTabStrip; +typedef struct _GtkTabStripClass GtkTabStripClass; + +struct _GtkTabStrip +{ + GtkBox parent; +}; + +struct _GtkTabStripClass +{ + GtkBoxClass parent_class; +}; + +GDK_AVAILABLE_IN_3_22 +GType gtk_tab_strip_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_3_22 +GtkWidget *gtk_tab_strip_new (void); +GDK_AVAILABLE_IN_3_22 +GtkStack *gtk_tab_strip_get_stack (GtkTabStrip *self); +GDK_AVAILABLE_IN_3_22 +void gtk_tab_strip_set_stack (GtkTabStrip *self, + GtkStack *stack); + +G_END_DECLS + +#endif /* __GTK_TAB_STRIP_H__ */