/* * 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; const 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_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_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->parent = NULL; tab_page->prev = NULL; tab_page->next = NULL; tab_page->first_child = tab_page->last_child = NULL; tab_page->position = 0; tab_page->tree_size = 1; } static void init_non_root(MqTabPage *tab_page) { GtkWidget *navigation_toolbar; GtkWidget *find_toolbar; 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"; 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)); /* FIXME: Replace tab_page->window->config */ navigation_toolbar = mq_navigation_toolbar_new(tab_page->window->config, 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); break; case CREATE_CHILD: init_non_root(tab_page); append_child(tab_page); break; case CREATE_NONE: case N_CREATE_TYPES: g_assert_not_reached(); break; } } 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: tab_page->uri = 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->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 G_GNUC_UNUSED *tab_page) { } 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; if (node->label) { mq_tab_label_set_position(MQ_TAB_LABEL(node->label), node->position); } if (node->next) { mq_tab_update_positions(node->next, step); } else if (node->parent && node->parent->next) { mq_tab_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_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_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_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_end_scrolling_tab_labels(node->first_child); } }