From 8fc032f2f64f4b7eb022adb0b1255a5a68df109f Mon Sep 17 00:00:00 2001 From: Patrick McDermott Date: Sun, 22 Oct 2017 19:09:22 -0400 Subject: Merge branch 'gobjectification' --- (limited to 'src') 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 . */ +#include "about.h" + #include #include -#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 #include #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 . */ +#include "application.h" + #include #include #include -#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 . */ -typedef struct MqApplication MqApplication; - #ifndef MQ_APPLICATION_H #define MQ_APPLICATION_H +typedef struct MqApplication MqApplication; + #include #include #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 #endif -#include +#include "config.h" + #include +#include #include -#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 . */ -typedef struct MqConfig MqConfig; - #ifndef MQ_CONFIG_H #define MQ_CONFIG_H -#include +typedef struct MqConfig MqConfig; -struct MqConfig { - gchar *profile; - gchar *file_name; - GKeyFile *key_file; - gboolean types_and_cbs_set; - GHashTable *types_and_cbs; -}; +#include 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 . */ -#include - #include "gpl-3-0.h" +#include + /* * From * 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 . */ +#include "html.h" + #include #include #include #include -#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 . - */ - -typedef struct MqTabBody MqTabBody; - -#ifndef MQ_TAB_BODY_H -#define MQ_TAB_BODY_H - -#include -#include - -#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 . - */ - -#include -#include - -#include -#include - -#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: 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 . - */ - -typedef struct MqTabChrome MqTabChrome; - -#ifndef MQ_TAB_CHROME_H -#define MQ_TAB_CHROME_H - -#include -#include - -#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 . + */ + +#include "tab-label.h" + +#include +#include +#include + +#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 . + */ + +#ifndef MQ_TAB_LABEL_H +#define MQ_TAB_LABEL_H + +typedef struct _MqTabLabel MqTabLabel; +typedef struct _MqTabLabelClass MqTabLabelClass; + +#include +#include +#include + +#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 . + */ + +#include "tab-page.h" + +#include +#include + +#include +#include +#include + +#include "application.h" +#include "tab-label.h" +#include "toolbars/find-toolbar.h" +#include "toolbars/navigation-toolbar.h" +#include "web-view.h" +#include "window.h" + +typedef enum { + CREATE_NONE, + CREATE_ROOT, + CREATE_SIBLING, + CREATE_CHILD, + N_CREATE_TYPES +} CreateType; + +struct _MqTabPage { + GtkBox parent_instance; + CreateType create_type; + MqWindow *window; + 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 . + */ + +#ifndef MQ_TAB_PAGE_H +#define MQ_TAB_PAGE_H + +typedef struct _MqTabPage MqTabPage; +typedef struct _MqTabPageClass MqTabPageClass; + +#include + +#include +#include +#include + +#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 . - */ - -#include - -#include - -#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 . - */ - -typedef struct MqTab MqTab; - -#ifndef MQ_TAB_H -#define MQ_TAB_H - -#include - -#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 . + */ + +#include "find-toolbar.h" + +#include +#include +#include + +#include "../web-view.h" + +struct _MqFindToolbar { + GtkRevealer parent_instance; + GtkWidget *search_entry; + GtkWidget *matches_label; + gboolean match_case; + gboolean searching; + WebKitFindController *find_controller; +}; + +enum { + PROP_WEB_VIEW = 1, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqFindToolbarClass { + GtkRevealerClass parent_class; +}; + +G_DEFINE_TYPE(MqFindToolbar, mq_find_toolbar, GTK_TYPE_REVEALER) + +static void +search(MqFindToolbar *find_toolbar, gboolean forward) +{ + guint32 find_options; + + find_options = WEBKIT_FIND_OPTIONS_WRAP_AROUND; + if (!find_toolbar->match_case) { + find_options |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE; + } + if (!forward) { + find_options |= WEBKIT_FIND_OPTIONS_BACKWARDS; + } + webkit_find_controller_search(find_toolbar->find_controller, + gtk_entry_get_text(GTK_ENTRY(find_toolbar->search_entry)), + find_options, G_MAXUINT); + find_toolbar->searching = TRUE; +} + +static void +search_finished(MqFindToolbar *find_toolbar) +{ + find_toolbar->searching = FALSE; + webkit_find_controller_search_finish(find_toolbar->find_controller); +} + +static void +hide(MqFindToolbar *find_toolbar) +{ + gtk_revealer_set_reveal_child(GTK_REVEALER(find_toolbar), + FALSE); + gtk_label_set_text(GTK_LABEL(find_toolbar->matches_label), NULL); + search_finished(find_toolbar); +} + +static void +search_changed_cb(GtkSearchEntry G_GNUC_UNUSED *entry, + MqFindToolbar *find_toolbar) +{ + search(find_toolbar, TRUE); +} + +static gboolean +search_key_press_event_cb(GtkSearchEntry G_GNUC_UNUSED *entry, + GdkEventKey *event, MqFindToolbar *find_toolbar) +{ + switch (event->keyval) { + case GDK_KEY_Escape: + hide(find_toolbar); + return TRUE; + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + search(find_toolbar, + !(event->state & GDK_SHIFT_MASK)); + return TRUE; + default: + return FALSE; + } +} + +static void +prev_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqFindToolbar *find_toolbar) +{ + /* Calling this method before webkit_find_controller_search() or + * webkit_find_controller_count_matches() is a programming error. */ + if (find_toolbar->searching) { + webkit_find_controller_search_previous( + find_toolbar->find_controller); + } else { + search(find_toolbar, FALSE); + } +} + +static void +next_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqFindToolbar *find_toolbar) +{ + /* Calling this method before webkit_find_controller_search() or + * webkit_find_controller_count_matches() is a programming error. */ + if (find_toolbar->searching) { + webkit_find_controller_search_next( + find_toolbar->find_controller); + } else { + search(find_toolbar, TRUE); + } +} + +static void +match_case_toggled_cb(GtkToggleButton *toggle_button, + MqFindToolbar *find_toolbar) +{ + find_toolbar->match_case = gtk_toggle_button_get_active(toggle_button); + search(find_toolbar, TRUE); +} + +static void +close_clicked_cb(GtkButton G_GNUC_UNUSED *button, MqFindToolbar *find_toolbar) +{ + hide(find_toolbar); +} + +static void +found_text_cb(WebKitFindController G_GNUC_UNUSED *find_controller, + guint match_count, MqFindToolbar *find_toolbar) +{ + gchar *text; + + if (match_count == 1) { + text = g_strdup("1 match"); + } else { + text = g_strdup_printf("%u matches", match_count); + } + gtk_label_set_text(GTK_LABEL(find_toolbar->matches_label), text); + g_free(text); + +} + +static void +failed_to_find_text_cb(WebKitFindController G_GNUC_UNUSED *find_controller, + MqFindToolbar *find_toolbar) +{ + gtk_label_set_text(GTK_LABEL(find_toolbar->matches_label), + "No matches"); +} + +static void +set_web_view(MqFindToolbar *find_toolbar, MqWebView *web_view) +{ + find_toolbar->find_controller = webkit_web_view_get_find_controller( + WEBKIT_WEB_VIEW(web_view)); + g_signal_connect(find_toolbar->find_controller, "found-text", + G_CALLBACK(found_text_cb), find_toolbar); + g_signal_connect(find_toolbar->find_controller, "failed-to-find-text", + G_CALLBACK(failed_to_find_text_cb), find_toolbar); +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + switch (property_id) { + case PROP_WEB_VIEW: + g_value_set_object(value, NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *param_spec) +{ + MqFindToolbar *find_toolbar; + + find_toolbar = MQ_FIND_TOOLBAR(object); + + switch (property_id) { + case PROP_WEB_VIEW: + set_web_view(find_toolbar, g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_find_toolbar_class_init(MqFindToolbarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + obj_properties[PROP_WEB_VIEW] = g_param_spec_object( + "web-view", + "MqWebView", + "The associated MqWebView instance", + MQ_TYPE_WEB_VIEW, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + g_object_class_install_properties(object_class, N_PROPERTIES, + obj_properties); +} + +static void +mq_find_toolbar_init(MqFindToolbar *find_toolbar) +{ + GtkWidget *close_button; + GtkWidget *prev_button; + GtkWidget *next_button; + GtkWidget *match_case_button; + GtkWidget *box; + + /* Search entry */ + find_toolbar->search_entry = gtk_search_entry_new(); + g_signal_connect(find_toolbar->search_entry, "search-changed", + G_CALLBACK(search_changed_cb), find_toolbar); + g_signal_connect(find_toolbar->search_entry, "key-press-event", + G_CALLBACK(search_key_press_event_cb), find_toolbar); + + /* Previous button */ + prev_button = gtk_button_new_from_icon_name("go-up", + GTK_ICON_SIZE_BUTTON); + gtk_widget_set_tooltip_text(prev_button, "Find previous occurrence"); + gtk_widget_set_can_focus(prev_button, FALSE); + g_signal_connect(prev_button, "clicked", + G_CALLBACK(prev_clicked_cb), find_toolbar); + + /* Next button */ + next_button = gtk_button_new_from_icon_name("go-down", + GTK_ICON_SIZE_BUTTON); + gtk_widget_set_tooltip_text(next_button, "Find next occurrence"); + gtk_widget_set_can_focus(next_button, FALSE); + g_signal_connect(next_button, "clicked", + G_CALLBACK(next_clicked_cb), find_toolbar); + + /* Case sensitivity button */ + match_case_button = gtk_toggle_button_new_with_label("Match case"); + gtk_widget_set_tooltip_text(match_case_button, + "Search with case sensitivity"); + gtk_widget_set_can_focus(match_case_button, FALSE); + g_signal_connect(match_case_button, "toggled", + G_CALLBACK(match_case_toggled_cb), find_toolbar); + + /* Matches label */ + find_toolbar->matches_label = gtk_label_new(NULL); + + /* Close button */ + close_button = gtk_button_new_from_icon_name("window-close", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE); + gtk_widget_set_tooltip_text(close_button, "Close find bar"); + gtk_widget_set_can_focus(close_button, FALSE); + g_signal_connect(close_button, "clicked", + G_CALLBACK(close_clicked_cb), find_toolbar); + + /* Box */ + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(box), find_toolbar->search_entry, + FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), prev_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), next_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), match_case_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), find_toolbar->matches_label, + FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(box), close_button, FALSE, FALSE, 0); + + /* Revealer */ + gtk_revealer_set_transition_type(GTK_REVEALER(find_toolbar), + GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN); + gtk_container_add(GTK_CONTAINER(find_toolbar), box); + + find_toolbar->match_case = FALSE; + find_toolbar->searching = FALSE; +} + +GtkWidget * +mq_find_toolbar_new(MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_FIND_TOOLBAR, + "web-view", web_view, + NULL); +} + +void +mq_find_toolbar_reveal(MqFindToolbar *find_toolbar) +{ + gtk_revealer_set_reveal_child(GTK_REVEALER(find_toolbar), TRUE); + gtk_widget_grab_focus(find_toolbar->search_entry); +} diff --git a/src/toolbars/find-toolbar.h b/src/toolbars/find-toolbar.h new file mode 100644 index 0000000..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 . + */ + +#ifndef MQ_TOOLBARS_FIND_TOOLBAR_H +#define MQ_TOOLBARS_FIND_TOOLBAR_H + +typedef struct _MqFindToolbar MqFindToolbar; +typedef struct _MqFindToolbarClass MqFindToolbarClass; + +#include +#include +#include + +#include "../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_FIND_TOOLBAR (mq_find_toolbar_get_type()) +#define MQ_FIND_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MQ_TYPE_FIND_TOOLBAR, MqFindToolbar)) +#define MQ_IS_FIND_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + MQ_TYPE_FIND_TOOLBAR)) +#define MQ_FIND_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + MQ_TYPE_FIND_TOOLBAR, \ + MqFindToolbarClass)) +#define MQ_IS_FIND_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + MQ_TYPE_FIND_TOOLBAR)) +#define MQ_FIND_TOOLBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + MQ_TYPE_FIND_TOOLBAR, \ + MqFindToolbarClass)) + +GType +mq_find_toolbar_get_type(void); + +GtkWidget * +mq_find_toolbar_new(MqWebView *web_view); + +void +mq_find_toolbar_reveal(MqFindToolbar *find_toolbar); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_FIND_TOOLBAR_H */ diff --git a/src/toolbars/local.mk b/src/toolbars/local.mk new file mode 100644 index 0000000..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 . + */ + +#include "navigation-toolbar.h" + +#include +#include +#include + +#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 . + */ + +#ifndef MQ_TOOLBARS_NAVIGATION_TOOLBAR_H +#define MQ_TOOLBARS_NAVIGATION_TOOLBAR_H + +typedef struct _MqNavigationToolbar MqNavigationToolbar; +typedef struct _MqNavigationToolbarClass MqNavigationToolbarClass; + +#include +#include + +#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 . + */ + +#include "back-forward-button-box.h" + +#include +#include +#include + +#include "../../web-view.h" + +struct _MqBackForwardButtonBox { + GtkToolItem parent_instance; + MqWebView *web_view; + GtkWidget *back_button; + GtkWidget *forward_button; + gint back_items; + GtkWidget *back_forward_popover; +}; + +enum { + PROP_WEB_VIEW = 1, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqBackForwardButtonBoxClass { + GtkToolItemClass parent_class; +}; + +G_DEFINE_TYPE(MqBackForwardButtonBox, mq_back_forward_button_box, + GTK_TYPE_TOOL_ITEM) + +static void +back_forward_list_changed_cb( + WebKitBackForwardList G_GNUC_UNUSED *back_forward_list, + WebKitBackForwardListItem G_GNUC_UNUSED *item_added, + gpointer G_GNUC_UNUSED items_removed, + MqBackForwardButtonBox *back_forward_button_box) +{ + gtk_widget_set_sensitive(back_forward_button_box->back_button, + webkit_web_view_can_go_back( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view))); + gtk_widget_set_sensitive(back_forward_button_box->forward_button, + webkit_web_view_can_go_forward( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view))); +} + +static void +back_clicked_cb(GtkButton G_GNUC_UNUSED *toolbutton, + MqBackForwardButtonBox *back_forward_button_box) +{ + webkit_web_view_go_back( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view)); +} + +static void +forward_clicked_cb(GtkButton G_GNUC_UNUSED *toolbutton, + MqBackForwardButtonBox *back_forward_button_box) +{ + webkit_web_view_go_forward( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view)); +} + +static GtkWidget * +list_item_new(WebKitBackForwardListItem *list_item, gint type) +{ + GtkWidget *label; + GtkWidget *icon_stack; + GtkWidget *icon; + GtkWidget *box; + + label = gtk_label_new(webkit_back_forward_list_item_get_title( + list_item)); + gtk_widget_set_halign(label, GTK_ALIGN_START); + + icon_stack = gtk_stack_new(); + switch (type) { + case 0: + icon = gtk_radio_button_new(NULL); + gtk_stack_add_named(GTK_STACK(icon_stack), icon, + "current"); + gtk_widget_set_can_focus(icon, FALSE); + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_image_new_from_icon_name("go-previous", + GTK_ICON_SIZE_BUTTON), "back"); + break; + case -1: + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_image_new_from_icon_name("go-previous", + GTK_ICON_SIZE_BUTTON), "back"); + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_radio_button_new(NULL), "current"); + break; + case 1: + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_image_new_from_icon_name("go-next", + GTK_ICON_SIZE_BUTTON), "forward"); + gtk_stack_add_named(GTK_STACK(icon_stack), + gtk_radio_button_new(NULL), "current"); + break; + } + + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(box), icon_stack, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); + gtk_widget_set_tooltip_text(box, + webkit_back_forward_list_item_get_uri(list_item)); + + return box; +} + +static void +list_box_row_activated_cb(GtkListBox G_GNUC_UNUSED *box, + GtkListBoxRow *row, MqBackForwardButtonBox *back_forward_button_box) +{ + webkit_web_view_go_to_back_forward_list_item( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view), + webkit_back_forward_list_get_nth_item( + webkit_web_view_get_back_forward_list(WEBKIT_WEB_VIEW( + back_forward_button_box->web_view)), + gtk_list_box_row_get_index(row) - + back_forward_button_box->back_items)); + + gtk_widget_hide(back_forward_button_box->back_forward_popover); +} + +static void +text_button_toggled_cb(GtkToggleButton *toggle_button, + GtkStack *stack) +{ + /* Use gtk_widget_show() and gtk_widget_hide() instead of + * gtk_stack_set_visible_child() so that the stack fits the size of only + * the visible child. */ + gtk_widget_show_all(gtk_stack_get_child_by_name(stack, + gtk_toggle_button_get_active(toggle_button) ? + "text" : "list")); + gtk_widget_hide(gtk_stack_get_child_by_name(stack, + gtk_toggle_button_get_active(toggle_button) ? + "list" : "text")); +} + +static gboolean +event_box_button_press_cb(GtkWidget *widget, + GdkEventButton G_GNUC_UNUSED *event, + MqBackForwardButtonBox *back_forward_button_box) +{ + WebKitBackForwardList *back_forward_list; + GtkWidget *list_box; + GtkWidget *text_view; + GtkTextBuffer *text_buffer; + GList *list_item; + GtkTextIter text_iter; + gchar *str; + GtkTextTag *text_tag; + GtkWidget *list_scrolled_window; + GtkWidget *text_scrolled_window; + GtkWidget *stack; + GtkWidget *toggle_button; + GtkWidget *box; + + /* Get the back/forward list for the Web view. */ + back_forward_list = webkit_web_view_get_back_forward_list( + WEBKIT_WEB_VIEW(back_forward_button_box->web_view)); + + /* Set up the list box. */ + list_box = gtk_list_box_new(); + gtk_list_box_set_selection_mode(GTK_LIST_BOX(list_box), + GTK_SELECTION_BROWSE); + gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(list_box), TRUE); + g_signal_connect(list_box, "row-activated", + G_CALLBACK(list_box_row_activated_cb), + back_forward_button_box); + + /* Set up the text view. */ + text_view = gtk_text_view_new(); + text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view)); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view), FALSE); + + /* Insert forward list items. */ + /* The forward list is backwards, so we need to prepend each item to our + * list box and text buffer. */ + list_item = webkit_back_forward_list_get_forward_list( + back_forward_list); + for (; list_item; list_item = list_item->next) { + gtk_list_box_prepend(GTK_LIST_BOX(list_box), + list_item_new(list_item->data, 1)); + gtk_text_buffer_get_start_iter(text_buffer, &text_iter); + str = g_strdup_printf("\n%s", + webkit_back_forward_list_item_get_uri(list_item->data)); + gtk_text_buffer_insert(text_buffer, &text_iter, str, -1); + g_free(str); + } + + /* Insert the current item. */ + gtk_list_box_prepend(GTK_LIST_BOX(list_box), list_item_new( + webkit_back_forward_list_get_current_item( + back_forward_list), 0)); + gtk_list_box_select_row(GTK_LIST_BOX(list_box), + gtk_list_box_get_row_at_index(GTK_LIST_BOX(list_box), 0)); + gtk_text_buffer_get_start_iter(text_buffer, &text_iter); + text_tag = gtk_text_buffer_create_tag(text_buffer, NULL, "weight", + PANGO_WEIGHT_BOLD, NULL); + gtk_text_buffer_insert_with_tags(text_buffer, &text_iter, + webkit_back_forward_list_item_get_uri( + webkit_back_forward_list_get_current_item( + back_forward_list)), -1, text_tag, NULL); + + /* Insert back list items. */ + list_item = webkit_back_forward_list_get_back_list( + back_forward_list); + back_forward_button_box->back_items = 0; + for (; list_item; list_item = list_item->next) { + gtk_list_box_prepend(GTK_LIST_BOX(list_box), + list_item_new(list_item->data, -1)); + gtk_text_buffer_get_start_iter(text_buffer, &text_iter); + str = g_strdup_printf("%s\n", + webkit_back_forward_list_item_get_uri(list_item->data)); + gtk_text_buffer_insert(text_buffer, &text_iter, str, -1); + g_free(str); + ++back_forward_button_box->back_items; + } + + /* + * The following GtkScrolledWindow widgets have hardcoded minimum sizes, + * because there seems to be (in GTK+ versions before 3.22) no way to + * set the natural size of GtkScrolledWindow and its GtkViewport. + * + * I tried: + * + * - Setting size requests of the sizes of child widgets plus + * scrollbars (commit 2205669) + * - Putting GtkScrolledWindow widgets in stacks with non-scrolled + * versions of children, to allocate more area for the + * GtkScrolledWindow widgets to fill (commits 922cdef and 90686fa) + * - Setting policies to disable scrollbars upon GtkScrolledWindow + * instantiation, then enabling scrollbars on the GtkScrolledWindow + * "size-allocate" signal (commits c534c7e and 42ca783) + * + * Attempts to match the size of a GtkScrolledWindow's GtkViewport to + * its child's size don't allow the GtkScrolledWindow and GtkViewport to + * shrink when necessary to fit within the window. Therefore, such + * methods are equivalent to simply not using GtkScrolledWindow at all. + * And there appears to be no easy way to calculate the available size + * (the size of the GtkWindow minus all of the surrounding widgets) to + * manually manage a GtkViewport's size. + * + * GtkScrolledWindow or GtkViewport ignores the child's preferred + * (minimum and natural) sizes and apparently just sets a hardcoded size + * of about 64x64 px. I considered subclassing GtkScrolledWindow or + * GtkViewport, but I'm not sure where the hardcoded size is set. + * + * The functions gtk_scrolled_window_set_propagate_natural_width() and + * gtk_scrolled_window_set_propagate_natural_height() were introduced in + * GTK+ 3.22 and probably do what I want. However, I want to maintain + * compatibility with at least GTK+ 3.12 or 3.14. + */ + + /* Set up the list scrolled window. */ + list_scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_min_content_width( + GTK_SCROLLED_WINDOW(list_scrolled_window), 400); + gtk_scrolled_window_set_min_content_height( + GTK_SCROLLED_WINDOW(list_scrolled_window), 200); + gtk_container_add(GTK_CONTAINER(list_scrolled_window), list_box); + + /* Set up the text scrolled window. */ + text_scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_min_content_width( + GTK_SCROLLED_WINDOW(text_scrolled_window), 400); + gtk_scrolled_window_set_min_content_height( + GTK_SCROLLED_WINDOW(text_scrolled_window), 200); + gtk_container_add(GTK_CONTAINER(text_scrolled_window), text_view); + + /* Set up the stack. */ + stack = gtk_stack_new(); + gtk_stack_add_named(GTK_STACK(stack), list_scrolled_window, "list"); + gtk_stack_add_named(GTK_STACK(stack), text_scrolled_window, "text"); + + /* Set up the toggle button. */ + toggle_button = gtk_toggle_button_new(); + gtk_button_set_image(GTK_BUTTON(toggle_button), + gtk_image_new_from_icon_name("edit-select-all", + GTK_ICON_SIZE_SMALL_TOOLBAR)); + gtk_widget_set_halign(toggle_button, GTK_ALIGN_START); + g_signal_connect(toggle_button, "toggled", + G_CALLBACK(text_button_toggled_cb), GTK_STACK(stack)); + + /* Set up the containing box. */ + box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start(GTK_BOX(box), toggle_button, TRUE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), stack, TRUE, FALSE, 0); + + /* Set up the popover. */ + back_forward_button_box->back_forward_popover = gtk_popover_new(widget); + gtk_container_add( + GTK_CONTAINER(back_forward_button_box->back_forward_popover), + box); + + /* NB: gtk_popover_popup() is new in GTK+ 3.22. */ + gtk_widget_show_all(back_forward_button_box->back_forward_popover); + gtk_widget_hide(text_view); + + return FALSE; +} + +static void +set_web_view(MqBackForwardButtonBox *back_forward_button_box, + MqWebView *web_view) +{ + back_forward_button_box->web_view = web_view; + + g_signal_connect(webkit_web_view_get_back_forward_list( + WEBKIT_WEB_VIEW(web_view)), + "changed", G_CALLBACK(back_forward_list_changed_cb), + back_forward_button_box); +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + MqBackForwardButtonBox *back_forward_button_box; + + back_forward_button_box = MQ_BACK_FORWARD_BUTTON_BOX(object); + + switch (property_id) { + case PROP_WEB_VIEW: + g_value_set_object(value, + back_forward_button_box->web_view); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *param_spec) +{ + MqBackForwardButtonBox *back_forward_button_box; + + back_forward_button_box = MQ_BACK_FORWARD_BUTTON_BOX(object); + + switch (property_id) { + case PROP_WEB_VIEW: + set_web_view(back_forward_button_box, + g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_back_forward_button_box_class_init(MqBackForwardButtonBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + obj_properties[PROP_WEB_VIEW] = g_param_spec_object( + "web-view", + "MqWebView", + "The associated MqWebView instance", + MQ_TYPE_WEB_VIEW, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + g_object_class_install_properties(object_class, N_PROPERTIES, + obj_properties); +} + +static void +mq_back_forward_button_box_init(MqBackForwardButtonBox *back_forward_button_box) +{ + GtkWidget *box; + GtkWidget *event_box; + + /* Back button */ + back_forward_button_box->back_button = gtk_button_new_from_icon_name( + "go-previous", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(back_forward_button_box->back_button, + "Go back one page"); + g_signal_connect(back_forward_button_box->back_button, "clicked", + G_CALLBACK(back_clicked_cb), back_forward_button_box); + + /* Forward button */ + back_forward_button_box->forward_button = gtk_button_new_from_icon_name( + "go-next", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_set_tooltip_text(back_forward_button_box->forward_button, + "Go forward one page"); + g_signal_connect(back_forward_button_box->forward_button, "clicked", + G_CALLBACK(forward_clicked_cb), back_forward_button_box); + + /* Box */ + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(box), + back_forward_button_box->back_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), + back_forward_button_box->forward_button, FALSE, FALSE, 0); + gtk_style_context_add_class(gtk_widget_get_style_context(box), + "linked"); + + /* Event box (MqBackForwardButtonBox) */ + event_box = gtk_event_box_new(); + gtk_container_add(GTK_CONTAINER(event_box), box); + g_signal_connect(event_box, "button-press-event", + G_CALLBACK(event_box_button_press_cb), back_forward_button_box); + + gtk_container_add(GTK_CONTAINER(back_forward_button_box), event_box); +} + +GtkToolItem * +mq_back_forward_button_box_new(MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_BACK_FORWARD_BUTTON_BOX, + "web-view", web_view, + NULL); +} diff --git a/src/toolbars/navigation/back-forward-button-box.h b/src/toolbars/navigation/back-forward-button-box.h new file mode 100644 index 0000000..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 . + */ + +#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 +#include + +#include "../../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_BACK_FORWARD_BUTTON_BOX \ + (mq_back_forward_button_box_get_type()) +#define MQ_BACK_FORWARD_BUTTON_BOX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MQ_TYPE_BACK_FORWARD_BUTTON_BOX, \ + MqBackForwardButtonBox)) +#define MQ_IS_BACK_FORWARD_BUTTON_BOX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MQ_TYPE_BACK_FORWARD_BUTTON_BOX)) +#define MQ_BACK_FORWARD_BUTTON_BOX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MQ_TYPE_BACK_FORWARD_BUTTON_BOX, \ + MqBackForwardButtonBoxClass)) +#define MQ_IS_BACK_FORWARD_BUTTON_BOX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MQ_TYPE_BACK_FORWARD_BUTTON_BOX)) +#define MQ_BACK_FORWARD_BUTTON_BOX_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MQ_TYPE_BACK_FORWARD_BUTTON_BOX, \ + MqBackForwardButtonBoxClass)) + +GType +mq_back_forward_button_box_get_type(void); + +GtkToolItem * +mq_back_forward_button_box_new(MqWebView *web_view); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_BACK_FORWARD_BUTTON_BOX_H */ diff --git a/src/toolbars/navigation/home-button.c b/src/toolbars/navigation/home-button.c new file mode 100644 index 0000000..a9d5f75 --- /dev/null +++ b/src/toolbars/navigation/home-button.c @@ -0,0 +1,159 @@ +/* + * Home button + * + * Copyright (C) 2017 Patrick McDermott + * + * This file is part of Marquee. + * + * Marquee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Marquee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Marquee. If not, see . + */ + +#include "home-button.h" + +#include +#include +#include + +#include "../../config.h" +#include "../../web-view.h" + +struct _MqHomeButton { + GtkToolButton parent_instance; + MqConfig *config; + MqWebView *web_view; + GtkWidget *stop_icon; + GtkWidget *reload_icon; +}; + +enum { + PROP_CONFIG = 1, + PROP_WEB_VIEW, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqHomeButtonClass { + GtkToolButtonClass parent_class; +}; + +G_DEFINE_TYPE(MqHomeButton, mq_home_button, GTK_TYPE_TOOL_BUTTON) + +static void +clicked_cb(MqHomeButton *home_button) +{ + const gchar *uri; + + uri = mq_config_get_string(home_button->config, "tabs.home"); + + mq_web_view_load_uri(home_button->web_view, uri); +} + +static void +set_config(MqHomeButton *home_button, MqConfig *config) +{ + home_button->config = config; +} + +static void +set_web_view(MqHomeButton *home_button, MqWebView *web_view) +{ + home_button->web_view = web_view; +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + switch (property_id) { + case PROP_CONFIG: + g_value_set_pointer(value, NULL); + break; + case PROP_WEB_VIEW: + g_value_set_object(value, NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *param_spec) +{ + MqHomeButton *home_button; + + home_button = MQ_HOME_BUTTON(object); + + switch (property_id) { + case PROP_CONFIG: + set_config(home_button, g_value_get_pointer(value)); + break; + case PROP_WEB_VIEW: + set_web_view(home_button, g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_home_button_class_init(MqHomeButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + obj_properties[PROP_CONFIG] = g_param_spec_pointer( + "config", + "MqConfig", + "The application's MqConfig instance", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + obj_properties[PROP_WEB_VIEW] = g_param_spec_object( + "web-view", + "MqWebView", + "The associated MqWebView instance", + MQ_TYPE_WEB_VIEW, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + g_object_class_install_properties(object_class, N_PROPERTIES, + obj_properties); +} + +static void +mq_home_button_init(MqHomeButton *home_button) +{ + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(home_button), + gtk_image_new_from_icon_name("go-home", + GTK_ICON_SIZE_SMALL_TOOLBAR)); + gtk_tool_button_set_label(GTK_TOOL_BUTTON(home_button), "Home"); + gtk_widget_set_tooltip_text(GTK_WIDGET(home_button), + "Load the home page"); + g_signal_connect(home_button, "clicked", G_CALLBACK(clicked_cb), NULL); +} + +GtkToolItem * +mq_home_button_new(MqConfig *config, MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_HOME_BUTTON, + "config", config, + "web-view", web_view, + NULL); +} diff --git a/src/toolbars/navigation/home-button.h b/src/toolbars/navigation/home-button.h new file mode 100644 index 0000000..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 . + */ + +#ifndef MQ_TOOLBARS_NAVIGATION_HOME_BUTTON_H +#define MQ_TOOLBARS_NAVIGATION_HOME_BUTTON_H + +typedef struct _MqHomeButton MqHomeButton; +typedef struct _MqHomeButtonClass MqHomeButtonClass; + +#include +#include + +#include "../../config.h" +#include "../../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_HOME_BUTTON (mq_home_button_get_type()) +#define MQ_HOME_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MQ_TYPE_HOME_BUTTON, MqHomeButton)) +#define MQ_IS_HOME_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + MQ_TYPE_HOME_BUTTON)) +#define MQ_HOME_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + MQ_TYPE_HOME_BUTTON, MqHomeButtonClass)) +#define MQ_IS_HOME_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + MQ_TYPE_HOME_BUTTON)) +#define MQ_HOME_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + MQ_TYPE_HOME_BUTTON, MqHomeButtonClass)) + +GType +mq_home_button_get_type(void); + +GtkToolItem * +mq_home_button_new(MqConfig *config, MqWebView *web_view); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_HOME_BUTTON_H */ diff --git a/src/toolbars/navigation/local.mk b/src/toolbars/navigation/local.mk new file mode 100644 index 0000000..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 . + */ + +#include "main-menu.h" + +#include +#include +#include + +#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 . + */ + +#ifndef MQ_TOOLBARS_NAVIGATION_MAIN_MENU_H +#define MQ_TOOLBARS_NAVIGATION_MAIN_MENU_H + +typedef struct _MqMainMenu MqMainMenu; +typedef struct _MqMainMenuClass MqMainMenuClass; + +#include +#include + +#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 . + */ + +#include "stop-reload-button.h" + +#include +#include +#include + +#include "../../web-view.h" + +struct _MqStopReloadButton { + GtkToolButton parent_instance; + GtkWidget *stop_icon; + GtkWidget *reload_icon; + MqWebView *web_view; +}; + +enum { + PROP_WEB_VIEW = 1, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqStopReloadButtonClass { + GtkToolButtonClass parent_class; +}; + +G_DEFINE_TYPE(MqStopReloadButton, mq_stop_reload_button, GTK_TYPE_TOOL_BUTTON) + +static void +loading_cb(MqWebView *web_view, GParamSpec G_GNUC_UNUSED *param_spec, + MqStopReloadButton *stop_reload_button) +{ + if (webkit_web_view_is_loading(WEBKIT_WEB_VIEW(web_view))) { + gtk_tool_button_set_icon_widget( + GTK_TOOL_BUTTON(stop_reload_button), + stop_reload_button->stop_icon); + gtk_tool_button_set_label( + GTK_TOOL_BUTTON(stop_reload_button), "Stop"); + gtk_widget_set_tooltip_text( + GTK_WIDGET(stop_reload_button), + "Stop loading the current page"); + } else { + gtk_tool_button_set_icon_widget( + GTK_TOOL_BUTTON(stop_reload_button), + stop_reload_button->reload_icon); + gtk_tool_button_set_label( + GTK_TOOL_BUTTON(stop_reload_button), "Reload"); + gtk_widget_set_tooltip_text( + GTK_WIDGET(stop_reload_button), + "Reload the current page"); + } + gtk_widget_show_all(GTK_WIDGET(stop_reload_button)); +} + +static void +clicked_cb(MqStopReloadButton *stop_reload_button) +{ + WebKitWebView *web_view; + + web_view = WEBKIT_WEB_VIEW(stop_reload_button->web_view); + + if (webkit_web_view_is_loading(web_view)) { + webkit_web_view_stop_loading(web_view); + } else { + webkit_web_view_reload(web_view); + } +} + +static void +set_web_view(MqStopReloadButton *stop_reload_button, MqWebView *web_view) +{ + stop_reload_button->web_view = web_view; + + g_signal_connect(web_view, "notify::is-loading", + G_CALLBACK(loading_cb), stop_reload_button); +} + +static void +finalize(GObject *object) +{ + MqStopReloadButton *stop_reload_button; + + stop_reload_button = MQ_STOP_RELOAD_BUTTON(object); + + g_object_unref(stop_reload_button->stop_icon); + g_object_unref(stop_reload_button->reload_icon); + + G_OBJECT_CLASS(mq_stop_reload_button_parent_class)->finalize(object); +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + switch (property_id) { + case PROP_WEB_VIEW: + g_value_set_object(value, NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *param_spec) +{ + MqStopReloadButton *stop_reload_button; + + stop_reload_button = MQ_STOP_RELOAD_BUTTON(object); + + switch (property_id) { + case PROP_WEB_VIEW: + set_web_view(stop_reload_button, + g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_stop_reload_button_class_init(MqStopReloadButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + obj_properties[PROP_WEB_VIEW] = g_param_spec_object( + "web-view", + "MqWebView", + "The associated MqWebView instance", + MQ_TYPE_WEB_VIEW, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + g_object_class_install_properties(object_class, N_PROPERTIES, + obj_properties); +} + +static void +mq_stop_reload_button_init(MqStopReloadButton *stop_reload_button) +{ + /* Stop icon */ + stop_reload_button->stop_icon = gtk_image_new_from_icon_name( + "process-stop", GTK_ICON_SIZE_SMALL_TOOLBAR); + g_object_ref_sink(stop_reload_button->stop_icon); + + /* Reload icon */ + stop_reload_button->reload_icon = gtk_image_new_from_icon_name( + "view-refresh", GTK_ICON_SIZE_SMALL_TOOLBAR); + g_object_ref_sink(stop_reload_button->reload_icon); + + /* Button */ + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(stop_reload_button), + stop_reload_button->stop_icon); + gtk_tool_button_set_label(GTK_TOOL_BUTTON(stop_reload_button), "Stop"); + gtk_widget_set_tooltip_text(GTK_WIDGET(stop_reload_button), + "Stop loading the current page"); + g_signal_connect(stop_reload_button, "clicked", + G_CALLBACK(clicked_cb), NULL); +} + +GtkToolItem * +mq_stop_reload_button_new(MqWebView *web_view) +{ + return g_object_new(MQ_TYPE_STOP_RELOAD_BUTTON, + "web-view", web_view, + NULL); +} diff --git a/src/toolbars/navigation/stop-reload-button.h b/src/toolbars/navigation/stop-reload-button.h new file mode 100644 index 0000000..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 . + */ + +#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 +#include + +#include "../../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_STOP_RELOAD_BUTTON \ + (mq_stop_reload_button_get_type()) +#define MQ_STOP_RELOAD_BUTTON(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MQ_TYPE_STOP_RELOAD_BUTTON, \ + MqStopReloadButton)) +#define MQ_IS_STOP_RELOAD_BUTTON(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MQ_TYPE_STOP_RELOAD_BUTTON)) +#define MQ_STOP_RELOAD_BUTTON_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MQ_TYPE_STOP_RELOAD_BUTTON, \ + MqStopReloadButtonClass)) +#define MQ_IS_STOP_RELOAD_BUTTON_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MQ_TYPE_STOP_RELOAD_BUTTON)) +#define MQ_STOP_RELOAD_BUTTON_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MQ_TYPE_STOP_RELOAD_BUTTON, \ + MqStopReloadButtonClass)) + +GType +mq_stop_reload_button_get_type(void); + +GtkToolItem * +mq_stop_reload_button_new(MqWebView *web_view); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_STOP_RELOAD_BUTTON_H */ diff --git a/src/toolbars/navigation/uri-entry.c b/src/toolbars/navigation/uri-entry.c new file mode 100644 index 0000000..8ae818a --- /dev/null +++ b/src/toolbars/navigation/uri-entry.c @@ -0,0 +1,306 @@ +/* + * URI entry + * + * Copyright (C) 2017 Patrick McDermott + * + * This file is part of Marquee. + * + * Marquee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Marquee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Marquee. If not, see . + */ + +#include "uri-entry.h" + +#include +#include +#include + +#include "../../web-view.h" + +struct _MqUriEntry { + GtkToolItem parent_instance; + MqWebView *web_view; + GtkWidget *uri_entry; + gchar *hovered_link_uri; + PangoAttrList *hovered_link_style; + gboolean load_failed; +}; + +enum { + PROP_WEB_VIEW = 1, + PROP_URI, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = {NULL,}; + +struct _MqUriEntryClass { + GtkToolItemClass parent_class; +}; + +G_DEFINE_TYPE(MqUriEntry, mq_uri_entry, GTK_TYPE_TOOL_ITEM) + +static void +load_changed_cb(MqWebView *web_view, WebKitLoadEvent load_event, + MqUriEntry *uri_entry) +{ + const gchar *uri; + + switch (load_event) { + case WEBKIT_LOAD_STARTED: + case WEBKIT_LOAD_REDIRECTED: + case WEBKIT_LOAD_COMMITTED: + uri = mq_web_view_get_uri(web_view); + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), uri); + gtk_entry_set_attributes(GTK_ENTRY(uri_entry->uri_entry), + NULL); + break; + case WEBKIT_LOAD_FINISHED: + gtk_entry_set_progress_fraction( + GTK_ENTRY(uri_entry->uri_entry), 0.0); + break; + } +} + +static gboolean +load_failed_cb(WebKitWebView G_GNUC_UNUSED *web_view, + WebKitLoadEvent G_GNUC_UNUSED load_event, + gchar G_GNUC_UNUSED *failing_uri, GError G_GNUC_UNUSED *error, + MqUriEntry *uri_entry) +{ + uri_entry->load_failed = TRUE; + return FALSE; +} + +/* TODO: key in URI bar should reset to URI of current hovered link, if + * any, even if different from uri_entry->hovered_link_uri. */ +static void +mouse_target_changed_cb(MqWebView G_GNUC_UNUSED *web_view, + WebKitHitTestResult *hit_test_result, guint G_GNUC_UNUSED modifiers, + MqUriEntry *uri_entry) +{ + const gchar *uri; + + uri = mq_web_view_get_uri(web_view); + if (webkit_hit_test_result_context_is_link(hit_test_result)) { + if (gtk_widget_has_focus(uri_entry->uri_entry)) { + } else if (uri_entry->hovered_link_uri && + g_strcmp0(gtk_entry_get_text( + GTK_ENTRY(uri_entry->uri_entry)), + uri_entry->hovered_link_uri) != 0) { + /* The user has edited the hovered link URI in the URI + * bar. */ + } else if (g_strcmp0(gtk_entry_get_text( + GTK_ENTRY(uri_entry->uri_entry)), uri) == + 0) { + g_free(uri_entry->hovered_link_uri); + uri_entry->hovered_link_uri = g_strdup( + webkit_hit_test_result_get_link_uri( + hit_test_result)); + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), + uri_entry->hovered_link_uri); + gtk_entry_set_attributes(GTK_ENTRY(uri_entry->uri_entry), + uri_entry->hovered_link_style); + } else if (gtk_entry_get_attributes( + GTK_ENTRY(uri_entry->uri_entry)) == + uri_entry->hovered_link_style) { + /* The URI bar's text differs from the Web view's URI + * because the mouse was already targeting a different + * link. */ + g_free(uri_entry->hovered_link_uri); + uri_entry->hovered_link_uri = g_strdup( + webkit_hit_test_result_get_link_uri( + hit_test_result)); + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), + uri_entry->hovered_link_uri); + } + } else if (gtk_entry_get_attributes(GTK_ENTRY(uri_entry->uri_entry)) == + uri_entry->hovered_link_style) { + if (gtk_widget_has_focus(uri_entry->uri_entry)) { + } else if (g_strcmp0(gtk_entry_get_text( + GTK_ENTRY(uri_entry->uri_entry)), + uri_entry->hovered_link_uri) == 0) { + /* The user hasn't edited the hovered link URI in the + * URI bar. */ + g_free(uri_entry->hovered_link_uri); + uri_entry->hovered_link_uri = NULL; + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), uri); + gtk_entry_set_attributes(GTK_ENTRY(uri_entry->uri_entry), + NULL); + } + } +} + +static void +load_progress_cb(WebKitWebView *web_view, GParamSpec G_GNUC_UNUSED *param_spec, + MqUriEntry *uri_entry) +{ + /* + * If loading fails, the WebKitWebView's "estimated-load-progress" is + * set to 1.0 after signals like "load-changed" and "load-failed" are + * emitted. So the only way to avoid leaving behind a full progress bar + * after, for example, canceling a page load is to save a flag on a + * failed load and only update the progress bar if the flag is unset. + */ + if (uri_entry->load_failed) { + uri_entry->load_failed = FALSE; + return; + } + gtk_entry_set_progress_fraction(GTK_ENTRY(uri_entry->uri_entry), + webkit_web_view_get_estimated_load_progress(web_view)); +} + +static void +uri_cb(MqWebView *web_view, GParamSpec G_GNUC_UNUSED *param_spec, + MqUriEntry *uri_entry) +{ + const gchar *uri; + + uri = mq_web_view_get_uri(web_view); + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), uri); + gtk_entry_set_attributes(GTK_ENTRY(uri_entry->uri_entry), NULL); +} + +static void +uri_activate_cb(GtkEntry *entry, MqUriEntry *uri_entry) +{ + const gchar *uri; + + uri = gtk_entry_get_text(GTK_ENTRY(entry)); + + mq_web_view_load_uri(uri_entry->web_view, uri); +} + +static void +set_uri(MqUriEntry *uri_entry, const gchar *uri) +{ + if (uri) { + gtk_entry_set_text(GTK_ENTRY(uri_entry->uri_entry), uri); + } +} + +static void +set_web_view(MqUriEntry *uri_entry, MqWebView *web_view) +{ + uri_entry->web_view = web_view; + + g_signal_connect(web_view, "load-changed", + G_CALLBACK(load_changed_cb), uri_entry); + g_signal_connect(web_view, "load-failed", + G_CALLBACK(load_failed_cb), uri_entry); + g_signal_connect(web_view, "mouse-target-changed", + G_CALLBACK(mouse_target_changed_cb), uri_entry); + g_signal_connect(web_view, "notify::estimated-load-progress", + G_CALLBACK(load_progress_cb), uri_entry); + g_signal_connect(web_view, "notify::rewritten-uri", + G_CALLBACK(uri_cb), uri_entry); +} + +static void +get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *param_spec) +{ + switch (property_id) { + case PROP_URI: + g_value_set_string(value, NULL); + break; + case PROP_WEB_VIEW: + g_value_set_object(value, NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *param_spec) +{ + MqUriEntry *uri_entry; + + uri_entry = MQ_URI_ENTRY(object); + + switch (property_id) { + case PROP_URI: + set_uri(uri_entry, g_value_get_string(value)); + break; + case PROP_WEB_VIEW: + set_web_view(uri_entry, g_value_get_object(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, + param_spec); + break; + } +} + +static void +mq_uri_entry_class_init(MqUriEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + obj_properties[PROP_URI] = g_param_spec_string( + "uri", + "URI", + "The initial URI in the URI entry", + "", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + obj_properties[PROP_WEB_VIEW] = g_param_spec_object( + "web-view", + "MqWebView", + "The associated MqWebView instance", + MQ_TYPE_WEB_VIEW, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + g_object_class_install_properties(object_class, N_PROPERTIES, + obj_properties); +} + +static void +mq_uri_entry_init(MqUriEntry *uri_entry) +{ + uri_entry->uri_entry = gtk_entry_new(); + gtk_entry_set_placeholder_text(GTK_ENTRY(uri_entry->uri_entry), + "URI..."); + gtk_entry_set_icon_from_icon_name(GTK_ENTRY(uri_entry->uri_entry), + GTK_ENTRY_ICON_PRIMARY, "text-x-generic"); + gtk_entry_set_progress_fraction(GTK_ENTRY(uri_entry->uri_entry), 0.0); + g_signal_connect(uri_entry->uri_entry, "activate", + G_CALLBACK(uri_activate_cb), uri_entry); + gtk_container_add(GTK_CONTAINER(uri_entry), uri_entry->uri_entry); + gtk_tool_item_set_expand(GTK_TOOL_ITEM(uri_entry), TRUE); + + uri_entry->hovered_link_uri = NULL; + + /* URI bar hovered link style */ + uri_entry->hovered_link_style = pango_attr_list_new(); + pango_attr_list_insert(uri_entry->hovered_link_style, + pango_attr_style_new(PANGO_STYLE_ITALIC)); + + uri_entry->load_failed = FALSE; +} + +GtkToolItem * +mq_uri_entry_new(MqWebView *web_view, const gchar *uri) +{ + return g_object_new(MQ_TYPE_URI_ENTRY, + "web-view", web_view, + "uri", uri, + NULL); +} diff --git a/src/toolbars/navigation/uri-entry.h b/src/toolbars/navigation/uri-entry.h new file mode 100644 index 0000000..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 . + */ + +#ifndef MQ_TOOLBARS_NAVIGATION_URI_ENTRY_H +#define MQ_TOOLBARS_NAVIGATION_URI_ENTRY_H + +typedef struct _MqUriEntry MqUriEntry; +typedef struct _MqUriEntryClass MqUriEntryClass; + +#include +#include + +#include "../../web-view.h" + +G_BEGIN_DECLS + +#define MQ_TYPE_URI_ENTRY (mq_uri_entry_get_type()) +#define MQ_URI_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MQ_TYPE_URI_ENTRY, MqUriEntry)) +#define MQ_IS_URI_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + MQ_TYPE_URI_ENTRY)) +#define MQ_URI_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + MQ_TYPE_URI_ENTRY, MqUriEntryClass)) +#define MQ_IS_URI_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + MQ_TYPE_URI_ENTRY)) +#define MQ_URI_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + MQ_TYPE_URI_ENTRY, MqUriEntryClass)) + +GType +mq_uri_entry_get_type(void); + +GtkToolItem * +mq_uri_entry_new(MqWebView *web_view, const gchar *uri); + +G_END_DECLS + +#endif /* MQ_TOOLBARS_NAVIGATION_URI_ENTRY_H */ 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 . */ +#include "web-settings.h" + #include -#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 . */ +#include "web-view.h" + #include #include +#include #include #include -#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 . + */ + +#ifndef MQ_WEB_VIEW_H +#define MQ_WEB_VIEW_H + +typedef struct _MqWebView MqWebView; +typedef struct _MqWebViewClass MqWebViewClass; + +#include +#include +#include + +#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 . */ +#include "window.h" + #include +#include #include -#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 . */ -typedef struct MqWindow MqWindow; - #ifndef MQ_WINDOW_H #define MQ_WINDOW_H +typedef struct _MqWindow MqWindow; +typedef struct _MqWindowClass MqWindowClass; + +#include #include #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 */ -- cgit v0.9.1