/*
* gedit-notebook-stack-switcher.h
* This file is part of gedit
*
* Copyright (C) 2014 - Paolo Borelli
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include "config.h"
#include "gedit-notebook-stack-switcher.h"
#include
#include
#include
/*
* This widget is a rather ugly kludge: it uses a GtkNotebook full of empty
* pages to create a stack switcher for the bottom pane. This is needed
* because we want to expose GtkStack in the API but we want the tabs styled
* as notebook tabs. Hopefully Gtk itself will grow a "tabs" stack switcher...
*/
struct _GeditNotebookStackSwitcherPrivate
{
GtkWidget *notebook;
GtkStack *stack;
};
enum {
PROP_0,
PROP_STACK
};
G_DEFINE_TYPE_WITH_PRIVATE (GeditNotebookStackSwitcher, gedit_notebook_stack_switcher, GTK_TYPE_BIN)
static void
gedit_notebook_stack_switcher_init (GeditNotebookStackSwitcher *switcher)
{
GeditNotebookStackSwitcherPrivate *priv;
priv = gedit_notebook_stack_switcher_get_instance_private (switcher);
switcher->priv = priv;
priv->notebook = gtk_notebook_new ();
gtk_notebook_set_tab_pos (GTK_NOTEBOOK (priv->notebook), GTK_POS_BOTTOM);
gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook), FALSE);
gtk_container_set_border_width (GTK_CONTAINER (priv->notebook), 0);
gtk_widget_show (priv->notebook);
gtk_container_add (GTK_CONTAINER (switcher), priv->notebook);
}
static GtkWidget *
find_notebook_child (GeditNotebookStackSwitcher *switcher,
GtkWidget *stack_child)
{
GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
GList *pages;
GList *p;
GtkWidget *ret = NULL;
if (stack_child == NULL)
{
return NULL;
}
pages = gtk_container_get_children (GTK_CONTAINER (priv->notebook));
for (p = pages; p != NULL; p = p->next)
{
GtkWidget *child;
child = g_object_get_data (p->data, "stack-child");
if (stack_child == child)
{
ret = p->data;
break;
}
}
g_list_free (pages);
return ret;
}
static void
sync_label (GeditNotebookStackSwitcher *switcher,
GtkWidget *stack_child,
GtkWidget *notebook_child)
{
GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
if (stack_child != NULL && notebook_child != NULL)
{
gchar *title;
gtk_widget_set_visible (notebook_child,
gtk_widget_get_visible (stack_child));
gtk_container_child_get (GTK_CONTAINER (priv->stack), stack_child,
"title", &title,
NULL);
gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (priv->notebook),
notebook_child,
title);
g_free (title);
}
}
static void
on_child_prop_changed (GtkWidget *widget,
GParamSpec *pspec,
GeditNotebookStackSwitcher *switcher)
{
GtkWidget *nb_child;
nb_child = find_notebook_child (switcher, widget);
sync_label (switcher, widget, nb_child);
}
static void
on_child_changed (GtkWidget *widget,
GParamSpec *pspec,
GeditNotebookStackSwitcher *switcher)
{
GtkNotebook *notebook;
GtkWidget *child;
GtkWidget *nb_child;
gint nb_page;
notebook = GTK_NOTEBOOK (switcher->priv->notebook);
child = gtk_stack_get_visible_child (GTK_STACK (widget));
nb_child = find_notebook_child (switcher, child);
nb_page = gtk_notebook_page_num (notebook, nb_child);
g_signal_handlers_block_by_func (widget, on_child_prop_changed, switcher);
gtk_notebook_set_current_page (notebook, nb_page);
g_signal_handlers_unblock_by_func (widget, on_child_prop_changed, switcher);
sync_label (switcher, child, nb_child);
}
static void
on_stack_child_added (GtkStack *stack,
GtkWidget *widget,
GeditNotebookStackSwitcher *switcher)
{
GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
GtkWidget *dummy;
dummy = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
g_object_set_data (G_OBJECT (dummy), "stack-child", widget);
gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook),
dummy,
NULL);
g_signal_connect (widget, "notify::visible",
G_CALLBACK (on_child_prop_changed), switcher);
g_signal_connect (widget, "child-notify::title",
G_CALLBACK (on_child_prop_changed), switcher);
sync_label (switcher, widget, dummy);
}
static void
on_stack_child_removed (GtkStack *stack,
GtkWidget *widget,
GeditNotebookStackSwitcher *switcher)
{
GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
GtkWidget *nb_child;
g_signal_handlers_disconnect_by_func (widget, on_child_prop_changed, switcher);
nb_child = find_notebook_child (switcher, widget);
gtk_container_remove (GTK_CONTAINER (priv->notebook), nb_child);
}
static void
on_notebook_switch_page (GtkNotebook *notebook,
GtkWidget *page,
guint page_num,
GeditNotebookStackSwitcher *switcher)
{
GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
GtkWidget *child;
child = g_object_get_data (G_OBJECT (page), "stack-child");
/* NOTE: we make the assumption here that if there is no visible child
* it means that the child does not contain any child already, this is
* to avoid an assertion when closing gedit, since the remove signal
* runs first on the stack handler so we try to set a visible child
* when the stack already does not handle that child
*/
if (child != NULL && gtk_stack_get_visible_child (priv->stack) != NULL)
{
gtk_stack_set_visible_child (priv->stack, child);
}
}
static void
disconnect_signals (GeditNotebookStackSwitcher *switcher)
{
GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_added, switcher);
g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_removed, switcher);
g_signal_handlers_disconnect_by_func (priv->stack, on_child_changed, switcher);
g_signal_handlers_disconnect_by_func (priv->stack, disconnect_signals, switcher);
g_signal_handlers_disconnect_by_func (priv->notebook, on_notebook_switch_page, switcher);
}
static void
connect_signals (GeditNotebookStackSwitcher *switcher)
{
GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
g_signal_connect (priv->stack, "add",
G_CALLBACK (on_stack_child_added), switcher);
g_signal_connect (priv->stack, "remove",
G_CALLBACK (on_stack_child_removed), switcher);
g_signal_connect (priv->stack, "notify::visible-child",
G_CALLBACK (on_child_changed), switcher);
g_signal_connect_swapped (priv->stack, "destroy",
G_CALLBACK (disconnect_signals), switcher);
g_signal_connect (priv->notebook, "switch-page",
G_CALLBACK (on_notebook_switch_page), switcher);
}
void
gedit_notebook_stack_switcher_set_stack (GeditNotebookStackSwitcher *switcher,
GtkStack *stack)
{
GeditNotebookStackSwitcherPrivate *priv;
g_return_if_fail (GEDIT_IS_NOTEBOOK_STACK_SWITCHER (switcher));
g_return_if_fail (stack == NULL || GTK_IS_STACK (stack));
priv = switcher->priv;
if (priv->stack == stack)
return;
if (priv->stack)
{
disconnect_signals (switcher);
g_clear_object (&priv->stack);
}
if (stack)
{
priv->stack = g_object_ref (stack);
connect_signals (switcher);
}
g_object_notify (G_OBJECT (switcher), "stack");
}
GtkStack *
gedit_notebook_stack_switcher_get_stack (GeditNotebookStackSwitcher *switcher)
{
g_return_val_if_fail (GEDIT_IS_NOTEBOOK_STACK_SWITCHER (switcher), NULL);
return switcher->priv->stack;
}
static void
gedit_notebook_stack_switcher_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GeditNotebookStackSwitcher *switcher = GEDIT_NOTEBOOK_STACK_SWITCHER (object);
GeditNotebookStackSwitcherPrivate *priv = switcher->priv;
switch (prop_id)
{
case PROP_STACK:
g_value_set_object (value, priv->stack);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gedit_notebook_stack_switcher_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GeditNotebookStackSwitcher *switcher = GEDIT_NOTEBOOK_STACK_SWITCHER (object);
switch (prop_id)
{
case PROP_STACK:
gedit_notebook_stack_switcher_set_stack (switcher, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gedit_notebook_stack_switcher_dispose (GObject *object)
{
GeditNotebookStackSwitcher *switcher = GEDIT_NOTEBOOK_STACK_SWITCHER (object);
gedit_notebook_stack_switcher_set_stack (switcher, NULL);
G_OBJECT_CLASS (gedit_notebook_stack_switcher_parent_class)->dispose (object);
}
static void
gedit_notebook_stack_switcher_class_init (GeditNotebookStackSwitcherClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = gedit_notebook_stack_switcher_get_property;
object_class->set_property = gedit_notebook_stack_switcher_set_property;
object_class->dispose = gedit_notebook_stack_switcher_dispose;
g_object_class_install_property (object_class,
PROP_STACK,
g_param_spec_object ("stack",
"Stack",
"Stack",
GTK_TYPE_STACK,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
GtkWidget *
gedit_notebook_stack_switcher_new (void)
{
return g_object_new (GEDIT_TYPE_NOTEBOOK_STACK_SWITCHER, NULL);
}
/* ex:set ts=8 noet: */