Verified Commit a365148d authored by Zander Brown's avatar Zander Brown

tabs: use HdyTabView/HdyTabBar

Inital port away from GtkNotebook, still a lot (e.g. close button) to do
parent c1ce1350
/* kgx-pages-tab.c
*
* Copyright 2019 Zander Brown
*
* 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 3 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 <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:kgx-pages-tab
* @title: KgxPagesTab
* @short_description: Tab representing a #KgxPage in a #KgxPages
*
* Since: 0.3.0
*/
#include <glib/gi18n.h>
#include "kgx-config.h"
#include "kgx-tab.h"
#include "kgx-pages-tab.h"
#include "kgx-enums.h"
#include "kgx-window.h"
typedef struct _KgxPagesTabPrivate KgxPagesTabPrivate;
struct _KgxPagesTabPrivate {
char *title;
KgxStatus status;
GActionMap *actions;
};
G_DEFINE_TYPE_WITH_PRIVATE (KgxPagesTab, kgx_pages_tab, GTK_TYPE_EVENT_BOX)
enum {
PROP_0,
PROP_TITLE,
PROP_DESCRIPTION,
PROP_STATUS,
LAST_PROP
};
static GParamSpec *pspecs[LAST_PROP] = { NULL, };
enum {
CLOSE,
DETACH,
N_SIGNALS
};
static guint signals[N_SIGNALS];
static void
kgx_pages_tab_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
KgxPagesTab *self = KGX_PAGES_TAB (object);
KgxPagesTabPrivate *priv = kgx_pages_tab_get_instance_private (self);
GtkStyleContext *context;
switch (property_id) {
case PROP_TITLE:
g_clear_pointer (&priv->title, g_free);
priv->title = g_value_dup_string (value);
break;
case PROP_DESCRIPTION:
gtk_widget_set_tooltip_markup (GTK_WIDGET (object),
g_value_get_string (value));
break;
case PROP_STATUS:
priv->status = g_value_get_flags (value);
context = gtk_widget_get_style_context (GTK_WIDGET (self));
if (priv->status & KGX_REMOTE) {
gtk_style_context_add_class (context, KGX_WINDOW_STYLE_REMOTE);
} else {
gtk_style_context_remove_class (context, KGX_WINDOW_STYLE_REMOTE);
}
if (priv->status & KGX_PRIVILEGED) {
gtk_style_context_add_class (context, KGX_WINDOW_STYLE_ROOT);
} else {
gtk_style_context_remove_class (context, KGX_WINDOW_STYLE_ROOT);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
kgx_pages_tab_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
KgxPagesTab *self = KGX_PAGES_TAB (object);
KgxPagesTabPrivate *priv = kgx_pages_tab_get_instance_private (self);
switch (property_id) {
case PROP_TITLE:
g_value_set_string (value, priv->title);
break;
case PROP_DESCRIPTION:
g_value_set_string (value,
gtk_widget_get_tooltip_markup (GTK_WIDGET (object)));
break;
case PROP_STATUS:
g_value_set_flags (value, priv->status);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
kgx_pages_tab_finalize (GObject *object)
{
KgxPagesTab *self = KGX_PAGES_TAB (object);
KgxPagesTabPrivate *priv = kgx_pages_tab_get_instance_private (self);
g_clear_pointer (&priv->title, g_free);
G_OBJECT_CLASS (kgx_pages_tab_parent_class)->finalize (object);
}
static void
context_menu (GtkWidget *widget,
int x,
int y,
GdkEvent *event)
{
GtkApplication *app;
GMenu *model;
GtkWidget *menu;
GdkRectangle rect = {x, y, 1, 1};
app = GTK_APPLICATION (g_application_get_default ());
model = gtk_application_get_menu_by_id (app, "tab-menu");
menu = gtk_popover_new_from_model (widget, G_MENU_MODEL (model));
gtk_popover_set_pointing_to (GTK_POPOVER (menu), &rect);
gtk_popover_popup (GTK_POPOVER (menu));
}
static gboolean
kgx_pages_tab_popup_menu (GtkWidget *self)
{
context_menu (self, 1, 1, NULL);
return TRUE;
}
static gboolean
kgx_pages_tab_button_press_event (GtkWidget *self, GdkEventButton *event)
{
if (gdk_event_triggers_context_menu ((GdkEvent *) event) &&
event->type == GDK_BUTTON_PRESS) {
context_menu (self, event->x, event->y, (GdkEvent *) event);
return TRUE;
}
return GTK_WIDGET_CLASS (kgx_pages_tab_parent_class)->button_press_event (self, event);
}
static void
kgx_pages_tab_class_init (KgxPagesTabClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->set_property = kgx_pages_tab_set_property;
object_class->get_property = kgx_pages_tab_get_property;
object_class->finalize = kgx_pages_tab_finalize;
widget_class->popup_menu = kgx_pages_tab_popup_menu;
widget_class->button_press_event = kgx_pages_tab_button_press_event;
pspecs[PROP_TITLE] =
g_param_spec_string ("title", "Title", "Tab title",
NULL,
G_PARAM_READWRITE);
pspecs[PROP_DESCRIPTION] =
g_param_spec_string ("description", "Description", "Tab description",
NULL,
G_PARAM_READWRITE);
pspecs[PROP_STATUS] =
g_param_spec_flags ("status", "Status", "Special state of the tab",
KGX_TYPE_STATUS,
KGX_NONE,
G_PARAM_READWRITE);
g_object_class_install_properties (object_class, LAST_PROP, pspecs);
signals[CLOSE] = g_signal_new ("close",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals[DETACH] = g_signal_new ("detach",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
0);
gtk_widget_class_set_template_from_resource (widget_class,
RES_PATH "kgx-pages-tab.ui");
}
static void
close_activated (GSimpleAction *action,
GVariant *parameter,
gpointer data)
{
g_signal_emit (data, signals[CLOSE], 0);
}
static void
detach_activated (GSimpleAction *action,
GVariant *parameter,
gpointer data)
{
g_signal_emit (data, signals[DETACH], 0);
}
static GActionEntry tab_entries[] =
{
{ "close", close_activated, NULL, NULL, NULL },
{ "detach", detach_activated, NULL, NULL, NULL },
};
static void
kgx_pages_tab_init (KgxPagesTab *self)
{
KgxPagesTabPrivate *priv = kgx_pages_tab_get_instance_private (self);
gtk_widget_set_has_window (GTK_WIDGET (self), TRUE);
priv->actions = G_ACTION_MAP (g_simple_action_group_new ());
g_action_map_add_action_entries (priv->actions,
tab_entries,
G_N_ELEMENTS (tab_entries),
self);
gtk_widget_insert_action_group (GTK_WIDGET (self),
"tab",
G_ACTION_GROUP (priv->actions));
gtk_widget_add_events (GTK_WIDGET (self), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
gtk_widget_init_template (GTK_WIDGET (self));
}
/* kgx-pages-tab.h
*
* Copyright 2019 Zander Brown
*
* 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 3 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define KGX_TYPE_PAGES_TAB (kgx_pages_tab_get_type())
G_DECLARE_DERIVABLE_TYPE (KgxPagesTab, kgx_pages_tab, KGX, PAGES_TAB, GtkEventBox)
struct _KgxPagesTabClass {
/*< private >*/
GtkEventBoxClass parent;
};
G_END_DECLS
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="KgxPagesTab" parent="GtkEventBox">
<property name="visible">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="valign">center</property>
<property name="xalign">0.0</property>
<property name="hexpand">True</property>
<property name="label" bind-source="KgxPagesTab" bind-property="title" bind-flags="sync-create"/>
<property name="ellipsize">end</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="relief">none</property>
<property name="margin-end">4</property>
<property name="tooltip-text" translatable="yes">Close</property>
<property name="action-name">tab.close</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="icon_name">window-close-symbolic</property>
</object>
</child>
<style>
<class name="circular"/>
<class name="close-tab"/>
</style>
</object>
</child>
<style>
<class name="content"/>
</style>
</object>
</child>
</template>
</interface>
......@@ -21,8 +21,7 @@
* @title: KgxPages
* @short_description: Container of #KgxTab s
*
* The container of open #KgxTab , through #GtkNotebook it also provides tabs
* in desktop mode
* The container of open #KgxTab (uses #HdyTabView internally)
*
* Since: 0.3.0
*/
......@@ -40,7 +39,8 @@
typedef struct _KgxPagesPrivate KgxPagesPrivate;
struct _KgxPagesPrivate {
GtkWidget *stack;
GtkWidget *notebook;
GtkWidget *view;
GtkWidget *tabbar;
GtkWidget *empty;
GtkWidget *status;
......@@ -76,6 +76,7 @@ G_DEFINE_TYPE_WITH_PRIVATE (KgxPages, kgx_pages, GTK_TYPE_OVERLAY)
enum {
PROP_0,
PROP_TAB_BAR,
PROP_TAB_COUNT,
PROP_TITLE,
PROP_PATH,
......@@ -108,7 +109,10 @@ kgx_pages_get_property (GObject *object,
switch (property_id) {
case PROP_TAB_COUNT:
g_value_set_uint (value, gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook)));
g_value_set_uint (value, hdy_tab_view_get_n_pages (HDY_TAB_VIEW (priv->view)));
break;
case PROP_TAB_BAR:
g_value_set_object (value, priv->tabbar);
break;
case PROP_TITLE:
g_value_set_string (value, priv->title);
......@@ -151,6 +155,14 @@ kgx_pages_set_property (GObject *object,
KgxPagesPrivate *priv = kgx_pages_get_instance_private (self);
switch (property_id) {
case PROP_TAB_BAR:
g_clear_object (&priv->tabbar);
priv->tabbar = g_value_dup_object (value);
if (priv->tabbar) {
hdy_tab_bar_set_view (HDY_TAB_BAR (priv->tabbar),
HDY_TAB_VIEW (priv->view));
}
break;
case PROP_TITLE:
g_clear_pointer (&priv->title, g_free);
priv->title = g_value_dup_string (value);
......@@ -201,9 +213,9 @@ size_timeout (KgxPages *self)
static void
size_changed (KgxTab *page,
guint cols,
size_changed (KgxTab *tab,
guint rows,
guint cols,
KgxPages *self)
{
KgxPagesPrivate *priv = kgx_pages_get_instance_private (self);
......@@ -233,30 +245,27 @@ size_changed (KgxTab *page,
static void
page_changed (GtkNotebook *notebook,
GtkWidget *page,
int page_num,
KgxPages *self)
page_changed (GObject *object, GParamSpec *pspec, KgxPages *self)
{
KgxPagesPrivate *priv = kgx_pages_get_instance_private (self);
g_return_if_fail (KGX_IS_TAB (page));
HdyTabPage *page = hdy_tab_view_get_selected_page (HDY_TAB_VIEW (priv->view));
KgxTab *tab = KGX_TAB (hdy_tab_page_get_child (page));
clear_signal_handler (&priv->size_watcher, priv->active_page);
priv->size_watcher = g_signal_connect (page,
priv->size_watcher = g_signal_connect (tab,
"size-changed",
G_CALLBACK (size_changed),
self);
g_clear_object (&priv->title_bind);
priv->title_bind = g_object_bind_property (page,
priv->title_bind = g_object_bind_property (tab,
"tab-title",
self,
"title",
G_BINDING_SYNC_CREATE);
g_clear_object (&priv->path_bind);
priv->path_bind = g_object_bind_property (page,
priv->path_bind = g_object_bind_property (tab,
"tab-path",
self,
"path",
......@@ -269,40 +278,23 @@ page_changed (GtkNotebook *notebook,
g_clear_object (&priv->is_active_bind);
priv->is_active_bind = g_object_bind_property (self,
"is-active",
page,
tab,
"is-active",
G_BINDING_SYNC_CREATE);
g_clear_object (&priv->page_status_bind);
priv->page_status_bind = g_object_bind_property (page,
priv->page_status_bind = g_object_bind_property (tab,
"tab-status",
self,
"status",
G_BINDING_SYNC_CREATE);
priv->active_page = KGX_TAB (page);
priv->active_page = KGX_TAB (tab);
}
static void
update_tabs (KgxPages *self)
{
KgxPagesPrivate *priv = kgx_pages_get_instance_private (self);
int width = gtk_widget_get_allocated_width (GTK_WIDGET (self));
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook)) > 1 &&
width >= 400) {
gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), TRUE);
} else {
gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), FALSE);
}
g_object_notify_by_pspec (G_OBJECT (self), pspecs[PROP_TAB_COUNT]);
}
static void
died (KgxTab *page,
died (KgxTab *page,
GtkMessageType type,
const char *message,
gboolean success,
......@@ -328,44 +320,44 @@ died (KgxTab *page,
static void
page_added (GtkNotebook *notebook,
GtkWidget *page,
guint id,
KgxPages *self)
page_added (HdyTabView *view,
HdyTabPage *page,
int position,
KgxPages *self)
{
KgxTab *tab;
KgxPagesPrivate *priv;
g_return_if_fail (KGX_IS_TAB (page));
g_return_if_fail (HDY_IS_TAB_PAGE (page));
priv = kgx_pages_get_instance_private (self);
tab = KGX_TAB (hdy_tab_page_get_child (page));
g_signal_connect (page, "died", G_CALLBACK (died), self);
gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), page, TRUE);
gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), page, TRUE);
priv = kgx_pages_get_instance_private (self);
update_tabs (self);
g_signal_connect (tab, "died", G_CALLBACK (died), self);
gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->notebook);
gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->view);
}
static void
page_removed (GtkNotebook *notebook,
GtkWidget *page,
guint id,
KgxPages *self)
page_removed (HdyTabView *view,
HdyTabPage *page,
int position,
KgxPages *self)
{
KgxTab *tab;
KgxPagesPrivate *priv;
g_return_if_fail (KGX_IS_TAB (page));
g_return_if_fail (HDY_IS_TAB_PAGE (page));
priv = kgx_pages_get_instance_private (self);
tab = KGX_TAB (hdy_tab_page_get_child (page));
g_signal_handlers_disconnect_by_data (page, self);
priv = kgx_pages_get_instance_private (self);
update_tabs (self);
g_signal_handlers_disconnect_by_data (tab, self);
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook)) == 0) {
if (hdy_tab_view_get_n_pages (HDY_TAB_VIEW (priv->view)) == 0) {
gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->empty);
priv->active_page = NULL;
......@@ -392,6 +384,20 @@ kgx_pages_class_init (KgxPagesClass *klass)
object_class->get_property = kgx_pages_get_property;
object_class->set_property = kgx_pages_set_property;
/**
* KgxPages:tab-bar:
*
* The #HdyTabBar
*
* Stability: Private
*
* Since: 0.3.0
*/
pspecs[PROP_TAB_BAR] =
g_param_spec_object ("tab-bar", "Tab Bar", "The tab bar",
HDY_TYPE_TAB_BAR,
G_PARAM_READWRITE);
/**
* KgxPages:tab-count:
*
......@@ -508,7 +514,7 @@ kgx_pages_class_init (KgxPagesClass *klass)
RES_PATH "kgx-pages.ui");
gtk_widget_class_bind_template_child_private (widget_class, KgxPages, stack);
gtk_widget_class_bind_template_child_private (widget_class, KgxPages, notebook);
gtk_widget_class_bind_template_child_private (widget_class, KgxPages, view);
gtk_widget_class_bind_template_child_private (widget_class, KgxPages, empty);
gtk_widget_class_bind_template_child_private (widget_class, KgxPages, status);
......@@ -597,22 +603,18 @@ kgx_pages_search (KgxPages *self,
void
kgx_pages_add_page (KgxPages *self,
KgxTab *page)
KgxTab *tab)
{
KgxPagesPrivate *priv;
KgxPagesTab *tab;
HdyTabPage *page;
g_return_if_fail (KGX_IS_PAGES (self));
priv = kgx_pages_get_instance_private (self);
tab = g_object_new (KGX_TYPE_PAGES_TAB,
"visible", TRUE,
NULL);
gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook),
GTK_WIDGET (page),
GTK_WIDGET (tab));
page = hdy_tab_view_append (HDY_TAB_VIEW (priv->view), GTK_WIDGET (tab));
g_object_bind_property (tab, "tab-title", page, "title", G_BINDING_SYNC_CREATE);
g_object_bind_property (tab, "tab-tooltip", page, "tooltip", G_BINDING_SYNC_CREATE);
}
......@@ -627,7 +629,7 @@ kgx_pages_remove_page (KgxPages *self,