Compare commits

...

40 Commits

Author SHA1 Message Date
Matthias Clasen
3166b30303 wip: experiment with animated insertion/removal
This is very unfinished, do a simple container that animates
insertion and removal.
2016-06-19 12:05:18 -04:00
Matthias Clasen
e7b0a47856 Always show close buttons
The resizing is too distracting otherwise, and reserving space
would be unnecessarily complicated.
2016-06-14 08:00:26 -04:00
Matthias Clasen
73e7219b53 Add needs-attention to the test 2016-06-13 08:35:44 -04:00
Matthias Clasen
6761f70a3a tab strip: Set .needs-attention on overflow nodes
When a needs-attention tab is scrolled out of view, set the
.needs-attention class on the corresponding overflow node,
so we can give it some styling.
2016-06-08 15:56:35 -04:00
Matthias Clasen
2e66f4201a Propagate needs-attention
Add the .needs-attention style class to tabs whose pages have
GtkStack::needs-attention set to TRUE.
2016-06-08 14:53:07 -04:00
Matthias Clasen
e82f0f517b tab strip: Scroll visible tab into view
Scroll the visible tab into view when it changes.
2016-05-31 08:47:58 -04:00
Matthias Clasen
123f39eb55 test: Make new tabs visible
This is typical case in web browsers or editors.
2016-05-31 08:47:21 -04:00
Matthias Clasen
9552ced2d8 tests: Better example
Populate the example with typical web page titles, and move the
new tab button to the headerbar.
2016-05-31 08:11:51 -04:00
Matthias Clasen
cdc42cec2f tab strip: Fix removing children
We were triggering warnings when a stack child goes away.
2016-05-31 08:11:51 -04:00
Matthias Clasen
b21d150144 tabs: Improve ellipsization behavior
This gets us a bit closer to the desired user experience.
2016-05-31 08:11:45 -04:00
Matthias Clasen
c9ec72be7a Theme updates
Cut down on padding, and make close buttons square.
2016-05-31 01:06:12 -04:00
Matthias Clasen
4d880268fc tab strip: Drop scroll buttons
Without scroll buttons, all the autoscroll machinery is no longer
needed.
2016-05-31 00:51:44 -04:00
Matthias Clasen
df07faa81b drop todo list 2016-05-31 00:45:38 -04:00
Matthias Clasen
ce2c6eff1e closable tab: Only show the button when active 2016-05-31 00:43:09 -04:00
Matthias Clasen
f86f439ed5 test: remove scrollable 2016-05-31 00:43:09 -04:00
Matthias Clasen
7dd60e3986 tab strip: Always scroll
No need to complicate things with options. For now, lets focus
on getting the scrolling right.
2016-05-31 00:43:09 -04:00
Matthias Clasen
98a80b0c8f Keep GtkTab private
Without custom tab creation in GtkTabStrip, this no longer needs
to be public.
2016-05-31 00:36:57 -04:00
Matthias Clasen
3afd1b14f0 test: remove custom tabs 2016-05-31 00:36:57 -04:00
Matthias Clasen
397332702e Remove custom tab support for now
Allowing custom tab implementations ties our hands for what we can
do with the tabs, so lets figure out the desired user experience first.
2016-05-31 00:24:28 -04:00
Matthias Clasen
c05ce8cf9f Drop edge support for now
This is distracting from getting the desired user experience.
It can be added later.
2016-05-31 00:10:38 -04:00
Matthias Clasen
2c944b7928 Improve scrolling
Arrange for autoscroll and keyboard scrolling to end on a
tab boundary.
2016-05-26 00:18:06 -04:00
Matthias Clasen
9f1fc47175 Remove an unused field 2016-05-25 22:20:15 -04:00
Matthias Clasen
4e0c88a3d8 Handle direction changes 2016-05-25 22:17:48 -04:00
Matthias Clasen
0a06bb66db Add some hover style
Just so we can see it is there.
2016-05-25 21:32:26 -04:00
Matthias Clasen
b875aaba99 Don't derive from GtkBox
Instead, derive from GtkContainer and use a box gadget to
manage our internal children.
2016-05-25 21:27:17 -04:00
Matthias Clasen
39d863caae Add some todo items 2016-05-24 23:29:19 -04:00
Jakub Steiner
3927f44a0d fix tab jumping on :checked for tabstrip 2016-05-25 00:36:57 +02:00
Matthias Clasen
68b1814e83 Fix scroll button styling
The selector was not specific enough, and the hover styling
was missing.
2016-05-24 18:13:15 -04:00
Matthias Clasen
d6f5af4df9 Fix state updates for tabs
This broke when I introduced the scrolling. We need to
iterate over the children of the tabs box to update their
state.
2016-05-24 17:58:41 -04:00
Jakub Steiner
bf97ddeaaa initial styling for tabstrip (stub) 2016-05-24 20:42:21 +02:00
Matthias Clasen
1525f4b2e7 tab strip: Support different edges
Add edge properties on both GtkTabStrip and GtkTab, and make
GtkTabStrip update its orientation and scrolling direction when
the edge changes.
2016-05-21 16:16:31 -04:00
Matthias Clasen
e13f564fc2 Demonstrate custom tabs
This makes testtabstrip use its own tab class, which puts
a close command in a popover, just to show that this is
possible.
2016-05-21 16:16:31 -04:00
Matthias Clasen
6558ab4bb3 tab strip: Allow custom tabs
Add a ::create-tab signal that can be used to override the default
choice of simple or closable tabs with a custom tab implementation.
2016-05-21 16:16:31 -04:00
Matthias Clasen
76da4b4860 tab strip: Add scrolling
Implement scrolling arrows. This is currently using a scrolled window.
2016-05-21 16:16:31 -04:00
Matthias Clasen
cb65a174a5 Add a quick test for tab strips 2016-05-21 16:16:30 -04:00
Matthias Clasen
f01d8b105a tab strip: Support closable tabs
Add a boolean property to instruct GtkTabStrip to use closable
tabs instead of simple tabs.
2016-05-21 16:16:15 -04:00
Matthias Clasen
2b0c34cbf8 Add a closable tab class
This adds a close button to the tab in addition to the label.
2016-05-21 16:14:25 -04:00
Matthias Clasen
941f1ad1a6 Add a simple tab class
This is a very barebones subclass of GtkTab, which shows the page
title in a label.
2016-05-21 16:14:25 -04:00
Matthias Clasen
e6e418f6e4 Add a tab strip widget
This adds GtkTabStrip and GtkTab, which will eventually allow
creating typical notebook scenarios with GtkStack.

