/*
* Tab page
*
* Copyright (C) 2017 Patrick McDermott
*
* This file is part of Marquee.
*
* Marquee 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.
*
* Marquee 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 Marquee. If not, see .
*/
#include "tab-page.h"
#include
#include
#include
#include
#include
#include "application.h"
#include "tab-label.h"
#include "toolbars/find-toolbar.h"
#include "toolbars/navigation-toolbar.h"
#include "web-view.h"
#include "window.h"
typedef enum {
CREATE_NONE,
CREATE_ROOT,
CREATE_SIBLING,
CREATE_CHILD,
N_CREATE_TYPES
} CreateType;
struct _MqTabPage {
GtkBox parent_instance;
CreateType create_type;
MqWindow *window;
gchar *uri;
MqTabPage *source;
MqTabPage *root;
MqTabPage *parent;
MqTabPage *prev;
MqTabPage *next;
MqTabPage *first_child;
MqTabPage *last_child;
guint position;
guint tree_size;
MqApplication *application;
GtkWidget *container;
GtkWidget *label;
const gchar *title;
WebKitWebView *web_view;
};
enum {
PROP_CREATE_TYPE = 1,
PROP_WINDOW,
PROP_URI,
PROP_SOURCE,
N_PROPERTIES
};
static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,};
struct _MqTabPageClass {
GtkBoxClass parent_class;
};
G_DEFINE_TYPE(MqTabPage, mq_tab_page, GTK_TYPE_BOX)
static void
update_tree_sizes(MqTabPage *node, guint step)
{
if (node) {
node->tree_size += step;
update_tree_sizes(node->parent, step);
}
}
static void
append_child(MqTabPage *new_node)
{
MqTabPage *parent;
parent = new_node->source;
new_node->root = parent->root;
new_node->parent = parent;
new_node->next = NULL;
new_node->prev = parent->last_child; /* May be NULL */
new_node->first_child = new_node->last_child = NULL;
new_node->tree_size = 0; /* Will be updated */
if (parent->last_child) {
new_node->position = parent->last_child->position;
parent->last_child->next = new_node;
} else {
new_node->position = parent->position;
parent->first_child = new_node;
}
parent->last_child = new_node;
mq_tab_page_update_positions(new_node, 1);
update_tree_sizes(new_node, 1);
}
static void
append_sibling(MqTabPage *new_node)
{
MqTabPage *prev_sibling;
prev_sibling = new_node->source;
new_node->root = prev_sibling->root;
new_node->parent = prev_sibling->parent;
new_node->prev = prev_sibling;
new_node->next = prev_sibling->next; /* May be NULL */
new_node->first_child = new_node->last_child = NULL;
new_node->position = prev_sibling->position; /* Will be updated */
new_node->tree_size = 0; /* Will be updated */
if (prev_sibling->next) {
prev_sibling->next->prev = new_node;
}
prev_sibling->next = new_node;
mq_tab_page_update_positions(new_node, 1);
update_tree_sizes(new_node, 1);
}
static void
title_cb(WebKitWebView *web_view, GParamSpec G_GNUC_UNUSED *param_spec,
MqTabPage *tab_page)
{
tab_page->title = webkit_web_view_get_title(web_view);
mq_window_update_tab_title(tab_page->window, tab_page->position,
tab_page->title);
}
static void
init_root(MqTabPage *tab_page)
{
tab_page->root = tab_page;
tab_page->position = 0;
}
static void
init_non_root(MqTabPage *tab_page)
{
GtkWidget *navigation_toolbar;
GtkWidget *find_toolbar;
tab_page->window = tab_page->source->window;
tab_page->application = mq_window_get_application(tab_page->window);
tab_page->web_view = WEBKIT_WEB_VIEW(mq_web_view_new(tab_page,
tab_page->uri));
g_signal_connect(tab_page->web_view, "notify::title",
G_CALLBACK(title_cb), tab_page);
tab_page->label = mq_tab_label_new(tab_page,
MQ_WEB_VIEW(tab_page->web_view));
find_toolbar = mq_find_toolbar_new(MQ_WEB_VIEW(tab_page->web_view));
navigation_toolbar = mq_navigation_toolbar_new(
mq_application_get_config(
mq_window_get_application(tab_page->window)),
tab_page, MQ_FIND_TOOLBAR(find_toolbar),
MQ_WEB_VIEW(tab_page->web_view), tab_page->uri);
gtk_box_pack_start(GTK_BOX(tab_page),
navigation_toolbar, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(tab_page),
find_toolbar, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(tab_page),
GTK_WIDGET(tab_page->web_view), TRUE, TRUE, 0);
}
static void
constructed(GObject *object)
{
MqTabPage *tab_page;
if (G_OBJECT_CLASS(mq_tab_page_parent_class)->constructed) {
G_OBJECT_CLASS(mq_tab_page_parent_class)->constructed(object);
}
tab_page = MQ_TAB_PAGE(object);
switch (tab_page->create_type) {
case CREATE_ROOT:
init_root(tab_page);
break;
case CREATE_SIBLING:
init_non_root(tab_page);
append_sibling(tab_page);
mq_window_insert_tab(tab_page->window,
GTK_WIDGET(tab_page), tab_page->label,
tab_page->position);
break;
case CREATE_CHILD:
init_non_root(tab_page);
append_child(tab_page);
mq_window_insert_tab(tab_page->window,
GTK_WIDGET(tab_page), tab_page->label,
tab_page->position);
break;
case CREATE_NONE:
case N_CREATE_TYPES:
g_assert_not_reached();
break;
}
}
static void
finalize(GObject *object)
{
MqTabPage *tab_page;
tab_page = MQ_TAB_PAGE(object);
g_free(tab_page->uri);
G_OBJECT_CLASS(mq_tab_page_parent_class)->finalize(object);
}
static void
get_property(GObject *object, guint property_id, GValue *value,
GParamSpec *param_spec)
{
MqTabPage *tab_page;
tab_page = MQ_TAB_PAGE(object);
switch (property_id) {
case PROP_CREATE_TYPE:
g_value_set_uint(value, tab_page->create_type);
break;
case PROP_WINDOW:
g_value_set_pointer(value, tab_page->window);
break;
case PROP_URI:
g_value_set_string(value, tab_page->uri);
break;
case PROP_SOURCE:
g_value_set_object(value, tab_page->source);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id,
param_spec);
break;
}
}
static void
set_property(GObject *object, guint property_id, const GValue *value,
GParamSpec *param_spec)
{
MqTabPage *tab_page;
tab_page = MQ_TAB_PAGE(object);
switch (property_id) {
case PROP_CREATE_TYPE:
tab_page->create_type = g_value_get_uint(value);
break;
case PROP_WINDOW:
tab_page->window = g_value_get_pointer(value);
break;
case PROP_URI:
g_free(tab_page->uri);
tab_page->uri = g_strdup(g_value_get_string(value));
break;
case PROP_SOURCE:
tab_page->source = g_value_get_object(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id,
param_spec);
break;
}
}
static void
mq_tab_page_class_init(MqTabPageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->constructed = constructed;
object_class->finalize = finalize;
object_class->get_property = get_property;
object_class->set_property = set_property;
obj_properties[PROP_CREATE_TYPE] = g_param_spec_uint(
"create-type",
"Type",
"The type of tab page to create (root, sibling, or child)",
CREATE_NONE, N_CREATE_TYPES, CREATE_NONE,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
obj_properties[PROP_WINDOW] = g_param_spec_pointer(
"window",
"MqWindow",
"The parent MqWindow instance",
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
obj_properties[PROP_URI] = g_param_spec_string(
"uri",
"URI",
"The URI to load",
"",
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
obj_properties[PROP_SOURCE] = g_param_spec_object(
"source",
"Source MqTabPage",
"The source (previous sibling or parent) MqTabPage instance",
MQ_TYPE_TAB_PAGE,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
g_object_class_install_properties(object_class, N_PROPERTIES,
obj_properties);
}
static void
mq_tab_page_init(MqTabPage *tab_page)
{
tab_page->parent = NULL;
tab_page->prev = NULL;
tab_page->next = NULL;
tab_page->first_child = tab_page->last_child = NULL;
tab_page->tree_size = 1;
tab_page->title = "New tab";
}
MqTabPage *
mq_tab_page_new(const gchar *uri, MqTabPage *source)
{
return g_object_new(MQ_TYPE_TAB_PAGE,
"orientation", GTK_ORIENTATION_VERTICAL,
"spacing", 0,
"create-type", CREATE_SIBLING,
"uri", uri,
"source", source,
NULL);
}
MqTabPage *
mq_tab_page_new_relative(const gchar *uri, MqTabPage *source)
{
return g_object_new(MQ_TYPE_TAB_PAGE,
"orientation", GTK_ORIENTATION_VERTICAL,
"spacing", 0,
"create-type", CREATE_CHILD,
"uri", uri,
"source", source,
NULL);
}
MqTabPage *
mq_tab_page_new_root(MqWindow *window)
{
return g_object_new(MQ_TYPE_TAB_PAGE,
"orientation", GTK_ORIENTATION_VERTICAL,
"spacing", 0,
"create-type", CREATE_ROOT,
"window", window, /* TODO: Use gtk_widget_get_parent()? */
NULL);
}
void
mq_tab_page_quit(MqTabPage *tab_page)
{
mq_window_quit(tab_page->window);
}
MqApplication *
mq_tab_page_get_application(MqTabPage *tab_page)
{
return tab_page->application;
}
MqWindow *
mq_tab_page_get_window(MqTabPage *tab_page)
{
return tab_page->window;
}
void
mq_tab_page_update_positions(MqTabPage *node, gint step)
{
if (node) {
node->position += step;
g_assert(node->label);
mq_tab_label_set_position(MQ_TAB_LABEL(node->label),
node->position);
if (node->next) {
mq_tab_page_update_positions(node->next, step);
} else if (node->parent && node->parent->next) {
mq_tab_page_update_positions(node->parent->next, step);
}
}
}
void
mq_tab_page_update_position(MqTabPage *tab_page, guint position)
{
tab_page->position = position;
mq_tab_label_set_position(MQ_TAB_LABEL(tab_page->label), position);
}
guint
mq_tab_page_get_position(MqTabPage *tab_page)
{
return tab_page->position;
}
guint
mq_tab_page_get_tree_size(MqTabPage *tab_page)
{
return tab_page->tree_size;
}
const gchar *
mq_tab_page_get_title(MqTabPage *tab_page)
{
return tab_page->title;
}
MqTabPage *
mq_tab_page_seek(MqTabPage *node, guint position)
{
/* Skip forward to the containing subtree. */
while (node && node->position + node->tree_size <= position) {
node = node->next;
}
/* Check whether we've gone past the end of the tree. */
if (!node) {
return NULL;
}
/* Check whether the sibling we've reached is the node we want. */
if (node->position == position) {
return node;
}
/* Recurse down the subtree. */
return mq_tab_page_seek(node->first_child, position);
}
static void
foreach_tab(MqTabPage *node, void (*cb)(MqTabPage *node, va_list ap),
va_list ap)
{
va_list aq;
for (; node; node = node->next) {
va_copy(ap, aq);
cb(node, aq);
va_end(aq);
va_copy(ap, aq);
foreach_tab(node->first_child, cb, aq);
va_end(aq);
}
}
void
mq_tab_page_foreach(MqTabPage *node, void (*cb)(MqTabPage *node, va_list ap),
...)
{
va_list ap;
va_start(ap, cb);
foreach_tab(node->root->first_child, cb, ap);
va_end(ap);
}
MqTabPage *
mq_tab_page_root(MqTabPage *node)
{
return node ? node->root : NULL;
}
MqTabPage *
mq_tab_page_previous(MqTabPage *node)
{
return node ? node->prev : NULL;
}
MqTabPage *
mq_tab_page_next(MqTabPage *node)
{
return node ? node->next : NULL;
}
MqTabPage *
mq_tab_page_first_child(MqTabPage *node)
{
return node ? node->first_child : NULL;
}
void
mq_tab_page_scroll_tab_labels(MqTabPage *node)
{
for (; node; node = node->next) {
if (node->label) {
mq_tab_label_scroll(MQ_TAB_LABEL(node->label));
}
mq_tab_page_scroll_tab_labels(node->first_child);
}
}
void
mq_tab_page_begin_scrolling_tab_labels(MqTabPage *node)
{
for (; node; node = node->next) {
if (node->label) {
mq_tab_label_begin_scrolling(MQ_TAB_LABEL(node->label));
}
mq_tab_page_begin_scrolling_tab_labels(node->first_child);
}
}
void
mq_tab_page_end_scrolling_tab_labels(MqTabPage *node)
{
for (; node; node = node->next) {
if (node->label) {
mq_tab_label_end_scrolling(MQ_TAB_LABEL(node->label));
}
mq_tab_page_end_scrolling_tab_labels(node->first_child);
}
}