/*
* 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), 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),
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
set_web_view(MqBackForwardButtonBox *back_forward_button_box,
MqWebView *web_view)
{
back_forward_button_box->web_view = web_view;
g_signal_connect(webkit_web_view_get_back_forward_list(
WEBKIT_WEB_VIEW(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:
set_web_view(back_forward_button_box,
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->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);
}