Inspired by the PnlTabStrip prototype that Christian Hergert
wrote for gnome-builder.
2016-05-21 16:14:25 -04:00
Matthias Clasen
21804b09b4 Move gtk_object_handled_accumulator
This used to live in gtknotebook.c. Since we may use it in
other places in the future, move it to gtkprivate.c where we
already have other shared accumulators.
2016-05-21 12:48:19 -04:00
20 changed files with 2620 additions and 16 deletions

View File

@@ -302,6 +302,7 @@ gtk_public_h_sources = \
gtkstylecontext.h \
gtkstyleprovider.h \
gtkswitch.h \
gtktabstrip.h \
gtktestutils.h \
gtktextattributes.h \
gtktextbuffer.h \
@@ -559,6 +560,9 @@ gtk_private_h_sources = \
gtkstylecontextprivate.h \
gtkstylepropertyprivate.h \
gtkstyleproviderprivate.h \
gtktab.h \
gtksimpletab.h \
gtkclosabletab.h \
gtktextattributesprivate.h \
gtktextbtree.h \
gtktextbufferprivate.h \
@@ -900,6 +904,11 @@ gtk_base_c_sources = \
gtkstyleprovider.c \
gtkstyleproviderprivate.c \
gtkswitch.c \
gtktab.c \
gtksimpletab.c \
gtkclosabletab.c \
gtktabs.c \
gtktabstrip.c \
gtktestutils.c \
gtktextattributes.c \
gtktextbtree.c \

View File

@@ -203,6 +203,7 @@
#include <gtk/gtkstylecontext.h>
#include <gtk/gtkstyleprovider.h>
#include <gtk/gtkswitch.h>
#include <gtk/gtktabstrip.h>
#include <gtk/gtktextattributes.h>
#include <gtk/gtktextbuffer.h>
#include <gtk/gtktextbufferrichtext.h>

85
gtk/gtkclosabletab.c Normal file
View File

@@ -0,0 +1,85 @@
/* gtkclosabletab.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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkclosabletab.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtkenums.h"
#include "gtklabel.h"
#include "gtkbox.h"
#include "gtkbutton.h"
struct _GtkClosableTab
{
GtkTab parent;
GtkWidget *box;
GtkWidget *label;
GtkWidget *button;
};
typedef struct _GtkClosableTabClass GtkClosableTabClass;
struct _GtkClosableTabClass
{
GtkTabClass parent_class;
};
G_DEFINE_TYPE (GtkClosableTab, gtk_closable_tab, GTK_TYPE_TAB)
static void
close_tab (GtkClosableTab *tab)
{
GtkWidget *widget;
GtkWidget *stack;
widget = gtk_tab_get_widget (GTK_TAB (tab));
stack = gtk_widget_get_parent (widget);
gtk_container_remove (GTK_CONTAINER (stack), widget);
}
static void
gtk_closable_tab_init (GtkClosableTab *self)
{
self->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
gtk_widget_show (self->box);
gtk_tab_set_child (GTK_TAB (self), self->box);
self->label = gtk_label_new ("");
gtk_widget_set_halign (self->label, GTK_ALIGN_START);
gtk_label_set_width_chars (GTK_LABEL (self->label), 10);
gtk_label_set_max_width_chars (GTK_LABEL (self->label), 25);
gtk_label_set_ellipsize (GTK_LABEL (self->label), PANGO_ELLIPSIZE_END);
gtk_widget_show (self->label);
gtk_box_pack_start (GTK_BOX (self->box), self->label, TRUE, TRUE, 0);
g_object_bind_property (self, "title", self->label, "label", G_BINDING_DEFAULT);
self->button = gtk_button_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_MENU);
gtk_widget_show (self->button);
gtk_button_set_relief (GTK_BUTTON (self->button), GTK_RELIEF_NONE);
gtk_box_pack_end (GTK_BOX (self->box), self->button, FALSE, FALSE, 0);
g_signal_connect_swapped (self->button, "clicked", G_CALLBACK (close_tab), self);
}
static void
gtk_closable_tab_class_init (GtkClosableTabClass *klass)
{
}

36
gtk/gtkclosabletab.h Normal file
View File

@@ -0,0 +1,36 @@
/* gtkclosabletab.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 <http://www.gnu.org/licenses/>.
*/
#ifndef __GTK_CLOSABLE_TAB_H__
#define __GTK_CLOSABLE_TAB_H__
#include <gtk/gtktab.h>
G_BEGIN_DECLS
#define GTK_TYPE_CLOSABLE_TAB (gtk_closable_tab_get_type ())
#define GTK_CLOSABLE_TAB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CLOSABLE_TAB, GtkClosableTab))
#define GTK_IS_CLOSABLE_TAB(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CLOSABLE_TAB))
typedef struct _GtkClosableTab GtkClosableTab;
GType gtk_closable_tab_get_type (void) G_GNUC_CONST;
G_END_DECLS
#endif /* __GTK_CLOSABLE_TAB_H__ */

View File

