Compare commits
2 Commits
shader-too
...
wip/matthi
Author | SHA1 | Date | |
---|---|---|---|
|
5215ae5228 | ||
|
389da86351 |
@@ -263,6 +263,7 @@
|
|||||||
#include <gtk/gtkstyleprovider.h>
|
#include <gtk/gtkstyleprovider.h>
|
||||||
#include <gtk/gtkswitch.h>
|
#include <gtk/gtkswitch.h>
|
||||||
#include <gtk/gtksymbolicpaintable.h>
|
#include <gtk/gtksymbolicpaintable.h>
|
||||||
|
#include <gtk/gtktabbar.h>
|
||||||
#include <gtk/gtktext.h>
|
#include <gtk/gtktext.h>
|
||||||
#include <gtk/gtktextbuffer.h>
|
#include <gtk/gtktextbuffer.h>
|
||||||
#include <gtk/gtktextchild.h>
|
#include <gtk/gtktextchild.h>
|
||||||
|
571
gtk/gtktabbar.c
Normal file
571
gtk/gtktabbar.c
Normal file
@@ -0,0 +1,571 @@
|
|||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "gtktabbar.h"
|
||||||
|
|
||||||
|
#include "gtkboxlayout.h"
|
||||||
|
#include "gtkenums.h"
|
||||||
|
#include "gtklabel.h"
|
||||||
|
#include "gtkorientable.h"
|
||||||
|
#include "gtkprivate.h"
|
||||||
|
#include "gtkselectionmodel.h"
|
||||||
|
#include "gtkstack.h"
|
||||||
|
#include "gtktabwidgetprivate.h"
|
||||||
|
#include "gtktypebuiltins.h"
|
||||||
|
#include "gtkwidgetprivate.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GtkTabBar:
|
||||||
|
*
|
||||||
|
* `GtkTabBar` is a stack switcher that can be used with `GtkStack`
|
||||||
|
* to prove an user experience similar to `GtkNotebook`.
|
||||||
|
*
|
||||||
|
* Since: 4.12
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct _GtkTabBar {
|
||||||
|
GtkWidget parent_instance;
|
||||||
|
|
||||||
|
GtkStack *stack;
|
||||||
|
GtkSelectionModel *pages;
|
||||||
|
GPtrArray *tabs;
|
||||||
|
|
||||||
|
GtkPositionType position;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _GtkTabBarClass {
|
||||||
|
GtkWidget parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_POSITION = 1,
|
||||||
|
PROP_STACK,
|
||||||
|
NUM_PROPERTIES,
|
||||||
|
PROP_ORIENTATION = NUM_PROPERTIES
|
||||||
|
};
|
||||||
|
|
||||||
|
static GParamSpec *properties[NUM_PROPERTIES] = { 0, };
|
||||||
|
|
||||||
|
G_DEFINE_TYPE_WITH_CODE (GtkTabBar, gtk_tab_bar, GTK_TYPE_WIDGET,
|
||||||
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
|
||||||
|
|
||||||
|
static void
|
||||||
|
create_tabs (GtkTabBar *self)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->pages)); i++)
|
||||||
|
{
|
||||||
|
GtkStackPage *page;
|
||||||
|
GtkWidget *tab;
|
||||||
|
|
||||||
|
page = g_list_model_get_item (G_LIST_MODEL (self->pages), i);
|
||||||
|
tab = gtk_tab_widget_new (page, i);
|
||||||
|
|
||||||
|
gtk_widget_set_parent (tab, GTK_WIDGET (self));
|
||||||
|
|
||||||
|
/* Note: self->tabs matches pages for order, not tabs */
|
||||||
|
g_ptr_array_add (self->tabs, tab);
|
||||||
|
|
||||||
|
g_object_unref (page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clear_tabs (GtkTabBar *self)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < self->tabs->len; i++)
|
||||||
|
{
|
||||||
|
GtkWidget *tab = g_ptr_array_index (self->tabs, i);
|
||||||
|
gtk_widget_unparent (tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ptr_array_set_size (self->tabs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
items_changed_cb (GListModel *model,
|
||||||
|
guint position,
|
||||||
|
guint removed,
|
||||||
|
guint added,
|
||||||
|
GtkTabBar *self)
|
||||||
|
{
|
||||||
|
clear_tabs (self);
|
||||||
|
create_tabs (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
selection_changed_cb (GtkSelectionModel *model,
|
||||||
|
guint position,
|
||||||
|
guint n_items,
|
||||||
|
GtkTabBar *self)
|
||||||
|
{
|
||||||
|
for (unsigned int i = position; i < position + n_items; i++)
|
||||||
|
{
|
||||||
|
GtkStackPage *page;
|
||||||
|
GtkWidget *tab;
|
||||||
|
gboolean selected;
|
||||||
|
|
||||||
|
page = g_list_model_get_item (G_LIST_MODEL (self->pages), i);
|
||||||
|
tab = g_ptr_array_index (self->tabs, i);
|
||||||
|
selected = gtk_selection_model_is_selected (self->pages, i);
|
||||||
|
|
||||||
|
if (selected)
|
||||||
|
gtk_widget_set_state_flags (tab, GTK_STATE_FLAG_SELECTED, FALSE);
|
||||||
|
else
|
||||||
|
gtk_widget_unset_state_flags (tab, GTK_STATE_FLAG_SELECTED);
|
||||||
|
|
||||||
|
gtk_accessible_update_state (GTK_ACCESSIBLE (tab),
|
||||||
|
GTK_ACCESSIBLE_STATE_SELECTED, selected,
|
||||||
|
-1);
|
||||||
|
|
||||||
|
g_object_unref (page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
set_stack (GtkTabBar *self,
|
||||||
|
GtkStack *stack)
|
||||||
|
{
|
||||||
|
g_assert (self->stack == NULL);
|
||||||
|
if (stack)
|
||||||
|
{
|
||||||
|
self->stack = g_object_ref (stack);
|
||||||
|
self->pages = gtk_stack_get_pages (stack);
|
||||||
|
|
||||||
|
create_tabs (self);
|
||||||
|
selection_changed_cb (self->pages,
|
||||||
|
0, g_list_model_get_n_items (G_LIST_MODEL (self->pages)),
|
||||||
|
self);
|
||||||
|
|
||||||
|
g_signal_connect (self->pages,
|
||||||
|
"items-changed", G_CALLBACK (items_changed_cb), self);
|
||||||
|
g_signal_connect (self->pages,
|
||||||
|
"selection-changed", G_CALLBACK (selection_changed_cb), self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
unset_stack (GtkTabBar *self)
|
||||||
|
{
|
||||||
|
if (self->stack)
|
||||||
|
{
|
||||||
|
g_signal_handlers_disconnect_by_func (self->pages, items_changed_cb, self);
|
||||||
|
g_signal_handlers_disconnect_by_func (self->pages, selection_changed_cb, self);
|
||||||
|
|
||||||
|
clear_tabs (self);
|
||||||
|
g_clear_object (&self->pages);
|
||||||
|
g_clear_object (&self->stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_bar_init (GtkTabBar *self)
|
||||||
|
{
|
||||||
|
self->position = GTK_POS_TOP;
|
||||||
|
self->tabs = g_ptr_array_new ();
|
||||||
|
gtk_widget_add_css_class (GTK_WIDGET (self), "top");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_bar_get_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
GtkTabBar *self = GTK_TAB_BAR (object);
|
||||||
|
GtkLayoutManager *box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
switch (prop_id)
|
||||||
|
{
|
||||||
|
case PROP_ORIENTATION:
|
||||||
|
g_value_set_enum (value, gtk_orientable_get_orientation (GTK_ORIENTABLE (box_layout)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROP_POSITION:
|
||||||
|
g_value_set_enum (value, self->position);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROP_STACK:
|
||||||
|
g_value_set_object (value, self->stack);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_bar_set_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
GtkTabBar *self = GTK_TAB_BAR (object);
|
||||||
|
GtkLayoutManager *box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
switch (prop_id)
|
||||||
|
{
|
||||||
|
case PROP_ORIENTATION:
|
||||||
|
{
|
||||||
|
GtkOrientation orientation = g_value_get_enum (value);
|
||||||
|
if (gtk_orientable_get_orientation (GTK_ORIENTABLE (box_layout)) != orientation)
|
||||||
|
{
|
||||||
|
gtk_orientable_set_orientation (GTK_ORIENTABLE (box_layout), orientation);
|
||||||
|
gtk_widget_update_orientation (GTK_WIDGET (self), orientation);
|
||||||
|
g_object_notify_by_pspec (object, pspec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROP_POSITION:
|
||||||
|
gtk_tab_bar_set_position (self, g_value_get_enum (value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROP_STACK:
|
||||||
|
gtk_tab_bar_set_stack (self, g_value_get_object (value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_bar_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
GtkTabBar *self = GTK_TAB_BAR (object);
|
||||||
|
|
||||||
|
unset_stack (self);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (gtk_tab_bar_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_bar_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
GtkTabBar *self = GTK_TAB_BAR (object);
|
||||||
|
|
||||||
|
g_assert (self->tabs->len == 0);
|
||||||
|
g_ptr_array_unref (self->tabs);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (gtk_tab_bar_parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_bar_switch_tab (GtkWidget *widget,
|
||||||
|
const char *action_name,
|
||||||
|
GVariant *parameters)
|
||||||
|
{
|
||||||
|
GtkTabBar *self = GTK_TAB_BAR (widget);
|
||||||
|
unsigned int position;
|
||||||
|
|
||||||
|
if (!self->stack)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_variant_get (parameters, "u", &position);
|
||||||
|
|
||||||
|
gtk_selection_model_select_item (self->pages, position, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkDirectionType
|
||||||
|
get_effective_direction (GtkTabBar *self,
|
||||||
|
GtkDirectionType direction)
|
||||||
|
{
|
||||||
|
/* Remap the directions into the effective direction it would be for a
|
||||||
|
* ltr-and-top tabbar.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define D(rest) GTK_DIR_##rest
|
||||||
|
|
||||||
|
static const GtkDirectionType translate_direction[2][4][6] = {
|
||||||
|
/* LEFT */ {{ D(TAB_FORWARD), D(TAB_BACKWARD), D(LEFT), D(RIGHT), D(UP), D(DOWN) },
|
||||||
|
/* RIGHT */ { D(TAB_BACKWARD), D(TAB_FORWARD), D(LEFT), D(RIGHT), D(DOWN), D(UP) },
|
||||||
|
/* TOP */ { D(TAB_FORWARD), D(TAB_BACKWARD), D(UP), D(DOWN), D(LEFT), D(RIGHT) },
|
||||||
|
/* BOTTOM */ { D(TAB_BACKWARD), D(TAB_FORWARD), D(DOWN), D(UP), D(LEFT), D(RIGHT) }},
|
||||||
|
/* LEFT */ {{ D(TAB_BACKWARD), D(TAB_FORWARD), D(LEFT), D(RIGHT), D(DOWN), D(UP) },
|
||||||
|
/* RIGHT */ { D(TAB_FORWARD), D(TAB_BACKWARD), D(LEFT), D(RIGHT), D(UP), D(DOWN) },
|
||||||
|
/* TOP */ { D(TAB_FORWARD), D(TAB_BACKWARD), D(UP), D(DOWN), D(RIGHT), D(LEFT) },
|
||||||
|
/* BOTTOM */ { D(TAB_BACKWARD), D(TAB_FORWARD), D(DOWN), D(UP), D(RIGHT), D(LEFT) }},
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef D
|
||||||
|
|
||||||
|
int text_dir = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL ? 1 : 0;
|
||||||
|
|
||||||
|
return translate_direction[text_dir][self->position][direction];
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkPositionType
|
||||||
|
get_effective_position (GtkTabBar *self)
|
||||||
|
{
|
||||||
|
if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
|
||||||
|
{
|
||||||
|
switch ((int)self->position)
|
||||||
|
{
|
||||||
|
case GTK_POS_LEFT:
|
||||||
|
return GTK_POS_RIGHT;
|
||||||
|
case GTK_POS_RIGHT:
|
||||||
|
return GTK_POS_LEFT;
|
||||||
|
default: ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gtk_tab_bar_focus (GtkWidget *widget,
|
||||||
|
GtkDirectionType dir)
|
||||||
|
{
|
||||||
|
GtkTabBar *self = GTK_TAB_BAR (widget);
|
||||||
|
GtkWidget *old_focus_child;
|
||||||
|
unsigned int n_items;
|
||||||
|
GtkDirectionType direction;
|
||||||
|
|
||||||
|
direction = get_effective_direction (self, dir);
|
||||||
|
|
||||||
|
n_items = g_list_model_get_n_items (G_LIST_MODEL (self->pages));
|
||||||
|
|
||||||
|
old_focus_child = gtk_widget_get_focus_child (widget);
|
||||||
|
|
||||||
|
if (old_focus_child)
|
||||||
|
{
|
||||||
|
unsigned int position;
|
||||||
|
|
||||||
|
g_object_get (old_focus_child, "position", &position, NULL);
|
||||||
|
|
||||||
|
if (direction == GTK_DIR_TAB_FORWARD ||
|
||||||
|
direction == GTK_DIR_TAB_BACKWARD)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (direction == GTK_DIR_LEFT ||
|
||||||
|
direction == GTK_DIR_RIGHT)
|
||||||
|
{
|
||||||
|
GtkWidget *tab;
|
||||||
|
|
||||||
|
if (direction == GTK_DIR_LEFT)
|
||||||
|
position = (position + n_items - 1) % n_items;
|
||||||
|
else
|
||||||
|
position = (position + 1) % n_items;
|
||||||
|
|
||||||
|
tab = g_ptr_array_index (self->tabs, position);
|
||||||
|
gtk_widget_grab_focus (tab);
|
||||||
|
gtk_selection_model_select_item (self->pages, position, TRUE);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gtk_widget_child_focus (old_focus_child, direction))
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < n_items; i++)
|
||||||
|
{
|
||||||
|
if (gtk_selection_model_is_selected (self->pages, i))
|
||||||
|
{
|
||||||
|
GtkWidget *tab;
|
||||||
|
|
||||||
|
tab = g_ptr_array_index (self->tabs, i);
|
||||||
|
gtk_widget_grab_focus (tab);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gtk_widget_focus_child (widget, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_bar_class_init (GtkTabBarClass *class)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||||||
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
||||||
|
|
||||||
|
object_class->get_property = gtk_tab_bar_get_property;
|
||||||
|
object_class->set_property = gtk_tab_bar_set_property;
|
||||||
|
object_class->dispose = gtk_tab_bar_dispose;
|
||||||
|
object_class->finalize = gtk_tab_bar_finalize;
|
||||||
|
|
||||||
|
widget_class->focus = gtk_tab_bar_focus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GtkTabBar:position: (attributes org.gtk.Property.get=gtk_tab_bar_get_position org.gtk.Property.set=gtk_tab_bar_set_position)
|
||||||
|
*
|
||||||
|
* The position of the tab bar relative to the stack it controls.
|
||||||
|
*
|
||||||
|
* This information is used in keynav and tab rendering.
|
||||||
|
*/
|
||||||
|
properties[PROP_POSITION] =
|
||||||
|
g_param_spec_enum ("position", NULL, NULL,
|
||||||
|
GTK_TYPE_POSITION_TYPE,
|
||||||
|
GTK_POS_TOP,
|
||||||
|
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GtkTabBar:stack: (attributes org.gtk.Property.get=gtk_tab_bar_get_stack org.gtk.Property.set=gtk_tab_bar_set_stack)
|
||||||
|
*
|
||||||
|
* The stack that is controlled by this tab bar.
|
||||||
|
*/
|
||||||
|
properties[PROP_STACK] =
|
||||||
|
g_param_spec_object ("stack", NULL, NULL,
|
||||||
|
GTK_TYPE_STACK,
|
||||||
|
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
|
||||||
|
|
||||||
|
g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
|
||||||
|
|
||||||
|
gtk_widget_class_install_action (widget_class, "tab.switch", "u",
|
||||||
|
gtk_tab_bar_switch_tab);
|
||||||
|
|
||||||
|
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
|
||||||
|
gtk_widget_class_set_css_name (widget_class, I_("tabbar"));
|
||||||
|
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TAB_LIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gtk_tab_bar_new:
|
||||||
|
*
|
||||||
|
* Creates a new `GtkTabBar`.
|
||||||
|
*
|
||||||
|
* Returns: the newly created widget
|
||||||
|
*
|
||||||
|
* Since: 4.12
|
||||||
|
*/
|
||||||
|
GtkTabBar *
|
||||||
|
gtk_tab_bar_new (void)
|
||||||
|
{
|
||||||
|
return g_object_new (GTK_TYPE_TAB_BAR, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gtk_stack_bar_set_stack:
|
||||||
|
* @self: a `GtkTabBar`
|
||||||
|
* @stack: (nullable): a `GtkStack`
|
||||||
|
*
|
||||||
|
* Sets the stack that is controlled by this tab bar.
|
||||||
|
*
|
||||||
|
* Since: 4.12
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
gtk_tab_bar_set_stack (GtkTabBar *self,
|
||||||
|
GtkStack *stack)
|
||||||
|
{
|
||||||
|
g_return_if_fail (GTK_IS_TAB_BAR (self));
|
||||||
|
g_return_if_fail (stack == NULL || GTK_IS_STACK (stack));
|
||||||
|
|
||||||
|
if (self->stack == stack)
|
||||||
|
return;
|
||||||
|
|
||||||
|
unset_stack (self);
|
||||||
|
set_stack (self, stack);
|
||||||
|
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STACK]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gtk_tab_bar_get_stack:
|
||||||
|
* @self: a `GtkStack`
|
||||||
|
*
|
||||||
|
* Returns the stack that is controlled by this tab bar.
|
||||||
|
*
|
||||||
|
* Returns: (nullable): the stack
|
||||||
|
*
|
||||||
|
* Since: 4.12
|
||||||
|
*/
|
||||||
|
GtkStack *
|
||||||
|
gtk_tab_bar_get_stack (GtkTabBar *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (GTK_IS_TAB_BAR (self), NULL);
|
||||||
|
|
||||||
|
return self->stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_css_class_for_position (GtkTabBar *self)
|
||||||
|
{
|
||||||
|
GtkWidget *widget = GTK_WIDGET (self);
|
||||||
|
|
||||||
|
gtk_widget_remove_css_class (widget, "top");
|
||||||
|
gtk_widget_remove_css_class (widget, "bottom");
|
||||||
|
gtk_widget_remove_css_class (widget, "left");
|
||||||
|
gtk_widget_remove_css_class (widget, "right");
|
||||||
|
|
||||||
|
switch (get_effective_position (self))
|
||||||
|
{
|
||||||
|
case GTK_POS_TOP:
|
||||||
|
gtk_widget_add_css_class (widget, "top");
|
||||||
|
break;
|
||||||
|
case GTK_POS_BOTTOM:
|
||||||
|
gtk_widget_add_css_class (widget, "bottom");
|
||||||
|
break;
|
||||||
|
case GTK_POS_LEFT:
|
||||||
|
gtk_widget_add_css_class (widget, "left");
|
||||||
|
break;
|
||||||
|
case GTK_POS_RIGHT:
|
||||||
|
gtk_widget_add_css_class (widget, "right");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gtk_tab_bar_set_position:
|
||||||
|
* @self: a `GtkTabBar`
|
||||||
|
* @position: the position of the tabs
|
||||||
|
*
|
||||||
|
* Sets the position of the tab bar relative to
|
||||||
|
* the stack that it controls.
|
||||||
|
*
|
||||||
|
* This information is used in keynav and for
|
||||||
|
* drawing the tabs. Setting the position also
|
||||||
|
* updates the orientation accordingly.
|
||||||
|
*
|
||||||
|
* Since: 4.12
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
gtk_tab_bar_set_position (GtkTabBar *self,
|
||||||
|
GtkPositionType position)
|
||||||
|
{
|
||||||
|
g_return_if_fail (GTK_IS_TAB_BAR (self));
|
||||||
|
|
||||||
|
if (self->position == position)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self->position = position;
|
||||||
|
|
||||||
|
if (position == GTK_POS_LEFT || position == GTK_POS_RIGHT)
|
||||||
|
gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
|
||||||
|
else
|
||||||
|
gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
|
||||||
|
|
||||||
|
update_css_class_for_position (self);
|
||||||
|
|
||||||
|
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_POSITION]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gtk_tab_bar_get_position:
|
||||||
|
* @self: a `GtkTabBar`
|
||||||
|
*
|
||||||
|
* Gets the position of the tab bar relative to
|
||||||
|
* the stack that it controls.
|
||||||
|
*
|
||||||
|
* Returns: the position of the tabss
|
||||||
|
*
|
||||||
|
* Since: 4.12
|
||||||
|
*/
|
||||||
|
GtkPositionType
|
||||||
|
gtk_tab_bar_get_position (GtkTabBar *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (GTK_IS_TAB_BAR (self), GTK_POS_TOP);
|
||||||
|
|
||||||
|
return self->position;
|
||||||
|
}
|
24
gtk/gtktabbar.h
Normal file
24
gtk/gtktabbar.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtk/gtkstack.h>
|
||||||
|
#include <gtk/gtkwidget.h>
|
||||||
|
|
||||||
|
#define GTK_TYPE_TAB_BAR (gtk_tab_bar_get_type ())
|
||||||
|
G_DECLARE_FINAL_TYPE (GtkTabBar, gtk_tab_bar, GTK, TAB_BAR, GtkWidget)
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_12
|
||||||
|
GtkTabBar * gtk_tab_bar_new (void);
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_12
|
||||||
|
void gtk_tab_bar_set_stack (GtkTabBar *self,
|
||||||
|
GtkStack *stack);
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_12
|
||||||
|
GtkStack * gtk_tab_bar_get_stack (GtkTabBar *self);
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_12
|
||||||
|
void gtk_tab_bar_set_position (GtkTabBar *self,
|
||||||
|
GtkPositionType position);
|
||||||
|
GDK_AVAILABLE_IN_4_12
|
||||||
|
GtkPositionType gtk_tab_bar_get_position (GtkTabBar *self);
|
||||||
|
|
277
gtk/gtktabwidget.c
Normal file
277
gtk/gtktabwidget.c
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "gtktabwidgetprivate.h"
|
||||||
|
|
||||||
|
#include "gtkaccessible.h"
|
||||||
|
#include "gtkbinlayout.h"
|
||||||
|
#include "gtkdropcontrollermotion.h"
|
||||||
|
#include "gtkgestureclick.h"
|
||||||
|
#include "gtklabel.h"
|
||||||
|
#include "gtkprivate.h"
|
||||||
|
#include "gtkstack.h"
|
||||||
|
#include "gtkwidget.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define TIMEOUT_EXPAND 500
|
||||||
|
|
||||||
|
struct _GtkTabWidget {
|
||||||
|
GtkWidget parent_instance;
|
||||||
|
|
||||||
|
GtkStackPage *page;
|
||||||
|
GtkWidget *label;
|
||||||
|
unsigned int position;
|
||||||
|
unsigned int switch_timeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _GtkTabWidgetClass {
|
||||||
|
GtkWidget parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_PAGE = 1,
|
||||||
|
PROP_POSITION,
|
||||||
|
NUM_PROPERTIES,
|
||||||
|
};
|
||||||
|
|
||||||
|
static GParamSpec *properties[NUM_PROPERTIES] = { 0, };
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (GtkTabWidget, gtk_tab_widget, GTK_TYPE_WIDGET)
|
||||||
|
|
||||||
|
static void
|
||||||
|
pressed_cb (GtkGestureClick *gesture,
|
||||||
|
unsigned int n_press,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
GtkTabWidget *self)
|
||||||
|
{
|
||||||
|
gtk_widget_activate_action (GTK_WIDGET (self), "tab.switch", "u", self->position);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_tab (GtkStackPage *page,
|
||||||
|
GParamSpec *pspec,
|
||||||
|
GtkTabWidget *self)
|
||||||
|
{
|
||||||
|
char *title;
|
||||||
|
char *icon_name;
|
||||||
|
gboolean needs_attention;
|
||||||
|
gboolean visible;
|
||||||
|
gboolean use_underline;
|
||||||
|
|
||||||
|
g_object_get (page,
|
||||||
|
"title", &title,
|
||||||
|
"icon-name", &icon_name,
|
||||||
|
"needs-attention", &needs_attention,
|
||||||
|
"visible", &visible,
|
||||||
|
"use-underline", &use_underline,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
gtk_label_set_label (GTK_LABEL (self->label), title);
|
||||||
|
|
||||||
|
gtk_accessible_update_property (GTK_ACCESSIBLE (self),
|
||||||
|
GTK_ACCESSIBLE_PROPERTY_LABEL, title,
|
||||||
|
-1);
|
||||||
|
|
||||||
|
gtk_widget_set_visible (GTK_WIDGET (self), visible && (title != NULL || icon_name != NULL));
|
||||||
|
|
||||||
|
if (needs_attention)
|
||||||
|
gtk_widget_add_css_class (GTK_WIDGET (self), "needs-attention");
|
||||||
|
else
|
||||||
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "needs-attention");
|
||||||
|
|
||||||
|
g_free (title);
|
||||||
|
g_free (icon_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
unset_page (GtkTabWidget *self)
|
||||||
|
{
|
||||||
|
if (!self->page)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_signal_handlers_disconnect_by_func (self->page, update_tab, self);
|
||||||
|
g_clear_object (&self->page);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
set_page (GtkTabWidget *self,
|
||||||
|
GtkStackPage *page)
|
||||||
|
{
|
||||||
|
if (self->page == page)
|
||||||
|
return;
|
||||||
|
|
||||||
|
unset_page (self);
|
||||||
|
|
||||||
|
g_set_object (&self->page, page);
|
||||||
|
g_signal_connect (self->page, "notify", G_CALLBACK (update_tab), self);
|
||||||
|
update_tab (page, NULL, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gtk_tab_widget_switch_timeout (gpointer data)
|
||||||
|
{
|
||||||
|
GtkTabWidget *self = data;
|
||||||
|
|
||||||
|
gtk_widget_activate_action (GTK_WIDGET (self), "tab.switch", "u", self->position);
|
||||||
|
|
||||||
|
self->switch_timeout = 0;
|
||||||
|
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_widget_drag_enter (GtkDropControllerMotion *motion,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
GtkTabWidget *self)
|
||||||
|
{
|
||||||
|
if ((gtk_widget_get_state_flags (GTK_WIDGET (self)) & GTK_STATE_FLAG_SELECTED) == 0)
|
||||||
|
{
|
||||||
|
self->switch_timeout = g_timeout_add (TIMEOUT_EXPAND,
|
||||||
|
gtk_tab_widget_switch_timeout,
|
||||||
|
self);
|
||||||
|
gdk_source_set_static_name_by_id (self->switch_timeout,
|
||||||
|
"[gtk] gtk_tab_widget_switch_timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_widget_drag_leave (GtkDropControllerMotion *motion,
|
||||||
|
GtkTabWidget *self)
|
||||||
|
{
|
||||||
|
if (self->switch_timeout)
|
||||||
|
{
|
||||||
|
g_source_remove (self->switch_timeout);
|
||||||
|
self->switch_timeout = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_widget_init (GtkTabWidget *self)
|
||||||
|
{
|
||||||
|
GtkEventController *controller;
|
||||||
|
|
||||||
|
gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
|
||||||
|
gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
|
||||||
|
|
||||||
|
self->label = gtk_label_new ("");
|
||||||
|
gtk_widget_set_parent (self->label, GTK_WIDGET (self));
|
||||||
|
|
||||||
|
controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
|
||||||
|
g_signal_connect (controller, "pressed", G_CALLBACK (pressed_cb), self);
|
||||||
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
||||||
|
|
||||||
|
controller = gtk_drop_controller_motion_new ();
|
||||||
|
g_signal_connect (controller, "enter", G_CALLBACK (gtk_tab_widget_drag_enter), self);
|
||||||
|
g_signal_connect (controller, "leave", G_CALLBACK (gtk_tab_widget_drag_leave), self);
|
||||||
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_widget_get_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
GtkTabWidget *self = GTK_TAB_WIDGET (object);
|
||||||
|
|
||||||
|
switch (prop_id)
|
||||||
|
{
|
||||||
|
case PROP_PAGE:
|
||||||
|
g_value_set_object (value, self->page);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROP_POSITION:
|
||||||
|
g_value_set_uint (value, self->position);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_widget_set_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
GtkTabWidget *self = GTK_TAB_WIDGET (object);
|
||||||
|
|
||||||
|
switch (prop_id)
|
||||||
|
{
|
||||||
|
case PROP_PAGE:
|
||||||
|
set_page (self, g_value_get_object (value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROP_POSITION:
|
||||||
|
self->position = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_widget_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
GtkTabWidget *self = GTK_TAB_WIDGET (object);
|
||||||
|
|
||||||
|
unset_page (self);
|
||||||
|
g_clear_pointer (&self->label, gtk_widget_unparent);
|
||||||
|
|
||||||
|
if (self->switch_timeout)
|
||||||
|
{
|
||||||
|
g_source_remove (self->switch_timeout);
|
||||||
|
self->switch_timeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (gtk_tab_widget_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_widget_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
G_OBJECT_CLASS (gtk_tab_widget_parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_tab_widget_class_init (GtkTabWidgetClass *class)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||||||
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
||||||
|
|
||||||
|
object_class->get_property = gtk_tab_widget_get_property;
|
||||||
|
object_class->set_property = gtk_tab_widget_set_property;
|
||||||
|
object_class->dispose = gtk_tab_widget_dispose;
|
||||||
|
object_class->finalize = gtk_tab_widget_finalize;
|
||||||
|
|
||||||
|
properties[PROP_PAGE] =
|
||||||
|
g_param_spec_object ("page", NULL, NULL,
|
||||||
|
GTK_TYPE_STACK_PAGE,
|
||||||
|
GTK_PARAM_READWRITE);
|
||||||
|
|
||||||
|
properties[PROP_POSITION] =
|
||||||
|
g_param_spec_uint ("position", NULL, NULL,
|
||||||
|
0, G_MAXUINT, 0,
|
||||||
|
GTK_PARAM_READWRITE);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
|
||||||
|
|
||||||
|
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
|
||||||
|
gtk_widget_class_set_css_name (widget_class, I_("tab"));
|
||||||
|
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
gtk_tab_widget_new (GtkStackPage *page,
|
||||||
|
unsigned int position)
|
||||||
|
{
|
||||||
|
return g_object_new (GTK_TYPE_TAB_WIDGET,
|
||||||
|
"page", page,
|
||||||
|
"position", position,
|
||||||
|
NULL);
|
||||||
|
}
|
10
gtk/gtktabwidgetprivate.h
Normal file
10
gtk/gtktabwidgetprivate.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtk/gtkwidget.h>
|
||||||
|
#include <gtk/gtkstack.h>
|
||||||
|
|
||||||
|
#define GTK_TYPE_TAB_WIDGET (gtk_tab_widget_get_type ())
|
||||||
|
G_DECLARE_FINAL_TYPE (GtkTabWidget, gtk_tab_widget, GTK, TAB_WIDGET, GtkWidget)
|
||||||
|
|
||||||
|
GtkWidget * gtk_tab_widget_new (GtkStackPage *page,
|
||||||
|
unsigned int position);
|
@@ -95,6 +95,7 @@ gtk_private_sources = files([
|
|||||||
'gtkcssstylechange.c',
|
'gtkcssstylechange.c',
|
||||||
'gtkcssstyleproperty.c',
|
'gtkcssstyleproperty.c',
|
||||||
'gtkcssstylepropertyimpl.c',
|
'gtkcssstylepropertyimpl.c',
|
||||||
|
'gtktabwidget.c',
|
||||||
'gtkcsstransformvalue.c',
|
'gtkcsstransformvalue.c',
|
||||||
'gtkcsstransientnode.c',
|
'gtkcsstransientnode.c',
|
||||||
'gtkcsstransition.c',
|
'gtkcsstransition.c',
|
||||||
@@ -365,6 +366,7 @@ gtk_public_sources = files([
|
|||||||
'gtkstyleprovider.c',
|
'gtkstyleprovider.c',
|
||||||
'gtkswitch.c',
|
'gtkswitch.c',
|
||||||
'gtksymbolicpaintable.c',
|
'gtksymbolicpaintable.c',
|
||||||
|
'gtktabbar.c',
|
||||||
'gtktestatcontext.c',
|
'gtktestatcontext.c',
|
||||||
'gtktestutils.c',
|
'gtktestutils.c',
|
||||||
'gtktext.c',
|
'gtktext.c',
|
||||||
@@ -594,6 +596,7 @@ gtk_public_headers = files([
|
|||||||
'gtkstyleprovider.h',
|
'gtkstyleprovider.h',
|
||||||
'gtkswitch.h',
|
'gtkswitch.h',
|
||||||
'gtksymbolicpaintable.h',
|
'gtksymbolicpaintable.h',
|
||||||
|
'gtktabbar.h',
|
||||||
'gtktestatcontext.h',
|
'gtktestatcontext.h',
|
||||||
'gtktestutils.h',
|
'gtktestutils.h',
|
||||||
'gtktext.h',
|
'gtktext.h',
|
||||||
|
@@ -2103,6 +2103,94 @@ menubar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/************
|
||||||
|
* Tabbar *
|
||||||
|
************/
|
||||||
|
|
||||||
|
tabbar {
|
||||||
|
padding: 1px;
|
||||||
|
border-color: $borders_color;
|
||||||
|
border-width: 1px;
|
||||||
|
background-color: $dark_fill;
|
||||||
|
margin: -1px;
|
||||||
|
|
||||||
|
&.top {
|
||||||
|
border-bottom-style: solid;
|
||||||
|
margin-bottom: -2px;
|
||||||
|
> tab {
|
||||||
|
&:hover { box-shadow: inset 0 -4px $borders_color; }
|
||||||
|
&:selected { box-shadow: inset 0 -4px $selected_bg_color; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bottom {
|
||||||
|
border-top-style: solid;
|
||||||
|
margin-top: -2px;
|
||||||
|
> tab {
|
||||||
|
&:hover { box-shadow: inset 0 4px $borders_color; }
|
||||||
|
&:selected { box-shadow: inset 0 4px $selected_bg_color; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
border-right-style: solid;
|
||||||
|
margin-right: -2px;
|
||||||
|
> tab {
|
||||||
|
&:hover { box-shadow: inset -4px 0 $borders_color; }
|
||||||
|
&:selected { box-shadow: inset -4px 0 $selected_bg_color; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
border-left-style: solid;
|
||||||
|
margin-left: -2px;
|
||||||
|
> tab {
|
||||||
|
&:hover { box-shadow: inset 4px 0 $borders_color; }
|
||||||
|
&:selected { box-shadow: inset 4px 0 $selected_bg_color; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> tab {
|
||||||
|
transition: $focus_transition;
|
||||||
|
min-height: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
padding: 3px 12px;
|
||||||
|
|
||||||
|
color: $fg_color;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
border-width: 1px; // for reorderable tabs
|
||||||
|
border-color: transparent; //
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $fg_color;
|
||||||
|
background-color: darken($dark_fill,4%);
|
||||||
|
|
||||||
|
&.reorderable-page {
|
||||||
|
border-color: transparentize($borders_color, 0.7);
|
||||||
|
background-color: transparentize($bg_color, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:selected) {
|
||||||
|
outline-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:selected {
|
||||||
|
color: $fg_color;
|
||||||
|
&.reorderable-page {
|
||||||
|
border-color: transparentize($borders_color, 0.5);
|
||||||
|
background-color: transparentize($bg_color, 0.5);
|
||||||
|
|
||||||
|
&:hover { background-color: transparentize($bg_color, 0.3); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.top > tab { padding-bottom: 4px; }
|
||||||
|
&.bottom > tab { padding-top: 4px; }
|
||||||
|
}
|
||||||
|
|
||||||
/*************
|
/*************
|
||||||
* Notebooks *
|
* Notebooks *
|
||||||
*************/
|
*************/
|
||||||
|
Reference in New Issue
Block a user