summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac4
-rw-r--r--src/about.c5
-rw-r--r--src/about.h1
-rw-r--r--src/application.c26
-rw-r--r--src/application.h11
-rw-r--r--src/config.c12
-rw-r--r--src/config.h12
-rw-r--r--src/gpl-3-0.c4
-rw-r--r--src/html.c4
-rw-r--r--src/local.mk17
-rw-r--r--src/tab-body.h49
-rw-r--r--src/tab-chrome.c1017
-rw-r--r--src/tab-chrome.h68
-rw-r--r--src/tab-label.c540
-rw-r--r--src/tab-label.h69
-rw-r--r--src/tab-page.c544
-rw-r--r--src/tab-page.h117
-rw-r--r--src/tab.c648
-rw-r--r--src/tab.h100
-rw-r--r--src/toolbars/find-toolbar.c325
-rw-r--r--src/toolbars/find-toolbar.h61
-rw-r--r--src/toolbars/local.mk5
-rw-r--r--src/toolbars/navigation-toolbar.c259
-rw-r--r--src/toolbars/navigation-toolbar.h62
-rw-r--r--src/toolbars/navigation/back-forward-button-box.c443
-rw-r--r--src/toolbars/navigation/back-forward-button-box.h59
-rw-r--r--src/toolbars/navigation/home-button.c159
-rw-r--r--src/toolbars/navigation/home-button.h56
-rw-r--r--src/toolbars/navigation/local.mk6
-rw-r--r--src/toolbars/navigation/main-menu.c301
-rw-r--r--src/toolbars/navigation/main-menu.h58
-rw-r--r--src/toolbars/navigation/stop-reload-button.c196
-rw-r--r--src/toolbars/navigation/stop-reload-button.h59
-rw-r--r--src/toolbars/navigation/uri-entry.c306
-rw-r--r--src/toolbars/navigation/uri-entry.h55
-rw-r--r--src/web-settings.c3
-rw-r--r--src/web-view.c (renamed from src/tab-body.c)465
-rw-r--r--src/web-view.h71
-rw-r--r--src/window.c244
-rw-r--r--src/window.h40
40 files changed, 4359 insertions, 2122 deletions
diff --git a/configure.ac b/configure.ac
index ca564bf..5468432 100644
--- a/configure.ac
+++ b/configure.ac
@@ -32,9 +32,7 @@ AM_CONDITIONAL([IN_GIT],
AC_PROG_CC()
AM_PROG_CC_C_O()
-AX_CHECK_COMPILE_FLAG([-std=c11], [CFLAGS="${CFLAGS} -std=c11"], [
- AX_CHECK_COMPILE_FLAG([-std=c1x], [CFLAGS="${CFLAGS} -std=c1x"], [], [])
-], [])
+AX_CHECK_COMPILE_FLAG([-std=c99], [CFLAGS="${CFLAGS} -std=c99"], [], [])
# Consider also:
# * -Wchkp
diff --git a/src/about.c b/src/about.c
index 39e6a7a..63cfaaa 100644
--- a/src/about.c
+++ b/src/about.c
@@ -19,13 +19,14 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "about.h"
+
#include <string.h>
#include <webkit2/webkit2.h>
-#include "about.h"
-#include "application.h"
#include "about/paths.h"
+#include "application.h"
static GHashTable *
parse_query_string(gchar *str)
diff --git a/src/about.h b/src/about.h
index 7e527ea..41fb65f 100644
--- a/src/about.h
+++ b/src/about.h
@@ -22,6 +22,7 @@
#ifndef MQ_ABOUT_H
#define MQ_ABOUT_H
+#include <glib.h>
#include <webkit2/webkit2.h>
#include "application.h"
diff --git a/src/application.c b/src/application.c
index f5edc7f..0047b0c 100644
--- a/src/application.c
+++ b/src/application.c
@@ -19,17 +19,25 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "application.h"
+
#include <stdlib.h>
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>
-#include "application.h"
+#include "about.h"
#include "config.h"
#include "web-settings.h"
-#include "about.h"
#include "window.h"
+struct MqApplication {
+ GList *windows;
+ MqConfig *config;
+ WebKitSettings *settings;
+ gboolean marquee_mode;
+};
+
static void
set_webkit_settings(MqApplication *application)
{
@@ -122,9 +130,15 @@ mq_application_quit(MqApplication *application, GtkWindow *parent)
}
/* Message */
- message = g_strdup_printf("You are about to close %d tabs "
- "in %d windows. Are you sure you want to continue?",
- num_tabs, num_windows);
+ if (num_windows == 1) {
+ message = g_strdup_printf("You are about to close %d tabs "
+ "in %d window. Are you sure you want to continue?",
+ num_tabs, num_windows);
+ } else {
+ message = g_strdup_printf("You are about to close %d tabs "
+ "in %d windows. Are you sure you want to continue?",
+ num_tabs, num_windows);
+ }
message_label = gtk_label_new(message);
g_free(message);
@@ -163,6 +177,8 @@ mq_application_quit(MqApplication *application, GtkWindow *parent)
hbox);
g_signal_connect(dialog, "response",
G_CALLBACK(quit_confirm_response_cb), NULL);
+ gtk_widget_grab_focus(gtk_dialog_get_widget_for_response(
+ GTK_DIALOG(dialog), GTK_RESPONSE_OK));
gtk_widget_show_all(dialog);
return;
diff --git a/src/application.h b/src/application.h
index 4b5d08f..0a9939d 100644
--- a/src/application.h
+++ b/src/application.h
@@ -19,24 +19,17 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
-typedef struct MqApplication MqApplication;
-
#ifndef MQ_APPLICATION_H
#define MQ_APPLICATION_H
+typedef struct MqApplication MqApplication;
+
#include <glib.h>
#include <webkit2/webkit2.h>
#include "config.h"
#include "window.h"
-struct MqApplication {
- GList *windows;
- MqConfig *config;
- WebKitSettings *settings;
- gboolean marquee_mode;
-};
-
MqApplication *
mq_application_new(gchar *profile, gboolean private);
diff --git a/src/config.c b/src/config.c
index a841141..ed2c4d9 100644
--- a/src/config.c
+++ b/src/config.c
@@ -23,12 +23,20 @@
#include <config.h>
#endif
-#include <string.h>
+#include "config.h"
+
#include <stdlib.h>
+#include <string.h>
#include <glib.h>
-#include "config.h"
+struct MqConfig {
+ gchar *profile;
+ gchar *file_name;
+ GKeyFile *key_file;
+ gboolean types_and_cbs_set;
+ GHashTable *types_and_cbs;
+};
enum type {
TYPE_BOOLEAN,
diff --git a/src/config.h b/src/config.h
index 55eeed4..93af5f8 100644
--- a/src/config.h
+++ b/src/config.h
@@ -19,20 +19,12 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
-typedef struct MqConfig MqConfig;
-
#ifndef MQ_CONFIG_H
#define MQ_CONFIG_H
-#include <glib.h>
+typedef struct MqConfig MqConfig;
-struct MqConfig {
- gchar *profile;
- gchar *file_name;
- GKeyFile *key_file;
- gboolean types_and_cbs_set;
- GHashTable *types_and_cbs;
-};
+#include <glib.h>
typedef void (*MqConfigBooleanCallback)(MqConfig *,
const gchar *, const gboolean, gpointer);
diff --git a/src/gpl-3-0.c b/src/gpl-3-0.c
index 52c101a..b63c279 100644
--- a/src/gpl-3-0.c
+++ b/src/gpl-3-0.c
@@ -19,10 +19,10 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <glib.h>
-
#include "gpl-3-0.h"
+#include <glib.h>
+
/*
* From <https://www.gnu.org/licenses/gpl-3.0-standalone.html>
*
diff --git a/src/html.c b/src/html.c
index e8f8e4c..9417db9 100644
--- a/src/html.c
+++ b/src/html.c
@@ -19,14 +19,14 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "html.h"
+
#include <stdarg.h>
#include <string.h>
#include <glib.h>
#include <gtk/gtk.h>
-#include "html.h"
-
static const gchar *styles =
/*
* General styles
diff --git a/src/local.mk b/src/local.mk
index b838bd8..89867eb 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -1,14 +1,15 @@
marquee_SOURCES += \
- %reldir%/main.c \
+ %reldir%/about.c \
%reldir%/application.c \
%reldir%/config.c \
- %reldir%/web-settings.c \
- %reldir%/window.c \
- %reldir%/tab.c \
- %reldir%/tab-chrome.c \
- %reldir%/tab-body.c \
- %reldir%/html.c \
%reldir%/gpl-3-0.c \
- %reldir%/about.c
+ %reldir%/html.c \
+ %reldir%/main.c \
+ %reldir%/tab-label.c \
+ %reldir%/tab-page.c \
+ %reldir%/web-settings.c \
+ %reldir%/web-view.c \
+ %reldir%/window.c
include %reldir%/about/local.mk
+include %reldir%/toolbars/local.mk
diff --git a/src/tab-body.h b/src/tab-body.h
deleted file mode 100644
index 0b75b4c..0000000
--- a/src/tab-body.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Tab body
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-typedef struct MqTabBody MqTabBody;
-
-#ifndef MQ_TAB_BODY_H
-#define MQ_TAB_BODY_H
-
-#include <gtk/gtk.h>
-#include <webkit2/webkit2.h>
-
-#include "tab.h"
-
-struct MqTabBody {
- MqTab *tab;
- GtkWidget *container;
- WebKitWebView *web_view;
- WebKitHitTestResult *hit_test_result;
- WebKitHitTestResult *mouse_target_hit_test_result;
-};
-
-MqTabBody *
-mq_tab_body_new(MqTab *tab, const gchar *uri);
-
-GtkWidget *
-mq_tab_body_get_container(MqTabBody *body);
-
-WebKitWebView *
-mq_tab_body_get_web_view(MqTabBody *body);
-
-#endif
diff --git a/src/tab-chrome.c b/src/tab-chrome.c
deleted file mode 100644
index 3a57f4b..0000000
--- a/src/tab-chrome.c
+++ /dev/null
@@ -1,1017 +0,0 @@
-/*
- * Tab chrome
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <gtk/gtk.h>
-#include <webkit2/webkit2.h>
-
-#include "tab-chrome.h"
-#include "tab.h"
-
-static void
-back_clicked_cb(GtkButton G_GNUC_UNUSED *toolbutton, MqTabChrome *chrome)
-{
- webkit_web_view_go_back(chrome->web_view);
-}
-
-static void
-forward_clicked_cb(GtkButton G_GNUC_UNUSED *toolbutton, MqTabChrome *chrome)
-{
- webkit_web_view_go_forward(chrome->web_view);
-}
-
-static GtkWidget *
-back_forward_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
-back_forward_list_box_row_activated_cb(GtkListBox G_GNUC_UNUSED *box,
- GtkListBoxRow *row, MqTabChrome *chrome)
-{
- webkit_web_view_go_to_back_forward_list_item(chrome->web_view,
- webkit_back_forward_list_get_nth_item(
- webkit_web_view_get_back_forward_list(chrome->web_view),
- gtk_list_box_row_get_index(row) - chrome->back_items));
-
- gtk_widget_hide(chrome->back_forward_popover);
-}
-
-static void
-back_forward_toggle_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
-back_forward_box_button_press_cb(GtkWidget *widget, GdkEvent *event,
- MqTabChrome *chrome)
-{
- 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;
-
- /* Make sure this is a mouse button press event. Either the middle or
- * right button is OK. */
- if (event->type != GDK_BUTTON_PRESS) {
- return FALSE;
- }
-
- /* Get the back/forward list for the Web view. */
- back_forward_list = webkit_web_view_get_back_forward_list(
- chrome->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(back_forward_list_box_row_activated_cb), chrome);
-
- /* 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),
- back_forward_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);
- chrome->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);
- ++chrome->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(back_forward_toggle_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. */
- chrome->back_forward_popover = gtk_popover_new(widget);
- gtk_container_add(GTK_CONTAINER(chrome->back_forward_popover), box);
-
- /* NB: gtk_popover_popup() is new in GTK+ 3.22. */
- gtk_widget_show_all(chrome->back_forward_popover);
- gtk_widget_hide(text_view);
-
- return FALSE;
-}
-
-static void
-stop_reload_clicked_cb(GtkToolButton G_GNUC_UNUSED *toolbutton,
- MqTabChrome *chrome)
-{
- if (webkit_web_view_is_loading(chrome->web_view)) {
- webkit_web_view_stop_loading(chrome->web_view);
- } else {
- webkit_web_view_reload(chrome->web_view);
- }
-}
-
-static void
-uri_activate_cb(GtkEntry *entry, MqTabChrome *chrome)
-{
- const gchar *uri;
- gchar *rw_uri;
-
- uri = gtk_entry_get_text(GTK_ENTRY(entry));
-
- if (g_str_has_prefix(uri, "about:")) {
- rw_uri = g_strconcat("mq-about:", uri + strlen("about:"), NULL);
- webkit_web_view_load_uri(chrome->web_view, rw_uri);
- g_free(rw_uri);
- } else {
- webkit_web_view_load_uri(chrome->web_view, uri);
- }
-}
-
-static void
-home_clicked_cb(GtkToolButton G_GNUC_UNUSED *toolbutton,
- MqTabChrome *chrome)
-{
- const gchar *uri;
- gchar *rw_uri;
-
- uri = mq_config_get_string(chrome->config, "tabs.home");
-
- if (g_str_has_prefix(uri, "about:")) {
- rw_uri = g_strconcat("mq-about:", uri + strlen("about:"), NULL);
- webkit_web_view_load_uri(chrome->web_view, rw_uri);
- g_free(rw_uri);
- } else {
- webkit_web_view_load_uri(chrome->web_view, uri);
- }
-}
-
-static void
-zoom_out_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- webkit_web_view_set_zoom_level(chrome->web_view,
- webkit_web_view_get_zoom_level(chrome->web_view) - 0.1);
-}
-
-static void
-zoom_reset_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- webkit_web_view_set_zoom_level(chrome->web_view,
- mq_config_get_double(chrome->config, "zoom.default"));
-}
-
-static void
-zoom_in_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- webkit_web_view_set_zoom_level(chrome->web_view,
- webkit_web_view_get_zoom_level(chrome->web_view) + 0.1);
-}
-
-static void
-find_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- gtk_revealer_set_reveal_child(GTK_REVEALER(chrome->find_revealer),
- TRUE);
- gtk_widget_grab_focus(chrome->find_search_entry);
- gtk_widget_hide(chrome->menu_popover);
-}
-
-static void
-fullscreen_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- mq_window_toggle_fullscreen(mq_tab_get_window(chrome->tab));
- gtk_widget_hide(chrome->menu_popover);
-}
-
-static void
-developer_tools_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- webkit_web_inspector_show(webkit_web_view_get_inspector(
- chrome->web_view));
- gtk_widget_hide(chrome->menu_popover);
-}
-
-static void
-preferences_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- mq_tab_new("about:preferences", chrome->tab);
- gtk_widget_hide(chrome->menu_popover);
- /* TODO: Hack: */
- gtk_notebook_next_page(GTK_NOTEBOOK(chrome->tab->window->notebook));
-}
-
-static void
-about_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- mq_tab_new("about:", chrome->tab);
- gtk_widget_hide(chrome->menu_popover);
- /* TODO: Hack: */
- gtk_notebook_next_page(GTK_NOTEBOOK(chrome->tab->window->notebook));
-}
-
-static void
-quit_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- mq_tab_quit(chrome->tab);
- /* mq_tab_quit() just calls mq_window_quit(), which just calls
- * mq_application_quit(), which is asynchronous. So close the menu. */
- gtk_widget_hide(chrome->menu_popover);
-}
-
-#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), chrome)
-
-static void
-menu_button_clicked_cb(GtkToolButton *tool_button,
- MqTabChrome G_GNUC_UNUSED *chrome)
-{
- 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. */
- chrome->menu_popover = gtk_popover_new(GTK_WIDGET(tool_button));
- gtk_container_add(GTK_CONTAINER(chrome->menu_popover), grid);
-
- /* NB: gtk_popover_popup() is new in GTK+ 3.22. */
- gtk_widget_show_all(chrome->menu_popover);
-}
-
-#undef BUTTON_ROWS
-#undef BUTTON_COLS
-#undef NEW_BUTTON
-
-static GtkWidget *
-navigation_toolbar_new(MqTabChrome *chrome, const gchar *uri)
-{
- GtkToolbar *navigation_toolbar;
- GtkToolItem *back_forward_tool_item;
- GtkWidget *back_forward_event_box;
- GtkToolItem *uri_tool_item;
- GtkToolItem *home_button;
-
- navigation_toolbar = GTK_TOOLBAR(gtk_toolbar_new());
-
- back_forward_tool_item = gtk_tool_item_new();
- chrome->back_forward_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
-
- /* Back button */
- chrome->back_button = gtk_button_new_from_icon_name("go-previous",
- GTK_ICON_SIZE_SMALL_TOOLBAR);
- gtk_widget_set_tooltip_text(GTK_WIDGET(chrome->back_button),
- "Go back one page");
- g_signal_connect(chrome->back_button, "clicked",
- G_CALLBACK(back_clicked_cb), chrome);
- gtk_box_pack_start(GTK_BOX(chrome->back_forward_box),
- chrome->back_button, FALSE, FALSE, 0);
-
- /* Forward button */
- chrome->forward_button = gtk_button_new_from_icon_name("go-next",
- GTK_ICON_SIZE_SMALL_TOOLBAR);
- gtk_widget_set_tooltip_text(GTK_WIDGET(chrome->forward_button),
- "Go forward one page");
- g_signal_connect(chrome->forward_button, "clicked",
- G_CALLBACK(forward_clicked_cb), chrome);
- gtk_box_pack_start(GTK_BOX(chrome->back_forward_box),
- chrome->forward_button, FALSE, FALSE, 0);
-
- gtk_style_context_add_class(
- gtk_widget_get_style_context(chrome->back_forward_box),
- "linked");
- back_forward_event_box = gtk_event_box_new();
- g_signal_connect(back_forward_event_box, "button-press-event",
- G_CALLBACK(back_forward_box_button_press_cb), chrome);
- gtk_container_add(GTK_CONTAINER(back_forward_event_box),
- chrome->back_forward_box);
- gtk_container_add(GTK_CONTAINER(back_forward_tool_item),
- back_forward_event_box);
- gtk_toolbar_insert(navigation_toolbar, back_forward_tool_item, -1);
-
- /* Stop/reload button */
- chrome->stop_icon = gtk_image_new_from_icon_name("process-stop",
- GTK_ICON_SIZE_SMALL_TOOLBAR);
- g_object_ref_sink(chrome->stop_icon);
- chrome->reload_icon = gtk_image_new_from_icon_name("view-refresh",
- GTK_ICON_SIZE_SMALL_TOOLBAR);
- g_object_ref_sink(chrome->reload_icon);
- chrome->stop_reload_button = gtk_tool_button_new(chrome->stop_icon,
- "Stop");
- gtk_widget_set_tooltip_text(GTK_WIDGET(chrome->stop_reload_button),
- "Stop loading the current page");
- g_signal_connect(chrome->stop_reload_button, "clicked",
- G_CALLBACK(stop_reload_clicked_cb), chrome);
- gtk_toolbar_insert(navigation_toolbar, chrome->stop_reload_button, -1);
-
- /* URI bar */
- uri_tool_item = gtk_tool_item_new();
- chrome->uri_entry = gtk_entry_new();
- if (uri) {
- gtk_entry_set_text(GTK_ENTRY(chrome->uri_entry), uri);
- }
- gtk_entry_set_placeholder_text(GTK_ENTRY(chrome->uri_entry),
- "URI...");
- gtk_entry_set_icon_from_icon_name(GTK_ENTRY(chrome->uri_entry),
- GTK_ENTRY_ICON_PRIMARY, "text-x-generic");
- gtk_entry_set_progress_fraction(GTK_ENTRY(chrome->uri_entry), 0.0);
- g_signal_connect(chrome->uri_entry, "activate",
- G_CALLBACK(uri_activate_cb), chrome);
- gtk_container_add(GTK_CONTAINER(uri_tool_item),
- chrome->uri_entry);
- gtk_tool_item_set_expand(uri_tool_item, TRUE);
- gtk_toolbar_insert(navigation_toolbar, uri_tool_item, -1);
-
- /* URI bar hovered link style */
- chrome->hovered_link_style = pango_attr_list_new();
- pango_attr_list_insert(chrome->hovered_link_style,
- pango_attr_style_new(PANGO_STYLE_ITALIC));
-
- /* Home button */
- home_button = gtk_tool_button_new(gtk_image_new_from_icon_name(
- "go-home", GTK_ICON_SIZE_SMALL_TOOLBAR), "Home");
- gtk_widget_set_tooltip_text(GTK_WIDGET(home_button),
- "Load the home page");
- g_signal_connect(home_button, "clicked",
- G_CALLBACK(home_clicked_cb), chrome);
- gtk_toolbar_insert(navigation_toolbar, home_button, -1);
-
- /* Menu button */
- chrome->menu_button = gtk_tool_button_new(
- gtk_image_new_from_icon_name("open-menu-symbolic",
- GTK_ICON_SIZE_SMALL_TOOLBAR), "Menu");
- gtk_widget_set_tooltip_text(GTK_WIDGET(chrome->menu_button),
- "Open menu");
- g_signal_connect(chrome->menu_button, "clicked",
- G_CALLBACK(menu_button_clicked_cb), chrome);
- gtk_toolbar_insert(navigation_toolbar, chrome->menu_button, -1);
-
- gtk_widget_set_hexpand(GTK_WIDGET(navigation_toolbar), TRUE);
-
- chrome->load_failed = FALSE;
-
- return GTK_WIDGET(navigation_toolbar);
-}
-
-static void
-find_search(MqTabChrome *chrome, gboolean forward)
-{
- guint32 find_options;
-
- find_options = WEBKIT_FIND_OPTIONS_WRAP_AROUND;
- if (!chrome->find_match_case) {
- find_options |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
- }
- if (!forward) {
- find_options |= WEBKIT_FIND_OPTIONS_BACKWARDS;
- }
- webkit_find_controller_search(chrome->find_controller,
- gtk_entry_get_text(GTK_ENTRY(chrome->find_search_entry)),
- find_options, G_MAXUINT);
- chrome->find_searching = TRUE;
-}
-
-static void
-find_search_finished(MqTabChrome *chrome)
-{
- chrome->find_searching = FALSE;
- webkit_find_controller_search_finish(chrome->find_controller);
-}
-
-static void
-find_close(MqTabChrome *chrome)
-{
- gtk_revealer_set_reveal_child(GTK_REVEALER(chrome->find_revealer),
- FALSE);
- gtk_label_set_text(GTK_LABEL(chrome->find_matches_label), NULL);
- find_search_finished(chrome);
-}
-
-static void
-find_search_changed_cb(GtkSearchEntry G_GNUC_UNUSED *entry, MqTabChrome *chrome)
-{
- find_search(chrome, TRUE);
-}
-
-static gboolean
-find_search_key_press_event_cb(GtkSearchEntry G_GNUC_UNUSED *entry,
- GdkEventKey *event, MqTabChrome *chrome)
-{
- switch (event->keyval) {
- case GDK_KEY_Escape:
- find_close(chrome);
- return TRUE;
- case GDK_KEY_Return:
- case GDK_KEY_KP_Enter:
- case GDK_KEY_ISO_Enter:
- find_search(chrome, !(event->state & GDK_SHIFT_MASK));
- return TRUE;
- default:
- return FALSE;
- }
-}
-
-static void
-find_prev_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- /* Calling this method before webkit_find_controller_search() or
- * webkit_find_controller_count_matches() is a programming error. */
- if (chrome->find_searching) {
- webkit_find_controller_search_previous(chrome->find_controller);
- } else {
- find_search(chrome, FALSE);
- }
-}
-
-static void
-find_next_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- /* Calling this method before webkit_find_controller_search() or
- * webkit_find_controller_count_matches() is a programming error. */
- if (chrome->find_searching) {
- webkit_find_controller_search_next(chrome->find_controller);
- } else {
- find_search(chrome, TRUE);
- }
-}
-
-static void
-find_match_case_toggled_cb(GtkToggleButton *toggle_button, MqTabChrome *chrome)
-{
- chrome->find_match_case = gtk_toggle_button_get_active(toggle_button);
- find_search(chrome, TRUE);
-}
-
-static void
-find_close_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqTabChrome *chrome)
-{
- find_close(chrome);
-}
-
-static GtkWidget *
-find_toolbar_new(MqTabChrome *chrome)
-{
- GtkWidget *close_button;
- GtkWidget *prev_button;
- GtkWidget *next_button;
- GtkWidget *match_case_button;
- GtkWidget *box;
-
- /* Search entry */
- chrome->find_search_entry = gtk_search_entry_new();
- g_signal_connect(chrome->find_search_entry, "search-changed",
- G_CALLBACK(find_search_changed_cb), chrome);
- g_signal_connect(chrome->find_search_entry, "key-press-event",
- G_CALLBACK(find_search_key_press_event_cb), chrome);
-
- /* 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(find_prev_clicked_cb), chrome);
-
- /* 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(find_next_clicked_cb), chrome);
-
- /* 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(find_match_case_toggled_cb), chrome);
-
- /* Matches label */
- chrome->find_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(find_close_clicked_cb), chrome);
-
- /* Box */
- box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
- gtk_box_pack_start(GTK_BOX(box), chrome->find_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), chrome->find_matches_label,
- FALSE, FALSE, 0);
- gtk_box_pack_end(GTK_BOX(box), close_button, FALSE, FALSE, 0);
-
- /* Revealer */
- chrome->find_revealer = gtk_revealer_new();
- gtk_revealer_set_transition_type(GTK_REVEALER(chrome->find_revealer),
- GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
- gtk_container_add(GTK_CONTAINER(chrome->find_revealer), box);
-
- chrome->find_match_case = FALSE;
- chrome->find_searching = FALSE;
-
- return chrome->find_revealer;
-}
-
-MqTabChrome *
-mq_tab_chrome_new(MqTab *tab, const gchar *uri)
-{
- MqTabChrome *chrome;
-
- chrome = malloc(sizeof(*chrome));
- chrome->config = mq_application_get_config(mq_tab_get_application(tab));
- chrome->tab = tab;
-
- chrome->container = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
- gtk_box_pack_start(GTK_BOX(chrome->container),
- navigation_toolbar_new(chrome, uri), FALSE, FALSE, 0);
- gtk_box_pack_start(GTK_BOX(chrome->container),
- find_toolbar_new(chrome), FALSE, FALSE, 0);
-
- return chrome;
-}
-
-GtkWidget *
-mq_tab_chrome_get_container(MqTabChrome *chrome)
-{
- return chrome->container;
-}
-
-static gchar *
-web_view_get_uri(WebKitWebView *web_view)
-{
- const gchar *uri;
- gchar *rw_uri;
-
- uri = webkit_web_view_get_uri(web_view);
-
- if (g_str_has_prefix(uri, "mq-about:")) {
- rw_uri = g_strconcat("about:", uri + strlen("mq-about:"), NULL);
- } else {
- rw_uri = g_strdup(uri);
- }
-
- return rw_uri;
-}
-
-static void
-load_changed_cb(WebKitWebView *web_view, WebKitLoadEvent load_event,
- MqTabChrome *chrome)
-{
- gchar *uri;
-
- switch (load_event) {
- case WEBKIT_LOAD_STARTED:
- case WEBKIT_LOAD_REDIRECTED:
- case WEBKIT_LOAD_COMMITTED:
- uri = web_view_get_uri(web_view);
- gtk_entry_set_text(GTK_ENTRY(chrome->uri_entry), uri);
- g_free(uri);
- gtk_entry_set_attributes(GTK_ENTRY(chrome->uri_entry),
- NULL);
- break;
- case WEBKIT_LOAD_FINISHED:
- gtk_entry_set_progress_fraction(
- GTK_ENTRY(chrome->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,
- MqTabChrome *chrome)
-{
- chrome->load_failed = TRUE;
- return FALSE;
-}
-
-/* TODO: <Esc> key in URI bar should reset to URI of current hovered link, if
- * any, even if different from chrome->hovered_link_uri. */
-static void
-mouse_target_changed_cb(WebKitWebView G_GNUC_UNUSED *web_view,
- WebKitHitTestResult *hit_test_result, guint G_GNUC_UNUSED modifiers,
- MqTabChrome *chrome)
-{
- gchar *uri;
-
- uri = web_view_get_uri(web_view);
- if (webkit_hit_test_result_context_is_link(hit_test_result)) {
- if (gtk_widget_has_focus(chrome->uri_entry)) {
- } else if (chrome->hovered_link_uri &&
- g_strcmp0(gtk_entry_get_text(
- GTK_ENTRY(chrome->uri_entry)),
- chrome->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(chrome->uri_entry)), uri) ==
- 0) {
- g_free(chrome->hovered_link_uri);
- chrome->hovered_link_uri = g_strdup(
- webkit_hit_test_result_get_link_uri(
- hit_test_result));
- gtk_entry_set_text(GTK_ENTRY(chrome->uri_entry),
- chrome->hovered_link_uri);
- gtk_entry_set_attributes(GTK_ENTRY(chrome->uri_entry),
- chrome->hovered_link_style);
- } else if (gtk_entry_get_attributes(
- GTK_ENTRY(chrome->uri_entry)) ==
- chrome->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(chrome->hovered_link_uri);
- chrome->hovered_link_uri = g_strdup(
- webkit_hit_test_result_get_link_uri(
- hit_test_result));
- gtk_entry_set_text(GTK_ENTRY(chrome->uri_entry),
- chrome->hovered_link_uri);
- }
- } else if (gtk_entry_get_attributes(GTK_ENTRY(chrome->uri_entry)) ==
- chrome->hovered_link_style) {
- if (gtk_widget_has_focus(chrome->uri_entry)) {
- } else if (g_strcmp0(gtk_entry_get_text(
- GTK_ENTRY(chrome->uri_entry)),
- chrome->hovered_link_uri) == 0) {
- /* The user hasn't edited the hovered link URI in the
- * URI bar. */
- g_free(chrome->hovered_link_uri);
- chrome->hovered_link_uri = NULL;
- gtk_entry_set_text(GTK_ENTRY(chrome->uri_entry), uri);
- gtk_entry_set_attributes(GTK_ENTRY(chrome->uri_entry),
- NULL);
- }
- }
-
- g_free(uri);
-}
-
-static void
-load_progress_cb(WebKitWebView *web_view, GParamSpec G_GNUC_UNUSED *paramspec,
- MqTabChrome *chrome)
-{
- /*
- * 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 (chrome->load_failed) {
- chrome->load_failed = FALSE;
- return;
- }
- gtk_entry_set_progress_fraction(GTK_ENTRY(chrome->uri_entry),
- webkit_web_view_get_estimated_load_progress(web_view));
-}
-
-static void
-uri_cb(WebKitWebView *web_view, GParamSpec G_GNUC_UNUSED *paramspec,
- MqTabChrome *chrome)
-{
- gchar *uri;
-
- uri = web_view_get_uri(web_view);
- gtk_entry_set_text(GTK_ENTRY(chrome->uri_entry), uri);
- g_free(uri);
- gtk_entry_set_attributes(GTK_ENTRY(chrome->uri_entry), NULL);
-}
-
-static void
-loading_cb(WebKitWebView *web_view, GParamSpec G_GNUC_UNUSED *paramspec,
- MqTabChrome *chrome)
-{
- if (webkit_web_view_is_loading(web_view)) {
- gtk_tool_button_set_icon_widget(
- GTK_TOOL_BUTTON(chrome->stop_reload_button),
- chrome->stop_icon);
- gtk_tool_button_set_label(
- GTK_TOOL_BUTTON(chrome->stop_reload_button), "Stop");
- gtk_widget_set_tooltip_text(
- GTK_WIDGET(chrome->stop_reload_button),
- "Stop loading the current page");
- } else {
- gtk_tool_button_set_icon_widget(
- GTK_TOOL_BUTTON(chrome->stop_reload_button),
- chrome->reload_icon);
- gtk_tool_button_set_label(
- GTK_TOOL_BUTTON(chrome->stop_reload_button), "Reload");
- gtk_widget_set_tooltip_text(
- GTK_WIDGET(chrome->stop_reload_button),
- "Reload the current page");
- }
- gtk_widget_show_all(GTK_WIDGET(chrome->stop_reload_button));
-}
-
-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, MqTabChrome *chrome)
-{
- gtk_widget_set_sensitive(GTK_WIDGET(chrome->back_button),
- webkit_web_view_can_go_back(chrome->web_view));
- gtk_widget_set_sensitive(GTK_WIDGET(chrome->forward_button),
- webkit_web_view_can_go_forward(chrome->web_view));
-}
-
-static void
-find_found_text_cb(WebKitFindController G_GNUC_UNUSED *find_controller,
- guint match_count, MqTabChrome *chrome)
-{
- 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(chrome->find_matches_label), text);
- g_free(text);
-
-}
-
-static void
-find_failed_to_find_text_cb(WebKitFindController G_GNUC_UNUSED *find_controller,
- MqTabChrome *chrome)
-{
- gtk_label_set_text(GTK_LABEL(chrome->find_matches_label), "No matches");
-}
-
-static void
-connect_web_view(MqTabChrome *chrome)
-{
- chrome->hovered_link_uri = NULL;
-
- g_signal_connect(chrome->web_view, "load-changed",
- G_CALLBACK(load_changed_cb), chrome);
- g_signal_connect(chrome->web_view, "load-failed",
- G_CALLBACK(load_failed_cb), chrome);
- g_signal_connect(chrome->web_view, "mouse-target-changed",
- G_CALLBACK(mouse_target_changed_cb), chrome);
- g_signal_connect(chrome->web_view, "notify::estimated-load-progress",
- G_CALLBACK(load_progress_cb), chrome);
- g_signal_connect(chrome->web_view, "notify::uri",
- G_CALLBACK(uri_cb), chrome);
- g_signal_connect(chrome->web_view, "notify::is-loading",
- G_CALLBACK(loading_cb), chrome);
- g_signal_connect(
- webkit_web_view_get_back_forward_list(chrome->web_view),
- "changed", G_CALLBACK(back_forward_list_changed_cb), chrome);
-
- g_signal_connect(chrome->find_controller, "found-text",
- G_CALLBACK(find_found_text_cb), chrome);
- g_signal_connect(chrome->find_controller, "failed-to-find-text",
- G_CALLBACK(find_failed_to_find_text_cb), chrome);
-}
-
-void
-mq_tab_chrome_set_web_view(MqTabChrome *chrome, WebKitWebView *web_view)
-{
- chrome->web_view = web_view;
- chrome->find_controller = webkit_web_view_get_find_controller(web_view);
- connect_web_view(chrome);
-}
diff --git a/src/tab-chrome.h b/src/tab-chrome.h
deleted file mode 100644
index f3bf883..0000000
--- a/src/tab-chrome.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Tab chrome
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-typedef struct MqTabChrome MqTabChrome;
-
-#ifndef MQ_TAB_CHROME_H
-#define MQ_TAB_CHROME_H
-
-#include <gtk/gtk.h>
-#include <webkit2/webkit2.h>
-
-#include "tab.h"
-
-struct MqTabChrome {
- MqConfig *config;
- MqTab *tab;
- GtkWidget *container;
- GtkWidget *back_forward_box;
- GtkWidget *back_button;
- GtkWidget *forward_button;
- GtkWidget *stop_icon;
- GtkWidget *reload_icon;
- GtkToolItem *stop_reload_button;
- GtkWidget *uri_entry;
- PangoAttrList *hovered_link_style;
- gchar *hovered_link_uri;
- GtkWidget *find_revealer;
- GtkWidget *find_search_entry;
- GtkWidget *find_matches_label;
- gboolean find_match_case;
- gboolean find_searching;
- WebKitWebView *web_view;
- WebKitFindController *find_controller;
- gboolean load_failed;
- GtkWidget *back_forward_popover;
- gint back_items;
- GtkToolItem *menu_button;
- GtkWidget *menu_popover;
-};
-
-MqTabChrome *
-mq_tab_chrome_new(MqTab *tab, const gchar *uri);
-
-GtkWidget *
-mq_tab_chrome_get_container(MqTabChrome *chrome);
-
-void
-mq_tab_chrome_set_web_view(MqTabChrome *chrome, WebKitWebView *web_view);
-
-#endif
diff --git a/src/tab-label.c b/src/tab-label.c
new file mode 100644
index 0000000..77e6b62
--- /dev/null
+++ b/src/tab-label.c
@@ -0,0 +1,540 @@
+/*
+ * Tab label
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "tab-label.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#include "tab-page.h"
+#include "web-view.h"
+
+struct _MqTabLabel {
+ GtkEventBox parent_instance;
+ MqTabPage *tab_page;
+ WebKitWebView *web_view;
+ GtkWidget *image;
+ GtkWidget *label;
+ guint position;
+ const gchar *title;
+ gboolean scrolling;
+ gchar *scrolled_title;
+ GtkWidget *popover;
+};
+
+enum {
+ PROP_TAB_PAGE = 1,
+ PROP_WEB_VIEW,
+ N_PROPERTIES
+};
+
+static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,};
+
+struct _MqTabLabelClass {
+ GtkEventBoxClass parent_class;
+};
+
+G_DEFINE_TYPE(MqTabLabel, mq_tab_label, GTK_TYPE_EVENT_BOX)
+
+static void
+reload_tab_clicked_cb(GtkWidget G_GNUC_UNUSED *button, MqTabLabel *tab_label)
+{
+ webkit_web_view_reload(tab_label->web_view);
+ gtk_widget_hide(tab_label->popover);
+}
+
+static void
+new_tab_clicked_cb(GtkWidget G_GNUC_UNUSED *button, MqTabLabel *tab_label)
+{
+ mq_tab_page_new(NULL, tab_label->tab_page);
+ gtk_widget_hide(tab_label->popover);
+}
+
+static void
+new_window_clicked_cb(GtkWidget G_GNUC_UNUSED *button, MqTabLabel *tab_label)
+{
+ mq_application_add_window(
+ mq_tab_page_get_application(tab_label->tab_page), NULL);
+ gtk_widget_hide(tab_label->popover);
+}
+
+static void
+tab_list_button_toggled_cb(GtkToggleButton *toggle_button, GtkWidget *tab_list)
+{
+ if (gtk_toggle_button_get_active(toggle_button)) {
+ gtk_widget_show(tab_list);
+ } else {
+ gtk_widget_hide(tab_list);
+ }
+}
+
+static void
+create_tree_model_recurse(MqTabPage *node, GtkTreeStore *tree_store,
+ GtkTreeIter *parent_tree_iter)
+{
+ GtkTreeIter tree_iter;
+
+ for (; node; node = mq_tab_page_next(node)) {
+ gtk_tree_store_append(tree_store, &tree_iter, parent_tree_iter);
+ gtk_tree_store_set(tree_store, &tree_iter, 0,
+ mq_tab_page_get_title(node), -1);
+ create_tree_model_recurse(mq_tab_page_first_child(node),
+ tree_store, &tree_iter);
+ }
+}
+
+static GtkTreeModel *
+create_tree_model(MqTabLabel *tab_label)
+{
+ GtkTreeStore *tree_store;
+
+ tree_store = gtk_tree_store_new(1, G_TYPE_STRING);
+
+ create_tree_model_recurse(mq_tab_page_root(tab_label->tab_page),
+ tree_store, NULL);
+
+ return GTK_TREE_MODEL(tree_store);
+}
+
+static void
+row_activated_cb(GtkTreeView G_GNUC_UNUSED *tree_view, GtkTreePath *tree_path,
+ GtkTreeViewColumn G_GNUC_UNUSED *tree_view_column,
+ MqTabLabel *tab_label)
+{
+ gint *indices;
+ gint depth;
+
+ indices = gtk_tree_path_get_indices_with_depth(tree_path, &depth);
+ g_assert(depth == 1);
+ mq_window_set_current_tab(mq_tab_page_get_window(tab_label->tab_page),
+ indices[0] + 1);
+ gtk_widget_hide(tab_label->popover);
+}
+
+static GtkWidget *
+create_tab_list(MqTabLabel *tab_label)
+{
+ GtkWidget *tree_view;
+ GtkTreeSelection *tree_selection;
+ GtkCellRenderer *cell_renderer;
+ GtkWidget *scrolled_window;
+
+ tree_view = gtk_tree_view_new_with_model(create_tree_model(tab_label));
+ tree_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+ gtk_tree_selection_set_mode(tree_selection, GTK_SELECTION_BROWSE);
+ gtk_tree_selection_select_path(tree_selection,
+ gtk_tree_path_new_from_indices(
+ mq_window_get_current_tab(mq_tab_page_get_window(
+ tab_label->tab_page)) - 1, -1));
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE);
+ gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(tree_view),
+ TRUE);
+ gtk_tree_view_expand_all(GTK_TREE_VIEW(tree_view));
+ gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_view), TRUE);
+ gtk_tree_view_set_enable_tree_lines(GTK_TREE_VIEW(tree_view), TRUE);
+ g_signal_connect(tree_view, "row-activated",
+ G_CALLBACK(row_activated_cb), tab_label);
+
+ cell_renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree_view),
+ -1, NULL, cell_renderer, "text", 0, NULL);
+
+ scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_min_content_width(
+ GTK_SCROLLED_WINDOW(scrolled_window), 400);
+ gtk_scrolled_window_set_min_content_height(
+ GTK_SCROLLED_WINDOW(scrolled_window), 200);
+ gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view);
+
+ return scrolled_window;
+}
+
+#define BUTTON_ROWS 2
+#define BUTTON_COLS 4
+#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_widget_set_can_focus(buttons[Y * BUTTON_COLS + X], FALSE); \
+ gtk_grid_attach(GTK_GRID(button_grid), \
+ buttons[Y * BUTTON_COLS + X], X, Y, 1, 1); \
+ } while (0)
+#define NEW_TOGGLE(Y, X, ICON, TOOLTIP) \
+ do { \
+ buttons[Y * BUTTON_COLS + X] = gtk_toggle_button_new(); \
+ gtk_button_set_image(GTK_BUTTON(buttons[Y * BUTTON_COLS + X]), \
+ gtk_image_new_from_icon_name(ICON, \
+ GTK_ICON_SIZE_BUTTON)); \
+ gtk_widget_set_tooltip_text(buttons[Y * BUTTON_COLS + X], \
+ TOOLTIP); \
+ gtk_widget_set_can_focus(buttons[Y * BUTTON_COLS + X], FALSE); \
+ gtk_grid_attach(GTK_GRID(button_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", CB, tab_label)
+
+static void
+create_tab_popover(GtkWidget *widget, MqTabLabel *tab_label)
+{
+ GtkWidget *button_grid;
+ GtkWidget *buttons[BUTTON_ROWS * BUTTON_COLS];
+ GtkWidget *tab_list;
+ GtkWidget *tab_list_scrolled_window;
+ GtkWidget *box;
+
+ /* Set up button grid. */
+ button_grid = gtk_grid_new();
+ gtk_widget_set_halign(button_grid, GTK_ALIGN_CENTER);
+
+ /* Set up buttons. */
+ NEW_BUTTON(0, 0, "view-refresh", "Reload tab");
+ NEW_BUTTON(0, 1, "edit-copy", "Duplicate tab");
+ NEW_BUTTON(0, 2, "window-new", "Move tab to new window");
+ NEW_BUTTON(0, 3, "window-close", "Close tab");
+ NEW_BUTTON(1, 0, "tab-new-symbolic", "New tab");
+ NEW_BUTTON(1, 1, "window-new", "New window");
+ NEW_BUTTON(1, 2, "edit-undo", "Undo close tab");
+ NEW_TOGGLE(1, 3, "view-list-symbolic", "Tab list...");
+
+ CLICKED_CB(0, 0, G_CALLBACK(reload_tab_clicked_cb));
+ CLICKED_CB(1, 0, G_CALLBACK(new_tab_clicked_cb));
+ CLICKED_CB(1, 1, G_CALLBACK(new_window_clicked_cb));
+
+ /* Set up the tab list. */
+ tab_list = create_tab_list(tab_label);
+
+ /* Set up the tab list scrolled window.
+ *
+ * The following GtkScrolledWindow widget has a hardcoded minimum size,
+ * because there seems to be (in GTK+ versions before 3.22) no way to
+ * set the natural size of GtkScrolledWindow and its GtkViewport.
+ *
+ * See tab-chrome.c for more information. */
+ tab_list_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_min_content_width(
+ GTK_SCROLLED_WINDOW(tab_list_scrolled_window), 400);
+ gtk_scrolled_window_set_min_content_height(
+ GTK_SCROLLED_WINDOW(tab_list_scrolled_window), 200);
+ gtk_container_add(GTK_CONTAINER(tab_list_scrolled_window), tab_list);
+
+ /* Add tab list toggle button handler. */
+ g_signal_connect(buttons[1 * BUTTON_COLS + 3], "toggled",
+ G_CALLBACK(tab_list_button_toggled_cb),
+ tab_list_scrolled_window);
+
+ /* Set up the button rows box. */
+ box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_pack_start(GTK_BOX(box), button_grid,
+ TRUE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(box), tab_list_scrolled_window,
+ TRUE, FALSE, 0);
+
+ /* Set up the popover. */
+ tab_label->popover = gtk_popover_new(widget);
+ gtk_container_add(GTK_CONTAINER(tab_label->popover), box);
+
+ /* NB: gtk_popover_popup() is new in GTK+ 3.22. */
+ gtk_widget_show_all(tab_label->popover);
+ gtk_widget_hide(tab_list_scrolled_window);
+}
+
+#undef BUTTON_ROWS
+#undef BUTTON_COLS
+#undef NEW_BUTTON
+#undef NEW_TOGGLE
+#undef CLICKED_CB
+
+static gboolean
+button_press_cb(GtkWidget *widget, GdkEventButton *event,
+ MqTabLabel *tab_label)
+{
+ /* Create a popover menu on right click. */
+ if (event->button == 3) {
+ create_tab_popover(widget, tab_label);
+ }
+
+ return FALSE;
+}
+
+static void
+update_image(MqTabLabel *tab_label, GdkPixbuf *favicon)
+{
+ if (favicon) {
+ gtk_image_set_from_pixbuf(GTK_IMAGE(tab_label->image), favicon);
+ } else {
+ gtk_image_set_from_icon_name(GTK_IMAGE(tab_label->image),
+ "text-x-generic", GTK_ICON_SIZE_BUTTON);
+ }
+}
+
+static void
+update_label(MqTabLabel *tab_label)
+{
+ const gchar *title;
+ gchar *label;
+
+ title = tab_label->scrolling ? tab_label->scrolled_title :
+ tab_label->title;
+ label = g_strdup_printf("%d. %s", tab_label->position, title);
+ gtk_label_set_text(GTK_LABEL(tab_label->label), label);
+ gtk_widget_set_tooltip_text(GTK_WIDGET(tab_label), label);
+ g_free(label);
+}
+
+static void
+favicon_cb(WebKitWebView G_GNUC_UNUSED *web_view,
+ GParamSpec G_GNUC_UNUSED *param_spec, MqTabLabel *tab_label)
+{
+ cairo_surface_t *surface;
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *scaled_pixbuf;
+
+ surface = webkit_web_view_get_favicon(tab_label->web_view);
+ scaled_pixbuf = NULL;
+ if (surface) {
+ pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0,
+ cairo_image_surface_get_width(surface),
+ cairo_image_surface_get_height(surface));
+ if (pixbuf) {
+ scaled_pixbuf = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
+ GDK_INTERP_BILINEAR);
+ g_object_unref(pixbuf);
+ }
+ }
+
+ update_image(tab_label, scaled_pixbuf);
+}
+
+static void
+title_cb(WebKitWebView G_GNUC_UNUSED *web_view,
+ GParamSpec G_GNUC_UNUSED *param_spec, MqTabLabel *tab_label)
+{
+ tab_label->title = webkit_web_view_get_title(tab_label->web_view);
+ if (tab_label->scrolling) {
+ tab_label->scrolled_title = g_strdup_printf("%s ",
+ tab_label->title);
+ }
+ update_label(tab_label);
+}
+
+static void
+set_web_view(MqTabLabel *tab_label, MqWebView *web_view)
+{
+ tab_label->web_view = WEBKIT_WEB_VIEW(web_view);
+
+ g_signal_connect(web_view, "notify::favicon",
+ G_CALLBACK(favicon_cb), tab_label);
+ g_signal_connect(web_view, "notify::title",
+ G_CALLBACK(title_cb), tab_label);
+}
+
+static void
+finalize(GObject *object)
+{
+ MqTabLabel *tab_label;
+
+ tab_label = MQ_TAB_LABEL(object);
+
+ if (tab_label->scrolled_title) {
+ g_free(tab_label->scrolled_title);
+ }
+
+ G_OBJECT_CLASS(mq_tab_label_parent_class)->finalize(object);
+}
+
+static void
+get_property(GObject *object, guint property_id, GValue *value,
+ GParamSpec *param_spec)
+{
+ MqTabLabel *tab_label;
+
+ tab_label = MQ_TAB_LABEL(object);
+
+ switch (property_id) {
+ case PROP_TAB_PAGE:
+ g_value_set_object(value, tab_label->tab_page);
+ break;
+ case PROP_WEB_VIEW:
+ g_value_set_object(value, tab_label->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)
+{
+ MqTabLabel *tab_label;
+
+ tab_label = MQ_TAB_LABEL(object);
+
+ switch (property_id) {
+ case PROP_TAB_PAGE:
+ tab_label->tab_page = g_value_get_object(value);
+ break;
+ case PROP_WEB_VIEW:
+ set_web_view(tab_label, g_value_get_object(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+mq_tab_label_class_init(MqTabLabelClass *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_TAB_PAGE] = g_param_spec_object(
+ "tab-page",
+ "MqTabPage",
+ "The ancestral MqTabPage instance",
+ MQ_TYPE_TAB_PAGE,
+ 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_tab_label_init(MqTabLabel *tab_label)
+{
+ GtkWidget *close_button;
+ GtkWidget *box;
+
+ tab_label->title = "New tab";
+
+ /* Set up tab image. */
+ tab_label->image = gtk_image_new_from_icon_name("text-x-generic",
+ GTK_ICON_SIZE_BUTTON);
+
+ /* Set up tab label. */
+ tab_label->label = gtk_label_new(NULL);
+ gtk_label_set_ellipsize(GTK_LABEL(tab_label->label),
+ PANGO_ELLIPSIZE_END);
+ gtk_widget_set_hexpand(tab_label->label, TRUE);
+ gtk_widget_set_size_request(tab_label->label, 50, 1);
+
+ /* Set up close button. */
+ close_button = gtk_button_new_from_icon_name("window-close",
+ GTK_ICON_SIZE_BUTTON);
+ gtk_widget_set_tooltip_text(close_button, "Close tab");
+
+ /* Pack tab box. */
+ box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start(GTK_BOX(box), tab_label->image, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(box), tab_label->label, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(box), close_button, FALSE, FALSE, 0);
+ gtk_widget_show_all(box);
+
+ /* Set up event box. */
+ g_signal_connect(tab_label, "button-press-event",
+ G_CALLBACK(button_press_cb), tab_label);
+ gtk_event_box_set_visible_window(GTK_EVENT_BOX(tab_label), FALSE);
+ gtk_container_add(GTK_CONTAINER(tab_label), box);
+}
+
+GtkWidget *
+mq_tab_label_new(MqTabPage *tab_page, MqWebView *web_view)
+{
+ return g_object_new(MQ_TYPE_TAB_LABEL,
+ "tab-page", tab_page,
+ "web-view", web_view,
+ NULL);
+}
+
+void
+mq_tab_label_set_position(MqTabLabel *tab_label, guint position)
+{
+ tab_label->position = position;
+ update_label(tab_label);
+}
+
+void
+mq_tab_label_begin_scrolling(MqTabLabel *tab_label)
+{
+ PangoFontDescription *font_desc;
+
+ tab_label->scrolling = TRUE;
+ tab_label->scrolled_title = g_strdup_printf("%s ", tab_label->title);
+
+ font_desc = pango_font_description_new();
+ pango_font_description_set_family_static(font_desc, "monospace");
+ gtk_widget_override_font(tab_label->label, font_desc);
+}
+
+void
+mq_tab_label_end_scrolling(MqTabLabel *tab_label)
+{
+ tab_label->scrolling = FALSE;
+
+ gtk_widget_override_font(tab_label->label, NULL);
+
+ update_label(tab_label);
+}
+
+void
+mq_tab_label_scroll(MqTabLabel *tab_label)
+{
+ gchar c[5]; /* Up to 4 bytes for a UTF-8 character, plus NUL */
+ guint i;
+ guint j;
+
+ /* Save the first (possibly multibyte) character. */
+ c[0] = tab_label->scrolled_title[0];
+ for (i = 1; tab_label->scrolled_title[i] & 0x80; ++i) {
+ c[i] = tab_label->scrolled_title[i];
+ }
+ c[i] = '\0';
+
+ /* Shift all characters. */
+ for (j = 0; tab_label->scrolled_title[i]; ++i, ++j) {
+ tab_label->scrolled_title[j] = tab_label->scrolled_title[i];
+ }
+
+ /* Set the last (possibly multibyte) character. */
+ for (--j, i = 0; c[i]; ++i, ++j) {
+ tab_label->scrolled_title[j] = c[i];
+ }
+
+ update_label(tab_label);
+}
diff --git a/src/tab-label.h b/src/tab-label.h
new file mode 100644
index 0000000..df760b5
--- /dev/null
+++ b/src/tab-label.h
@@ -0,0 +1,69 @@
+/*
+ * Tab label
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_TAB_LABEL_H
+#define MQ_TAB_LABEL_H
+
+typedef struct _MqTabLabel MqTabLabel;
+typedef struct _MqTabLabelClass MqTabLabelClass;
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#include "tab-page.h"
+#include "web-view.h"
+
+G_BEGIN_DECLS
+
+#define MQ_TYPE_TAB_LABEL (mq_tab_label_get_type())
+#define MQ_TAB_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ MQ_TYPE_TAB_LABEL, MqTabLabel))
+#define MQ_IS_TAB_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ MQ_TYPE_TAB_LABEL))
+#define MQ_TAB_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+ MQ_TYPE_TAB_LABEL, MqTabLabelClass))
+#define MQ_IS_TAB_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ MQ_TYPE_TAB_LABEL))
+#define MQ_TAB_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ MQ_TYPE_TAB_LABEL, MqTabLabelClass))
+
+GType
+mq_tab_label_get_type(void);
+
+GtkWidget *
+mq_tab_label_new(MqTabPage *tab_page, MqWebView *web_view);
+
+void
+mq_tab_label_set_position(MqTabLabel *tab_label, guint position);
+
+void
+mq_tab_label_begin_scrolling(MqTabLabel *tab_label);
+
+void
+mq_tab_label_end_scrolling(MqTabLabel *tab_label);
+
+void
+mq_tab_label_scroll(MqTabLabel *tab_label);
+
+G_END_DECLS
+
+#endif /* MQ_TAB_LABEL_H */
diff --git a/src/tab-page.c b/src/tab-page.c
new file mode 100644
index 0000000..75f5283
--- /dev/null
+++ b/src/tab-page.c
@@ -0,0 +1,544 @@
+/*
+ * Tab page
+ *
+ * Copyright (C) 2017 Patrick McDermott
+ *
+ * This file is part of Marquee.
+ *
+ * Marquee is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Marquee is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Marquee. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tab-page.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#include "application.h"
+#include "tab-label.h"
+#include "toolbars/find-toolbar.h"
+#include "toolbars/navigation-toolbar.h"
+#include "web-view.h"
+#include "window.h"
+
+typedef enum {
+ CREATE_NONE,
+ CREATE_ROOT,
+ CREATE_SIBLING,
+ CREATE_CHILD,
+ N_CREATE_TYPES
+} CreateType;
+
+struct _MqTabPage {
+ GtkBox parent_instance;
+ CreateType create_type;
+ MqWindow *window;
+ gchar *uri;
+ MqTabPage *source;
+ MqTabPage *root;
+ MqTabPage *parent;
+ MqTabPage *prev;
+ MqTabPage *next;
+ MqTabPage *first_child;
+ MqTabPage *last_child;
+ guint position;
+ guint tree_size;
+ MqApplication *application;
+ GtkWidget *container;
+ GtkWidget *label;
+ const gchar *title;
+ WebKitWebView *web_view;
+};
+
+enum {
+ PROP_CREATE_TYPE = 1,
+ PROP_WINDOW,
+ PROP_URI,
+ PROP_SOURCE,
+ N_PROPERTIES
+};
+
+static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,};
+
+struct _MqTabPageClass {
+ GtkBoxClass parent_class;
+};
+
+G_DEFINE_TYPE(MqTabPage, mq_tab_page, GTK_TYPE_BOX)
+
+static void
+update_tree_sizes(MqTabPage *node, guint step)
+{
+ if (node) {
+ node->tree_size += step;
+ update_tree_sizes(node->parent, step);
+ }
+}
+
+static void
+append_child(MqTabPage *new_node)
+{
+ MqTabPage *parent;
+
+ parent = new_node->source;
+
+ new_node->root = parent->root;
+ new_node->parent = parent;
+ new_node->next = NULL;
+ new_node->prev = parent->last_child; /* May be NULL */
+ new_node->first_child = new_node->last_child = NULL;
+ new_node->tree_size = 0; /* Will be updated */
+ if (parent->last_child) {
+ new_node->position = parent->last_child->position;
+ parent->last_child->next = new_node;
+ } else {
+ new_node->position = parent->position;
+ parent->first_child = new_node;
+ }
+ parent->last_child = new_node;
+ mq_tab_page_update_positions(new_node, 1);
+ update_tree_sizes(new_node, 1);
+}
+
+static void
+append_sibling(MqTabPage *new_node)
+{
+ MqTabPage *prev_sibling;
+
+ prev_sibling = new_node->source;
+
+ new_node->root = prev_sibling->root;
+ new_node->parent = prev_sibling->parent;
+ new_node->prev = prev_sibling;
+ new_node->next = prev_sibling->next; /* May be NULL */
+ new_node->first_child = new_node->last_child = NULL;
+ new_node->position = prev_sibling->position; /* Will be updated */
+ new_node->tree_size = 0; /* Will be updated */
+ if (prev_sibling->next) {
+ prev_sibling->next->prev = new_node;
+ }
+ prev_sibling->next = new_node;
+ mq_tab_page_update_positions(new_node, 1);
+ update_tree_sizes(new_node, 1);
+}
+
+static void
+title_cb(WebKitWebView *web_view, GParamSpec G_GNUC_UNUSED *param_spec,
+ MqTabPage *tab_page)
+{
+ tab_page->title = webkit_web_view_get_title(web_view);
+ mq_window_update_tab_title(tab_page->window, tab_page->position,
+ tab_page->title);
+}
+
+static void
+init_root(MqTabPage *tab_page)
+{
+ tab_page->root = tab_page;
+ tab_page->position = 0;
+}
+
+static void
+init_non_root(MqTabPage *tab_page)
+{
+ GtkWidget *navigation_toolbar;
+ GtkWidget *find_toolbar;
+
+ tab_page->window = tab_page->source->window;
+ tab_page->application = mq_window_get_application(tab_page->window);
+
+ tab_page->web_view = WEBKIT_WEB_VIEW(mq_web_view_new(tab_page,
+ tab_page->uri));
+ g_signal_connect(tab_page->web_view, "notify::title",
+ G_CALLBACK(title_cb), tab_page);
+
+ tab_page->label = mq_tab_label_new(tab_page,
+ MQ_WEB_VIEW(tab_page->web_view));
+
+ find_toolbar = mq_find_toolbar_new(MQ_WEB_VIEW(tab_page->web_view));
+
+ navigation_toolbar = mq_navigation_toolbar_new(
+ mq_application_get_config(
+ mq_window_get_application(tab_page->window)),
+ tab_page, MQ_FIND_TOOLBAR(find_toolbar),
+ MQ_WEB_VIEW(tab_page->web_view), tab_page->uri);
+
+ gtk_box_pack_start(GTK_BOX(tab_page),
+ navigation_toolbar, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(tab_page),
+ find_toolbar, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(tab_page),
+ GTK_WIDGET(tab_page->web_view), TRUE, TRUE, 0);
+}
+
+static void
+constructed(GObject *object)
+{
+ MqTabPage *tab_page;
+
+ if (G_OBJECT_CLASS(mq_tab_page_parent_class)->constructed) {
+ G_OBJECT_CLASS(mq_tab_page_parent_class)->constructed(object);
+ }
+
+ tab_page = MQ_TAB_PAGE(object);
+
+ switch (tab_page->create_type) {
+ case CREATE_ROOT:
+ init_root(tab_page);
+ break;
+ case CREATE_SIBLING:
+ init_non_root(tab_page);
+ append_sibling(tab_page);
+ mq_window_insert_tab(tab_page->window,
+ GTK_WIDGET(tab_page), tab_page->label,
+ tab_page->position);
+ break;
+ case CREATE_CHILD:
+ init_non_root(tab_page);
+ append_child(tab_page);
+ mq_window_insert_tab(tab_page->window,
+ GTK_WIDGET(tab_page), tab_page->label,
+ tab_page->position);
+ break;
+ case CREATE_NONE:
+ case N_CREATE_TYPES:
+ g_assert_not_reached();
+ break;
+ }
+}
+
+static void
+finalize(GObject *object)
+{
+ MqTabPage *tab_page;
+
+ tab_page = MQ_TAB_PAGE(object);
+
+ g_free(tab_page->uri);
+
+ G_OBJECT_CLASS(mq_tab_page_parent_class)->finalize(object);
+}
+
+static void
+get_property(GObject *object, guint property_id, GValue *value,
+ GParamSpec *param_spec)
+{
+ MqTabPage *tab_page;
+
+ tab_page = MQ_TAB_PAGE(object);
+
+ switch (property_id) {
+ case PROP_CREATE_TYPE:
+ g_value_set_uint(value, tab_page->create_type);
+ break;
+ case PROP_WINDOW:
+ g_value_set_pointer(value, tab_page->window);
+ break;
+ case PROP_URI:
+ g_value_set_string(value, tab_page->uri);
+ break;
+ case PROP_SOURCE:
+ g_value_set_object(value, tab_page->source);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+set_property(GObject *object, guint property_id, const GValue *value,
+ GParamSpec *param_spec)
+{
+ MqTabPage *tab_page;
+
+ tab_page = MQ_TAB_PAGE(object);
+
+ switch (property_id) {
+ case PROP_CREATE_TYPE:
+ tab_page->create_type = g_value_get_uint(value);
+ break;
+ case PROP_WINDOW:
+ tab_page->window = g_value_get_pointer(value);
+ break;
+ case PROP_URI:
+ g_free(tab_page->uri);
+ tab_page->uri = g_strdup(g_value_get_string(value));
+ break;
+ case PROP_SOURCE:
+ tab_page->source = g_value_get_object(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+mq_tab_page_class_init(MqTabPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->constructed = constructed;
+ object_class->finalize = finalize;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ obj_properties[PROP_CREATE_TYPE] = g_param_spec_uint(
+ "create-type",
+ "Type",
+ "The type of tab page to create (root, sibling, or child)",
+ CREATE_NONE, N_CREATE_TYPES, CREATE_NONE,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ obj_properties[PROP_WINDOW] = g_param_spec_pointer(
+ "window",
+ "MqWindow",
+ "The parent MqWindow instance",
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ obj_properties[PROP_URI] = g_param_spec_string(
+ "uri",
+ "URI",
+ "The URI to load",
+ "",
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ obj_properties[PROP_SOURCE] = g_param_spec_object(
+ "source",
+ "Source MqTabPage",
+ "The source (previous sibling or parent) MqTabPage instance",
+ MQ_TYPE_TAB_PAGE,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_properties(object_class, N_PROPERTIES,
+ obj_properties);
+}
+
+static void
+mq_tab_page_init(MqTabPage *tab_page)
+{
+ tab_page->parent = NULL;
+ tab_page->prev = NULL;
+ tab_page->next = NULL;
+ tab_page->first_child = tab_page->last_child = NULL;
+ tab_page->tree_size = 1;
+ tab_page->title = "New tab";
+}
+
+MqTabPage *
+mq_tab_page_new(const gchar *uri, MqTabPage *source)
+{
+ return g_object_new(MQ_TYPE_TAB_PAGE,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "spacing", 0,
+ "create-type", CREATE_SIBLING,
+ "uri", uri,
+ "source", source,
+ NULL);
+}
+
+MqTabPage *
+mq_tab_page_new_relative(const gchar *uri, MqTabPage *source)
+{
+ return g_object_new(MQ_TYPE_TAB_PAGE,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "spacing", 0,
+ "create-type", CREATE_CHILD,
+ "uri", uri,
+ "source", source,
+ NULL);
+}
+
+MqTabPage *
+mq_tab_page_new_root(MqWindow *window)
+{
+ return g_object_new(MQ_TYPE_TAB_PAGE,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "spacing", 0,
+ "create-type", CREATE_ROOT,
+ "window", window, /* TODO: Use gtk_widget_get_parent()? */
+ NULL);
+}
+
+void
+mq_tab_page_quit(MqTabPage *tab_page)
+{
+ mq_window_quit(tab_page->window);
+}
+
+MqApplication *
+mq_tab_page_get_application(MqTabPage *tab_page)
+{
+ return tab_page->application;
+}
+
+MqWindow *
+mq_tab_page_get_window(MqTabPage *tab_page)
+{
+ return tab_page->window;
+}
+
+void
+mq_tab_page_update_positions(MqTabPage *node, gint step)
+{
+ if (node) {
+ node->position += step;
+ g_assert(node->label);
+ mq_tab_label_set_position(MQ_TAB_LABEL(node->label),
+ node->position);
+ if (node->next) {
+ mq_tab_page_update_positions(node->next, step);
+ } else if (node->parent && node->parent->next) {
+ mq_tab_page_update_positions(node->parent->next, step);
+ }
+ }
+}
+
+void
+mq_tab_page_update_position(MqTabPage *tab_page, guint position)
+{
+ tab_page->position = position;
+ mq_tab_label_set_position(MQ_TAB_LABEL(tab_page->label), position);
+}
+
+guint
+mq_tab_page_get_position(MqTabPage *tab_page)
+{
+ return tab_page->position;
+}
+
+guint
+mq_tab_page_get_tree_size(MqTabPage *tab_page)
+{
+ return tab_page->tree_size;
+}
+
+const gchar *
+mq_tab_page_get_title(MqTabPage *tab_page)
+{
+ return tab_page->title;
+}
+
+MqTabPage *
+mq_tab_page_seek(MqTabPage *node, guint position)
+{
+ /* Skip forward to the containing subtree. */
+ while (node && node->position + node->tree_size <= position) {
+ node = node->next;
+ }
+
+ /* Check whether we've gone past the end of the tree. */
+ if (!node) {
+ return NULL;
+ }
+
+ /* Check whether the sibling we've reached is the node we want. */
+ if (node->position == position) {
+ return node;
+ }
+
+ /* Recurse down the subtree. */
+ return mq_tab_page_seek(node->first_child, position);
+}
+
+static void
+foreach_tab(MqTabPage *node, void (*cb)(MqTabPage *node, va_list ap),
+ va_list ap)
+{
+ va_list aq;
+
+ for (; node; node = node->next) {
+ va_copy(ap, aq);
+ cb(node, aq);
+ va_end(aq);
+
+ va_copy(ap, aq);
+ foreach_tab(node->first_child, cb, aq);
+ va_end(aq);
+ }
+}
+
+void
+mq_tab_page_foreach(MqTabPage *node, void (*cb)(MqTabPage *node, va_list ap),
+ ...)
+{
+ va_list ap;
+
+ va_start(ap, cb);
+ foreach_tab(node->root->first_child, cb, ap);
+ va_end(ap);
+}
+
+MqTabPage *
+mq_tab_page_root(MqTabPage *node)
+{
+ return node ? node->root : NULL;
+}
+
+MqTabPage *
+mq_tab_page_previous(MqTabPage *node)
+{
+ return node ? node->prev : NULL;
+}
+
+MqTabPage *
+mq_tab_page_next(MqTabPage *node)
+{
+ return node ? node->next : NULL;
+}
+
+MqTabPage *
+mq_tab_page_first_child(MqTabPage *node)
+{
+ return node ? node->first_child : NULL;
+}
+
+void
+mq_tab_page_scroll_tab_labels(MqTabPage *node)
+{
+ for (; node; node = node->next) {
+ if (node->label) {
+ mq_tab_label_scroll(MQ_TAB_LABEL(node->label));
+ }
+ mq_tab_page_scroll_tab_labels(node->first_child);
+ }
+}
+
+void
+mq_tab_page_begin_scrolling_tab_labels(MqTabPage *node)
+{
+ for (; node; node = node->next) {
+ if (node->label) {
+ mq_tab_label_begin_scrolling(MQ_TAB_LABEL(node->label));
+ }
+ mq_tab_page_begin_scrolling_tab_labels(node->first_child);
+ }
+}
+
+void
+mq_tab_page_end_scrolling_tab_labels(MqTabPage *node)
+{
+ for (; node; node = node->next) {
+ if (node->label) {
+ mq_tab_label_end_scrolling(MQ_TAB_LABEL(node->label));
+ }
+ mq_tab_page_end_scrolling_tab_labels(node->first_child);
+ }
+}
diff --git a/src/tab-page.h b/src/tab-page.h
new file mode 100644
index 0000000..85e332d
--- /dev/null
+++ b/src/tab-page.h
@@ -0,0 +1,117 @@
+/*
+ * Tab page
+ *
+ * Copyright (C) 2017 Patrick McDermott
+ *
+ * This file is part of Marquee.
+ *
+ * Marquee is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Marquee is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Marquee. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_TAB_PAGE_H
+#define MQ_TAB_PAGE_H
+
+typedef struct _MqTabPage MqTabPage;
+typedef struct _MqTabPageClass MqTabPageClass;
+
+#include <stdarg.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#include "application.h"
+#include "window.h"
+
+G_BEGIN_DECLS
+
+#define MQ_TYPE_TAB_PAGE (mq_tab_page_get_type())
+#define MQ_TAB_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ MQ_TYPE_TAB_PAGE, MqTabPage))
+#define MQ_IS_TAB_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ MQ_TYPE_TAB_PAGE))
+#define MQ_TAB_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+ MQ_TYPE_TAB_PAGE, MqTabPageClass))
+#define MQ_IS_TAB_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ MQ_TYPE_TAB_PAGE))
+#define MQ_TAB_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ MQ_TYPE_TAB_PAGE, MqTabPageClass))
+
+GType
+mq_tab_page_get_type(void);
+
+MqTabPage *
+mq_tab_page_new(const gchar *uri, MqTabPage *source);
+
+MqTabPage *
+mq_tab_page_new_relative(const gchar *uri, MqTabPage *source);
+
+MqTabPage *
+mq_tab_page_new_root(MqWindow *window);
+
+void
+mq_tab_page_quit(MqTabPage *tab_page);
+
+MqApplication *
+mq_tab_page_get_application(MqTabPage *tab_page);
+
+MqWindow *
+mq_tab_page_get_window(MqTabPage *tab_page);
+
+void
+mq_tab_page_update_positions(MqTabPage *node, gint step);
+
+void
+mq_tab_page_update_position(MqTabPage *tab_page, guint position);
+
+guint
+mq_tab_page_get_position(MqTabPage *tab_page);
+
+guint
+mq_tab_page_get_tree_size(MqTabPage *tab_page);
+
+const gchar *
+mq_tab_page_get_title(MqTabPage *tab_page);
+
+MqTabPage *
+mq_tab_page_seek(MqTabPage *node, guint position);
+
+void
+mq_tab_page_foreach(MqTabPage *node, void (*cb)(MqTabPage *node, va_list ap),
+ ...);
+
+MqTabPage *
+mq_tab_page_root(MqTabPage *node);
+
+MqTabPage *
+mq_tab_page_previous(MqTabPage *node);
+
+MqTabPage *
+mq_tab_page_next(MqTabPage *node);
+
+MqTabPage *
+mq_tab_page_first_child(MqTabPage *node);
+
+void
+mq_tab_page_scroll_tab_labels(MqTabPage *node);
+
+void
+mq_tab_page_begin_scrolling_tab_labels(MqTabPage *node);
+
+void
+mq_tab_page_end_scrolling_tab_labels(MqTabPage *node);
+
+G_END_DECLS
+
+#endif /* MQ_TAB_PAGE_H */
diff --git a/src/tab.c b/src/tab.c
deleted file mode 100644
index 3e70fe0..0000000
--- a/src/tab.c
+++ /dev/null
@@ -1,648 +0,0 @@
-/*
- * Tab
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#include <stdlib.h>
-
-#include <gtk/gtk.h>
-
-#include "tab.h"
-#include "tab-chrome.h"
-#include "tab-body.h"
-
-static void
-foreach_tab(MqTab *node, void (*cb)(MqTab *node))
-{
- for (; node; node = node->next) {
- cb(node);
- foreach_tab(node->first_child, cb);
- }
-}
-
-static void
-update_tab_image(MqTab *tab, GdkPixbuf *favicon)
-{
- if (favicon) {
- gtk_image_set_from_pixbuf(GTK_IMAGE(tab->tab_image), favicon);
- } else {
- gtk_image_set_from_icon_name(GTK_IMAGE(tab->tab_image),
- "text-x-generic", GTK_ICON_SIZE_BUTTON);
- }
-}
-
-static void
-update_tab_label(MqTab *tab)
-{
- const gchar *title;
- gchar *label;
-
- title = tab->scrolling ? tab->scrolled_title : tab->title;
- label = g_strdup_printf("%d. %s", tab->position, title);
- gtk_label_set_text(GTK_LABEL(tab->tab_label), label);
- gtk_widget_set_tooltip_text(tab->tab, label);
- g_free(label);
-}
-
-static void
-update_positions(MqTab *node, gint step)
-{
- if (node) {
- node->position += step;
- update_tab_label(node);
- if (node->next) {
- update_positions(node->next, step);
- } else if (node->parent && node->parent->next) {
- update_positions(node->parent->next, step);
- }
- }
-}
-
-static void
-update_tree_sizes(MqTab *node, guint step)
-{
- if (node) {
- node->tree_size += step;
- update_tree_sizes(node->parent, step);
- }
-}
-
-static void
-append_child(MqTab *new_node, MqTab *parent)
-{
- new_node->root = parent->root;
- new_node->parent = parent;
- new_node->next = NULL;
- new_node->prev = parent->last_child; /* May be NULL */
- new_node->first_child = new_node->last_child = NULL;
- new_node->tree_size = 0; /* Will be updated */
- if (parent->last_child) {
- new_node->position = parent->last_child->position;
- parent->last_child->next = new_node;
- } else {
- new_node->position = parent->position;
- parent->first_child = new_node;
- }
- parent->last_child = new_node;
- update_positions(new_node, 1);
- update_tree_sizes(new_node, 1);
-}
-
-static void
-append_sibling(MqTab *new_node, MqTab *prev_sibling)
-{
- new_node->root = prev_sibling->root;
- new_node->parent = prev_sibling->parent;
- new_node->prev = prev_sibling;
- new_node->next = prev_sibling->next; /* May be NULL */
- new_node->first_child = new_node->last_child = NULL;
- new_node->position = prev_sibling->position; /* Will be updated */
- new_node->tree_size = 0; /* Will be updated */
- if (prev_sibling->next) {
- prev_sibling->next->prev = new_node;
- }
- prev_sibling->next = new_node;
- update_positions(new_node, 1);
- update_tree_sizes(new_node, 1);
-}
-
-static void
-reload_tab_clicked_cb(GtkWidget G_GNUC_UNUSED *button, MqTab *tab)
-{
- webkit_web_view_reload(tab->web_view);
- gtk_widget_hide(tab->popover);
-}
-
-static void
-new_tab_clicked_cb(GtkWidget G_GNUC_UNUSED *button, MqTab *tab)
-{
- mq_tab_new(NULL, tab);
- gtk_widget_hide(tab->popover);
-}
-
-static void
-new_window_clicked_cb(GtkWidget G_GNUC_UNUSED *button, MqTab *tab)
-{
- mq_application_add_window(tab->application, NULL);
- gtk_widget_hide(tab->popover);
-}
-
-static void
-tab_list_button_toggled_cb(GtkToggleButton *toggle_button, GtkWidget *tab_list)
-{
- if (gtk_toggle_button_get_active(toggle_button)) {
- gtk_widget_show(tab_list);
- } else {
- gtk_widget_hide(tab_list);
- }
-}
-
-static void
-create_tree_model_recurse(MqTab *node, GtkTreeStore *tree_store,
- GtkTreeIter *parent_tree_iter)
-{
- GtkTreeIter tree_iter;
-
- for (; node; node = node->next) {
- gtk_tree_store_append(tree_store, &tree_iter, parent_tree_iter);
- gtk_tree_store_set(tree_store, &tree_iter, 0, node->title, -1);
- create_tree_model_recurse(node->first_child, tree_store,
- &tree_iter);
- }
-}
-
-static GtkTreeModel *
-create_tree_model(MqTab *tab)
-{
- GtkTreeStore *tree_store;
-
- tree_store = gtk_tree_store_new(1, G_TYPE_STRING);
-
- create_tree_model_recurse(tab->root->first_child, tree_store, NULL);
-
- return GTK_TREE_MODEL(tree_store);
-}
-
-static void
-row_activated_cb(GtkTreeView G_GNUC_UNUSED *tree_view, GtkTreePath *tree_path,
- GtkTreeViewColumn G_GNUC_UNUSED *tree_view_column, MqTab *tab)
-{
- gint *indices;
- gint depth;
-
- indices = gtk_tree_path_get_indices_with_depth(tree_path, &depth);
- g_assert(depth == 1);
- mq_window_set_current_tab(tab->window, indices[0] + 1);
- gtk_widget_hide(tab->popover);
-}
-
-static GtkWidget *
-create_tab_list(MqTab *tab)
-{
- GtkWidget *tree_view;
- GtkTreeSelection *tree_selection;
- GtkCellRenderer *cell_renderer;
- GtkWidget *scrolled_window;
-
- tree_view = gtk_tree_view_new_with_model(create_tree_model(tab));
- tree_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
- gtk_tree_selection_set_mode(tree_selection, GTK_SELECTION_BROWSE);
- gtk_tree_selection_select_path(tree_selection,
- gtk_tree_path_new_from_indices(
- mq_window_get_current_tab(tab->window) - 1, -1));
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE);
- gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(tree_view),
- TRUE);
- gtk_tree_view_expand_all(GTK_TREE_VIEW(tree_view));
- gtk_tree_view_set_reorderable(GTK_TREE_VIEW(tree_view), TRUE);
- gtk_tree_view_set_enable_tree_lines(GTK_TREE_VIEW(tree_view), TRUE);
- g_signal_connect(tree_view, "row-activated",
- G_CALLBACK(row_activated_cb), tab);
-
- cell_renderer = gtk_cell_renderer_text_new();
- gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree_view),
- -1, NULL, cell_renderer, "text", 0, NULL);
-
- scrolled_window = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_min_content_width(
- GTK_SCROLLED_WINDOW(scrolled_window), 400);
- gtk_scrolled_window_set_min_content_height(
- GTK_SCROLLED_WINDOW(scrolled_window), 200);
- gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view);
-
- return scrolled_window;
-}
-
-#define BUTTON_ROWS 2
-#define BUTTON_COLS 4
-#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_widget_set_can_focus(buttons[Y * BUTTON_COLS + X], FALSE); \
- gtk_grid_attach(GTK_GRID(button_grid), \
- buttons[Y * BUTTON_COLS + X], X, Y, 1, 1); \
- } while (0)
-#define NEW_TOGGLE(Y, X, ICON, TOOLTIP) \
- do { \
- buttons[Y * BUTTON_COLS + X] = gtk_toggle_button_new(); \
- gtk_button_set_image(GTK_BUTTON(buttons[Y * BUTTON_COLS + X]), \
- gtk_image_new_from_icon_name(ICON, \
- GTK_ICON_SIZE_BUTTON)); \
- gtk_widget_set_tooltip_text(buttons[Y * BUTTON_COLS + X], \
- TOOLTIP); \
- gtk_widget_set_can_focus(buttons[Y * BUTTON_COLS + X], FALSE); \
- gtk_grid_attach(GTK_GRID(button_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", CB, tab)
-
-static void
-create_tab_popover(GtkWidget *widget, MqTab *tab)
-{
- GtkWidget *button_grid;
- GtkWidget *buttons[BUTTON_ROWS * BUTTON_COLS];
- GtkWidget *tab_list;
- GtkWidget *tab_list_scrolled_window;
- GtkWidget *box;
-
- /* Set up button grid. */
- button_grid = gtk_grid_new();
- gtk_widget_set_halign(button_grid, GTK_ALIGN_CENTER);
-
- /* Set up buttons. */
- NEW_BUTTON(0, 0, "view-refresh", "Reload tab");
- NEW_BUTTON(0, 1, "edit-copy", "Duplicate tab");
- NEW_BUTTON(0, 2, "window-new", "Move tab to new window");
- NEW_BUTTON(0, 3, "window-close", "Close tab");
- NEW_BUTTON(1, 0, "tab-new-symbolic", "New tab");
- NEW_BUTTON(1, 1, "window-new", "New window");
- NEW_BUTTON(1, 2, "edit-undo", "Undo close tab");
- NEW_TOGGLE(1, 3, "view-list-symbolic", "Tab list...");
-
- CLICKED_CB(0, 0, G_CALLBACK(reload_tab_clicked_cb));
- CLICKED_CB(1, 0, G_CALLBACK(new_tab_clicked_cb));
- CLICKED_CB(1, 1, G_CALLBACK(new_window_clicked_cb));
-
- /* Set up the tab list. */
- tab_list = create_tab_list(tab);
-
- /* Set up the tab list scrolled window.
- *
- * The following GtkScrolledWindow widget has a hardcoded minimum size,
- * because there seems to be (in GTK+ versions before 3.22) no way to
- * set the natural size of GtkScrolledWindow and its GtkViewport.
- *
- * See tab-chrome.c for more information. */
- tab_list_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_min_content_width(
- GTK_SCROLLED_WINDOW(tab_list_scrolled_window), 400);
- gtk_scrolled_window_set_min_content_height(
- GTK_SCROLLED_WINDOW(tab_list_scrolled_window), 200);
- gtk_container_add(GTK_CONTAINER(tab_list_scrolled_window), tab_list);
-
- /* Add tab list toggle button handler. */
- g_signal_connect(buttons[1 * BUTTON_COLS + 3], "toggled",
- G_CALLBACK(tab_list_button_toggled_cb),
- tab_list_scrolled_window);
-
- /* Set up the button rows box. */
- box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
- gtk_box_pack_start(GTK_BOX(box), button_grid,
- TRUE, FALSE, 0);
- gtk_box_pack_start(GTK_BOX(box), tab_list_scrolled_window,
- TRUE, FALSE, 0);
-
- /* Set up the popover. */
- tab->popover = gtk_popover_new(widget);
- gtk_container_add(GTK_CONTAINER(tab->popover), box);
-
- /* NB: gtk_popover_popup() is new in GTK+ 3.22. */
- gtk_widget_show_all(tab->popover);
- gtk_widget_hide(tab_list_scrolled_window);
-}
-
-#undef BUTTON_ROWS
-#undef BUTTON_COLS
-#undef NEW_BUTTON
-#undef NEW_TOGGLE
-#undef CLICKED_CB
-
-static gboolean
-tab_label_button_press_cb(GtkWidget *widget, GdkEvent *event, MqTab *tab)
-{
- /* Make sure this is a mouse button press event. */
- if (event->type != GDK_BUTTON_PRESS) {
- return FALSE;
- }
-
- /* Create a popover menu on right click. */
- if (event->button.button == 3) {
- create_tab_popover(widget, tab);
- }
-
- return FALSE;
-}
-
-static void
-mq_tab_populate_tab(MqTab *tab)
-{
- GtkWidget *close_button;
- GtkWidget *box;
-
- tab->title = "New tab";
-
- /* Set up tab image. */
- tab->tab_image = gtk_image_new_from_icon_name("text-x-generic",
- GTK_ICON_SIZE_BUTTON);
-
- /* Set up tab label. */
- tab->tab_label = gtk_label_new(NULL);
- gtk_label_set_ellipsize(GTK_LABEL(tab->tab_label),
- PANGO_ELLIPSIZE_END);
- gtk_widget_set_hexpand(tab->tab_label, TRUE);
- gtk_widget_set_size_request(tab->tab_label, 50, 1);
-
- /* Set up close button. */
- close_button = gtk_button_new_from_icon_name("window-close",
- GTK_ICON_SIZE_BUTTON);
- gtk_widget_set_tooltip_text(close_button, "Close tab");
-
- /* Pack tab box. */
- box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
- gtk_box_pack_start(GTK_BOX(box), tab->tab_image, FALSE, FALSE, 0);
- gtk_box_pack_start(GTK_BOX(box), tab->tab_label, TRUE, TRUE, 0);
- gtk_box_pack_start(GTK_BOX(box), close_button, FALSE, FALSE, 0);
- gtk_widget_show_all(box);
-
- /* Set up event box. */
- tab->tab = gtk_event_box_new();
- g_signal_connect(tab->tab, "button-press-event",
- G_CALLBACK(tab_label_button_press_cb), tab);
- gtk_event_box_set_visible_window(GTK_EVENT_BOX(tab->tab),
- FALSE);
- gtk_container_add(GTK_CONTAINER(tab->tab), box);
-}
-
-static void
-favicon_cb(WebKitWebView G_GNUC_UNUSED *web_view,
- GParamSpec G_GNUC_UNUSED *paramspec, MqTab *tab)
-{
- cairo_surface_t *surface;
- GdkPixbuf *pixbuf;
- GdkPixbuf *scaled_pixbuf;
-
- surface = webkit_web_view_get_favicon(tab->web_view);
- scaled_pixbuf = NULL;
- if (surface) {
- pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0,
- cairo_image_surface_get_width(surface),
- cairo_image_surface_get_height(surface));
- if (pixbuf) {
- scaled_pixbuf = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
- GDK_INTERP_BILINEAR);
- g_object_unref(pixbuf);
- }
- }
-
- update_tab_image(tab, scaled_pixbuf);
-}
-
-static void
-title_cb(WebKitWebView G_GNUC_UNUSED *web_view,
- GParamSpec G_GNUC_UNUSED *paramspec, MqTab *tab)
-{
- tab->title = webkit_web_view_get_title(tab->web_view);
- if (tab->scrolling) {
- tab->scrolled_title = g_strdup_printf("%s ", tab->title);
- }
- update_tab_label(tab);
- mq_window_update_tab_title(tab->window, tab->position, tab->title);
-}
-
-static MqTab *
-init_non_root(const gchar *uri, MqTab *source)
-{
- MqTab *tab;
-
- tab = malloc(sizeof(*tab));
- tab->parent = NULL;
- tab->prev = NULL;
- tab->next = NULL;
- tab->first_child = tab->last_child = NULL;
- tab->tree_size = 1;
-
- tab->window = source->window;
- tab->application = mq_window_get_application(tab->window);
-
- mq_tab_populate_tab(tab);
-
- tab->chrome = mq_tab_chrome_new(tab, uri);
- tab->body = mq_tab_body_new(tab, uri);
- tab->web_view = mq_tab_body_get_web_view(tab->body);
- g_signal_connect(tab->web_view, "notify::favicon",
- G_CALLBACK(favicon_cb), tab);
- g_signal_connect(tab->web_view, "notify::title",
- G_CALLBACK(title_cb), tab);
- mq_tab_chrome_set_web_view(tab->chrome, tab->web_view);
-
- tab->container = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
- gtk_box_pack_start(GTK_BOX(tab->container),
- mq_tab_chrome_get_container(tab->chrome), FALSE, FALSE, 0);
- gtk_box_pack_start(GTK_BOX(tab->container),
- mq_tab_body_get_container(tab->body), TRUE, TRUE, 0);
-
- return tab;
-}
-
-static void
-scroll_tab_label(MqTab *tab)
-{
- gchar c[5]; /* Up to 4 bytes for a UTF-8 character, plus NUL */
- guint i;
- guint j;
-
- /* Save the first (possibly multibyte) character. */
- c[0] = tab->scrolled_title[0];
- for (i = 1; tab->scrolled_title[i] & 0x80; ++i) {
- c[i] = tab->scrolled_title[i];
- }
- c[i] = '\0';
-
- /* Shift all characters. */
- for (j = 0; tab->scrolled_title[i]; ++i, ++j) {
- tab->scrolled_title[j] = tab->scrolled_title[i];
- }
-
- /* Set the last (possibly multibyte) character. */
- for (--j, i = 0; c[i]; ++i, ++j) {
- tab->scrolled_title[j] = c[i];
- }
-
- update_tab_label(tab);
-}
-
-static void
-begin_scrolling_tab_label(MqTab *tab)
-{
- PangoFontDescription *font_desc;
-
- tab->scrolling = TRUE;
- tab->scrolled_title = g_strdup_printf("%s ", tab->title);
-
- font_desc = pango_font_description_new();
- pango_font_description_set_family_static(font_desc, "monospace");
- gtk_widget_override_font(tab->tab_label, font_desc);
-}
-
-static void
-end_scrolling_tab_label(MqTab *tab)
-{
- tab->scrolling = FALSE;
-
- gtk_widget_override_font(tab->tab_label, NULL);
-
- update_tab_label(tab);
-}
-
-MqTab *
-mq_tab_new(const gchar *uri, MqTab *source)
-{
- MqTab *tab;
-
- tab = init_non_root(uri, source);
-
- if (mq_application_marquee_mode_on(tab->application)) {
- begin_scrolling_tab_label(tab);
- } else {
- tab->scrolling = FALSE;
- }
-
- append_sibling(tab, source);
-
- mq_window_insert_tab(tab->window, tab->container, tab->tab,
- tab->position);
-
- return tab;
-}
-
-MqTab *
-mq_tab_new_relative(const gchar *uri, MqTab *source)
-{
- MqTab *tab;
-
- tab = init_non_root(uri, source);
-
- if (mq_application_marquee_mode_on(tab->application)) {
- begin_scrolling_tab_label(tab);
- } else {
- tab->scrolling = FALSE;
- }
-
- append_child(tab, source);
-
- mq_window_insert_tab(tab->window, tab->container, tab->tab,
- tab->position);
-
- return tab;
-}
-
-MqTab *
-mq_tab_new_root(MqWindow *window)
-{
- MqTab *tab;
-
- tab = malloc(sizeof(*tab));
- tab->root = tab;
- tab->parent = NULL;
- tab->prev = NULL;
- tab->next = NULL;
- tab->first_child = tab->last_child = NULL;
- tab->position = 0;
- tab->tree_size = 1;
- tab->window = window;
-
- return tab;
-}
-
-void
-mq_tab_quit(MqTab *tab)
-{
- mq_window_quit(tab->window);
-}
-
-MqApplication *
-mq_tab_get_application(MqTab *tab)
-{
- return tab->application;
-}
-
-MqWindow *
-mq_tab_get_window(MqTab *tab)
-{
- return tab->window;
-}
-
-void
-mq_tab_update_position(MqTab *tab, guint position)
-{
- tab->position = position;
- update_tab_label(tab);
-}
-
-guint
-mq_tab_get_position(MqTab *tab)
-{
- return tab->position;
-}
-
-guint
-mq_tab_get_tree_size(MqTab *tab)
-{
- return tab->tree_size;
-}
-
-const gchar *
-mq_tab_get_title(MqTab *tab)
-{
- return tab->title;
-}
-
-MqTab *
-mq_tab_seek(MqTab *node, guint position)
-{
- /* Skip forward to the containing subtree. */
- while (node && node->position + node->tree_size <= position) {
- node = node->next;
- }
-
- /* Check whether we've gone past the end of the tree. */
- if (!node) {
- return NULL;
- }
-
- /* Check whether the sibling we've reached is the node we want. */
- if (node->position == position) {
- return node;
- }
-
- /* Recurse down the subtree. */
- return mq_tab_seek(node->first_child, position);
-}
-
-void
-mq_tab_scroll_tab_labels(MqTab *root)
-{
- foreach_tab(root->first_child, scroll_tab_label);
-}
-
-void
-mq_tab_begin_scrolling_tab_labels(MqTab *root)
-{
- foreach_tab(root->first_child, begin_scrolling_tab_label);
-}
-
-void
-mq_tab_end_scrolling_tab_labels(MqTab *root)
-{
- foreach_tab(root->first_child, end_scrolling_tab_label);
-}
diff --git a/src/tab.h b/src/tab.h
deleted file mode 100644
index 7d61bbd..0000000
--- a/src/tab.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Tab
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-typedef struct MqTab MqTab;
-
-#ifndef MQ_TAB_H
-#define MQ_TAB_H
-
-#include <gtk/gtk.h>
-
-#include "application.h"
-#include "window.h"
-#include "tab-chrome.h"
-#include "tab-body.h"
-
-struct MqTab {
- MqTab *root;
- MqTab *parent;
- MqTab *prev;
- MqTab *next;
- MqTab *first_child;
- MqTab *last_child;
- guint position;
- guint tree_size;
- MqApplication *application;
- MqWindow *window;
- GtkWidget *container;
- MqTabChrome *chrome;
- MqTabBody *body;
- GtkWidget *tab;
- GtkWidget *tab_image;
- GtkWidget *tab_label;
- const gchar *title;
- WebKitWebView *web_view;
- GtkWidget *popover;
- gboolean scrolling;
- gchar *scrolled_title;
-};
-
-MqTab *
-mq_tab_new(const gchar *uri, MqTab *source);
-
-MqTab *
-mq_tab_new_relative(const gchar *uri, MqTab *source);
-
-MqTab *
-mq_tab_new_root(MqWindow *window);
-
-void
-mq_tab_quit(MqTab *tab);
-
-MqApplication *
-mq_tab_get_application(MqTab *tab);
-
-MqWindow *
-mq_tab_get_window(MqTab *tab);
-
-void
-mq_tab_update_position(MqTab *tab, guint position);
-
-guint
-mq_tab_get_position(MqTab *tab);
-
-guint
-mq_tab_get_tree_size(MqTab *tab);
-
-const gchar *
-mq_tab_get_title(MqTab *tab);
-
-MqTab *
-mq_tab_seek(MqTab *node, guint position);
-
-void
-mq_tab_scroll_tab_labels(MqTab *root);
-
-void
-mq_tab_begin_scrolling_tab_labels(MqTab *root);
-
-void
-mq_tab_end_scrolling_tab_labels(MqTab *root);
-
-#endif
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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "find-toolbar.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#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..d9e2216
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_TOOLBARS_FIND_TOOLBAR_H
+#define MQ_TOOLBARS_FIND_TOOLBAR_H
+
+typedef struct _MqFindToolbar MqFindToolbar;
+typedef struct _MqFindToolbarClass MqFindToolbarClass;
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#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..b687e6a
--- /dev/null
+++ b/src/toolbars/local.mk
@@ -0,0 +1,5 @@
+marquee_SOURCES += \
+ %reldir%/find-toolbar.c \
+ %reldir%/navigation-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..692a565
--- /dev/null
+++ b/src/toolbars/navigation-toolbar.c
@@ -0,0 +1,259 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "navigation-toolbar.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#include "../config.h"
+#include "../tab-page.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;
+ MqTabPage *tab_page;
+ MqFindToolbar *find_toolbar;
+ MqWebView *web_view;
+ gchar *uri;
+};
+
+enum {
+ PROP_CONFIG = 1,
+ PROP_TAB_PAGE,
+ 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_page,
+ 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);
+ }
+
+ G_OBJECT_CLASS(mq_navigation_toolbar_parent_class)->finalize(object);
+}
+
+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_PAGE:
+ g_value_set_object(value, navigation_toolbar->tab_page);
+ break;
+ case PROP_FIND_TOOLBAR:
+ g_value_set_object(value,
+ navigation_toolbar->find_toolbar);
+ break;
+ 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_PAGE:
+ navigation_toolbar->tab_page =
+ g_value_get_object(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_PAGE] = g_param_spec_object(
+ "tab-page",
+ "MqTabPage",
+ "The ancestral MqTabPage instance",
+ MQ_TYPE_TAB_PAGE,
+ 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, MqTabPage *tab_page,
+ MqFindToolbar *find_toolbar, MqWebView *web_view, const gchar *uri)
+{
+ return g_object_new(MQ_TYPE_NAVIGATION_TOOLBAR,
+ "config", config,
+ "tab-page", tab_page,
+ "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..3dd9959
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_TOOLBARS_NAVIGATION_TOOLBAR_H
+#define MQ_TOOLBARS_NAVIGATION_TOOLBAR_H
+
+typedef struct _MqNavigationToolbar MqNavigationToolbar;
+typedef struct _MqNavigationToolbarClass MqNavigationToolbarClass;
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "../tab-page.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, MqTabPage *tab_page,
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "back-forward-button-box.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#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..b00c079
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_TOOLBARS_NAVIGATION_BACK_FORWARD_BUTTON_BOX_H
+#define MQ_TOOLBARS_NAVIGATION_BACK_FORWARD_BUTTON_BOX_H
+
+typedef struct _MqBackForwardButtonBox MqBackForwardButtonBox;
+typedef struct _MqBackForwardButtonBoxClass MqBackForwardButtonBoxClass;
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "home-button.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#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..af1d448
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_TOOLBARS_NAVIGATION_HOME_BUTTON_H
+#define MQ_TOOLBARS_NAVIGATION_HOME_BUTTON_H
+
+typedef struct _MqHomeButton MqHomeButton;
+typedef struct _MqHomeButtonClass MqHomeButtonClass;
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#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..c59a7b9
--- /dev/null
+++ b/src/toolbars/navigation/local.mk
@@ -0,0 +1,6 @@
+marquee_SOURCES += \
+ %reldir%/back-forward-button-box.c \
+ %reldir%/home-button.c \
+ %reldir%/main-menu.c \
+ %reldir%/stop-reload-button.c \
+ %reldir%/uri-entry.c
diff --git a/src/toolbars/navigation/main-menu.c b/src/toolbars/navigation/main-menu.c
new file mode 100644
index 0000000..055f489
--- /dev/null
+++ b/src/toolbars/navigation/main-menu.c
@@ -0,0 +1,301 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "main-menu.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#include "../../tab-page.h"
+#include "../../web-view.h"
+#include "../find-toolbar.h"
+
+struct _MqMainMenu {
+ GtkToolButton parent_instance;
+ MqTabPage *tab_page;
+ MqFindToolbar *find_toolbar;
+ MqWebView *web_view;
+ GtkWidget *popover;
+};
+
+enum {
+ PROP_TAB_PAGE = 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_page_get_window(main_menu->tab_page));
+}
+
+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_page_new("about:preferences", main_menu->tab_page);
+ /* TODO: Hack: */
+ gtk_notebook_next_page(GTK_NOTEBOOK(gtk_widget_get_parent(
+ GTK_WIDGET(main_menu->tab_page))));
+}
+
+static void
+about_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu)
+{
+ gtk_widget_hide(main_menu->popover);
+ mq_tab_page_new("about:", main_menu->tab_page);
+ /* TODO: Hack: */
+ gtk_notebook_next_page(GTK_NOTEBOOK(gtk_widget_get_parent(
+ GTK_WIDGET(main_menu->tab_page))));
+}
+
+static void
+quit_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqMainMenu *main_menu)
+{
+ /* mq_tab_page_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_page_quit(main_menu->tab_page);
+}
+
+#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_PAGE:
+ g_value_set_object(value, main_menu->tab_page);
+ 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_PAGE:
+ main_menu->tab_page = g_value_get_object(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_PAGE] = g_param_spec_object(
+ "tab-page",
+ "MqTabPage",
+ "The ancestral MqTabPage instance",
+ MQ_TYPE_TAB_PAGE,
+ 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(MqTabPage *tab_page, MqFindToolbar *find_toolbar,
+ MqWebView *web_view)
+{
+ return g_object_new(MQ_TYPE_MAIN_MENU,
+ "tab-page", tab_page,
+ "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..13a6c33
--- /dev/null
+++ b/src/toolbars/navigation/main-menu.h
@@ -0,0 +1,58 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_TOOLBARS_NAVIGATION_MAIN_MENU_H
+#define MQ_TOOLBARS_NAVIGATION_MAIN_MENU_H
+
+typedef struct _MqMainMenu MqMainMenu;
+typedef struct _MqMainMenuClass MqMainMenuClass;
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "../../tab-page.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(MqTabPage *tab_page, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "stop-reload-button.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#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..82fd8a5
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_TOOLBARS_NAVIGATION_STOP_RELOAD_BUTTON_H
+#define MQ_TOOLBARS_NAVIGATION_STOP_RELOAD_BUTTON_H
+
+typedef struct _MqStopReloadButton MqStopReloadButton;
+typedef struct _MqStopReloadButtonClass MqStopReloadButtonClass;
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "uri-entry.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#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: <Esc> 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..046d243
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_TOOLBARS_NAVIGATION_URI_ENTRY_H
+#define MQ_TOOLBARS_NAVIGATION_URI_ENTRY_H
+
+typedef struct _MqUriEntry MqUriEntry;
+typedef struct _MqUriEntryClass MqUriEntryClass;
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#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 */
diff --git a/src/web-settings.c b/src/web-settings.c
index e8ed669..c6b9c44 100644
--- a/src/web-settings.c
+++ b/src/web-settings.c
@@ -19,9 +19,10 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "web-settings.h"
+
#include <webkit2/webkit2.h>
-#include "web-settings.h"
#include "config.h"
enum mapping_type {
diff --git a/src/tab-body.c b/src/web-view.c
index 475fa2f..2f6d765 100644
--- a/src/tab-body.c
+++ b/src/web-view.c
@@ -1,5 +1,5 @@
/*
- * Tab body
+ * Web view
*
* Copyright (C) 2017 Patrick McDermott
*
@@ -19,123 +19,161 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "web-view.h"
+
#include <stdlib.h>
#include <string.h>
+#include <glib.h>
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>
-#include "tab-body.h"
-#include "tab.h"
+#include "config.h"
+#include "tab-page.h"
+
+struct _MqWebView {
+ WebKitWebView parent_instance;
+ MqTabPage *tab_page;
+ gchar *uri;
+ MqConfig *config;
+ WebKitHitTestResult *hit_test_result;
+ WebKitHitTestResult *mouse_target_hit_test_result;
+};
+
+enum {
+ PROP_TAB_PAGE = 1,
+ PROP_REWRITTEN_URI,
+ N_PROPERTIES
+};
+
+static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,};
+
+struct _MqWebViewClass {
+ WebKitWebViewClass parent_class;
+};
+
+G_DEFINE_TYPE(MqWebView, mq_web_view, WEBKIT_TYPE_WEB_VIEW)
#define WKCMA(ACTION) \
WEBKIT_CONTEXT_MENU_ACTION_##ACTION
static void
-menu_open_link_activate_cb(GtkAction G_GNUC_UNUSED *action, MqTabBody *body)
+menu_open_link_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view)
{
- webkit_web_view_load_uri(body->web_view,
- webkit_hit_test_result_get_link_uri(body->hit_test_result));
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view),
+ webkit_hit_test_result_get_link_uri(
+ web_view->hit_test_result));
}
static void
menu_open_link_tab_activate_cb(GtkAction G_GNUC_UNUSED *action,
- MqTabBody *body)
+ MqWebView *web_view)
{
- mq_tab_new_relative(
- webkit_hit_test_result_get_link_uri(body->hit_test_result),
- body->tab);
+ mq_tab_page_new_relative(
+ webkit_hit_test_result_get_link_uri(web_view->hit_test_result),
+ web_view->tab_page);
}
static void
-menu_open_link_win_activate_cb(GtkAction G_GNUC_UNUSED *action, MqTabBody *body)
+menu_open_link_win_activate_cb(GtkAction G_GNUC_UNUSED *action,
+ MqWebView *web_view)
{
const gchar *uris[2] = {
- webkit_hit_test_result_get_link_uri(body->hit_test_result),
+ webkit_hit_test_result_get_link_uri(web_view->hit_test_result),
NULL
};
- mq_application_add_window(body->tab->application, uris);
+ mq_application_add_window(
+ mq_tab_page_get_application(web_view->tab_page), uris);
}
static void
-menu_open_image_activate_cb(GtkAction G_GNUC_UNUSED *action, MqTabBody *body)
+menu_open_image_activate_cb(GtkAction G_GNUC_UNUSED *action,
+ MqWebView *web_view)
{
- webkit_web_view_load_uri(body->web_view,
- webkit_hit_test_result_get_image_uri(body->hit_test_result));
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view),
+ webkit_hit_test_result_get_image_uri(
+ web_view->hit_test_result));
}
static void
menu_open_image_tab_activate_cb(GtkAction G_GNUC_UNUSED *action,
- MqTabBody *body)
+ MqWebView *web_view)
{
- mq_tab_new_relative(
- webkit_hit_test_result_get_image_uri(body->hit_test_result),
- body->tab);
+ mq_tab_page_new_relative(
+ webkit_hit_test_result_get_image_uri(web_view->hit_test_result),
+ web_view->tab_page);
}
static void
menu_open_image_win_activate_cb(GtkAction G_GNUC_UNUSED *action,
- MqTabBody *body)
+ MqWebView *web_view)
{
const gchar *uris[2] = {
- webkit_hit_test_result_get_image_uri(body->hit_test_result),
+ webkit_hit_test_result_get_image_uri(web_view->hit_test_result),
NULL
};
- mq_application_add_window(body->tab->application, uris);
+ mq_application_add_window(
+ mq_tab_page_get_application(web_view->tab_page), uris);
}
static void
-menu_open_video_activate_cb(GtkAction G_GNUC_UNUSED *action, MqTabBody *body)
+menu_open_video_activate_cb(GtkAction G_GNUC_UNUSED *action,
+ MqWebView *web_view)
{
- webkit_web_view_load_uri(body->web_view,
- webkit_hit_test_result_get_media_uri(body->hit_test_result));
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view),
+ webkit_hit_test_result_get_media_uri(
+ web_view->hit_test_result));
}
static void
menu_open_video_tab_activate_cb(GtkAction G_GNUC_UNUSED *action,
- MqTabBody *body)
+ MqWebView *web_view)
{
- mq_tab_new_relative(
- webkit_hit_test_result_get_media_uri(body->hit_test_result),
- body->tab);
+ mq_tab_page_new_relative(
+ webkit_hit_test_result_get_media_uri(web_view->hit_test_result),
+ web_view->tab_page);
}
static void
menu_open_video_win_activate_cb(GtkAction G_GNUC_UNUSED *action,
- MqTabBody *body)
+ MqWebView *web_view)
{
const gchar *uris[2] = {
- webkit_hit_test_result_get_media_uri(body->hit_test_result),
+ webkit_hit_test_result_get_media_uri(web_view->hit_test_result),
NULL
};
- mq_application_add_window(body->tab->application, uris);
+ mq_application_add_window(
+ mq_tab_page_get_application(web_view->tab_page), uris);
}
static void
-menu_open_audio_activate_cb(GtkAction G_GNUC_UNUSED *action, MqTabBody *body)
+menu_open_audio_activate_cb(GtkAction G_GNUC_UNUSED *action,
+ MqWebView *web_view)
{
- webkit_web_view_load_uri(body->web_view,
- webkit_hit_test_result_get_media_uri(body->hit_test_result));
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view),
+ webkit_hit_test_result_get_media_uri(
+ web_view->hit_test_result));
}
static void
menu_open_audio_tab_activate_cb(GtkAction G_GNUC_UNUSED *action,
- MqTabBody *body)
+ MqWebView *web_view)
{
- mq_tab_new_relative(
- webkit_hit_test_result_get_media_uri(body->hit_test_result),
- body->tab);
+ mq_tab_page_new_relative(
+ webkit_hit_test_result_get_media_uri(web_view->hit_test_result),
+ web_view->tab_page);
}
static void
menu_open_audio_win_activate_cb(GtkAction G_GNUC_UNUSED *action,
- MqTabBody *body)
+ MqWebView *web_view)
{
const gchar *uris[2] = {
- webkit_hit_test_result_get_media_uri(body->hit_test_result),
+ webkit_hit_test_result_get_media_uri(web_view->hit_test_result),
NULL
};
- mq_application_add_window(body->tab->application, uris);
+ mq_application_add_window(
+ mq_tab_page_get_application(web_view->tab_page), uris);
}
#define ITEM_DECLS \
@@ -150,7 +188,7 @@ menu_open_audio_win_activate_cb(GtkAction G_GNUC_UNUSED *action,
action = gtk_action_new(#NAME, (LABEL), NULL, NULL); \
G_GNUC_END_IGNORE_DEPRECATIONS \
g_signal_connect(action, "activate", \
- G_CALLBACK(menu_##NAME##_activate_cb), body); \
+ G_CALLBACK(menu_##NAME##_activate_cb), web_view); \
menu_item = webkit_context_menu_item_new(action); \
webkit_context_menu_append(context_menu, menu_item); \
} while (0)
@@ -174,7 +212,7 @@ menu_open_audio_win_activate_cb(GtkAction G_GNUC_UNUSED *action,
} while (0)
static void
-context_menu_link_cb(WebKitContextMenu *context_menu, MqTabBody *body)
+context_menu_link_cb(WebKitContextMenu *context_menu, MqWebView *web_view)
{
ITEM_DECLS
@@ -187,7 +225,7 @@ context_menu_link_cb(WebKitContextMenu *context_menu, MqTabBody *body)
}
static void
-context_menu_image_cb(WebKitContextMenu *context_menu, MqTabBody *body)
+context_menu_image_cb(WebKitContextMenu *context_menu, MqWebView *web_view)
{
ITEM_DECLS
@@ -202,7 +240,7 @@ context_menu_image_cb(WebKitContextMenu *context_menu, MqTabBody *body)
static void
context_menu_media_cb(WebKitContextMenu *context_menu, GList *media_ctrl_items,
- GList *media_toggle_items, gboolean is_video, MqTabBody *body)
+ GList *media_toggle_items, gboolean is_video, MqWebView *web_view)
{
ITEM_DECLS
@@ -234,7 +272,7 @@ context_menu_media_cb(WebKitContextMenu *context_menu, GList *media_ctrl_items,
static void
context_menu_editable_cb(WebKitContextMenu *context_menu,
GList *spell_repl_items, GList *spell_ctrl_items, GList *edit_items,
- GList *input_items, MqTabBody G_GNUC_UNUSED *body)
+ GList *input_items, MqWebView G_GNUC_UNUSED *web_view)
{
ITEM_DECLS_NO_CUSTOM
@@ -250,7 +288,7 @@ context_menu_editable_cb(WebKitContextMenu *context_menu,
}
static void
context_menu_document_cb(WebKitContextMenu *context_menu, GList *nav_items,
- MqTabBody G_GNUC_UNUSED *body)
+ MqWebView G_GNUC_UNUSED *web_view)
{
ITEM_DECLS_NO_CUSTOM
@@ -268,10 +306,10 @@ context_menu_document_cb(WebKitContextMenu *context_menu, GList *nav_items,
} while (0)
static gboolean
-context_menu_cb(WebKitWebView G_GNUC_UNUSED *web_view,
- WebKitContextMenu *context_menu, GdkEvent G_GNUC_UNUSED *event,
- WebKitHitTestResult *hit_test_result, MqTabBody *body)
+context_menu_cb(WebKitWebView *wk_web_view, WebKitContextMenu *context_menu,
+ GdkEvent G_GNUC_UNUSED *event, WebKitHitTestResult *hit_test_result)
{
+ MqWebView *web_view;
GList *items;
GList *nav_items;
GList *edit_items;
@@ -287,6 +325,8 @@ context_menu_cb(WebKitWebView G_GNUC_UNUSED *web_view,
gboolean context_handled;
WebKitContextMenuItem *menu_item;
+ web_view = MQ_WEB_VIEW(wk_web_view);
+
/* Get more hints about the context, since WebKit doesn't describe
* context very well in hit test results. Also, preserve menu items
* that aren't easy to reproduce (e.g. the Unicode menu and spelling
@@ -372,33 +412,33 @@ context_menu_cb(WebKitWebView G_GNUC_UNUSED *web_view,
/* Get the reported context (which isn't very descriptive) and save the
* hit test result for use by action callbacks. */
context = webkit_hit_test_result_get_context(hit_test_result);
- if (body->hit_test_result) {
- g_object_unref(body->hit_test_result);
+ if (web_view->hit_test_result) {
+ g_object_unref(web_view->hit_test_result);
}
- body->hit_test_result = hit_test_result;
- g_object_ref(body->hit_test_result);
+ web_view->hit_test_result = hit_test_result;
+ g_object_ref(web_view->hit_test_result);
context_handled = FALSE;
/* Build the context menu. */
if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
- context_menu_link_cb(context_menu, body);
+ context_menu_link_cb(context_menu, web_view);
context_handled = TRUE;
}
if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
if (context_handled) {
NEW_SEPARATOR_ITEM();
}
- context_menu_image_cb(context_menu, body);
+ context_menu_image_cb(context_menu, web_view);
context_handled = TRUE;
}
if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA) {
context_menu_media_cb(context_menu, media_ctrl_items,
- media_toggle_items, is_video, body);
+ media_toggle_items, is_video, web_view);
context_handled = TRUE;
}
if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
context_menu_editable_cb(context_menu, spell_repl_items,
- spell_ctrl_items, edit_items, input_items, body);
+ spell_ctrl_items, edit_items, input_items, web_view);
context_handled = TRUE;
}
if (!context_handled &&
@@ -407,7 +447,8 @@ context_menu_cb(WebKitWebView G_GNUC_UNUSED *web_view,
RESTORE_ITEMS(edit_items); /* _Copy */
context_handled = TRUE;
} else {
- context_menu_document_cb(context_menu, nav_items, body);
+ context_menu_document_cb(context_menu, nav_items,
+ web_view);
context_handled = TRUE;
}
}
@@ -423,10 +464,35 @@ context_menu_cb(WebKitWebView G_GNUC_UNUSED *web_view,
}
static void
+uri_cb(WebKitWebView *wk_web_view, GParamSpec G_GNUC_UNUSED *param_spec)
+{
+ const gchar *uri;
+ MqWebView *web_view;
+
+ web_view = MQ_WEB_VIEW(wk_web_view);
+
+ if (web_view->uri) {
+ g_free(web_view->uri);
+ }
+
+ uri = webkit_web_view_get_uri(wk_web_view);
+
+ if (g_str_has_prefix(uri, "mq-about:")) {
+ web_view->uri = g_strconcat("about:", uri + strlen("mq-about:"),
+ NULL);
+ } else {
+ web_view->uri = g_strdup(uri);
+ }
+
+ g_object_notify_by_pspec(G_OBJECT(web_view),
+ obj_properties[PROP_REWRITTEN_URI]);
+}
+
+static void
clipboard_text_recv_cb(GtkClipboard G_GNUC_UNUSED *clipboard,
- const gchar *text, MqTabBody *body)
+ const gchar *text, MqWebView *web_view)
{
- webkit_web_view_load_uri(body->web_view, text);
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view), text);
}
/* This callback is a hack to determine on middle mouse click whether to open a
@@ -434,32 +500,37 @@ clipboard_text_recv_cb(GtkClipboard G_GNUC_UNUSED *clipboard,
* API provided webkit_web_view_get_hit_test_result() which would have been
* easier. */
static void
-mouse_target_changed_cb(WebKitWebView G_GNUC_UNUSED *web_view,
- WebKitHitTestResult *hit_test_result, guint G_GNUC_UNUSED modifiers,
- MqTabBody *body)
+mouse_target_changed_cb(WebKitWebView *wk_web_view,
+ WebKitHitTestResult *hit_test_result, guint G_GNUC_UNUSED modifiers)
{
- body->mouse_target_hit_test_result = hit_test_result;
- g_object_ref(body->mouse_target_hit_test_result);
+ MqWebView *web_view;
+
+ web_view = MQ_WEB_VIEW(wk_web_view);
+
+ web_view->mouse_target_hit_test_result = hit_test_result;
+ g_object_ref(web_view->mouse_target_hit_test_result);
}
static gboolean
-button_press_cb(WebKitWebView G_GNUC_UNUSED *web_view, GdkEvent *event,
- MqTabBody *body)
+button_press_cb(GtkWidget *widget, GdkEventButton *event)
{
+ MqWebView *web_view;
WebKitHitTestResult *hit_test_result;
GtkClipboard *clipboard;
+ web_view = MQ_WEB_VIEW(widget);
+
/* Make sure this is a middle mouse button press event. */
- if (event->type != GDK_BUTTON_PRESS || event->button.button != 2) {
+ if (event->button != 2) {
return FALSE;
}
- hit_test_result = body->mouse_target_hit_test_result;
+ hit_test_result = web_view->mouse_target_hit_test_result;
if (webkit_hit_test_result_context_is_link(hit_test_result)) {
- mq_tab_new_relative(
+ mq_tab_page_new_relative(
webkit_hit_test_result_get_link_uri(hit_test_result),
- body->tab);
+ web_view->tab_page);
} else if (webkit_hit_test_result_context_is_editable(hit_test_result)){
/* Let WebKit handle pasting from the primary clipboard into an
* editable element. */
@@ -469,7 +540,7 @@ button_press_cb(WebKitWebView G_GNUC_UNUSED *web_view, GdkEvent *event,
clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
gtk_clipboard_request_text(clipboard,
(GtkClipboardTextReceivedFunc) clipboard_text_recv_cb,
- body);
+ web_view);
}
g_object_unref(hit_test_result);
@@ -477,36 +548,44 @@ button_press_cb(WebKitWebView G_GNUC_UNUSED *web_view, GdkEvent *event,
return TRUE;
}
-MqTabBody *
-mq_tab_body_new(MqTab *tab, const gchar *uri)
+static void
+constructed(GObject *object)
{
- MqTabBody *body;
- gchar *rw_uri;
- MqConfig *config;
- gchar *new_tab_page;
+ MqWebView *web_view;
+ gchar *rw_uri;
+ gchar *new_tab_page;
- body = malloc(sizeof(*body));
- body->tab = tab;
+ if (G_OBJECT_CLASS(mq_web_view_parent_class)->constructed) {
+ G_OBJECT_CLASS(mq_web_view_parent_class)->constructed(object);
+ }
+
+ web_view = MQ_WEB_VIEW(object);
+
+ web_view->config = mq_application_get_config(
+ mq_tab_page_get_application(web_view->tab_page));
- body->web_view = WEBKIT_WEB_VIEW(webkit_web_view_new_with_settings(
- mq_application_get_webkit_settings(
- mq_tab_get_application(tab))));
+ webkit_web_view_set_settings(WEBKIT_WEB_VIEW(web_view),
+ mq_application_get_webkit_settings(
+ mq_tab_page_get_application(web_view->tab_page)));
- if (uri) {
- if (g_str_has_prefix(uri, "about:")) {
+ if (web_view->uri) {
+ if (g_str_has_prefix(web_view->uri, "about:")) {
rw_uri = g_strconcat("mq-about:",
- uri + strlen("about:"), NULL);
- webkit_web_view_load_uri(body->web_view, rw_uri);
+ web_view->uri + strlen("about:"), NULL);
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view),
+ rw_uri);
g_free(rw_uri);
} else {
- webkit_web_view_load_uri(body->web_view, uri);
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view),
+ web_view->uri);
}
} else {
- config = mq_application_get_config(mq_tab_get_application(tab));
- new_tab_page = mq_config_get_string(config, "tabs.new");
+ new_tab_page = mq_config_get_string(web_view->config,
+ "tabs.new");
if (g_strcmp0(new_tab_page, "home") == 0) {
- webkit_web_view_load_uri(body->web_view,
- mq_config_get_string(config, "tabs.home"));
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view),
+ mq_config_get_string(web_view->config,
+ "tabs.home"));
} else if (g_strcmp0(new_tab_page, "blank") == 0) {
/* Don't load any URI. */
} else {
@@ -514,34 +593,190 @@ mq_tab_body_new(MqTab *tab, const gchar *uri)
}
}
- webkit_web_view_set_zoom_level(body->web_view, mq_config_get_double(
- mq_application_get_config(mq_tab_get_application(tab)),
- "zoom.default"));
+ mq_web_view_zoom_reset(web_view);
- body->container = GTK_WIDGET(body->web_view);
- gtk_widget_set_vexpand(body->container, TRUE);
+ gtk_widget_set_vexpand(GTK_WIDGET(web_view), TRUE);
/* FIXME: This doesn't seem to be working. */
- gtk_widget_grab_focus(body->container);
+ gtk_widget_grab_focus(GTK_WIDGET(web_view));
+}
+
+static void
+finalize(GObject *object)
+{
+ MqWebView *web_view;
+
+ web_view = MQ_WEB_VIEW(object);
+
+ if (web_view->uri) {
+ g_free(web_view->uri);
+ }
+
+ G_OBJECT_CLASS(mq_web_view_parent_class)->finalize(object);
+}
+
+static void
+get_property(GObject *object, guint property_id, GValue *value,
+ GParamSpec *param_spec)
+{
+ MqWebView *web_view;
+
+ web_view = MQ_WEB_VIEW(object);
+
+ switch (property_id) {
+ case PROP_TAB_PAGE:
+ g_value_set_object(value, web_view->tab_page);
+ break;
+ case PROP_REWRITTEN_URI:
+ g_value_set_string(value, web_view->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)
+{
+ MqWebView *web_view;
+
+ web_view = MQ_WEB_VIEW(object);
+
+ switch (property_id) {
+ case PROP_TAB_PAGE:
+ web_view->tab_page = g_value_get_object(value);
+ break;
+ case PROP_REWRITTEN_URI:
+ mq_web_view_load_uri(web_view,
+ g_value_get_string(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+mq_web_view_class_init(MqWebViewClass *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_TAB_PAGE] = g_param_spec_object(
+ "tab-page",
+ "MqTabPage",
+ "The parent MqTabPage instance",
+ MQ_TYPE_TAB_PAGE,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ obj_properties[PROP_REWRITTEN_URI] = g_param_spec_string(
+ "rewritten-uri",
+ "URI",
+ "The current active URI of the Web view, "
+ "with \"mq-about:\" rewritten to \"about:\"",
+ "",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_properties(object_class, N_PROPERTIES,
+ obj_properties);
+}
+
+static void
+mq_web_view_init(MqWebView *web_view)
+{
+ web_view->hit_test_result = NULL;
+
+ g_signal_connect(web_view, "context-menu",
+ G_CALLBACK(context_menu_cb), NULL);
+ g_signal_connect(web_view, "notify::uri",
+ G_CALLBACK(uri_cb), NULL);
+ g_signal_connect(web_view, "mouse-target-changed",
+ G_CALLBACK(mouse_target_changed_cb), NULL);
+ g_signal_connect(web_view, "button-press-event",
+ G_CALLBACK(button_press_cb), NULL);
+}
+
+MqWebView *
+mq_web_view_new(MqTabPage *tab_page, const gchar *uri)
+{
+ return g_object_new(MQ_TYPE_WEB_VIEW,
+ "tab-page", tab_page, /* TODO: Use gtk_widget_get_parent()? */
+ "rewritten-uri", uri,
+ "web-context", webkit_web_context_get_default(),
+ NULL);
+}
- body->hit_test_result = NULL;
- g_signal_connect(body->web_view, "context-menu",
- G_CALLBACK(context_menu_cb), body);
- g_signal_connect(body->web_view, "mouse-target-changed",
- G_CALLBACK(mouse_target_changed_cb), body);
- g_signal_connect(body->web_view, "button-press-event",
- G_CALLBACK(button_press_cb), body);
+const gchar *
+mq_web_view_get_uri(MqWebView *web_view)
+{
+ return web_view->uri;
+}
+
+void
+mq_web_view_load_uri(MqWebView *web_view, const gchar *uri)
+{
+ gchar *rw_uri;
+
+ if (!uri) {
+ /* Happens during object construction. */
+ return;
+ }
+
+ if (web_view->uri) {
+ g_free(web_view->uri);
+ }
+ web_view->uri = g_strdup(uri);
- return body;
+ if (g_str_has_prefix(uri, "about:")) {
+ rw_uri = g_strconcat("mq-about:", uri + strlen("about:"), NULL);
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view), rw_uri);
+ g_free(rw_uri);
+ } else {
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(web_view), uri);
+ }
+
+ g_object_notify_by_pspec(G_OBJECT(web_view),
+ obj_properties[PROP_REWRITTEN_URI]);
}
-GtkWidget *
-mq_tab_body_get_container(MqTabBody *body)
+void
+mq_web_view_zoom_in(MqWebView *web_view)
{
- return body->container;
+ gdouble zoom_level;
+
+ zoom_level = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(web_view));
+ zoom_level += 0.1;
+ if (zoom_level < 0) {
+ zoom_level = G_MAXDOUBLE;
+ }
+ webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(web_view), zoom_level);
}
-WebKitWebView *
-mq_tab_body_get_web_view(MqTabBody *body)
+void
+mq_web_view_zoom_out(MqWebView *web_view)
{
- return body->web_view;
+ gdouble zoom_level;
+
+ zoom_level = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(web_view));
+ zoom_level -= 0.1;
+ if (zoom_level < 0) {
+ zoom_level = 0;
+ }
+ webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(web_view), zoom_level);
+}
+
+void
+mq_web_view_zoom_reset(MqWebView *web_view)
+{
+ gdouble zoom_level;
+
+ zoom_level = mq_config_get_double(web_view->config, "zoom.default");
+ webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(web_view), zoom_level);
}
diff --git a/src/web-view.h b/src/web-view.h
new file mode 100644
index 0000000..fa209c1
--- /dev/null
+++ b/src/web-view.h
@@ -0,0 +1,71 @@
+/*
+ * Web view
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MQ_WEB_VIEW_H
+#define MQ_WEB_VIEW_H
+
+typedef struct _MqWebView MqWebView;
+typedef struct _MqWebViewClass MqWebViewClass;
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+#include "tab-page.h"
+
+G_BEGIN_DECLS
+
+#define MQ_TYPE_WEB_VIEW (mq_web_view_get_type())
+#define MQ_WEB_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ MQ_TYPE_WEB_VIEW, MqWebView))
+#define MQ_IS_WEB_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ MQ_TYPE_WEB_VIEW))
+#define MQ_WEB_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+ MQ_TYPE_WEB_VIEW, MqWebViewClass))
+#define MQ_IS_WEB_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ MQ_TYPE_WEB_VIEW))
+#define MQ_WEB_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ MQ_TYPE_WEB_VIEW, MqWebViewClass))
+
+GType
+mq_web_view_get_type(void);
+
+MqWebView *
+mq_web_view_new(MqTabPage *tab_page, const gchar *uri);
+
+const gchar *
+mq_web_view_get_uri(MqWebView *web_view);
+
+void
+mq_web_view_load_uri(MqWebView *web_view, const gchar *uri);
+
+void
+mq_web_view_zoom_in(MqWebView *web_view);
+
+void
+mq_web_view_zoom_out(MqWebView *web_view);
+
+void
+mq_web_view_zoom_reset(MqWebView *web_view);
+
+G_END_DECLS
+
+#endif /* MQ_WEB_VIEW_H */
diff --git a/src/window.c b/src/window.c
index a4807db..8a72a01 100644
--- a/src/window.c
+++ b/src/window.c
@@ -19,35 +19,59 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "window.h"
+
#include <stdlib.h>
+#include <glib.h>
#include <gtk/gtk.h>
-#include "window.h"
#include "application.h"
-#include "tab.h"
+#include "tab-page.h"
+
+struct _MqWindow {
+ GtkWindow parent_instance;
+ MqApplication *application;
+ const gchar **uris;
+ MqConfig *config;
+ GtkWidget *notebook;
+ MqTabPage *root_tab;
+ guint current_tab;
+ gboolean fullscreen;
+};
+
+enum {
+ PROP_APPLICATION = 1,
+ PROP_URIS,
+ N_PROPERTIES
+};
+
+static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,};
+
+struct _MqWindowClass {
+ GtkWindowClass parent_class;
+};
+
+G_DEFINE_TYPE(MqWindow, mq_window, GTK_TYPE_WINDOW)
static void
-is_maximized_cb(GtkWindow *window, GParamSpec G_GNUC_UNUSED *paramspec,
- MqConfig *config)
+is_maximized_cb(MqWindow *window, GParamSpec G_GNUC_UNUSED *param_spec)
{
- mq_config_set_boolean(config, "window.maximized",
- gtk_window_is_maximized(window));
- mq_config_save(config);
+ mq_config_set_boolean(window->config, "window.maximized",
+ gtk_window_is_maximized(GTK_WINDOW(window)));
+ mq_config_save(window->config);
}
static void
-configure_event_cb(GtkWindow G_GNUC_UNUSED *window, GdkEventConfigure *event,
- MqConfig *config)
+configure_event_cb(MqWindow G_GNUC_UNUSED *window, GdkEventConfigure *event)
{
- mq_config_set_integer(config, "window.width", event->width);
- mq_config_set_integer(config, "window.height", event->height);
- mq_config_save(config);
+ mq_config_set_integer(window->config, "window.width", event->width);
+ mq_config_set_integer(window->config, "window.height", event->height);
+ mq_config_save(window->config);
}
static gboolean
-window_state_event_cb(GtkWidget G_GNUC_UNUSED *widget,
- GdkEventWindowState *event, MqWindow *window)
+window_state_event_cb(MqWindow *window, GdkEventWindowState *event)
{
window->fullscreen =
event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
@@ -69,13 +93,12 @@ close_confirm_response_cb(GtkWidget *dialog, gint response_id, MqWindow *window)
{
gtk_widget_destroy(dialog);
if (response_id == GTK_RESPONSE_OK) {
- gtk_widget_destroy(window->window);
+ gtk_widget_destroy(GTK_WIDGET(window));
}
}
static gboolean
-delete_event_cb(GtkWindow *widget, GdkEvent G_GNUC_UNUSED *event,
- MqWindow *window)
+delete_event_cb(MqWindow *window, GdkEvent G_GNUC_UNUSED *event)
{
guint num_tabs;
gchar *message;
@@ -125,8 +148,8 @@ delete_event_cb(GtkWindow *widget, GdkEvent G_GNUC_UNUSED *event,
gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
/* Dialog */
- dialog = gtk_dialog_new_with_buttons("Confirm Close", widget,
- GTK_DIALOG_DESTROY_WITH_PARENT,
+ dialog = gtk_dialog_new_with_buttons("Confirm Close",
+ GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT,
"_Cancel", GTK_RESPONSE_CANCEL,
"Cl_ose Tabs", GTK_RESPONSE_OK,
NULL);
@@ -135,13 +158,15 @@ delete_event_cb(GtkWindow *widget, GdkEvent G_GNUC_UNUSED *event,
hbox);
g_signal_connect(dialog, "response",
G_CALLBACK(close_confirm_response_cb), window);
+ gtk_widget_grab_focus(gtk_dialog_get_widget_for_response(
+ GTK_DIALOG(dialog), GTK_RESPONSE_OK));
gtk_widget_show_all(dialog);
return TRUE;
}
static void
-destroy_cb(GtkWidget G_GNUC_UNUSED *widget, MqWindow *window)
+destroy_cb(MqWindow *window)
{
mq_application_delete_window(window->application, window);
}
@@ -152,7 +177,7 @@ set_title(MqWindow *window, const gchar *title)
gchar *window_title;
window_title = g_strdup_printf("%s - Marquee", title);
- gtk_window_set_title(GTK_WINDOW(window->window), window_title);
+ gtk_window_set_title(GTK_WINDOW(window), window_title);
g_free(window_title);
}
@@ -162,8 +187,8 @@ switch_page_cb(GtkNotebook G_GNUC_UNUSED *notebook,
{
window->current_tab = ++page_num;
- set_title(window, mq_tab_get_title(mq_tab_seek(window->root_tab,
- page_num)));
+ set_title(window, mq_tab_page_get_title(
+ mq_tab_page_seek(window->root_tab, page_num)));
}
static void
@@ -171,75 +196,160 @@ update_positions(GtkNotebook G_GNUC_UNUSED *notebook,
GtkWidget G_GNUC_UNUSED *child, guint G_GNUC_UNUSED page_num,
MqWindow *window)
{
- /* TODO: Once MqWindow has a data structure for tabs, loop through them
- * all and call mq_tab_update_position(). */
+ /* TODO: After MqTab becomes MqTabPage, derived from GtkBox, call
+ * mq_tab_page_update_positions() on child or
+ * mq_tab_page_seek(page_num + 1), whichever has the lower position. */
/* TODO: Should this also update the tabs data structure? Probably. */
/* Temporarily "use" window. */
window = window;
}
-MqWindow *
-mq_window_new(MqApplication *application, const gchar **uris)
+static void
+constructed(GObject *object)
{
MqWindow *window;
- guint i;
+ gsize i;
- window = malloc(sizeof(*window));
- window->application = application;
- window->config = mq_application_get_config(application);
+ window = MQ_WINDOW(object);
- window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
if (mq_config_get_boolean(window->config, "window.maximized")) {
- gtk_window_maximize(GTK_WINDOW(window->window));
+ gtk_window_maximize(GTK_WINDOW(window));
} else {
- gtk_window_unmaximize(GTK_WINDOW(window->window));
+ gtk_window_unmaximize(GTK_WINDOW(window));
}
- gtk_window_set_default_size(GTK_WINDOW(window->window),
+ gtk_window_set_default_size(GTK_WINDOW(window),
mq_config_get_integer(window->config, "window.width"),
mq_config_get_integer(window->config, "window.height"));
- g_signal_connect(window->window, "notify::is-maximized",
- G_CALLBACK(is_maximized_cb), window->config);
- g_signal_connect(window->window, "configure-event",
- G_CALLBACK(configure_event_cb), window->config);
- g_signal_connect(window->window, "window-state-event",
- G_CALLBACK(window_state_event_cb), window);
- g_signal_connect(window->window, "delete-event",
- G_CALLBACK(delete_event_cb), window);
- g_signal_connect(window->window, "destroy",
- G_CALLBACK(destroy_cb), window);
+
+ window->root_tab = mq_tab_page_new_root(window);
+
+ if (window->uris && window->uris[0]) {
+ for (i = 0; window->uris && window->uris[i]; ++i) {
+ mq_tab_page_new_relative(window->uris[i],
+ window->root_tab);
+ }
+ } else {
+ mq_tab_page_new_relative(NULL, window->root_tab);
+ }
+
+ gtk_widget_show_all(GTK_WIDGET(window));
+}
+
+static void
+get_property(GObject *object, guint property_id, GValue *value,
+ GParamSpec *param_spec)
+{
+ MqWindow *window;
+
+ window = MQ_WINDOW(object);
+
+ switch (property_id) {
+ case PROP_APPLICATION:
+ g_value_set_pointer(value, window->application);
+ break;
+ case PROP_URIS:
+ g_value_set_pointer(value, window->uris);
+ 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)
+{
+ MqWindow *window;
+
+ window = MQ_WINDOW(object);
+
+ switch (property_id) {
+ case PROP_APPLICATION:
+ window->application = g_value_get_pointer(value);
+ window->config = mq_application_get_config(
+ window->application);
+ break;
+ case PROP_URIS:
+ window->uris = g_value_get_pointer(value);
+ break;
+ ;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id,
+ param_spec);
+ break;
+ }
+}
+
+static void
+mq_window_class_init(MqWindowClass *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_APPLICATION] = g_param_spec_pointer(
+ "application",
+ "Application",
+ "The parent MqApplication instance",
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ obj_properties[PROP_URIS] = g_param_spec_pointer(
+ "uris",
+ "URIs",
+ "A NULL-terminated string array of URIs 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_window_init(MqWindow *window)
+{
window->fullscreen = FALSE;
+ g_signal_connect(window, "notify::is-maximized",
+ G_CALLBACK(is_maximized_cb), NULL);
+ g_signal_connect(window, "configure-event",
+ G_CALLBACK(configure_event_cb), NULL);
+ g_signal_connect(window, "window-state-event",
+ G_CALLBACK(window_state_event_cb), NULL);
+ g_signal_connect(window, "delete-event",
+ G_CALLBACK(delete_event_cb), NULL);
+ g_signal_connect(window, "destroy",
+ G_CALLBACK(destroy_cb), NULL);
+
window->notebook = gtk_notebook_new();
gtk_notebook_set_scrollable(GTK_NOTEBOOK(window->notebook), TRUE);
gtk_notebook_set_group_name(GTK_NOTEBOOK(window->notebook), "mq-tabs");
gtk_widget_set_can_focus(window->notebook, FALSE);
- gtk_container_add(GTK_CONTAINER(window->window),
- window->notebook);
+ gtk_container_add(GTK_CONTAINER(window), window->notebook);
g_signal_connect(window->notebook, "switch-page",
G_CALLBACK(switch_page_cb), window);
g_signal_connect(window->notebook, "page-reordered",
G_CALLBACK(update_positions), window);
+}
- window->root_tab = mq_tab_new_root(window);
-
- if (uris && uris[0]) {
- for (i = 0; uris && uris[i]; ++i) {
- mq_tab_new_relative(uris[i], window->root_tab);
- }
- } else {
- mq_tab_new_relative(NULL, window->root_tab);
- }
-
- gtk_widget_show_all(window->window);
-
- return window;
+MqWindow *
+mq_window_new(MqApplication *application, const gchar **uris)
+{
+ return g_object_new(MQ_TYPE_WINDOW,
+ "type", GTK_WINDOW_TOPLEVEL,
+ "application", application,
+ "uris", uris,
+ NULL);
}
void
mq_window_quit(MqWindow *window)
{
- mq_application_quit(window->application, GTK_WINDOW(window->window));
+ mq_application_quit(window->application, GTK_WINDOW(window));
}
MqApplication *
@@ -252,9 +362,9 @@ void
mq_window_toggle_fullscreen(MqWindow *window)
{
if (!window->fullscreen) {
- gtk_window_fullscreen(GTK_WINDOW(window->window));
+ gtk_window_fullscreen(GTK_WINDOW(window));
} else {
- gtk_window_unfullscreen(GTK_WINDOW(window->window));
+ gtk_window_unfullscreen(GTK_WINDOW(window));
}
}
@@ -288,7 +398,7 @@ mq_window_get_current_tab(MqWindow *window)
guint
mq_window_get_num_tabs(MqWindow *window)
{
- return mq_tab_get_tree_size(window->root_tab) - 1;
+ return mq_tab_page_get_tree_size(window->root_tab) - 1;
}
void
@@ -302,17 +412,17 @@ mq_window_update_tab_title(MqWindow *window, guint position, const gchar *title)
void
mq_window_scroll_tab_labels(MqWindow *window)
{
- mq_tab_scroll_tab_labels(window->root_tab);
+ mq_tab_page_scroll_tab_labels(window->root_tab);
}
void
mq_window_begin_scrolling_tab_labels(MqWindow *window)
{
- mq_tab_begin_scrolling_tab_labels(window->root_tab);
+ mq_tab_page_begin_scrolling_tab_labels(window->root_tab);
}
void
mq_window_end_scrolling_tab_labels(MqWindow *window)
{
- mq_tab_end_scrolling_tab_labels(window->root_tab);
+ mq_tab_page_end_scrolling_tab_labels(window->root_tab);
}
diff --git a/src/window.h b/src/window.h
index 94c526b..3e82263 100644
--- a/src/window.h
+++ b/src/window.h
@@ -19,25 +19,35 @@
* along with Marquee. If not, see <http://www.gnu.org/licenses/>.
*/
-typedef struct MqWindow MqWindow;
-
#ifndef MQ_WINDOW_H
#define MQ_WINDOW_H
+typedef struct _MqWindow MqWindow;
+typedef struct _MqWindowClass MqWindowClass;
+
+#include <glib.h>
#include <gtk/gtk.h>
#include "application.h"
-#include "tab.h"
-
-struct MqWindow {
- MqApplication *application;
- MqConfig *config;
- GtkWidget *window;
- GtkWidget *notebook;
- MqTab *root_tab;
- guint current_tab;
- gboolean fullscreen;
-};
+#include "config.h"
+#include "tab-page.h"
+
+G_BEGIN_DECLS
+
+#define MQ_TYPE_WINDOW (mq_window_get_type())
+#define MQ_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ MQ_TYPE_WINDOW, MqWindow))
+#define MQ_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ MQ_TYPE_WINDOW))
+#define MQ_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
+ MQ_TYPE_WINDOW, MqWindowClass))
+#define MQ_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ MQ_TYPE_WINDOW))
+#define MQ_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+ MQ_TYPE_WINDOW, MqWindowClass))
+
+GType
+mq_window_get_type(void);
MqWindow *
mq_window_new(MqApplication *application, const gchar **uris);
@@ -77,4 +87,6 @@ mq_window_begin_scrolling_tab_labels(MqWindow *window);
void
mq_window_end_scrolling_tab_labels(MqWindow *window);
-#endif
+G_END_DECLS
+
+#endif /* MQ_WINDOW_H */