From 55aa5c787ec1c05a03f22a9f8f91da9a6c0360eb Mon Sep 17 00:00:00 2001 From: Patrick McDermott Date: Thu, 12 Oct 2017 22:15:07 -0400 Subject: MqBackForwardButtonBox: New class --- (limited to 'src/back-forward-button-box.c') diff --git a/src/back-forward-button-box.c b/src/back-forward-button-box.c new file mode 100644 index 0000000..5531f56 --- /dev/null +++ b/src/back-forward-button-box.c @@ -0,0 +1,463 @@ +/* + * Back/forward button box + * + * 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 +#include +#include + +#include "back-forward-button-box.h" +#include "web-view.h" + +struct _MqBackForwardButtonBox { + GtkEventBox parent_instance; + MqTab *tab; + MqWebView *web_view; + GtkWidget *back_button; + GtkWidget *forward_button; + gint back_items; + GtkWidget *back_forward_popover; +}; + +enum { + PROP_TAB = 1, + PROP_WEB_VIEW, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqBackForwardButtonBoxClass { + GtkEventBoxClass parent_class; +}; + +G_DEFINE_TYPE(MqBackForwardButtonBox, mq_back_forward_button_box, + GTK_TYPE_EVENT_BOX) + +static void +back_forward_list_changed_cb( + WebKitBackForwardList G_GNUC_UNUSED *back_forward_list, + WebKitBackForwardListItem G_GNUC_UNUSED *item_added, + gpointer G_GNUC_UNUSED items_removed, + MqBackForwardButtonBox *back_forward_button_box) +{ + gtk_widget_set_sensitive(back_forward_button_box->back_button, + webkit_web_view_can_go_back( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view))); + gtk_widget_set_sensitive(back_forward_button_box->forward_button, + webkit_web_view_can_go_forward( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view))); +} + +static void +back_clicked_cb(GtkButton G_GNUC_UNUSED *toolbutton, + MqBackForwardButtonBox *back_forward_button_box) +{ + webkit_web_view_go_back( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view)); +} + +static void +forward_clicked_cb(GtkButton G_GNUC_UNUSED *toolbutton, + MqBackForwardButtonBox *back_forward_button_box) +{ + webkit_web_view_go_forward( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view)); +} + +static GtkWidget * +list_item_new(WebKitBackForwardListItem *list_item, gint type) +{ + GtkWidget *label; + GtkWidget *icon_stack; + GtkWidget *icon; + GtkWidget *box; + + label = gtk_label_new(webkit_back_forward_list_item_get_title( + list_item)); + gtk_widget_set_halign(label, GTK_ALIGN_START); + + icon_stack = gtk_stack_new(); + switch (type) { + case 0: + icon = gtk_radio_button_new(NULL); + gtk_stack_add_named(GTK_STACK(icon_stack), icon, + "current"); + gtk_widget_set_can_focus(icon, FALSE); + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_image_new_from_icon_name("go-previous", + GTK_ICON_SIZE_BUTTON), "back"); + break; + case -1: + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_image_new_from_icon_name("go-previous", + GTK_ICON_SIZE_BUTTON), "back"); + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_radio_button_new(NULL), "current"); + break; + case 1: + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_image_new_from_icon_name("go-next", + GTK_ICON_SIZE_BUTTON), "forward"); + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_radio_button_new(NULL), "current"); + break; + } + + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(box), icon_stack, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); + gtk_widget_set_tooltip_text(box, + webkit_back_forward_list_item_get_uri(list_item)); + + return box; +} + +static void +list_box_row_activated_cb(GtkListBox G_GNUC_UNUSED *box, + GtkListBoxRow *row, MqBackForwardButtonBox *back_forward_button_box) +{ + webkit_web_view_go_to_back_forward_list_item( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view), + webkit_back_forward_list_get_nth_item( + webkit_web_view_get_back_forward_list(WEBKIT_WEB_VIEW( + back_forward_button_box->web_view)), + gtk_list_box_row_get_index(row) - + back_forward_button_box->back_items)); + + gtk_widget_hide(back_forward_button_box->back_forward_popover); +} + +static void +text_button_toggled_cb(GtkToggleButton *toggle_button, + GtkStack *stack) +{ + /* Use gtk_widget_show() and gtk_widget_hide() instead of + * gtk_stack_set_visible_child() so that the stack fits the size of only + * the visible child. */ + gtk_widget_show_all(gtk_stack_get_child_by_name(stack, + gtk_toggle_button_get_active(toggle_button) ? + "text" : "list")); + gtk_widget_hide(gtk_stack_get_child_by_name(stack, + gtk_toggle_button_get_active(toggle_button) ? + "list" : "text")); +} + +static gboolean +event_box_button_press_cb(GtkWidget *widget, + GdkEventButton G_GNUC_UNUSED *event, + MqBackForwardButtonBox *back_forward_button_box) +{ + WebKitBackForwardList *back_forward_list; + GtkWidget *list_box; + GtkWidget *text_view; + GtkTextBuffer *text_buffer; + GList *list_item; + GtkTextIter text_iter; + gchar *str; + GtkTextTag *text_tag; + GtkWidget *list_scrolled_window; + GtkWidget *text_scrolled_window; + GtkWidget *stack; + GtkWidget *toggle_button; + GtkWidget *box; + + /* Get the back/forward list for the Web view. */ + back_forward_list = webkit_web_view_get_back_forward_list( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view)); + + /* Set up the list box. */ + list_box = gtk_list_box_new(); + gtk_list_box_set_selection_mode(GTK_LIST_BOX(list_box), + GTK_SELECTION_BROWSE); + gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(list_box), TRUE); + g_signal_connect(list_box, "row-activated", + G_CALLBACK(list_box_row_activated_cb), + back_forward_button_box); + + /* Set up the text view. */ + text_view = gtk_text_view_new(); + text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view)); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view), FALSE); + + /* Insert forward list items. */ + /* The forward list is backwards, so we need to prepend each item to our + * list box and text buffer. */ + list_item = webkit_back_forward_list_get_forward_list( + back_forward_list); + for (; list_item; list_item = list_item->next) { + gtk_list_box_prepend(GTK_LIST_BOX(list_box), + list_item_new(list_item->data, 1)); + gtk_text_buffer_get_start_iter(text_buffer, &text_iter); + str = g_strdup_printf("\n%s", + webkit_back_forward_list_item_get_uri(list_item->data)); + gtk_text_buffer_insert(text_buffer, &text_iter, str, -1); + g_free(str); + } + + /* Insert the current item. */ + gtk_list_box_prepend(GTK_LIST_BOX(list_box), back_forward_list_item_new( + webkit_back_forward_list_get_current_item( + back_forward_list), 0)); + gtk_list_box_select_row(GTK_LIST_BOX(list_box), + gtk_list_box_get_row_at_index(GTK_LIST_BOX(list_box), 0)); + gtk_text_buffer_get_start_iter(text_buffer, &text_iter); + text_tag = gtk_text_buffer_create_tag(text_buffer, NULL, "weight", + PANGO_WEIGHT_BOLD, NULL); + gtk_text_buffer_insert_with_tags(text_buffer, &text_iter, + webkit_back_forward_list_item_get_uri( + webkit_back_forward_list_get_current_item( + back_forward_list)), -1, text_tag, NULL); + + /* Insert back list items. */ + list_item = webkit_back_forward_list_get_back_list( + back_forward_list); + back_forward_button_box->back_items = 0; + for (; list_item; list_item = list_item->next) { + gtk_list_box_prepend(GTK_LIST_BOX(list_box), + back_forward_list_item_new(list_item->data, -1)); + gtk_text_buffer_get_start_iter(text_buffer, &text_iter); + str = g_strdup_printf("%s\n", + webkit_back_forward_list_item_get_uri(list_item->data)); + gtk_text_buffer_insert(text_buffer, &text_iter, str, -1); + g_free(str); + ++back_forward_button_box->back_items; + } + + /* + * The following GtkScrolledWindow widgets have hardcoded minimum sizes, + * because there seems to be (in GTK+ versions before 3.22) no way to + * set the natural size of GtkScrolledWindow and its GtkViewport. + * + * I tried: + * + * - Setting size requests of the sizes of child widgets plus + * scrollbars (commit 2205669) + * - Putting GtkScrolledWindow widgets in stacks with non-scrolled + * versions of children, to allocate more area for the + * GtkScrolledWindow widgets to fill (commits 922cdef and 90686fa) + * - Setting policies to disable scrollbars upon GtkScrolledWindow + * instantiation, then enabling scrollbars on the GtkScrolledWindow + * "size-allocate" signal (commits c534c7e and 42ca783) + * + * Attempts to match the size of a GtkScrolledWindow's GtkViewport to + * its child's size don't allow the GtkScrolledWindow and GtkViewport to + * shrink when necessary to fit within the window. Therefore, such + * methods are equivalent to simply not using GtkScrolledWindow at all. + * And there appears to be no easy way to calculate the available size + * (the size of the GtkWindow minus all of the surrounding widgets) to + * manually manage a GtkViewport's size. + * + * GtkScrolledWindow or GtkViewport ignores the child's preferred + * (minimum and natural) sizes and apparently just sets a hardcoded size + * of about 64x64 px. I considered subclassing GtkScrolledWindow or + * GtkViewport, but I'm not sure where the hardcoded size is set. + * + * The functions gtk_scrolled_window_set_propagate_natural_width() and + * gtk_scrolled_window_set_propagate_natural_height() were introduced in + * GTK+ 3.22 and probably do what I want. However, I want to maintain + * compatibility with at least GTK+ 3.12 or 3.14. + */ + + /* Set up the list scrolled window. */ + list_scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_min_content_width( + GTK_SCROLLED_WINDOW(list_scrolled_window), 400); + gtk_scrolled_window_set_min_content_height( + GTK_SCROLLED_WINDOW(list_scrolled_window), 200); + gtk_container_add(GTK_CONTAINER(list_scrolled_window), list_box); + + /* Set up the text scrolled window. */ + text_scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_min_content_width( + GTK_SCROLLED_WINDOW(text_scrolled_window), 400); + gtk_scrolled_window_set_min_content_height( + GTK_SCROLLED_WINDOW(text_scrolled_window), 200); + gtk_container_add(GTK_CONTAINER(text_scrolled_window), text_view); + + /* Set up the stack. */ + stack = gtk_stack_new(); + gtk_stack_add_named(GTK_STACK(stack), list_scrolled_window, "list"); + gtk_stack_add_named(GTK_STACK(stack), text_scrolled_window, "text"); + + /* Set up the toggle button. */ + toggle_button = gtk_toggle_button_new(); + gtk_button_set_image(GTK_BUTTON(toggle_button), + gtk_image_new_from_icon_name("edit-select-all", + GTK_ICON_SIZE_SMALL_TOOLBAR)); + gtk_widget_set_halign(toggle_button, GTK_ALIGN_START); + g_signal_connect(toggle_button, "toggled", + G_CALLBACK(text_button_toggled_cb), GTK_STACK(stack)); + + /* Set up the containing box. */ + box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start(GTK_BOX(box), toggle_button, TRUE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), stack, TRUE, FALSE, 0); + + /* Set up the popover. */ + back_forward_button_box->back_forward_popover = gtk_popover_new(widget); + gtk_container_add( + GTK_CONTAINER(back_forward_button_box->back_forward_popover), + box); + + /* NB: gtk_popover_popup() is new in GTK+ 3.22. */ + gtk_widget_show_all(back_forward_button_box->back_forward_popover); + gtk_widget_hide(text_view); + + return FALSE; +} + +static void +constructed(GObject *object) +{ + MqBackForwardButtonBox *back_forward_button_box; + + if (G_OBJECT_CLASS(mq_back_forward_button_box_parent_class)-> + constructed) { + G_OBJECT_CLASS(mq_back_forward_button_box_parent_class)-> + constructed(object); + } + + back_forward_button_box = MQ_BACK_FORWARD_BUTTON_BOX(object); + + g_signal_connect(webkit_web_view_get_back_forward_list( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view)), + "changed", G_CALLBACK(back_forward_list_changed_cb), + back_forward_button_box); +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) +{ + MqBackForwardButtonBox *back_forward_button_box; + + back_forward_button_box = MQ_BACK_FORWARD_BUTTON_BOX(object); + + switch (property_id) { + case PROP_TAB: + g_value_set_pointer(value, + back_forward_button_box->tab); + break; + case PROP_WEB_VIEW: + g_value_set_object(value, + back_forward_button_box->web_view); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + pspec); + break; + } +} + +static void +set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *pspec) +{ + MqBackForwardButtonBox *back_forward_button_box; + + back_forward_button_box = MQ_BACK_FORWARD_BUTTON_BOX(object); + + switch (property_id) { + case PROP_TAB: + back_forward_button_box->tab = + g_value_get_pointer(value); + break; + case PROP_WEB_VIEW: + back_forward_button_box->web_view = + g_value_get_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + pspec); + break; + } +} + +static void +mq_back_forward_button_box_class_init(MqBackForwardButtonBoxClass *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_TAB] = g_param_spec_pointer( + "tab", + "MqTab", + "The ancestral MqTab instance", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + obj_properties[PROP_WEB_VIEW] = g_param_spec_object( + "web-view", + "MqWebView", + "The associated MqWebView instance", + MQ_TYPE_WEB_VIEW, + 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_back_forward_button_box_init(MqBackForwardButtonBox *back_forward_button_box) +{ + GtkWidget *box; + + /* Back button */ + back_forward_button_box->back_button = gtk_button_new_from_icon_name( + "go-previous", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(back_forward_button_box->back_button, + "Go back one page"); + g_signal_connect(back_forward_button_box->back_button, "clicked", + G_CALLBACK(back_clicked_cb), back_forward_button_box); + + /* Forward button */ + back_forward_button_box->forward_button = gtk_button_new_from_icon_name( + "go-next", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(back_forward_button_box->forward_button, + "Go forward one page"); + g_signal_connect(back_forward_button_box->forward_button, "clicked", + G_CALLBACK(forward_clicked_cb), back_forward_button_box); + + /* Box */ + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(box), + back_forward_button_box->back_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), + back_forward_button_box->forward_button, FALSE, FALSE, 0); + gtk_style_context_add_class(gtk_widget_get_style_context(box), + "linked"); + + /* Event box (MqBackForwardButtonBox) */ + gtk_container_add(GTK_CONTAINER(back_forward_button_box), box); + g_signal_connect(back_forward_button_box, "button-press-event", + G_CALLBACK(event_box_button_press_cb), back_forward_button_box); +} + +GtkWidget * +mq_back_forward_button_box_new(MqTab *tab, MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_BACK_FORWARD_BUTTON_BOX, + "tab", tab, + "web-view", web_view, + NULL); +} -- cgit v0.9.1