@@ -644,22 +644,6 @@ add_reorder_bindings (GtkBindingSet *binding_set,
G_TYPE_BOOLEAN, move_to_last);
}
static gboolean
gtk_object_handled_accumulator (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy)
{
gboolean continue_emission;
GObject *object;
object = g_value_get_object (handler_return);
g_value_set_object (return_accu, object);
continue_emission = !object;
return continue_emission;
}
static void
gtk_notebook_compute_expand (GtkWidget *widget,
gboolean *hexpand_p,

View File

@@ -158,6 +158,22 @@ _gtk_single_string_accumulator (GSignalInvocationHint *ihint,
return continue_emission;
}
gboolean
gtk_object_handled_accumulator (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy)
{
gboolean continue_emission;
GObject *object;
object = g_value_get_object (handler_return);
g_value_set_object (return_accu, object);
continue_emission = !object;
return continue_emission;
}
GdkModifierType
_gtk_replace_virtual_modifiers (GdkKeymap *keymap,
GdkModifierType modifiers)

View File

@@ -73,6 +73,11 @@ gboolean _gtk_single_string_accumulator (GSignalInvocationHint *ihint,
const GValue *handler_return,
gpointer dummy);
gboolean gtk_object_handled_accumulator (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy);
GdkModifierType _gtk_replace_virtual_modifiers (GdkKeymap *keymap,
GdkModifierType modifiers);
GdkModifierType _gtk_get_primary_accel_mod (void);

62
gtk/gtksimpletab.c Normal file
View File

@@ -0,0 +1,62 @@
/* gtksimpletab.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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtksimpletab.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtkenums.h"
#include "gtklabel.h"
struct _GtkSimpleTab
{
GtkTab parent;
GtkWidget *label;
};
typedef struct _GtkSimpleTabClass GtkSimpleTabClass;
struct _GtkSimpleTabClass
{
GtkTabClass parent_class;
};
G_DEFINE_TYPE (GtkSimpleTab, gtk_simple_tab, GTK_TYPE_TAB)
static void
gtk_simple_tab_class_init (GtkSimpleTabClass *klass)
{
}
static void
gtk_simple_tab_init (GtkSimpleTab *self)
{
self->label = gtk_label_new ("");
gtk_widget_set_halign (self->label, GTK_ALIGN_START);
gtk_label_set_width_chars (GTK_LABEL (self->label), 10);
gtk_label_set_max_width_chars (GTK_LABEL (self->label), 25);
gtk_label_set_ellipsize (GTK_LABEL (self->label), PANGO_ELLIPSIZE_END);
gtk_widget_show (self->label);
gtk_widget_set_halign (self->label, GTK_ALIGN_CENTER);
gtk_tab_set_child (GTK_TAB (self), self->label);
g_object_bind_property (self, "title", self->label, "label", G_BINDING_DEFAULT);
}

36
gtk/gtksimpletab.h Normal file
View File

@@ -0,0 +1,36 @@
/* gtksimpletab.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 <http://www.gnu.org/licenses/>.
*/
#ifndef __GTK_SIMPLE_TAB_H__
#define __GTK_SIMPLE_TAB_H__
#include <gtk/gtktab.h>
G_BEGIN_DECLS
#define GTK_TYPE_SIMPLE_TAB (gtk_simple_tab_get_type ())
#define GTK_SIMPLE_TAB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SIMPLE_TAB, GtkSimpleTab))
#define GTK_IS_SIMPLE_TAB(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SIMPLE_TAB))
typedef struct _GtkSimpleTab GtkSimpleTab;
GType gtk_simple_tab_get_type (void) G_GNUC_CONST;
G_END_DECLS
#endif /* __GTK_SIMPLE_TAB_H__ */

519
gtk/gtktab.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

69
gtk/gtktab.h Normal file
View File

@@ -0,0 +1,69 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __GTK_TAB_H__
#define __GTK_TAB_H__
#include <gtk/gtkcontainer.h>
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);
};
GType gtk_tab_get_type (void) G_GNUC_CONST;
const gchar *gtk_tab_get_title (GtkTab *self);
void gtk_tab_set_title (GtkTab *self,
const gchar *title);
GtkWidget *gtk_tab_get_widget (GtkTab *self);
void gtk_tab_set_widget (GtkTab *self,
GtkWidget *widget);
void gtk_tab_set_child (GtkTab *self,
GtkWidget *child);
G_END_DECLS
#endif /* __GTK_TAB_H__ */

615
gtk/gtktabs.c Normal file
View File

