From 69bc9ea5a935f03e3be9c97686120af7adc0e176 Mon Sep 17 00:00:00 2001 From: Patrick McDermott Date: Fri, 13 Oct 2017 15:58:41 -0400 Subject: Move toolbar widget source files to src/toolbars/ And move navigation toolbar item widget source files to src/toolbars/navigation/. --- (limited to 'src/toolbars') diff --git a/src/toolbars/find-toolbar.c b/src/toolbars/find-toolbar.c new file mode 100644 index 0000000..0b16ea1 --- /dev/null +++ b/src/toolbars/find-toolbar.c @@ -0,0 +1,325 @@ +/* + * Revealable find toolbar + * + * 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 "find-toolbar.h" + +#include +#include +#include + +#include "../web-view.h" + +struct _MqFindToolbar { + GtkRevealer parent_instance; + GtkWidget *search_entry; + GtkWidget *matches_label; + gboolean match_case; + gboolean searching; + WebKitFindController *find_controller; +}; + +enum { + PROP_WEB_VIEW = 1, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqFindToolbarClass { + GtkRevealerClass parent_class; +}; + +G_DEFINE_TYPE(MqFindToolbar, mq_find_toolbar, GTK_TYPE_REVEALER) + +static void +search(MqFindToolbar *find_toolbar, gboolean forward) +{ + guint32 find_options; + + find_options = WEBKIT_FIND_OPTIONS_WRAP_AROUND; + if (!find_toolbar->match_case) { + find_options |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE; + } + if (!forward) { + find_options |= WEBKIT_FIND_OPTIONS_BACKWARDS; + } + webkit_find_controller_search(find_toolbar->find_controller, + gtk_entry_get_text(GTK_ENTRY(find_toolbar->search_entry)), + find_options, G_MAXUINT); + find_toolbar->searching = TRUE; +} + +static void +search_finished(MqFindToolbar *find_toolbar) +{ + find_toolbar->searching = FALSE; + webkit_find_controller_search_finish(find_toolbar->find_controller); +} + +static void +hide(MqFindToolbar *find_toolbar) +{ + gtk_revealer_set_reveal_child(GTK_REVEALER(find_toolbar), + FALSE); + gtk_label_set_text(GTK_LABEL(find_toolbar->matches_label), NULL); + search_finished(find_toolbar); +} + +static void +search_changed_cb(GtkSearchEntry G_GNUC_UNUSED *entry, + MqFindToolbar *find_toolbar) +{ + search(find_toolbar, TRUE); +} + +static gboolean +search_key_press_event_cb(GtkSearchEntry G_GNUC_UNUSED *entry, + GdkEventKey *event, MqFindToolbar *find_toolbar) +{ + switch (event->keyval) { + case GDK_KEY_Escape: + hide(find_toolbar); + return TRUE; + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + search(find_toolbar, + !(event->state & GDK_SHIFT_MASK)); + return TRUE; + default: + return FALSE; + } +} + +static void +prev_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqFindToolbar *find_toolbar) +{ + /* Calling this method before webkit_find_controller_search() or + * webkit_find_controller_count_matches() is a programming error. */ + if (find_toolbar->searching) { + webkit_find_controller_search_previous( + find_toolbar->find_controller); + } else { + search(find_toolbar, FALSE); + } +} + +static void +next_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqFindToolbar *find_toolbar) +{ + /* Calling this method before webkit_find_controller_search() or + * webkit_find_controller_count_matches() is a programming error. */ + if (find_toolbar->searching) { + webkit_find_controller_search_next( + find_toolbar->find_controller); + } else { + search(find_toolbar, TRUE); + } +} + +static void +match_case_toggled_cb(GtkToggleButton *toggle_button, + MqFindToolbar *find_toolbar) +{ + find_toolbar->match_case = gtk_toggle_button_get_active(toggle_button); + search(find_toolbar, TRUE); +} + +static void +close_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqFindToolbar *find_toolbar) +{ + hide(find_toolbar); +} + +static void +found_text_cb(WebKitFindController G_GNUC_UNUSED *find_controller, + guint match_count, MqFindToolbar *find_toolbar) +{ + gchar *text; + + if (match_count == 1) { + text = g_strdup("1 match"); + } else { + text = g_strdup_printf("%u matches", match_count); + } + gtk_label_set_text(GTK_LABEL(find_toolbar->matches_label), text); + g_free(text); + +} + +static void +failed_to_find_text_cb(WebKitFindController G_GNUC_UNUSED *find_controller, + MqFindToolbar *find_toolbar) +{ + gtk_label_set_text(GTK_LABEL(find_toolbar->matches_label), + "No matches"); +} + +static void +set_web_view(MqFindToolbar *find_toolbar, MqWebView *web_view) +{ + find_toolbar->find_controller = webkit_web_view_get_find_controller( + WEBKIT_WEB_VIEW(web_view)); + g_signal_connect(find_toolbar->find_controller, "found-text", + G_CALLBACK(found_text_cb), find_toolbar); + g_signal_connect(find_toolbar->find_controller, "failed-to-find-text", + G_CALLBACK(failed_to_find_text_cb), find_toolbar); +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + switch (property_id) { + case PROP_WEB_VIEW: + g_value_set_object(value, NULL); + 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) +{ + MqFindToolbar *find_toolbar; + + find_toolbar = MQ_FIND_TOOLBAR(object); + + switch (property_id) { + case PROP_WEB_VIEW: + set_web_view(find_toolbar, g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_find_toolbar_class_init(MqFindToolbarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + 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_find_toolbar_init(MqFindToolbar *find_toolbar) +{ + GtkWidget *close_button; + GtkWidget *prev_button; + GtkWidget *next_button; + GtkWidget *match_case_button; + GtkWidget *box; + + /* Search entry */ + find_toolbar->search_entry = gtk_search_entry_new(); + g_signal_connect(find_toolbar->search_entry, "search-changed", + G_CALLBACK(search_changed_cb), find_toolbar); + g_signal_connect(find_toolbar->search_entry, "key-press-event", + G_CALLBACK(search_key_press_event_cb), find_toolbar); + + /* Previous button */ + prev_button = gtk_button_new_from_icon_name("go-up", + GTK_ICON_SIZE_BUTTON); + gtk_widget_set_tooltip_text(prev_button, "Find previous occurrence"); + gtk_widget_set_can_focus(prev_button, FALSE); + g_signal_connect(prev_button, "clicked", + G_CALLBACK(prev_clicked_cb), find_toolbar); + + /* Next button */ + next_button = gtk_button_new_from_icon_name("go-down", + GTK_ICON_SIZE_BUTTON); + gtk_widget_set_tooltip_text(next_button, "Find next occurrence"); + gtk_widget_set_can_focus(next_button, FALSE); + g_signal_connect(next_button, "clicked", + G_CALLBACK(next_clicked_cb), find_toolbar); + + /* Case sensitivity button */ + match_case_button = gtk_toggle_button_new_with_label("Match case"); + gtk_widget_set_tooltip_text(match_case_button, + "Search with case sensitivity"); + gtk_widget_set_can_focus(match_case_button, FALSE); + g_signal_connect(match_case_button, "toggled", + G_CALLBACK(match_case_toggled_cb), find_toolbar); + + /* Matches label */ + find_toolbar->matches_label = gtk_label_new(NULL); + + /* Close button */ + close_button = gtk_button_new_from_icon_name("window-close", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE); + gtk_widget_set_tooltip_text(close_button, "Close find bar"); + gtk_widget_set_can_focus(close_button, FALSE); + g_signal_connect(close_button, "clicked", + G_CALLBACK(close_clicked_cb), find_toolbar); + + /* Box */ + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(box), find_toolbar->search_entry, + FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), prev_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), next_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), match_case_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), find_toolbar->matches_label, + FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(box), close_button, FALSE, FALSE, 0); + + /* Revealer */ + gtk_revealer_set_transition_type(GTK_REVEALER(find_toolbar), + GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN); + gtk_container_add(GTK_CONTAINER(find_toolbar), box); + + find_toolbar->match_case = FALSE; + find_toolbar->searching = FALSE; +} + +GtkWidget * +mq_find_toolbar_new(MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_FIND_TOOLBAR, + "web-view", web_view, + NULL); +} + +void +mq_find_toolbar_reveal(MqFindToolbar *find_toolbar) +{ + gtk_revealer_set_reveal_child(GTK_REVEALER(find_toolbar), TRUE); + gtk_widget_grab_focus(find_toolbar->search_entry); +} diff --git a/src/toolbars/find-toolbar.h b/src/toolbars/find-toolbar.h new file mode 100644 index 0000000..386cab4 --- /dev/null +++ b/src/toolbars/find-toolbar.h @@ -0,0 +1,61 @@ +/* + * Revealable find toolbar + * + * 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 . + */ + +typedef struct _MqFindToolbar MqFindToolbar; +typedef struct _MqFindToolbarClass MqFindToolbarClass; + +#ifndef MQ_TOOLBARS_FIND_TOOLBAR_H +#define MQ_TOOLBARS_FIND_TOOLBAR_H + +#include +#include +#include + +#include "../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_FIND_TOOLBAR (mq_find_toolbar_get_type()) +#define MQ_FIND_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MQ_TYPE_FIND_TOOLBAR, MqFindToolbar)) +#define MQ_IS_FIND_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + MQ_TYPE_FIND_TOOLBAR)) +#define MQ_FIND_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + MQ_TYPE_FIND_TOOLBAR, \ + MqFindToolbarClass)) +#define MQ_IS_FIND_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + MQ_TYPE_FIND_TOOLBAR)) +#define MQ_FIND_TOOLBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + MQ_TYPE_FIND_TOOLBAR, \ + MqFindToolbarClass)) + +GType +mq_find_toolbar_get_type(void); + +GtkWidget * +mq_find_toolbar_new(MqWebView *web_view); + +void +mq_find_toolbar_reveal(MqFindToolbar *find_toolbar); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_FIND_TOOLBAR_H */ diff --git a/src/toolbars/local.mk b/src/toolbars/local.mk new file mode 100644 index 0000000..ccfb75a --- /dev/null +++ b/src/toolbars/local.mk @@ -0,0 +1,5 @@ +marquee_SOURCES += \ + %reldir%/navigation-toolbar.c \ + %reldir%/find-toolbar.c + +include %reldir%/navigation/local.mk diff --git a/src/toolbars/navigation-toolbar.c b/src/toolbars/navigation-toolbar.c new file mode 100644 index 0000000..8a6ecfe --- /dev/null +++ b/src/toolbars/navigation-toolbar.c @@ -0,0 +1,254 @@ +/* + * Navigation toolbar + * + * 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 "navigation-toolbar.h" + +#include +#include +#include + +#include "../config.h" +#include "../tab.h" +#include "../web-view.h" +#include "find-toolbar.h" +#include "navigation/back-forward-button-box.h" +#include "navigation/home-button.h" +#include "navigation/main-menu.h" +#include "navigation/stop-reload-button.h" +#include "navigation/uri-entry.h" + +struct _MqNavigationToolbar { + GtkToolbar parent_instance; + MqConfig *config; + MqTab *tab; + MqFindToolbar *find_toolbar; + MqWebView *web_view; + gchar *uri; +}; + +enum { + PROP_CONFIG = 1, + PROP_TAB, + PROP_FIND_TOOLBAR, + PROP_WEB_VIEW, + PROP_URI, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqNavigationToolbarClass { + GtkToolbarClass parent_class; +}; + +G_DEFINE_TYPE(MqNavigationToolbar, mq_navigation_toolbar, GTK_TYPE_TOOLBAR) + +static void +constructed(GObject *object) +{ + MqNavigationToolbar *navigation_toolbar; + GtkToolItem *back_forward_button_box; + GtkToolItem *stop_reload_button; + GtkToolItem *uri_entry; + GtkToolItem *home_button; + GtkToolItem *menu_button; + + if (G_OBJECT_CLASS(mq_navigation_toolbar_parent_class)->constructed) { + G_OBJECT_CLASS(mq_navigation_toolbar_parent_class)->constructed( + object); + } + + navigation_toolbar = MQ_NAVIGATION_TOOLBAR(object); + + /* Back/forward button box */ + back_forward_button_box = mq_back_forward_button_box_new( + navigation_toolbar->web_view); + + /* Stop/reload button */ + stop_reload_button = mq_stop_reload_button_new( + navigation_toolbar->web_view); + + /* URI entry */ + uri_entry = mq_uri_entry_new(navigation_toolbar->web_view, + navigation_toolbar->uri); + + /* Home button */ + home_button = mq_home_button_new(navigation_toolbar->config, + navigation_toolbar->web_view); + + /* Menu button */ + menu_button = mq_main_menu_new(navigation_toolbar->tab, + navigation_toolbar->find_toolbar, navigation_toolbar->web_view); + + /* Navigation toolbar */ + gtk_toolbar_insert(GTK_TOOLBAR(navigation_toolbar), + back_forward_button_box, -1); + gtk_toolbar_insert(GTK_TOOLBAR(navigation_toolbar), + stop_reload_button, -1); + gtk_toolbar_insert(GTK_TOOLBAR(navigation_toolbar), uri_entry, -1); + gtk_toolbar_insert(GTK_TOOLBAR(navigation_toolbar), home_button, -1); + gtk_toolbar_insert(GTK_TOOLBAR(navigation_toolbar), menu_button, -1); + + gtk_widget_set_hexpand(GTK_WIDGET(navigation_toolbar), TRUE); +} + +static void +finalize(GObject *object) +{ + MqNavigationToolbar *navigation_toolbar; + + navigation_toolbar = MQ_NAVIGATION_TOOLBAR(object); + + if (navigation_toolbar->uri) { + g_free(navigation_toolbar->uri); + } +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + MqNavigationToolbar *navigation_toolbar; + + navigation_toolbar = MQ_NAVIGATION_TOOLBAR(object); + + switch (property_id) { + case PROP_CONFIG: + g_value_set_pointer(value, navigation_toolbar->config); + break; + case PROP_TAB: + g_value_set_pointer(value, navigation_toolbar->tab); + break; + case PROP_FIND_TOOLBAR: + g_value_set_object(value, + navigation_toolbar->find_toolbar); + case PROP_WEB_VIEW: + g_value_set_object(value, navigation_toolbar->web_view); + break; + case PROP_URI: + g_value_set_string(value, navigation_toolbar->uri); + 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) +{ + MqNavigationToolbar *navigation_toolbar; + + navigation_toolbar = MQ_NAVIGATION_TOOLBAR(object); + + switch (property_id) { + case PROP_CONFIG: + navigation_toolbar->config = g_value_get_pointer(value); + break; + case PROP_TAB: + navigation_toolbar->tab = g_value_get_pointer(value); + break; + case PROP_FIND_TOOLBAR: + navigation_toolbar->find_toolbar = + g_value_get_object(value); + break; + case PROP_WEB_VIEW: + navigation_toolbar->web_view = + g_value_get_object(value); + break; + case PROP_URI: + navigation_toolbar->uri = g_strdup(g_value_get_string( + value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_navigation_toolbar_class_init(MqNavigationToolbarClass *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_CONFIG] = g_param_spec_pointer( + "config", + "MqConfig", + "The application's MqConfig instance", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + 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_FIND_TOOLBAR] = g_param_spec_object( + "find-toolbar", + "MqFindToolbar", + "The associated MqFindToolbar instance", + MQ_TYPE_FIND_TOOLBAR, + 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); + 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); + g_object_class_install_properties(object_class, N_PROPERTIES, + obj_properties); +} + +static void +mq_navigation_toolbar_init( + MqNavigationToolbar G_GNUC_UNUSED *navigation_toolbar) +{ +} + +GtkWidget * +mq_navigation_toolbar_new(MqConfig *config, MqTab *tab, + MqFindToolbar *find_toolbar, MqWebView *web_view, const gchar *uri) +{ + return g_object_new(MQ_TYPE_NAVIGATION_TOOLBAR, + "config", config, + "tab", tab, + "find-toolbar", find_toolbar, + "web-view", web_view, + "uri", uri, + NULL); +} diff --git a/src/toolbars/navigation-toolbar.h b/src/toolbars/navigation-toolbar.h new file mode 100644 index 0000000..efaf2f6 --- /dev/null +++ b/src/toolbars/navigation-toolbar.h @@ -0,0 +1,62 @@ +/* + * Navigation toolbar + * + * 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 . + */ + +typedef struct _MqNavigationToolbar MqNavigationToolbar; +typedef struct _MqNavigationToolbarClass MqNavigationToolbarClass; + +#ifndef MQ_TOOLBARS_NAVIGATION_TOOLBAR_H +#define MQ_TOOLBARS_NAVIGATION_TOOLBAR_H + +#include +#include + +#include "../tab.h" +#include "../web-view.h" +#include "find-toolbar.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_NAVIGATION_TOOLBAR \ + (mq_navigation_toolbar_get_type()) +#define MQ_NAVIGATION_TOOLBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MQ_TYPE_NAVIGATION_TOOLBAR, \ + MqNavigationToolbar)) +#define MQ_IS_NAVIGATION_TOOLBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MQ_TYPE_NAVIGATION_TOOLBAR)) +#define MQ_NAVIGATION_TOOLBAR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MQ_TYPE_NAVIGATION_TOOLBAR, \ + MqNavigationToolbarClass)) +#define MQ_IS_NAVIGATION_TOOLBAR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MQ_TYPE_NAVIGATION_TOOLBAR)) +#define MQ_NAVIGATION_TOOLBAR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MQ_TYPE_NAVIGATION_TOOLBAR, \ + MqNavigationToolbarClass)) + +GType +mq_navigation_toolbar_get_type(void); + +GtkWidget * +mq_navigation_toolbar_new(MqConfig *config, MqTab *tab, + MqFindToolbar *find_toolbar, MqWebView *web_view, const gchar *uri); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_TOOLBAR_H */ diff --git a/src/toolbars/navigation/back-forward-button-box.c b/src/toolbars/navigation/back-forward-button-box.c new file mode 100644 index 0000000..491b939 --- /dev/null +++ b/src/toolbars/navigation/back-forward-button-box.c @@ -0,0 +1,443 @@ +/* + * 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 "back-forward-button-box.h" + +#include +#include +#include + +#include "../../web-view.h" + +struct _MqBackForwardButtonBox { + GtkToolItem parent_instance; + MqWebView *web_view; + GtkWidget *back_button; + GtkWidget *forward_button; + gint back_items; + GtkWidget *back_forward_popover; +}; + +enum { + PROP_WEB_VIEW = 1, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqBackForwardButtonBoxClass { + GtkToolItemClass parent_class; +}; + +G_DEFINE_TYPE(MqBackForwardButtonBox, mq_back_forward_button_box, + GTK_TYPE_TOOL_ITEM) + +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 *param_spec) +{ + MqBackForwardButtonBox *back_forward_button_box; + + back_forward_button_box = MQ_BACK_FORWARD_BUTTON_BOX(object); + + switch (property_id) { + 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, + param_spec); + break; + } +} + +static void +set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *param_spec) +{ + MqBackForwardButtonBox *back_forward_button_box; + + back_forward_button_box = MQ_BACK_FORWARD_BUTTON_BOX(object); + + switch (property_id) { + 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, + param_spec); + 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_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; + GtkWidget *event_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) */ + event_box = gtk_event_box_new(); + gtk_container_add(GTK_CONTAINER(event_box), box); + g_signal_connect(event_box, "button-press-event", + G_CALLBACK(event_box_button_press_cb), back_forward_button_box); + + gtk_container_add(GTK_CONTAINER(back_forward_button_box), event_box); +} + +GtkToolItem * +mq_back_forward_button_box_new(MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_BACK_FORWARD_BUTTON_BOX, + "web-view", web_view, + NULL); +} diff --git a/src/toolbars/navigation/back-forward-button-box.h b/src/toolbars/navigation/back-forward-button-box.h new file mode 100644 index 0000000..a11c4de --- /dev/null +++ b/src/toolbars/navigation/back-forward-button-box.h @@ -0,0 +1,59 @@ +/* + * 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 . + */ + +typedef struct _MqBackForwardButtonBox MqBackForwardButtonBox; +typedef struct _MqBackForwardButtonBoxClass MqBackForwardButtonBoxClass; + +#ifndef MQ_TOOLBARS_NAVIGATION_BACK_FORWARD_BUTTON_BOX_H +#define MQ_TOOLBARS_NAVIGATION_BACK_FORWARD_BUTTON_BOX_H + +#include +#include + +#include "../../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_BACK_FORWARD_BUTTON_BOX \ + (mq_back_forward_button_box_get_type()) +#define MQ_BACK_FORWARD_BUTTON_BOX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MQ_TYPE_BACK_FORWARD_BUTTON_BOX, \ + MqBackForwardButtonBox)) +#define MQ_IS_BACK_FORWARD_BUTTON_BOX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MQ_TYPE_BACK_FORWARD_BUTTON_BOX)) +#define MQ_BACK_FORWARD_BUTTON_BOX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MQ_TYPE_BACK_FORWARD_BUTTON_BOX, \ + MqBackForwardButtonBoxClass)) +#define MQ_IS_BACK_FORWARD_BUTTON_BOX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MQ_TYPE_BACK_FORWARD_BUTTON_BOX)) +#define MQ_BACK_FORWARD_BUTTON_BOX_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MQ_TYPE_BACK_FORWARD_BUTTON_BOX, \ + MqBackForwardButtonBoxClass)) + +GType +mq_back_forward_button_box_get_type(void); + +GtkToolItem * +mq_back_forward_button_box_new(MqWebView *web_view); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_BACK_FORWARD_BUTTON_BOX_H */ diff --git a/src/toolbars/navigation/home-button.c b/src/toolbars/navigation/home-button.c new file mode 100644 index 0000000..a9d5f75 --- /dev/null +++ b/src/toolbars/navigation/home-button.c @@ -0,0 +1,159 @@ +/* + * Home button + * + * 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 "home-button.h" + +#include +#include +#include + +#include "../../config.h" +#include "../../web-view.h" + +struct _MqHomeButton { + GtkToolButton parent_instance; + MqConfig *config; + MqWebView *web_view; + GtkWidget *stop_icon; + GtkWidget *reload_icon; +}; + +enum { + PROP_CONFIG = 1, + PROP_WEB_VIEW, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqHomeButtonClass { + GtkToolButtonClass parent_class; +}; + +G_DEFINE_TYPE(MqHomeButton, mq_home_button, GTK_TYPE_TOOL_BUTTON) + +static void +clicked_cb(MqHomeButton *home_button) +{ + const gchar *uri; + + uri = mq_config_get_string(home_button->config, "tabs.home"); + + mq_web_view_load_uri(home_button->web_view, uri); +} + +static void +set_config(MqHomeButton *home_button, MqConfig *config) +{ + home_button->config = config; +} + +static void +set_web_view(MqHomeButton *home_button, MqWebView *web_view) +{ + home_button->web_view = web_view; +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + switch (property_id) { + case PROP_CONFIG: + g_value_set_pointer(value, NULL); + break; + case PROP_WEB_VIEW: + g_value_set_object(value, NULL); + 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) +{ + MqHomeButton *home_button; + + home_button = MQ_HOME_BUTTON(object); + + switch (property_id) { + case PROP_CONFIG: + set_config(home_button, g_value_get_pointer(value)); + break; + case PROP_WEB_VIEW: + set_web_view(home_button, g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_home_button_class_init(MqHomeButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + obj_properties[PROP_CONFIG] = g_param_spec_pointer( + "config", + "MqConfig", + "The application's MqConfig 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_home_button_init(MqHomeButton *home_button) +{ + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(home_button), + gtk_image_new_from_icon_name("go-home", + GTK_ICON_SIZE_SMALL_TOOLBAR)); + gtk_tool_button_set_label(GTK_TOOL_BUTTON(home_button), "Home"); + gtk_widget_set_tooltip_text(GTK_WIDGET(home_button), + "Load the home page"); + g_signal_connect(home_button, "clicked", G_CALLBACK(clicked_cb), NULL); +} + +GtkToolItem * +mq_home_button_new(MqConfig *config, MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_HOME_BUTTON, + "config", config, + "web-view", web_view, + NULL); +} diff --git a/src/toolbars/navigation/home-button.h b/src/toolbars/navigation/home-button.h new file mode 100644 index 0000000..0eae927 --- /dev/null +++ b/src/toolbars/navigation/home-button.h @@ -0,0 +1,56 @@ +/* + * Home button + * + * 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 . + */ + +typedef struct _MqHomeButton MqHomeButton; +typedef struct _MqHomeButtonClass MqHomeButtonClass; + +#ifndef MQ_TOOLBARS_NAVIGATION_HOME_BUTTON_H +#define MQ_TOOLBARS_NAVIGATION_HOME_BUTTON_H + +#include +#include + +#include "../../config.h" +#include "../../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_HOME_BUTTON (mq_home_button_get_type()) +#define MQ_HOME_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MQ_TYPE_HOME_BUTTON, MqHomeButton)) +#define MQ_IS_HOME_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + MQ_TYPE_HOME_BUTTON)) +#define MQ_HOME_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + MQ_TYPE_HOME_BUTTON, MqHomeButtonClass)) +#define MQ_IS_HOME_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + MQ_TYPE_HOME_BUTTON)) +#define MQ_HOME_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + MQ_TYPE_HOME_BUTTON, MqHomeButtonClass)) + +GType +mq_home_button_get_type(void); + +GtkToolItem * +mq_home_button_new(MqConfig *config, MqWebView *web_view); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_HOME_BUTTON_H */ diff --git a/src/toolbars/navigation/local.mk b/src/toolbars/navigation/local.mk new file mode 100644 index 0000000..38f3213 --- /dev/null +++ b/src/toolbars/navigation/local.mk @@ -0,0 +1,6 @@ +marquee_SOURCES += \ + %reldir%/back-forward-button-box.c \ + %reldir%/stop-reload-button.c \ + %reldir%/uri-entry.c \ + %reldir%/home-button.c \ + %reldir%/main-menu.c diff --git a/src/toolbars/navigation/main-menu.c b/src/toolbars/navigation/main-menu.c new file mode 100644 index 0000000..ad6809b --- /dev/null +++ b/src/toolbars/navigation/main-menu.c @@ -0,0 +1,297 @@ +/* + * Main menu and button + * + * 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 "main-menu.h" + +#include +#include +#include + +#include "../../tab.h" +#include "../../web-view.h" +#include "../find-toolbar.h" + +struct _MqMainMenu { + GtkToolButton parent_instance; + MqTab *tab; + MqFindToolbar *find_toolbar; + MqWebView *web_view; + GtkWidget *popover; +}; + +enum { + PROP_TAB = 1, + PROP_FIND_TOOLBAR, + PROP_WEB_VIEW, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqMainMenuClass { + GtkToolButtonClass parent_class; +}; + +G_DEFINE_TYPE(MqMainMenu, mq_main_menu, GTK_TYPE_TOOL_BUTTON) + +static void +zoom_out_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu) +{ + mq_web_view_zoom_out(main_menu->web_view); +} + +static void +zoom_reset_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu) +{ + mq_web_view_zoom_reset(main_menu->web_view); +} + +static void +zoom_in_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu) +{ + mq_web_view_zoom_in(main_menu->web_view); +} + +static void +find_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu) +{ + gtk_widget_hide(main_menu->popover); + mq_find_toolbar_reveal(main_menu->find_toolbar); +} + +static void +fullscreen_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu) +{ + gtk_widget_hide(main_menu->popover); + mq_window_toggle_fullscreen(mq_tab_get_window(main_menu->tab)); +} + +static void +developer_tools_clicked_cb(GtkButton G_GNUC_UNUSED *button, + MqMainMenu *main_menu) +{ + gtk_widget_hide(main_menu->popover); + webkit_web_inspector_show(webkit_web_view_get_inspector( + WEBKIT_WEB_VIEW(main_menu->web_view))); +} + +static void +preferences_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu) +{ + gtk_widget_hide(main_menu->popover); + mq_tab_new("about:preferences", main_menu->tab); + /* TODO: Hack: */ + gtk_notebook_next_page(GTK_NOTEBOOK(main_menu->tab->window->notebook)); +} + +static void +about_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu) +{ + gtk_widget_hide(main_menu->popover); + mq_tab_new("about:", main_menu->tab); + /* TODO: Hack: */ + gtk_notebook_next_page(GTK_NOTEBOOK(main_menu->tab->window->notebook)); +} + +static void +quit_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu) +{ + /* mq_tab_quit() just calls mq_window_quit(), which just calls + * mq_application_quit(), which is asynchronous. So close the menu. */ + gtk_widget_hide(main_menu->popover); + mq_tab_quit(main_menu->tab); +} + +#define BUTTON_ROWS 6 +#define BUTTON_COLS 3 +#define NEW_BUTTON(Y, X, ICON, TOOLTIP) \ + do { \ + buttons[Y * BUTTON_COLS + X] = gtk_button_new_from_icon_name(\ + ICON, GTK_ICON_SIZE_BUTTON); \ + gtk_widget_set_tooltip_text(buttons[Y * BUTTON_COLS + X], \ + TOOLTIP); \ + gtk_grid_attach(GTK_GRID(grid), buttons[Y * BUTTON_COLS + X], \ + X, Y, 1, 1); \ + } while (0) +#define CLICKED_CB(Y, X, CB) \ + g_signal_connect(buttons[Y * BUTTON_COLS + X], "clicked", \ + G_CALLBACK(CB), main_menu) + +static void +menu_clicked_cb(MqMainMenu *main_menu) +{ + GtkWidget *grid; + GtkWidget *buttons[BUTTON_ROWS * BUTTON_COLS]; + + /* Set up the grid. */ + grid = gtk_grid_new(); + + NEW_BUTTON(0, 0, "zoom-out", "Zoom out"); + NEW_BUTTON(0, 1, "zoom-original", "Reset zoom"); + NEW_BUTTON(0, 2, "zoom-in", "Zoom in"); + NEW_BUTTON(1, 0, "document-open", "Open file"); + NEW_BUTTON(1, 1, "document-save-as", "Save page"); + NEW_BUTTON(1, 2, "mail-message-new", "E-mail link"); + NEW_BUTTON(2, 0, "edit-find", "Find"); + NEW_BUTTON(2, 1, "document-print-preview", "Print preview"); + NEW_BUTTON(2, 2, "document-print", "Print"); + NEW_BUTTON(3, 0, "bookmark-new", "Bookmarks"); + NEW_BUTTON(3, 1, "document-open-recent", "History"); + NEW_BUTTON(3, 2, "document-save", "Downloads"); + NEW_BUTTON(4, 0, "view-fullscreen", "Full screen"); + NEW_BUTTON(4, 1, "document-properties", "Developer tools"); + NEW_BUTTON(4, 2, "system-run", "Preferences"); + NEW_BUTTON(5, 0, "help-about", "About Marquee"); + NEW_BUTTON(5, 2, "application-exit", "Quit"); + + CLICKED_CB(0, 0, zoom_out_clicked_cb); + CLICKED_CB(0, 1, zoom_reset_clicked_cb); + CLICKED_CB(0, 2, zoom_in_clicked_cb); + /* TODO: 1, 0: Open file */ + /* TODO: 1, 1: Save page */ + /* TODO: 1, 2: E-mail link */ + CLICKED_CB(2, 0, find_clicked_cb); + /* TODO: 2, 1: Print preview */ + /* TODO: 2, 2: Print */ + /* TODO: 3, 0: Bookmarks */ + /* TODO: 3, 1: History */ + /* TODO: 3, 2: Downloads */ + CLICKED_CB(4, 0, fullscreen_clicked_cb); + CLICKED_CB(4, 1, developer_tools_clicked_cb); + CLICKED_CB(4, 2, preferences_clicked_cb); + CLICKED_CB(5, 0, about_clicked_cb); + CLICKED_CB(5, 2, quit_clicked_cb); + + /* Set up the popover. */ + main_menu->popover = gtk_popover_new(GTK_WIDGET(main_menu)); + gtk_container_add(GTK_CONTAINER(main_menu->popover), grid); + + /* NB: gtk_popover_popup() is new in GTK+ 3.22. */ + gtk_widget_show_all(main_menu->popover); +} + +#undef BUTTON_ROWS +#undef BUTTON_COLS +#undef NEW_BUTTON + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + MqMainMenu *main_menu; + + main_menu = MQ_MAIN_MENU(object); + + switch (property_id) { + case PROP_TAB: + g_value_set_pointer(value, main_menu->tab); + break; + case PROP_FIND_TOOLBAR: + g_value_set_object(value, main_menu->find_toolbar); + break; + case PROP_WEB_VIEW: + g_value_set_object(value, main_menu->web_view); + 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) +{ + MqMainMenu *main_menu; + + main_menu = MQ_MAIN_MENU(object); + + switch (property_id) { + case PROP_TAB: + main_menu->tab = g_value_get_pointer(value); + break; + case PROP_FIND_TOOLBAR: + main_menu->find_toolbar = g_value_get_object(value); + break; + case PROP_WEB_VIEW: + main_menu->web_view = g_value_get_object(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_main_menu_class_init(MqMainMenuClass *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_FIND_TOOLBAR] = g_param_spec_object( + "find-toolbar", + "MqFindToolbar", + "The associated MqFindToolbar instance", + MQ_TYPE_FIND_TOOLBAR, + 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_main_menu_init(MqMainMenu *main_menu) +{ + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(main_menu), + gtk_image_new_from_icon_name("open-menu-symbolic", + GTK_ICON_SIZE_SMALL_TOOLBAR)); + gtk_tool_button_set_label(GTK_TOOL_BUTTON(main_menu), "Menu"); + gtk_widget_set_tooltip_text(GTK_WIDGET(main_menu), "Open menu"); + g_signal_connect(main_menu, "clicked", + G_CALLBACK(menu_clicked_cb), NULL); +} + +GtkToolItem * +mq_main_menu_new(MqTab *tab, MqFindToolbar *find_toolbar, MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_MAIN_MENU, + "tab", tab, + "find-toolbar", find_toolbar, + "web-view", web_view, + NULL); +} diff --git a/src/toolbars/navigation/main-menu.h b/src/toolbars/navigation/main-menu.h new file mode 100644 index 0000000..e1e85da --- /dev/null +++ b/src/toolbars/navigation/main-menu.h @@ -0,0 +1,57 @@ +/* + * Main menu and button + * + * 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 . + */ + +typedef struct _MqMainMenu MqMainMenu; +typedef struct _MqMainMenuClass MqMainMenuClass; + +#ifndef MQ_TOOLBARS_NAVIGATION_MAIN_MENU_H +#define MQ_TOOLBARS_NAVIGATION_MAIN_MENU_H + +#include +#include + +#include "../../tab.h" +#include "../../web-view.h" +#include "../find-toolbar.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_MAIN_MENU (mq_main_menu_get_type()) +#define MQ_MAIN_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MQ_TYPE_MAIN_MENU, MqMainMenu)) +#define MQ_IS_MAIN_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + MQ_TYPE_MAIN_MENU)) +#define MQ_MAIN_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + MQ_TYPE_MAIN_MENU, MqMainMenuClass)) +#define MQ_IS_MAIN_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + MQ_TYPE_MAIN_MENU)) +#define MQ_MAIN_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + MQ_TYPE_MAIN_MENU, MqMainMenuClass)) + +GType +mq_main_menu_get_type(void); + +GtkToolItem * +mq_main_menu_new(MqTab *tab, MqFindToolbar *find_toolbar, MqWebView *web_view); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_MAIN_MENU_H */ diff --git a/src/toolbars/navigation/stop-reload-button.c b/src/toolbars/navigation/stop-reload-button.c new file mode 100644 index 0000000..1adaeeb --- /dev/null +++ b/src/toolbars/navigation/stop-reload-button.c @@ -0,0 +1,196 @@ +/* + * Stop/reload button + * + * 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 "stop-reload-button.h" + +#include +#include +#include + +#include "../../web-view.h" + +struct _MqStopReloadButton { + GtkToolButton parent_instance; + GtkWidget *stop_icon; + GtkWidget *reload_icon; + MqWebView *web_view; +}; + +enum { + PROP_WEB_VIEW = 1, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqStopReloadButtonClass { + GtkToolButtonClass parent_class; +}; + +G_DEFINE_TYPE(MqStopReloadButton, mq_stop_reload_button, GTK_TYPE_TOOL_BUTTON) + +static void +loading_cb(MqWebView *web_view, GParamSpec G_GNUC_UNUSED *param_spec, + MqStopReloadButton *stop_reload_button) +{ + if (webkit_web_view_is_loading(WEBKIT_WEB_VIEW(web_view))) { + gtk_tool_button_set_icon_widget( + GTK_TOOL_BUTTON(stop_reload_button), + stop_reload_button->stop_icon); + gtk_tool_button_set_label( + GTK_TOOL_BUTTON(stop_reload_button), "Stop"); + gtk_widget_set_tooltip_text( + GTK_WIDGET(stop_reload_button), + "Stop loading the current page"); + } else { + gtk_tool_button_set_icon_widget( + GTK_TOOL_BUTTON(stop_reload_button), + stop_reload_button->reload_icon); + gtk_tool_button_set_label( + GTK_TOOL_BUTTON(stop_reload_button), "Reload"); + gtk_widget_set_tooltip_text( + GTK_WIDGET(stop_reload_button), + "Reload the current page"); + } + gtk_widget_show_all(GTK_WIDGET(stop_reload_button)); +} + +static void +clicked_cb(MqStopReloadButton *stop_reload_button) +{ + WebKitWebView *web_view; + + web_view = WEBKIT_WEB_VIEW(stop_reload_button->web_view); + + if (webkit_web_view_is_loading(web_view)) { + webkit_web_view_stop_loading(web_view); + } else { + webkit_web_view_reload(web_view); + } +} + +static void +set_web_view(MqStopReloadButton *stop_reload_button, MqWebView *web_view) +{ + stop_reload_button->web_view = web_view; + + g_signal_connect(web_view, "notify::is-loading", + G_CALLBACK(loading_cb), stop_reload_button); +} + +static void +finalize(GObject *object) +{ + MqStopReloadButton *stop_reload_button; + + stop_reload_button = MQ_STOP_RELOAD_BUTTON(object); + + g_object_unref(stop_reload_button->stop_icon); + g_object_unref(stop_reload_button->reload_icon); + + G_OBJECT_CLASS(mq_stop_reload_button_parent_class)->finalize(object); +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + switch (property_id) { + case PROP_WEB_VIEW: + g_value_set_object(value, NULL); + 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) +{ + MqStopReloadButton *stop_reload_button; + + stop_reload_button = MQ_STOP_RELOAD_BUTTON(object); + + switch (property_id) { + case PROP_WEB_VIEW: + set_web_view(stop_reload_button, + g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_stop_reload_button_class_init(MqStopReloadButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + 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_stop_reload_button_init(MqStopReloadButton *stop_reload_button) +{ + /* Stop icon */ + stop_reload_button->stop_icon = gtk_image_new_from_icon_name( + "process-stop", GTK_ICON_SIZE_SMALL_TOOLBAR); + g_object_ref_sink(stop_reload_button->stop_icon); + + /* Reload icon */ + stop_reload_button->reload_icon = gtk_image_new_from_icon_name( + "view-refresh", GTK_ICON_SIZE_SMALL_TOOLBAR); + g_object_ref_sink(stop_reload_button->reload_icon); + + /* Button */ + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(stop_reload_button), + stop_reload_button->stop_icon); + gtk_tool_button_set_label(GTK_TOOL_BUTTON(stop_reload_button), "Stop"); + gtk_widget_set_tooltip_text(GTK_WIDGET(stop_reload_button), + "Stop loading the current page"); + g_signal_connect(stop_reload_button, "clicked", + G_CALLBACK(clicked_cb), NULL); +} + +GtkToolItem * +mq_stop_reload_button_new(MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_STOP_RELOAD_BUTTON, + "web-view", web_view, + NULL); +} diff --git a/src/toolbars/navigation/stop-reload-button.h b/src/toolbars/navigation/stop-reload-button.h new file mode 100644 index 0000000..2b85109 --- /dev/null +++ b/src/toolbars/navigation/stop-reload-button.h @@ -0,0 +1,59 @@ +/* + * Stop/reload button + * + * 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 . + */ + +typedef struct _MqStopReloadButton MqStopReloadButton; +typedef struct _MqStopReloadButtonClass MqStopReloadButtonClass; + +#ifndef MQ_TOOLBARS_NAVIGATION_STOP_RELOAD_BUTTON_H +#define MQ_TOOLBARS_NAVIGATION_STOP_RELOAD_BUTTON_H + +#include +#include + +#include "../../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_STOP_RELOAD_BUTTON \ + (mq_stop_reload_button_get_type()) +#define MQ_STOP_RELOAD_BUTTON(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MQ_TYPE_STOP_RELOAD_BUTTON, \ + MqStopReloadButton)) +#define MQ_IS_STOP_RELOAD_BUTTON(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MQ_TYPE_STOP_RELOAD_BUTTON)) +#define MQ_STOP_RELOAD_BUTTON_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MQ_TYPE_STOP_RELOAD_BUTTON, \ + MqStopReloadButtonClass)) +#define MQ_IS_STOP_RELOAD_BUTTON_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MQ_TYPE_STOP_RELOAD_BUTTON)) +#define MQ_STOP_RELOAD_BUTTON_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MQ_TYPE_STOP_RELOAD_BUTTON, \ + MqStopReloadButtonClass)) + +GType +mq_stop_reload_button_get_type(void); + +GtkToolItem * +mq_stop_reload_button_new(MqWebView *web_view); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_STOP_RELOAD_BUTTON_H */ diff --git a/src/toolbars/navigation/uri-entry.c b/src/toolbars/navigation/uri-entry.c new file mode 100644 index 0000000..8ae818a --- /dev/null +++ b/src/toolbars/navigation/uri-entry.c @@ -0,0 +1,306 @@ +/* + * URI entry + * + * 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 "uri-entry.h" + +#include +#include +#include + +#include "../../web-view.h" + +struct _MqUriEntry { + GtkToolItem parent_instance; + MqWebView *web_view; + GtkWidget *uri_entry; + gchar *hovered_link_uri; + PangoAttrList *hovered_link_style; + gboolean load_failed; +}; + +enum { + PROP_WEB_VIEW = 1, + PROP_URI, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqUriEntryClass { + GtkToolItemClass parent_class; +}; + +G_DEFINE_TYPE(MqUriEntry, mq_uri_entry, GTK_TYPE_TOOL_ITEM) + +static void +load_changed_cb(MqWebView *web_view, WebKitLoadEvent load_event, + MqUriEntry *uri_entry) +{ + const gchar *uri; + + switch (load_event) { + case WEBKIT_LOAD_STARTED: + case WEBKIT_LOAD_REDIRECTED: + case WEBKIT_LOAD_COMMITTED: + uri = mq_web_view_get_uri(web_view); + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), uri); + gtk_entry_set_attributes(GTK_ENTRY(uri_entry->uri_entry), + NULL); + break; + case WEBKIT_LOAD_FINISHED: + gtk_entry_set_progress_fraction( + GTK_ENTRY(uri_entry->uri_entry), 0.0); + break; + } +} + +static gboolean +load_failed_cb(WebKitWebView G_GNUC_UNUSED *web_view, + WebKitLoadEvent G_GNUC_UNUSED load_event, + gchar G_GNUC_UNUSED *failing_uri, GError G_GNUC_UNUSED *error, + MqUriEntry *uri_entry) +{ + uri_entry->load_failed = TRUE; + return FALSE; +} + +/* TODO: key in URI bar should reset to URI of current hovered link, if + * any, even if different from uri_entry->hovered_link_uri. */ +static void +mouse_target_changed_cb(MqWebView G_GNUC_UNUSED *web_view, + WebKitHitTestResult *hit_test_result, guint G_GNUC_UNUSED modifiers, + MqUriEntry *uri_entry) +{ + const gchar *uri; + + uri = mq_web_view_get_uri(web_view); + if (webkit_hit_test_result_context_is_link(hit_test_result)) { + if (gtk_widget_has_focus(uri_entry->uri_entry)) { + } else if (uri_entry->hovered_link_uri && + g_strcmp0(gtk_entry_get_text( + GTK_ENTRY(uri_entry->uri_entry)), + uri_entry->hovered_link_uri) != 0) { + /* The user has edited the hovered link URI in the URI + * bar. */ + } else if (g_strcmp0(gtk_entry_get_text( + GTK_ENTRY(uri_entry->uri_entry)), uri) == + 0) { + g_free(uri_entry->hovered_link_uri); + uri_entry->hovered_link_uri = g_strdup( + webkit_hit_test_result_get_link_uri( + hit_test_result)); + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), + uri_entry->hovered_link_uri); + gtk_entry_set_attributes(GTK_ENTRY(uri_entry->uri_entry), + uri_entry->hovered_link_style); + } else if (gtk_entry_get_attributes( + GTK_ENTRY(uri_entry->uri_entry)) == + uri_entry->hovered_link_style) { + /* The URI bar's text differs from the Web view's URI + * because the mouse was already targeting a different + * link. */ + g_free(uri_entry->hovered_link_uri); + uri_entry->hovered_link_uri = g_strdup( + webkit_hit_test_result_get_link_uri( + hit_test_result)); + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), + uri_entry->hovered_link_uri); + } + } else if (gtk_entry_get_attributes(GTK_ENTRY(uri_entry->uri_entry)) == + uri_entry->hovered_link_style) { + if (gtk_widget_has_focus(uri_entry->uri_entry)) { + } else if (g_strcmp0(gtk_entry_get_text( + GTK_ENTRY(uri_entry->uri_entry)), + uri_entry->hovered_link_uri) == 0) { + /* The user hasn't edited the hovered link URI in the + * URI bar. */ + g_free(uri_entry->hovered_link_uri); + uri_entry->hovered_link_uri = NULL; + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), uri); + gtk_entry_set_attributes(GTK_ENTRY(uri_entry->uri_entry), + NULL); + } + } +} + +static void +load_progress_cb(WebKitWebView *web_view, GParamSpec G_GNUC_UNUSED *param_spec, + MqUriEntry *uri_entry) +{ + /* + * If loading fails, the WebKitWebView's "estimated-load-progress" is + * set to 1.0 after signals like "load-changed" and "load-failed" are + * emitted. So the only way to avoid leaving behind a full progress bar + * after, for example, canceling a page load is to save a flag on a + * failed load and only update the progress bar if the flag is unset. + */ + if (uri_entry->load_failed) { + uri_entry->load_failed = FALSE; + return; + } + gtk_entry_set_progress_fraction(GTK_ENTRY(uri_entry->uri_entry), + webkit_web_view_get_estimated_load_progress(web_view)); +} + +static void +uri_cb(MqWebView *web_view, GParamSpec G_GNUC_UNUSED *param_spec, + MqUriEntry *uri_entry) +{ + const gchar *uri; + + uri = mq_web_view_get_uri(web_view); + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), uri); + gtk_entry_set_attributes(GTK_ENTRY(uri_entry->uri_entry), NULL); +} + +static void +uri_activate_cb(GtkEntry *entry, MqUriEntry *uri_entry) +{ + const gchar *uri; + + uri = gtk_entry_get_text(GTK_ENTRY(entry)); + + mq_web_view_load_uri(uri_entry->web_view, uri); +} + +static void +set_uri(MqUriEntry *uri_entry, const gchar *uri) +{ + if (uri) { + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), uri); + } +} + +static void +set_web_view(MqUriEntry *uri_entry, MqWebView *web_view) +{ + uri_entry->web_view = web_view; + + g_signal_connect(web_view, "load-changed", + G_CALLBACK(load_changed_cb), uri_entry); + g_signal_connect(web_view, "load-failed", + G_CALLBACK(load_failed_cb), uri_entry); + g_signal_connect(web_view, "mouse-target-changed", + G_CALLBACK(mouse_target_changed_cb), uri_entry); + g_signal_connect(web_view, "notify::estimated-load-progress", + G_CALLBACK(load_progress_cb), uri_entry); + g_signal_connect(web_view, "notify::rewritten-uri", + G_CALLBACK(uri_cb), uri_entry); +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + switch (property_id) { + case PROP_URI: + g_value_set_string(value, NULL); + break; + case PROP_WEB_VIEW: + g_value_set_object(value, NULL); + 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) +{ + MqUriEntry *uri_entry; + + uri_entry = MQ_URI_ENTRY(object); + + switch (property_id) { + case PROP_URI: + set_uri(uri_entry, g_value_get_string(value)); + break; + case PROP_WEB_VIEW: + set_web_view(uri_entry, g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_uri_entry_class_init(MqUriEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + obj_properties[PROP_URI] = g_param_spec_string( + "uri", + "URI", + "The initial URI in the URI entry", + "", + 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_uri_entry_init(MqUriEntry *uri_entry) +{ + uri_entry->uri_entry = gtk_entry_new(); + gtk_entry_set_placeholder_text(GTK_ENTRY(uri_entry->uri_entry), + "URI..."); + gtk_entry_set_icon_from_icon_name(GTK_ENTRY(uri_entry->uri_entry), + GTK_ENTRY_ICON_PRIMARY, "text-x-generic"); + gtk_entry_set_progress_fraction(GTK_ENTRY(uri_entry->uri_entry), 0.0); + g_signal_connect(uri_entry->uri_entry, "activate", + G_CALLBACK(uri_activate_cb), uri_entry); + gtk_container_add(GTK_CONTAINER(uri_entry), uri_entry->uri_entry); + gtk_tool_item_set_expand(GTK_TOOL_ITEM(uri_entry), TRUE); + + uri_entry->hovered_link_uri = NULL; + + /* URI bar hovered link style */ + uri_entry->hovered_link_style = pango_attr_list_new(); + pango_attr_list_insert(uri_entry->hovered_link_style, + pango_attr_style_new(PANGO_STYLE_ITALIC)); + + uri_entry->load_failed = FALSE; +} + +GtkToolItem * +mq_uri_entry_new(MqWebView *web_view, const gchar *uri) +{ + return g_object_new(MQ_TYPE_URI_ENTRY, + "web-view", web_view, + "uri", uri, + NULL); +} diff --git a/src/toolbars/navigation/uri-entry.h b/src/toolbars/navigation/uri-entry.h new file mode 100644 index 0000000..248ba22 --- /dev/null +++ b/src/toolbars/navigation/uri-entry.h @@ -0,0 +1,55 @@ +/* + * URI entry + * + * 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 . + */ + +typedef struct _MqUriEntry MqUriEntry; +typedef struct _MqUriEntryClass MqUriEntryClass; + +#ifndef MQ_TOOLBARS_NAVIGATION_URI_ENTRY_H +#define MQ_TOOLBARS_NAVIGATION_URI_ENTRY_H + +#include +#include + +#include "../../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_URI_ENTRY (mq_uri_entry_get_type()) +#define MQ_URI_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MQ_TYPE_URI_ENTRY, MqUriEntry)) +#define MQ_IS_URI_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + MQ_TYPE_URI_ENTRY)) +#define MQ_URI_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + MQ_TYPE_URI_ENTRY, MqUriEntryClass)) +#define MQ_IS_URI_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + MQ_TYPE_URI_ENTRY)) +#define MQ_URI_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + MQ_TYPE_URI_ENTRY, MqUriEntryClass)) + +GType +mq_uri_entry_get_type(void); + +GtkToolItem * +mq_uri_entry_new(MqWebView *web_view, const gchar *uri); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_URI_ENTRY_H */ -- cgit v0.9.1