@@ -0,0 +1,615 @@
/*
* Copyright 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 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtktabsprivate.h"
#include "gtkcontainerprivate.h"
#include "gtkcsscustomgadgetprivate.h"
#include "gtkcssnodeprivate.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include "gtkcsscustomgadgetprivate.h"
#include "a11y/gtkcontaineraccessible.h"
#include "gtksettingsprivate.h"
typedef struct {
GArray *children;
GtkCssGadget *gadget;
guint tick_id;
} GtkTabsPrivate;
typedef struct {
GtkWidget *child;
gboolean animating;
guint64 starttime;
gdouble factor;
gint width;
} GtkTabsChild;
G_DEFINE_TYPE_WITH_PRIVATE (GtkTabs, gtk_tabs, GTK_TYPE_CONTAINER)
static void
gtk_tabs_finalize (GObject *object)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (object));
g_clear_object (&priv->gadget);
g_array_free (priv->children, TRUE);
G_OBJECT_CLASS (gtk_tabs_parent_class)->finalize (object);
}
static gboolean
gtk_tabs_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
gtk_css_gadget_draw (priv->gadget, cr);
return FALSE;
}
static void
gtk_tabs_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
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);
}
static void
gtk_tabs_get_preferred_width (GtkWidget *widget,
gint *minimum_size,
gint *natural_size)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
gtk_css_gadget_get_preferred_size (priv->gadget,
GTK_ORIENTATION_HORIZONTAL,
-1,
minimum_size, natural_size,
NULL, NULL);
}
static void
gtk_tabs_get_preferred_height (GtkWidget *widget,
gint *minimum_size,
gint *natural_size)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
gtk_css_gadget_get_preferred_size (priv->gadget,
GTK_ORIENTATION_VERTICAL,
-1,
minimum_size, natural_size,
NULL, NULL);
}
static void
gtk_tabs_get_preferred_width_for_height (GtkWidget *widget,
gint height,
gint *minimum_size,
gint *natural_size)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
gtk_css_gadget_get_preferred_size (priv->gadget,
GTK_ORIENTATION_HORIZONTAL,
height,
minimum_size, natural_size,
NULL, NULL);
}
static void
gtk_tabs_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_size,
gint *natural_size)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
gtk_css_gadget_get_preferred_size (priv->gadget,
GTK_ORIENTATION_VERTICAL,
width,
minimum_size, natural_size,
NULL, NULL);
}
static void
gtk_tabs_direction_changed (GtkWidget *widget,
GtkTextDirection previous_direction)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
int i, j;
gtk_css_node_reverse_children (gtk_widget_get_css_node (widget));
for (i = 0, j = priv->children->len - 1; i < j; i++, j--)
{
GtkTabsChild *child1 = &g_array_index (priv->children, GtkTabsChild, i);
GtkTabsChild *child2 = &g_array_index (priv->children, GtkTabsChild, j);
GtkTabsChild tmp;
tmp = *child1;
*child1 = *child2;
*child2 = tmp;
}
GTK_WIDGET_CLASS (gtk_tabs_parent_class)->direction_changed (widget, previous_direction);
}
#define DURATION (0.25 * 1e6)
static gdouble
ease_out_cubic (gdouble t)
{
gdouble p = t - 1;
return p * p * p + 1;
}
static gboolean
gtk_tabs_animate_cb (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer user_data)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
int i;
guint64 time;
int animating;
animating = 0;
time = gdk_frame_clock_get_frame_time (frame_clock);
for (i = 0; i < priv->children->len; i++)
{
GtkTabsChild *child = &g_array_index (priv->children, GtkTabsChild, i);
if (child->animating)
{
child->factor = ease_out_cubic ((time - child->starttime) / DURATION);
if (child->factor >= 1.0)
child->animating = FALSE;
else
animating += 1;
}
}
for (i = priv->children->len - 1; i >= 0; i--)
{
GtkTabsChild *child = &g_array_index (priv->children, GtkTabsChild, i);
if (child->child == NULL && !child->animating)
g_array_remove_index (priv->children, i);
}
gtk_widget_queue_allocate (widget);
if (animating == 0)
{
gtk_widget_remove_tick_callback (widget, priv->tick_id);
priv->tick_id = 0;
}
return G_SOURCE_CONTINUE;
}
static void
ensure_tick_callback (GtkTabs *tabs)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (tabs);
if (priv->tick_id == 0)
priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (tabs),
gtk_tabs_animate_cb, NULL, NULL);
}
void
gtk_tabs_insert (GtkTabs *tabs,
int pos,
GtkWidget *widget)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (tabs);
GtkTabsChild child;
child.child = widget;
if (gtk_widget_get_mapped (GTK_WIDGET (tabs)) &&
gtk_settings_get_enable_animations (gtk_widget_get_settings (GTK_WIDGET (tabs))))
{
child.animating = TRUE;
child.starttime = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (GTK_WIDGET (tabs)));
child.factor = 0.0;
ensure_tick_callback (tabs);
}
else
{
child.animating = FALSE;
child.starttime = 0;
child.factor = 1.0;
}
if (pos < 0 || pos >= priv->children->len)
{
g_array_append_val (priv->children, child);
gtk_css_node_insert_before (gtk_widget_get_css_node (GTK_WIDGET (tabs)),
gtk_widget_get_css_node (widget),
NULL);
}
else
{
g_array_insert_val (priv->children, pos, child);
gtk_css_node_insert_before (gtk_widget_get_css_node (GTK_WIDGET (tabs)),
gtk_widget_get_css_node (widget),
gtk_widget_get_css_node (g_array_index (priv->children,
GtkTabsChild,
pos + 1).child));
}
gtk_widget_set_parent (widget, GTK_WIDGET (tabs));
}
static void
gtk_tabs_add (GtkContainer *container,
GtkWidget *widget)
{
gtk_tabs_insert (GTK_TABS (container), -1, widget);
}
static GtkTabsChild *
gtk_tabs_find_child (GtkTabs *tabs,
GtkWidget *widget,
int *position)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (tabs);
int i;
for (i = 0; i < priv->children->len; i++)
{
GtkTabsChild *child = &g_array_index (priv->children, GtkTabsChild, i);
if (child->child == widget)
{
if (position)
*position = i;
return child;
}
}
return NULL;
}
static void
gtk_tabs_remove (GtkContainer *container,
GtkWidget *widget)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (container));
GtkTabsChild *child;
int position;
child = gtk_tabs_find_child (GTK_TABS (container), widget, &position);
if (child)
{
gtk_widget_unparent (child->child);
if (gtk_widget_get_mapped (GTK_WIDGET (container)) &&
gtk_settings_get_enable_animations (gtk_widget_get_settings (GTK_WIDGET (container))))
{
child->child = NULL;
child->animating = TRUE;
child->starttime = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (GTK_WIDGET (container)));
ensure_tick_callback (GTK_TABS (container));
}
else
{
g_array_remove_index (priv->children, position);
}
}
}
static void
gtk_tabs_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (container));
int i;
for (i = 0; i < priv->children->len; i++)
{
GtkTabsChild *child = &g_array_index (priv->children, GtkTabsChild, i);
if (child->child)
callback (child->child, callback_data);
}
}
static void
gtk_tabs_class_init (GtkTabsClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
object_class->finalize = gtk_tabs_finalize;
widget_class->draw = gtk_tabs_draw;
widget_class->size_allocate = gtk_tabs_size_allocate;
widget_class->get_preferred_width = gtk_tabs_get_preferred_width;
widget_class->get_preferred_height = gtk_tabs_get_preferred_height;
widget_class->get_preferred_height_for_width = gtk_tabs_get_preferred_height_for_width;
widget_class->get_preferred_width_for_height = gtk_tabs_get_preferred_width_for_height;
widget_class->direction_changed = gtk_tabs_direction_changed;
container_class->add = gtk_tabs_add;
container_class->remove = gtk_tabs_remove;
container_class->forall = gtk_tabs_forall;
gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_FILLER);
}
static void
measure_child (GtkTabsChild *child,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural)
{
int child_min, child_nat;
if (child->child)
{
_gtk_widget_get_preferred_size_for_size (child->child,
orientation,
for_size,
&child_min,
&child_nat,
NULL, NULL);
if (orientation == GTK_ORIENTATION_HORIZONTAL && child->animating)
{
child_min = MAX ((int) (child->factor * child_min), 1);
child_nat = MAX ((int) (child->factor * child_nat), 1);
}
}
else if (child->animating)
{
if (orientation == GTK_ORIENTATION_HORIZONTAL)
child_min = child_nat = MAX ((1.0 - child->factor) * child->width, 1);
else
child_min = child_nat = 1;
}
*minimum = child_min;
*natural = child_nat;
}
static void
gtk_tabs_measure_orientation (GtkCssGadget *gadget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
int child_min, child_nat;
int i;
*minimum = 0;
*natural = 0;
for (i = 0; i < priv->children->len; i++)
{
GtkTabsChild *child = &g_array_index (priv->children, GtkTabsChild, i);
measure_child (child, orientation, for_size, &child_min, &child_nat);
*minimum += child_min;
*natural += child_nat;
}
}
static void
gtk_tabs_distribute (GtkTabs *tabs,
int for_size,
int size,
GtkRequestedSize *sizes)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (tabs);
int i;
for (i = 0; i < priv->children->len; i++)
{
GtkTabsChild *child = &g_array_index (priv->children, GtkTabsChild, i);
measure_child (child,
GTK_ORIENTATION_HORIZONTAL,
for_size,
&sizes[i].minimum_size,
&sizes[i].natural_size);
size -= sizes[i].minimum_size;
}
size = gtk_distribute_natural_allocation (size, priv->children->len, sizes);
}
static void
gtk_tabs_measure_opposite (GtkCssGadget *gadget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
int child_min, child_nat;
GtkRequestedSize *sizes;
int i;
*minimum = 0;
*natural = 0;
if (for_size >= 0)
{
sizes = g_newa (GtkRequestedSize, priv->children->len);
gtk_tabs_distribute (GTK_TABS (widget), -1, for_size, sizes);
}
for (i = 0; i < priv->children->len; i++)
{
GtkTabsChild *child = &g_array_index (priv->children, GtkTabsChild, i);
measure_child (child,
orientation,
for_size >= 0 ? sizes[i].minimum_size : -1,
&child_min, &child_nat);
*minimum = MAX (*minimum, child_min);
*natural = MAX (*natural, child_nat);
}
}
static void
gtk_tabs_measure (GtkCssGadget *gadget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline,
gpointer unused)
{
if (orientation == GTK_ORIENTATION_HORIZONTAL)
gtk_tabs_measure_orientation (gadget, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline);
else
gtk_tabs_measure_opposite (gadget, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline);
}
static void
gtk_tabs_allocate (GtkCssGadget *gadget,
const GtkAllocation *allocation,
int baseline,
GtkAllocation *out_clip,
gpointer unused)
{
GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
GtkRequestedSize *sizes;
GtkAllocation child_allocation, child_clip;
int i;
child_allocation = *allocation;
sizes = g_newa (GtkRequestedSize, priv->children->len);
gtk_tabs_distribute (GTK_TABS (widget), allocation->height, allocation->width, sizes);
for (i = 0; i < priv->children->len; i++)
{
GtkTabsChild *child = &g_array_index (priv->children, GtkTabsChild, i);
child_allocation.width = sizes[i].minimum_size;
child_allocation.height = allocation->height;
if (!child->animating)
{
child->width = child_allocation.width;
gtk_widget_size_allocate_with_baseline (child->child, &child_allocation, baseline);
gtk_widget_get_clip (child->child, &child_clip);
if (i == 0)
*out_clip = child_clip;
else
gdk_rectangle_union (out_clip, &child_clip, out_clip);
}
child_allocation.x += sizes[i].minimum_size;
}
}
static gboolean
gtk_tabs_render (GtkCssGadget *gadget,
cairo_t *cr,
int x,
int y,
int width,
int height,
gpointer data)
{
GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (GTK_TABS (widget));
int i;
for (i = 0; i < priv->children->len; i++)
{
GtkTabsChild *child = &g_array_index (priv->children, GtkTabsChild, i);
if (!child->animating)
gtk_container_propagate_draw (GTK_CONTAINER (widget), child->child, cr);
}
return FALSE;
}
static void
gtk_tabs_init (GtkTabs *tabs)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (tabs);
GtkCssNode *widget_node;
gtk_widget_set_has_window (GTK_WIDGET (tabs), FALSE);
priv->children = g_array_new (FALSE, FALSE, sizeof (GtkTabsChild));
widget_node = gtk_widget_get_css_node (GTK_WIDGET (tabs));
priv->gadget = gtk_css_custom_gadget_new_for_node (widget_node,
GTK_WIDGET (tabs),
gtk_tabs_measure,
gtk_tabs_allocate,
gtk_tabs_render,
NULL, NULL);
}
void
gtk_tabs_reorder_child (GtkTabs *tabs,
GtkWidget *widget,
int position)
{
GtkTabsPrivate *priv = gtk_tabs_get_instance_private (tabs);
GtkTabsChild *child;
GtkTabsChild tmp;
int old_pos;
child = gtk_tabs_find_child (tabs, widget, &old_pos);
if (!child || position == old_pos)
return;
tmp = *child;
g_array_remove_index (priv->children, old_pos);
g_array_insert_val (priv->children, position, tmp);
gtk_widget_queue_allocate (GTK_WIDGET (tabs));
}

70
gtk/gtktabsprivate.h Normal file
View File

@@ -0,0 +1,70 @@
/* GTK - The GIMP Toolkit
* 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, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#ifndef __GTK_TABS_H__
#define __GTK_TABS_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkcontainer.h>
#include <gtk/gtkcssgadgetprivate.h>
G_BEGIN_DECLS
#define GTK_TYPE_TABS (gtk_tabs_get_type ())
#define GTK_TABS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TABS, GtkTabs))
#define GTK_TABS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TABS, GtkTabsClass))
#define GTK_IS_TABS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TABS))
#define GTK_IS_TABS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TABS))
#define GTK_TABS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TABS, GtkTabsClass))
typedef struct _GtkTabs GtkTabs;
typedef struct _GtkTabsClass GtkTabsClass;
struct _GtkTabs
{
GtkContainer container;
};
struct _GtkTabsClass
{
GtkContainerClass parent_class;
};
GType gtk_tabs_get_type (void) G_GNUC_CONST;
void gtk_tabs_reorder_child (GtkTabs *tabs,
GtkWidget *child,
int position);
G_END_DECLS
#endif

756
gtk/gtktabstrip.c Normal file
View File

@@ -0,0 +1,756 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtktabstrip.h"
#include "gtktab.h"
#include "gtksimpletab.h"
#include "gtkclosabletab.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtkorientable.h"
#include "gtkscrolledwindow.h"
#include "gtkbutton.h"
#include "gtkbox.h"
#include "gtktabsprivate.h"
#include "gtkadjustmentprivate.h"
#include "gtkboxgadgetprivate.h"
#include "gtkwidgetprivate.h"
#include "gtkcssnodeprivate.h"
typedef struct
{
GtkCssGadget *gadget;
GtkStack *stack;
gboolean closable;
gboolean reversed;
GtkWidget *scrolledwindow;
GtkWidget *tabs;
GtkScrollType autoscroll_mode;
guint autoscroll_id;
gboolean autoscroll_goal_set;
gdouble autoscroll_goal;
} GtkTabStripPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GtkTabStrip, gtk_tab_strip, GTK_TYPE_CONTAINER)
enum {
PROP_0,
PROP_STACK,
PROP_CLOSABLE,
N_PROPS
};
static GParamSpec *properties[N_PROPS];
static void
gtk_tab_strip_add (GtkContainer *container,
GtkWidget *widget)
{
g_warning ("Can't add children to %s", G_OBJECT_TYPE_NAME (container));
}
static void
gtk_tab_strip_remove (GtkContainer *container,
GtkWidget *widget)
{
g_warning ("Can't remove children from %s", G_OBJECT_TYPE_NAME (container));
}
static void
gtk_tab_strip_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data)
{
GtkTabStrip *self = GTK_TAB_STRIP (container);
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
if (include_internals)
(*callback) (priv->scrolledwindow, callback_data);
}
static GType
gtk_tab_strip_child_type (GtkContainer *container)
{
return G_TYPE_NONE;
}
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_preferred_width (GtkWidget *widget,
gint *minimum_size,
gint *natural_size)
{
GtkTabStrip *self = GTK_TAB_STRIP (widget);
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
gtk_css_gadget_get_preferred_size (priv->gadget,
GTK_ORIENTATION_HORIZONTAL,
-1,
minimum_size, natural_size,
NULL, NULL);
}
static void
gtk_tab_strip_get_preferred_height (GtkWidget *widget,
gint *minimum_size,
gint *natural_size)
{
GtkTabStrip *self = GTK_TAB_STRIP (widget);
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
gtk_css_gadget_get_preferred_size (priv->gadget,
GTK_ORIENTATION_VERTICAL,
-1,
minimum_size, natural_size,
NULL, NULL);
}
static void
gtk_tab_strip_get_preferred_width_for_height (GtkWidget *widget,
gint height,
gint *minimum_size,
gint *natural_size)
{
GtkTabStrip *self = GTK_TAB_STRIP (widget);
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
gtk_css_gadget_get_preferred_size (priv->gadget,
GTK_ORIENTATION_HORIZONTAL,
height,
minimum_size, natural_size,
NULL, NULL);
}
static void
gtk_tab_strip_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_size,
gint *natural_size)
{
GtkTabStrip *self = GTK_TAB_STRIP (widget);
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
gtk_css_gadget_get_preferred_size (priv->gadget,
GTK_ORIENTATION_VERTICAL,
width,
minimum_size, natural_size,
NULL, NULL);
}
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);
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);
}
static gboolean
gtk_tab_strip_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkTabStrip *self = GTK_TAB_STRIP (widget);
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
gtk_css_gadget_draw (priv->gadget, cr);
return FALSE;
}
static void
update_node_ordering (GtkTabStrip *self)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
gboolean reverse;
reverse = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
if ((reverse && !priv->reversed) ||
(!reverse && priv->reversed))
{
gtk_box_gadget_reverse_children (GTK_BOX_GADGET (priv->gadget));
priv->reversed = reverse;
}
}
static void
gtk_tab_strip_direction_changed (GtkWidget *widget,
GtkTextDirection previous_direction)
{
update_node_ordering (GTK_TAB_STRIP (widget));
GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->direction_changed (widget, previous_direction);
}
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;
case PROP_CLOSABLE:
g_value_set_boolean (value, gtk_tab_strip_get_closable (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;
case PROP_CLOSABLE:
gtk_tab_strip_set_closable (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gtk_tab_strip_finalize (GObject *object)
{
GtkTabStrip *self = GTK_TAB_STRIP (object);
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
g_clear_object (&priv->gadget);
G_OBJECT_CLASS (gtk_tab_strip_parent_class)->finalize (object);
}
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;
object_class->finalize = gtk_tab_strip_finalize;
widget_class->destroy = gtk_tab_strip_destroy;
widget_class->get_preferred_width = gtk_tab_strip_get_preferred_width;
widget_class->get_preferred_height = gtk_tab_strip_get_preferred_height;
widget_class->get_preferred_width_for_height = gtk_tab_strip_get_preferred_width_for_height;
widget_class->get_preferred_height_for_width = gtk_tab_strip_get_preferred_height_for_width;
widget_class->size_allocate = gtk_tab_strip_size_allocate;
widget_class->draw = gtk_tab_strip_draw;
widget_class->direction_changed = gtk_tab_strip_direction_changed;
container_class->add = gtk_tab_strip_add;
container_class->remove = gtk_tab_strip_remove;
container_class->forall = gtk_tab_strip_forall;
container_class->child_type = gtk_tab_strip_child_type;
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);
properties[PROP_CLOSABLE] =
g_param_spec_boolean ("closable", P_("Closable"), P_("Whether tabs can be closed"),
FALSE,
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
update_scrolling (GtkTabStrip *self)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
GtkPolicyType hscroll, vscroll;
hscroll = GTK_POLICY_EXTERNAL;
vscroll = GTK_POLICY_NEVER;
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolledwindow),
hscroll, vscroll);
}
static void
update_needs_attention_overflow (GtkTabStrip *self)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
GtkAdjustment *adj;
gdouble value, page_size;
GList *tabs, *l;
GtkCssNode *scroll_node, *node;
gboolean needs_attention_below = FALSE;
gboolean needs_attention_above = FALSE;
adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow));
value = gtk_adjustment_get_value (adj);
page_size = gtk_adjustment_get_page_size (adj);
tabs = gtk_container_get_children (GTK_CONTAINER (priv->tabs));
for (l = tabs; l; l = l->next)
{
GtkWidget *tab = l->data;
GtkStyleContext *context;
GtkAllocation tab_alloc;
context = gtk_widget_get_style_context (tab);
if (!gtk_style_context_has_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION))
continue;
gtk_widget_get_allocation (tab, &tab_alloc);
if (tab_alloc.x + tab_alloc.width < value)
needs_attention_below = TRUE;
else if (tab_alloc.x >= value + page_size)
needs_attention_above = TRUE;
}
g_list_free (tabs);
scroll_node = gtk_widget_get_css_node (priv->scrolledwindow);
for (node = gtk_css_node_get_first_child (scroll_node);
node;
node = gtk_css_node_get_next_sibling (node))
{
if (strcmp (gtk_css_node_get_name (node), "undershoot") != 0)
continue;
if (gtk_css_node_has_class (node, g_quark_from_static_string ("left")))
{
if (needs_attention_below)
gtk_css_node_add_class (node, g_quark_from_static_string (GTK_STYLE_CLASS_NEEDS_ATTENTION));
else
gtk_css_node_remove_class (node, g_quark_from_static_string (GTK_STYLE_CLASS_NEEDS_ATTENTION));
}
else if (gtk_css_node_has_class (node, g_quark_from_static_string ("right")))
{
if (needs_attention_above)
gtk_css_node_add_class (node, g_quark_from_static_string (GTK_STYLE_CLASS_NEEDS_ATTENTION));
else
gtk_css_node_remove_class (node, g_quark_from_static_string (GTK_STYLE_CLASS_NEEDS_ATTENTION));
}
}
}
static void
gtk_tab_strip_init (GtkTabStrip *self)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
GtkCssNode *widget_node;
GtkAdjustment *adj;
gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
priv->closable = 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_orientation (GTK_BOX_GADGET (priv->gadget), GTK_ORIENTATION_HORIZONTAL);
priv->scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_show (priv->scrolledwindow);
gtk_widget_set_parent (priv->scrolledwindow, GTK_WIDGET (self));
gtk_box_gadget_insert_widget (GTK_BOX_GADGET (priv->gadget), 1, priv->scrolledwindow);
gtk_box_gadget_set_gadget_expand (GTK_BOX_GADGET (priv->gadget), G_OBJECT (priv->scrolledwindow), TRUE);
update_scrolling (self);
priv->tabs = g_object_new (GTK_TYPE_TABS, NULL);
gtk_widget_show (priv->tabs);
gtk_container_add (GTK_CONTAINER (priv->scrolledwindow), priv->tabs);
adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow));
g_signal_connect_swapped (adj, "changed", G_CALLBACK (update_needs_attention_overflow), self);
g_signal_connect_swapped (adj, "value-changed", G_CALLBACK (update_needs_attention_overflow), self);
}
static void
gtk_tab_strip_child_position_changed (GtkTabStrip *self,
GParamSpec *pspec,
GtkWidget *child)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
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_tabs_reorder_child (GTK_TABS (priv->tabs), GTK_WIDGET (tab), position);
}
static void
gtk_tab_strip_child_needs_attention_changed (GtkTabStrip *self,
GParamSpec *pspec,
GtkWidget *child)
{
GtkWidget *parent;
GtkWidget *tab;
gboolean needs_attention;
GtkStyleContext *context;
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), GTK_WIDGET (child),
"needs-attention", &needs_attention,
NULL);
context = gtk_widget_get_style_context (tab);
if (needs_attention)
gtk_style_context_add_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION);
else
gtk_style_context_remove_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION);
update_needs_attention_overflow (self);
}
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
scroll_to_tab (GtkTabStrip *self,
GtkWidget *tab)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
GtkAllocation tab_alloc;
GtkAdjustment *adj;
gdouble value, page_size;
gtk_widget_get_allocation (tab, &tab_alloc);
adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow));
value = gtk_adjustment_get_value (adj);
page_size = gtk_adjustment_get_page_size (adj);
if (tab_alloc.x < value)
gtk_adjustment_animate_to_value (adj, tab_alloc.x);
else if (tab_alloc.x + tab_alloc.width >= value + page_size)
gtk_adjustment_animate_to_value (adj, tab_alloc.x + tab_alloc.width - page_size);
}
static gboolean
scroll_to_visible_tab (gpointer data)
{
GtkTabStrip *self = data;
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
GtkWidget *child;
child = gtk_stack_get_visible_child (GTK_STACK (priv->stack));
if (child)
{
GtkWidget *tab;
tab = (GtkWidget *)g_object_get_data (G_OBJECT (child), "GTK_TAB");
scroll_to_tab (self, tab);
}
return G_SOURCE_REMOVE;
}
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 *child;
child = gtk_stack_get_visible_child (stack);
gtk_container_foreach (GTK_CONTAINER (priv->tabs), update_visible_child, child);
g_idle_add (scroll_to_visible_tab, self);
}
static void
tab_activated (GtkTab *tab,
GtkTabStrip *self)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
GtkWidget *widget;
GtkWidget *visible_child;
widget = gtk_tab_get_widget (tab);
if (widget == NULL)
return;
visible_child = gtk_stack_get_visible_child (GTK_STACK (priv->stack));
if (widget == visible_child)
scroll_to_tab (self, GTK_WIDGET (tab));
else
gtk_stack_set_visible_child (priv->stack, widget);
}
static void
gtk_tab_strip_stack_add (GtkTabStrip *self,
GtkWidget *widget,
GtkStack *stack)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
GtkTab *tab;
gint position = 0;
gtk_container_child_get (GTK_CONTAINER (stack), widget,
"position", &position,
NULL);
tab = g_object_new (priv->closable ? GTK_TYPE_CLOSABLE_TAB : 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);
g_signal_connect_object (widget, "child-notify::needs-attention",
G_CALLBACK (gtk_tab_strip_child_needs_attention_changed), self,
G_CONNECT_SWAPPED);
gtk_container_add (GTK_CONTAINER (priv->tabs), GTK_WIDGET (tab));
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)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
GtkTab *tab;
tab = g_object_get_data (G_OBJECT (widget), "GTK_TAB");
if (GTK_IS_TAB (tab))
gtk_container_remove (GTK_CONTAINER (priv->tabs), 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]);
}
void
gtk_tab_strip_set_closable (GtkTabStrip *self,
gboolean closable)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
g_return_if_fail (GTK_IS_TAB_STRIP (self));
if (priv->closable == closable)
return;
priv->closable = closable;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CLOSABLE]);
}
gboolean
gtk_tab_strip_get_closable (GtkTabStrip *self)
{
GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
g_return_val_if_fail (GTK_IS_TAB_STRIP (self), FALSE);
return priv->closable;
}

79
gtk/gtktabstrip.h Normal file
View File

@@ -0,0 +1,79 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#if !defined(__GTK_H_INSIDE__) && !defined(GTK_COMPILATION)
# error "Only <gtk/gtk.h> can be included directly."
#endif
#ifndef __GTK_TAB_STRIP_H__
#define __GTK_TAB_STRIP_H__
#include <gtk/gtkcontainer.h>
#include <gtk/gtkstack.h>
#include <gtk/gtktab.h>
#include <gtk/gtktypebuiltins.h>
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
{
GtkContainer parent;
};
struct _GtkTabStripClass
{
GtkContainerClass parent_class;
/* 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_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);
GDK_AVAILABLE_IN_3_22
gboolean gtk_tab_strip_get_closable (GtkTabStrip *self);
GDK_AVAILABLE_IN_3_22
void gtk_tab_strip_set_closable (GtkTabStrip *self,
gboolean closable);
G_END_DECLS
#endif /* __GTK_TAB_STRIP_H__ */

View File

@@ -2288,6 +2288,39 @@ notebook {
}
}
/************
* Tabstrip *
************/
tabs {
button.flat.image-button {
@include button(undecorated);
margin: 10px;
padding: 0;
&:hover {
@include button(hover);
color: currentColor;
-gtk-icon-effect: highlight;
}
&, &:backdrop { color: gtkalpha(currentColor, 0.3); }
}
tab {
min-width: 6em;
padding: 3px; // keeping width when paging buttons show up
border-right: 1px solid transparentize($borders_color,0.5);
&:first-child { border-left: 1px solid transparentize($borders_color,0.5); }
&:checked,
&:checked:hover { //FIXME not really working
border-bottom: 2px solid $selected_bg_color;
padding-bottom: 1px;
}
&:hover {
border-bottom: 2px solid $borders_color;
padding-bottom: 1px;
}
}
}
/**************
* Scrollbars *

View File

@@ -2570,6 +2570,44 @@ notebook > stack:not(:only-child) {
notebook > stack:not(:only-child):backdrop {
background-color: #252a2c; }
/************
* Tabstrip *
************/
tabs button.flat.image-button {
border-color: transparent;
background-color: transparent;
background-image: none;
box-shadow: inset 0 1px rgba(255, 255, 255, 0);
text-shadow: none;
-gtk-icon-shadow: none;
margin: 10px;
padding: 0; }
tabs button.flat.image-button:hover {
color: #eeeeec;
outline-color: rgba(238, 238, 236, 0.3);
border-color: #1b1f20;
border-bottom-color: #0b0c0c;
background-image: linear-gradient(to bottom, #353c3e, #2e3436 60%, #2a2e30);
text-shadow: 0 -1px rgba(0, 0, 0, 0.77945);
-gtk-icon-shadow: 0 -1px rgba(0, 0, 0, 0.77945);
box-shadow: inset 0 1px rgba(255, 255, 255, 0.05);
color: currentColor;
-gtk-icon-effect: highlight; }
tabs button.flat.image-button, tabs button.flat.image-button:backdrop {
color: alpha(currentColor,0.3); }
tabs tab {
min-width: 6em;
padding: 3px;
border-right: 1px solid rgba(27, 31, 32, 0.5); }
tabs tab:first-child {
border-left: 1px solid rgba(27, 31, 32, 0.5); }
tabs tab:checked, tabs tab:checked:hover {
border-bottom: 2px solid #215d9c;
padding-bottom: 1px; }
tabs tab:hover {
border-bottom: 2px solid #1b1f20;
padding-bottom: 1px; }
/**************
* Scrollbars *
**************/

View File

@@ -2583,6 +2583,44 @@ notebook > stack:not(:only-child) {
notebook > stack:not(:only-child):backdrop {
background-color: #fcfcfc; }
/************
* Tabstrip *
************/
tabs button.flat.image-button {
border-color: transparent;
background-color: transparent;
background-image: none;
box-shadow: inset 0 1px rgba(255, 255, 255, 0);
text-shadow: none;
-gtk-icon-shadow: none;
margin: 10px;
padding: 0; }
tabs button.flat.image-button:hover {
color: #2e3436;
outline-color: rgba(46, 52, 54, 0.3);
border-color: #b6b6b3;
border-bottom-color: #91918c;
background-image: linear-gradient(to bottom, #f7f7f7, #e8e8e7 60%, #dededd);
text-shadow: 0 1px rgba(255, 255, 255, 0.76923);
-gtk-icon-shadow: 0 1px rgba(255, 255, 255, 0.76923);
box-shadow: inset 0 1px white;
color: currentColor;
-gtk-icon-effect: highlight; }
tabs button.flat.image-button, tabs button.flat.image-button:backdrop {
color: alpha(currentColor,0.3); }
tabs tab {
min-width: 6em;
padding: 3px;
border-right: 1px solid rgba(182, 182, 179, 0.5); }
tabs tab:first-child {
border-left: 1px solid rgba(182, 182, 179, 0.5); }
tabs tab:checked, tabs tab:checked:hover {
border-bottom: 2px solid #4a90d9;
padding-bottom: 1px; }
tabs tab:hover {
border-bottom: 2px solid #b6b6b3;
padding-bottom: 1px; }
/**************
* Scrollbars *
**************/

View File

@@ -172,6 +172,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
gdkgears \
listmodel \
testpopup \
testtabstrip \
$(NULL)
if USE_X11

152
tests/testtabstrip.c Normal file
View File

@@ -0,0 +1,152 @@
#include <gtk/gtk.h>
static int count = 1;
static GtkWidget *
make_child (const char *title)
{
GtkWidget *sw, *tv;
sw = gtk_scrolled_window_new (NULL, NULL);
tv = gtk_text_view_new ();
gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)), title, -1);
g_object_set (tv, "expand", TRUE, NULL);
gtk_container_add (GTK_CONTAINER (sw), tv);
gtk_widget_show_all (sw);
return sw;
}
static void
add_child (GtkWidget *stack,
const char *title)
{
char *name;
GtkWidget *child;
name = g_strdup_printf ("tab%d", count++);
child = make_child (title);
gtk_stack_add_titled (GTK_STACK (stack), child, name, title);
gtk_stack_set_visible_child (GTK_STACK (stack), child);
g_free (name);
}
static void
add_stack_child (GtkWidget *stack)
{
GtkWidget *child;
char *name;
char *title;
name = g_strdup_printf ("tab%d", count++);
title = g_strdup_printf ("Page %d", count);
child = make_child (title);
gtk_container_add_with_properties (GTK_CONTAINER (stack),
child,
"name", name,
"title", title,
"position", 3,
NULL);
g_free (title);
g_free (name);
}
static void
populate (GtkWidget *stack)
{
add_child (stack, "Enigma machine - Wikipedia, the free ecyclopedia");
add_child (stack, "Ada Lovelace");
add_child (stack, "Analytical Engine | computer | Britannica.com");
add_child (stack, "Charles Babbage");
add_child (stack, "Turing Machine — from Wolfram MathWorld");
add_child (stack, "Enigma machine - Wikipedia, the free ecyclopedia");
add_child (stack, "Enigma machine - Wikipedia, the free ecyclopedia");
add_child (stack, "Enigma machine - Wikipedia, the free ecyclopedia");
add_child (stack, "Enigma machine - Wikipedia, the free ecyclopedia");
}
static void
attention (GtkButton *button, GtkStack *stack)
{
GList *children;
children = gtk_container_get_children (GTK_CONTAINER (stack));
gtk_container_child_set (GTK_CONTAINER (stack), children->data,
"needs-attention", TRUE,
NULL);
gtk_container_child_set (GTK_CONTAINER (stack), g_list_last (children)->data,
"needs-attention", TRUE,
NULL);
g_list_free (children);
}
static const char css[] =
"tab.needs-attention { border-bottom: solid 4px red; }"
"scrolledwindow undershoot.left.needs-attention { background: none; border-left: solid 4px red; }"
"scrolledwindow undershoot.right.needs-attention { background: none; border-right: solid 4px red; }";
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *titlebar;
GtkWidget *vbox;
GtkWidget *tabs;
GtkWidget *stack;
GtkWidget *button;
GtkWidget *hbox;
GtkCssProvider *provider;
gtk_init (NULL, NULL);
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, css, -1, NULL);
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
GTK_STYLE_PROVIDER (provider),
800);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (window), 600, 800);
titlebar = gtk_header_bar_new ();
gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (titlebar), TRUE);
gtk_widget_show (titlebar);
gtk_window_set_titlebar (GTK_WINDOW (window), titlebar);
gtk_window_set_title (GTK_WINDOW (window), "Tab Strip Test");
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (window), vbox);
stack = gtk_stack_new ();
g_object_set (stack, "expand", TRUE, NULL);
tabs = gtk_tab_strip_new ();
gtk_tab_strip_set_closable (GTK_TAB_STRIP (tabs), TRUE);
gtk_tab_strip_set_stack (GTK_TAB_STRIP (tabs), GTK_STACK (stack));
button = gtk_button_new_from_icon_name ("tab-new-symbolic", GTK_ICON_SIZE_BUTTON);
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
gtk_header_bar_pack_end (GTK_HEADER_BAR (titlebar), button);
g_signal_connect_swapped (button, "clicked",
G_CALLBACK (add_stack_child), stack);
gtk_box_pack_start (GTK_BOX (vbox), tabs, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), stack, TRUE, TRUE, 0);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
button = gtk_button_new_with_label ("Attention");
g_signal_connect (button, "clicked", G_CALLBACK (attention), stack);
gtk_container_add (GTK_CONTAINER (hbox), button);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
populate (stack);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}