PIKApp/plug-ins/help-browser/dialog.c

1361 lines
49 KiB
C

/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* PIKA Help Browser
* Copyright (C) 1999-2008 Sven Neumann <sven@gimp.org>
* Michael Natterer <mitch@gimp.org>
* Róman Joost <romanofski@gimp.org>
* Niels De Graef <nielsdg@redhat.com>
*
* dialog.c
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <webkit2/webkit2.h>
#include "libpika/pika.h"
#include "libpika/pikaui.h"
#include "plug-ins/help/pikahelp.h"
#include "dialog.h"
#include "uri.h"
#include "libpika/stdplugins-intl.h"
#define PIKA_HELP_BROWSER_DIALOG_DATA "pika-help-browser-dialog"
#define PIKA_HELP_BROWSER_INDEX_MAX_DEPTH 4
typedef struct
{
int width;
int height;
int paned_position;
gboolean show_index;
double zoom;
} DialogData;
struct _PikaHelpBrowserDialog
{
GtkApplicationWindow parent_instance;
GHashTable *uri_hash_table; /* (char*) → (GtkTreeIter) */
GtkWidget *webview;
GtkWidget *paned;
GtkWidget *sidebar;
GtkWidget *searchbar;
GtkWidget *search_entry;
GtkWidget *tree_view;
GtkWidget *button_prev;
GtkWidget *button_next;
GdkCursor *busy_cursor;
GMenuModel *popup_menu_model;
GMenuModel *copy_popup_menu_model;
PikaProcedureConfig *config;
};
G_DEFINE_TYPE (PikaHelpBrowserDialog, pika_help_browser_dialog, GTK_TYPE_APPLICATION_WINDOW)
static void pika_help_browser_dialog_finalize (GObject *object);
/* Actions. */
static void back_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void step_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void forward_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void reload_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void stop_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void home_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void find_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void find_again_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void search_next_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void search_previous_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void copy_location_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void copy_selection_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void show_index_change_state (GSimpleAction *action,
GVariant *new_state,
gpointer user_data);
static void zoom_in_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void zoom_out_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void load_uri_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
static void close_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data);
/* Callbacks. */
static void webview_realize (GtkWidget *webview,
gpointer user_data);
static void webview_unrealize (GtkWidget *widget,
gpointer user_data);
static void do_popup_menu (PikaHelpBrowserDialog *self,
GtkWidget *webview,
GdkEvent *event);
static gboolean webview_popup_menu (GtkWidget *webview,
gpointer user_data);
static gboolean webview_button_press (GtkWidget *webview,
GdkEventButton *event,
gpointer user_data);
static gboolean webview_key_press (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data);
static void webview_title_changed (WebKitWebView *webview,
GParamSpec *pspec,
gpointer user_data);
static void select_index (PikaHelpBrowserDialog *self,
const char *uri);
static gboolean webview_decide_policy (WebKitWebView *webview,
WebKitPolicyDecision *decision,
WebKitPolicyDecisionType decision_type,
gpointer user_data);
static gboolean webview_load_failed (WebKitWebView *webview,
WebKitLoadEvent load_event,
char *failing_uri,
GError *error,
gpointer user_data);
static void webview_load_changed (WebKitWebView *webview,
WebKitLoadEvent event,
gpointer user_data);
static void row_activated (GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column,
gpointer user_data);
static void search_close_clicked (GtkWidget *button,
gpointer user_data);
static void search_entry_changed (GtkWidget *search_entry,
gpointer user_data);
static gboolean search_entry_key_press (GtkWidget *search_entry,
GdkEventKey *event,
gpointer user_data);
static void dialog_unmap (GtkWidget *window,
gpointer user_data);
/* Utilities. */
static void search (PikaHelpBrowserDialog *self,
const char *text);
static GtkWidget * build_menu (const GList *items,
gboolean back);
static void update_actions (PikaHelpBrowserDialog *self);
static void add_tool_button (GtkWidget *toolbar,
const char *action,
const char *icon,
const char *label,
const char *tooltip);
static GtkWidget * build_searchbar (PikaHelpBrowserDialog *self);
static void browser_dialog_make_index_foreach (const gchar *help_id,
PikaHelpItem *item,
PikaHelpLocale *locale);
static gint help_item_compare (gconstpointer a,
gconstpointer b);
static void add_child (PikaHelpBrowserDialog *self,
GtkTreeStore *store,
PikaHelpDomain *domain,
PikaHelpLocale *locale,
GtkTreeIter *parent,
PikaHelpItem *item,
int depth);
static const GActionEntry ACTIONS[] =
{
{ "back", back_action },
{ "forward", forward_action },
{ "step", step_action, "i" },
{ "reload", reload_action },
{ "stop", stop_action },
{ "home", home_action },
{ "load-uri", load_uri_action, "s" },
{ "copy-location", copy_location_action },
{ "copy-selection", copy_selection_action },
{ "zoom-in", zoom_in_action },
{ "zoom-out", zoom_out_action },
{ "find", find_action },
{ "find-again", find_again_action },
{ "search-next", search_next_action },
{ "search-previous", search_previous_action },
{ "close", close_action },
{ "show-index", NULL, NULL, "true", show_index_change_state },
};
static void
pika_help_browser_dialog_class_init (PikaHelpBrowserDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = pika_help_browser_dialog_finalize;
}
static void
pika_help_browser_dialog_init (PikaHelpBrowserDialog *self)
{
GtkWindow *window = GTK_WINDOW (self);
GtkWidget *vbox;
GtkWidget *toolbar;
GtkBuilder *builder;
GtkToolItem *item;
GtkWidget *main_vbox;
WebKitSettings *settings;
/* the dialog window */
gtk_window_set_title (GTK_WINDOW (window), _("PIKA Help Browser"));
gtk_window_set_icon_name (GTK_WINDOW (window), PIKA_ICON_HELP_USER_MANUAL);
g_action_map_add_action_entries (G_ACTION_MAP (self),
ACTIONS, G_N_ELEMENTS (ACTIONS),
self);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
gtk_container_add (GTK_CONTAINER (window), vbox);
gtk_widget_show (vbox);
/* Toolbar */
toolbar = gtk_toolbar_new ();
add_tool_button (toolbar, "win.reload", PIKA_ICON_VIEW_REFRESH, _("_Reload"), _("Reload current page"));
add_tool_button (toolbar, "win.stop", PIKA_ICON_PROCESS_STOP, _("_Stop"), _("Stop loading this page"));
add_tool_button (toolbar, "win.home", PIKA_ICON_GO_HOME, NULL, _("Go to the index page"));
item = gtk_separator_tool_item_new ();
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (item), FALSE);
gtk_tool_item_set_expand (item, TRUE);
gtk_widget_show (GTK_WIDGET (item));
add_tool_button (toolbar, "win.load-uri('https://docs.pika.org')", PIKA_ICON_HELP_USER_MANUAL, "docs.pika.org", _("Visit the PIKA documentation website"));
item = gtk_menu_tool_button_new (gtk_image_new_from_icon_name (PIKA_ICON_GO_NEXT, GTK_ICON_SIZE_BUTTON), NULL);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0);
gtk_widget_show (GTK_WIDGET (item));
gtk_actionable_set_action_name (GTK_ACTIONABLE (item), "win.forward");
self->button_next = GTK_WIDGET (item);
item = gtk_menu_tool_button_new (gtk_image_new_from_icon_name (PIKA_ICON_GO_PREVIOUS, GTK_ICON_SIZE_BUTTON), NULL);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0);
gtk_widget_show (GTK_WIDGET (item));
gtk_actionable_set_action_name (GTK_ACTIONABLE (item), "win.back");
self->button_prev = GTK_WIDGET (item);
gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS);
gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0);
gtk_widget_show (toolbar);
/* Context menu */
builder = gtk_builder_new_from_resource ("/technology.heckin/help/help-menu.ui");
self->popup_menu_model = G_MENU_MODEL (gtk_builder_get_object (builder, "help_browser_popup_menu"));
g_object_ref (self->popup_menu_model);
self->copy_popup_menu_model = G_MENU_MODEL (gtk_builder_get_object (builder, "help_browser_copy_popup_menu"));
g_object_ref (self->copy_popup_menu_model);
g_object_unref (builder);
/* the horizontal paned */
self->paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
gtk_box_pack_start (GTK_BOX (vbox), self->paned, TRUE, TRUE, 0);
gtk_widget_show (self->paned);
self->sidebar = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (self->sidebar),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_paned_add1 (GTK_PANED (self->paned), self->sidebar);
self->tree_view = gtk_tree_view_new ();
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self->tree_view), FALSE);
gtk_container_add (GTK_CONTAINER (self->sidebar), self->tree_view);
gtk_widget_show (self->tree_view);
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (self->tree_view), -1,
NULL,
gtk_cell_renderer_text_new (),
"text", 1,
NULL);
g_signal_connect (self->tree_view, "row-activated",
G_CALLBACK (row_activated),
self);
/* HTML webview */
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_show (main_vbox);
gtk_paned_pack2 (GTK_PANED (self->paned), main_vbox, TRUE, TRUE);
settings = webkit_settings_new_with_settings ("default-charset", "utf-8",
NULL);
self->webview = webkit_web_view_new_with_settings (settings);
g_object_unref (settings);
gtk_widget_set_size_request (self->webview, 300, 200);
gtk_widget_show (self->webview);
gtk_box_pack_start (GTK_BOX (main_vbox), self->webview, TRUE, TRUE, 0);
g_signal_connect (self->webview, "realize",
G_CALLBACK (webview_realize),
self);
g_signal_connect (self->webview, "unrealize",
G_CALLBACK (webview_unrealize),
self);
g_signal_connect (self->webview, "popup-menu",
G_CALLBACK (webview_popup_menu),
self);
g_signal_connect (self->webview, "button-press-event",
G_CALLBACK (webview_button_press),
self);
g_signal_connect (self->webview, "key-press-event",
G_CALLBACK (webview_key_press),
self);
g_signal_connect (self->webview, "notify::title",
G_CALLBACK (webview_title_changed),
self);
g_signal_connect (self->webview, "load-changed",
G_CALLBACK (webview_load_changed),
self);
g_signal_connect (self->webview, "load-failed",
G_CALLBACK (webview_load_failed),
self);
g_signal_connect (self->webview, "decide-policy",
G_CALLBACK (webview_decide_policy),
self);
gtk_widget_grab_focus (self->webview);
g_signal_connect (window, "unmap",
G_CALLBACK (dialog_unmap),
self);
update_actions (self);
/* Searchbar */
self->searchbar = build_searchbar (self);
gtk_box_pack_start (GTK_BOX (main_vbox), self->searchbar, FALSE, FALSE, 0);
}
static void
pika_help_browser_dialog_finalize (GObject *object)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (object);
g_clear_pointer (&self->uri_hash_table, g_hash_table_unref);
g_clear_object (&self->popup_menu_model);
g_clear_object (&self->copy_popup_menu_model);
G_OBJECT_CLASS (pika_help_browser_dialog_parent_class)->finalize (object);
}
/* Public functions. */
PikaHelpBrowserDialog *
pika_help_browser_dialog_new (const gchar *plug_in_binary,
GApplication *app,
PikaProcedureConfig *config)
{
PikaHelpBrowserDialog *window;
GBytes *bytes = NULL;
DialogData data = { 720, 560, 240, TRUE, 1.0 };
g_object_get (config, "dialog-data", &bytes, NULL);
if (bytes != NULL && g_bytes_get_size (bytes) == sizeof (DialogData))
data = *((DialogData *) g_bytes_get_data (bytes, NULL));
g_bytes_unref (bytes);
pika_ui_init (plug_in_binary);
window = g_object_new (PIKA_TYPE_HELP_BROWSER_DIALOG,
"application", app,
"role", plug_in_binary,
"default-width", data.width,
"default-height", data.height,
NULL);
window->config = config;
gtk_paned_set_position (GTK_PANED (window->paned), data.paned_position);
if (data.show_index)
gtk_widget_show (window->sidebar);
webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (window->webview), data.zoom);
return window;
}
void
pika_help_browser_dialog_load (PikaHelpBrowserDialog *self,
const char *uri)
{
g_return_if_fail (uri && *uri);
webkit_web_view_load_uri (WEBKIT_WEB_VIEW (self->webview), uri);
select_index (self, uri);
gtk_window_present (GTK_WINDOW (gtk_widget_get_toplevel (self->webview)));
}
void
pika_help_browser_dialog_make_index (PikaHelpBrowserDialog *self,
PikaHelpDomain *domain,
PikaHelpLocale *locale)
{
GtkTreeStore *store;
GList *list;
if (! locale->toplevel_items)
{
g_hash_table_foreach (locale->help_id_mapping,
(GHFunc) browser_dialog_make_index_foreach,
locale);
locale->toplevel_items = g_list_sort (locale->toplevel_items,
help_item_compare);
}
store = gtk_tree_store_new (2,
G_TYPE_POINTER,
G_TYPE_STRING);
g_object_set_data (G_OBJECT (store), "domain", domain);
g_object_set_data (G_OBJECT (store), "locale", locale);
if (self->uri_hash_table)
g_hash_table_unref (self->uri_hash_table);
self->uri_hash_table = g_hash_table_new_full (g_str_hash,
g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) gtk_tree_iter_free);
for (list = locale->toplevel_items; list; list = g_list_next (list))
{
PikaHelpItem *item = list->data;
add_child (self, store, domain, locale, NULL, item, 0);
}
gtk_tree_view_set_model (GTK_TREE_VIEW (self->tree_view), GTK_TREE_MODEL (store));
g_object_unref (store);
}
/* Private functions. */
static void
back_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
webkit_web_view_go_back (WEBKIT_WEB_VIEW (self->webview));
}
static void
step_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
gint steps;
WebKitBackForwardList *back_fw_list;
WebKitBackForwardListItem *back_fw_list_item;
g_return_if_fail (parameter);
steps = g_variant_get_int32 (parameter);
back_fw_list =
webkit_web_view_get_back_forward_list (WEBKIT_WEB_VIEW (self->webview));
back_fw_list_item = webkit_back_forward_list_get_nth_item (back_fw_list, steps);
if (back_fw_list_item)
webkit_web_view_go_to_back_forward_list_item (WEBKIT_WEB_VIEW (self->webview),
back_fw_list_item);
}
static void
forward_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
webkit_web_view_go_forward (WEBKIT_WEB_VIEW (self->webview));
}
static void
reload_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
webkit_web_view_reload (WEBKIT_WEB_VIEW (self->webview));
}
static void
stop_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
webkit_web_view_stop_loading (WEBKIT_WEB_VIEW (self->webview));
}
static void
home_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
GtkTreeModel *model;
PikaHelpDomain *domain;
PikaHelpLocale *locale;
model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->tree_view));
domain = g_object_get_data (G_OBJECT (model), "domain");
locale = g_object_get_data (G_OBJECT (model), "locale");
if (domain && locale)
{
gchar *uri = g_strconcat (domain->help_uri, "/",
locale->locale_id, "/",
pika_help_locale_map (locale,
PIKA_HELP_DEFAULT_ID),
NULL);
pika_help_browser_dialog_load (self, uri);
g_free (uri);
}
}
static void
find_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
gtk_widget_show (self->searchbar);
gtk_widget_grab_focus (self->search_entry);
}
static void
find_again_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
gtk_widget_show (self->searchbar);
gtk_widget_grab_focus (self->search_entry);
search (self, gtk_entry_get_text (GTK_ENTRY (self->search_entry)));
}
static void
search_next_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
WebKitFindController *find_controller;
find_controller =
webkit_web_view_get_find_controller (WEBKIT_WEB_VIEW (self->webview));
webkit_find_controller_search_next (find_controller);
}
static void
search_previous_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
WebKitFindController *find_controller;
find_controller =
webkit_web_view_get_find_controller (WEBKIT_WEB_VIEW (self->webview));
webkit_find_controller_search_previous (find_controller);
}
static void
copy_location_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
const char *uri;
uri = webkit_web_view_get_uri (WEBKIT_WEB_VIEW (self->webview));
if (uri)
{
GtkClipboard *clipboard;
clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (self->webview),
GDK_SELECTION_CLIPBOARD);
gtk_clipboard_set_text (clipboard, uri, -1);
}
}
static void
copy_selection_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
WebKitEditorState *editor_state;
editor_state = webkit_web_view_get_editor_state (WEBKIT_WEB_VIEW (self->webview));
if (webkit_editor_state_is_copy_available (editor_state))
{
webkit_web_view_execute_editing_command (WEBKIT_WEB_VIEW (self->webview),
WEBKIT_EDITING_COMMAND_COPY);
}
}
static void
show_index_change_state (GSimpleAction *action,
GVariant *new_state,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
gboolean show_index;
show_index = g_variant_get_boolean (new_state);
g_warning ("NEW STATE %s", show_index? "true" : "false");
gtk_widget_set_visible (self->sidebar, show_index);
g_simple_action_set_state (action, new_state);
}
static void
zoom_in_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
double zoom_level;
zoom_level = webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (self->webview));
if (zoom_level < 10.0)
webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (self->webview), zoom_level + 0.1);
}
static void
zoom_out_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
double zoom_level;
zoom_level = webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (self->webview));
if (zoom_level > 0.1)
webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (self->webview), zoom_level - 0.1);
}
static void
load_uri_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
const char *uri;
uri = g_variant_get_string (parameter, NULL);
pika_help_browser_dialog_load (self, uri);
}
static void
close_action (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
gtk_widget_destroy (GTK_WIDGET (self));
}
static void
webview_realize (GtkWidget *webview,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
g_return_if_fail (self->busy_cursor == NULL);
self->busy_cursor = gdk_cursor_new_for_display (gtk_widget_get_display (webview),
GDK_WATCH);
}
static void
webview_unrealize (GtkWidget *widget,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
g_clear_object (&self->busy_cursor);
}
static void
do_popup_menu (PikaHelpBrowserDialog *self,
GtkWidget *webview,
GdkEvent *event)
{
WebKitEditorState *editor_state;
GMenuModel *menu_model;
GtkWidget *menu;
editor_state = webkit_web_view_get_editor_state (WEBKIT_WEB_VIEW (webview));
if (webkit_editor_state_is_copy_available (editor_state))
menu_model = self->copy_popup_menu_model;
else
menu_model = self->popup_menu_model;
menu = gtk_menu_new_from_model (menu_model);
g_signal_connect (menu, "deactivate",
G_CALLBACK (gtk_widget_destroy), NULL);
gtk_menu_attach_to_widget (GTK_MENU (menu), webview, NULL);
gtk_menu_popup_at_pointer (GTK_MENU (menu), event);
}
static gboolean
webview_popup_menu (GtkWidget *webview,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
do_popup_menu (self, webview, NULL);
return TRUE;
}
static gboolean
webview_button_press (GtkWidget *webview,
GdkEventButton *event,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
if (gdk_event_triggers_context_menu ((GdkEvent *) event))
{
do_popup_menu (self, webview, (GdkEvent *) event);
return TRUE;
}
return FALSE;
}
static gboolean
webview_key_press (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
if (event->keyval == GDK_KEY_slash)
{
g_action_group_activate_action (G_ACTION_GROUP (self),
"find",
NULL);
return TRUE;
}
return FALSE;
}
static void
webview_title_changed (WebKitWebView *webview,
GParamSpec *pspec,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
const char *title;
char *full_title;
title = webkit_web_view_get_title (webview);
full_title = g_strdup_printf ("%s - %s",
title ? title : _("Untitled"),
_("PIKA Help Browser"));
gtk_window_set_title (GTK_WINDOW (self), full_title);
g_free (full_title);
update_actions (self);
}
static void
select_index (PikaHelpBrowserDialog *self,
const char *uri)
{
GtkTreeSelection *selection;
GtkTreeIter *iter = NULL;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->tree_view));
if (uri)
iter = g_hash_table_lookup (self->uri_hash_table, uri);
if (iter)
{
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->tree_view));
GtkTreePath *path;
GtkTreePath *scroll_path;
path = gtk_tree_model_get_path (model, iter);
scroll_path = gtk_tree_path_copy (path);
gtk_tree_path_up (path);
gtk_tree_view_expand_to_path (GTK_TREE_VIEW (self->tree_view), path);
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self->tree_view), scroll_path,
NULL, FALSE, 0.0, 0.0);
gtk_tree_path_free (path);
gtk_tree_path_free (scroll_path);
gtk_tree_selection_select_iter (selection, iter);
}
else
{
gtk_tree_selection_unselect_all (selection);
}
}
static gboolean
webview_decide_policy (WebKitWebView *webview,
WebKitPolicyDecision *decision,
WebKitPolicyDecisionType decision_type,
gpointer user_data)
{
/* Some files return mime types like application/x-extension-html,
* which is not supported by default */
webkit_policy_decision_use (decision);
return FALSE;
}
static gboolean
webview_load_failed (WebKitWebView *webview,
WebKitLoadEvent load_event,
char *failing_uri,
GError *error,
gpointer user_data)
{
g_warning ("Failed to load page: %s", error->message);
return TRUE;
}
static void
webview_load_changed (WebKitWebView *webview,
WebKitLoadEvent event,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
GAction *action;
action = g_action_map_lookup_action (G_ACTION_MAP (self), "back");
switch (event)
{
case WEBKIT_LOAD_STARTED:
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
break;
case WEBKIT_LOAD_FINISHED:
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
update_actions (self);
select_index (self, webkit_web_view_get_uri (webview));
break;
case WEBKIT_LOAD_REDIRECTED:
case WEBKIT_LOAD_COMMITTED:
break;
}
}
static void
row_activated (GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
GtkTreeModel *model;
GtkTreeIter iter;
PikaHelpDomain *domain;
PikaHelpLocale *locale;
PikaHelpItem *item;
char *uri;
model = gtk_tree_view_get_model (tree_view);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter,
0, &item,
-1);
domain = g_object_get_data (G_OBJECT (model), "domain");
locale = g_object_get_data (G_OBJECT (model), "locale");
uri = g_strconcat (domain->help_uri, "/",
locale->locale_id, "/",
item->ref,
NULL);
pika_help_browser_dialog_load (self, uri);
g_free (uri);
}
static void
search_close_clicked (GtkWidget *button,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
WebKitFindController *find_controller =
webkit_web_view_get_find_controller (WEBKIT_WEB_VIEW (self->webview));
gtk_widget_hide (self->searchbar);
webkit_find_controller_search_finish (find_controller);
}
static void
search_entry_changed (GtkWidget *search_entry,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
search (self, gtk_entry_get_text (GTK_ENTRY (search_entry)));
}
static gboolean
search_entry_key_press (GtkWidget *search_entry,
GdkEventKey *event,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
WebKitFindController *find_controller;
find_controller = webkit_web_view_get_find_controller (WEBKIT_WEB_VIEW (self->webview));
switch (event->keyval)
{
case GDK_KEY_Escape:
gtk_widget_hide (self->searchbar);
webkit_find_controller_search_finish (find_controller);
return TRUE;
case GDK_KEY_Return:
case GDK_KEY_KP_Enter:
case GDK_KEY_ISO_Enter:
search (self, gtk_entry_get_text (GTK_ENTRY (search_entry)));
return TRUE;
}
return FALSE;
}
static void
dialog_unmap (GtkWidget *window,
gpointer user_data)
{
PikaHelpBrowserDialog *self = PIKA_HELP_BROWSER_DIALOG (user_data);
GBytes *bytes;
DialogData data = { 720, 560, 240, TRUE, 1.0 };
gtk_window_get_size (GTK_WINDOW (window), &data.width, &data.height);
data.paned_position = gtk_paned_get_position (GTK_PANED (self->paned));
data.show_index = gtk_widget_get_visible (self->sidebar);
data.zoom = (self->webview ?
webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (self->webview)) : 1.0);
bytes = g_bytes_new (&data, sizeof (DialogData));
g_object_set (self->config, "dialog-data", bytes, NULL);
g_bytes_unref (bytes);
}
static void
search (PikaHelpBrowserDialog *self,
const char *text)
{
WebKitFindController *find_controller;
find_controller = webkit_web_view_get_find_controller (WEBKIT_WEB_VIEW (self->webview));
if (text)
{
const char *prev_text =
webkit_find_controller_get_search_text (find_controller);
/* The previous search, if any, may need to be canceled. */
if (prev_text && strcmp (text, prev_text) != 0)
webkit_find_controller_search_finish (find_controller);
webkit_find_controller_search (find_controller,
text,
WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE |
WEBKIT_FIND_OPTIONS_WRAP_AROUND,
G_MAXUINT);
}
else
{
webkit_find_controller_search_finish (find_controller);
}
}
static GtkWidget *
build_menu (const GList *items,
gboolean back)
{
GMenu *menu;
const GList *iter;
int steps;
if (!items)
return NULL;
menu = g_menu_new ();
/* Go over every item in the back_fw list and add it to the menu */
for (iter = items, steps = 1; iter; iter = g_list_next (iter), steps++)
{
WebKitBackForwardListItem *item = iter->data;
const char *title;
char *action;
title = webkit_back_forward_list_item_get_title (item);
if (title == NULL)
continue;
action = g_strdup_printf ("steps(%d)", steps);
g_menu_insert (menu, steps - 1, title, action);
g_free (action);
}
return gtk_menu_new_from_model (G_MENU_MODEL (menu));
}
static void
update_actions (PikaHelpBrowserDialog *self)
{
GActionMap *action_map = G_ACTION_MAP (self);
GAction *action;
WebKitBackForwardList *back_forward_list;
back_forward_list =
webkit_web_view_get_back_forward_list (WEBKIT_WEB_VIEW (self->webview));
/* update the back button and its menu */
action = g_action_map_lookup_action (action_map, "back");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
webkit_web_view_can_go_back (WEBKIT_WEB_VIEW (self->webview)));
if (back_forward_list)
{
const GList *list;
list = webkit_back_forward_list_get_back_list_with_limit (back_forward_list,
12);
gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (self->button_prev),
build_menu (list, TRUE));
}
else
{
gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (self->button_prev), NULL);
}
/* update the forward button and its menu */
action = g_action_map_lookup_action (action_map, "forward");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
webkit_web_view_can_go_forward (WEBKIT_WEB_VIEW (self->webview)));
if (back_forward_list)
{
const GList *list;
list = webkit_back_forward_list_get_forward_list_with_limit (back_forward_list,
12);
gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (self->button_next),
build_menu (list, FALSE));
}
else
{
gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (self->button_next), NULL);
}
/* update the copy-location action */
action = g_action_map_lookup_action (action_map, "copy-location");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
webkit_web_view_get_uri (WEBKIT_WEB_VIEW (self->webview)) != NULL);
/* update the show-index action */
action = g_action_map_lookup_action (action_map, "show-index");
g_simple_action_set_state (G_SIMPLE_ACTION (action),
g_variant_new_boolean (gtk_widget_get_visible (self->sidebar)));
}
static void
add_tool_button (GtkWidget *toolbar,
const char *action,
const char *icon,
const char *label,
const char *tooltip)
{
GtkWidget *tool_icon;
GtkToolItem *tool_button;
tool_icon = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_BUTTON);
gtk_widget_show (GTK_WIDGET (tool_icon));
tool_button = gtk_tool_button_new (tool_icon, label);
gtk_widget_show (GTK_WIDGET (tool_button));
gtk_tool_item_set_tooltip_text (tool_button, tooltip);
gtk_actionable_set_detailed_action_name (GTK_ACTIONABLE (tool_button), action);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_button, -1);
}
static GtkWidget *
build_searchbar (PikaHelpBrowserDialog *self)
{
GtkWidget *button;
GtkWidget *hbox;
GtkWidget *label;
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
label = gtk_label_new (_("Find:"));
gtk_widget_show (label);
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
self->search_entry = gtk_entry_new ();
gtk_widget_show (self->search_entry);
gtk_box_pack_start (GTK_BOX (hbox), self->search_entry, TRUE, TRUE, 0);
g_signal_connect (self->search_entry, "changed",
G_CALLBACK (search_entry_changed),
self);
g_signal_connect (self->search_entry, "key-press-event",
G_CALLBACK (search_entry_key_press),
self);
button = gtk_button_new_with_mnemonic (C_("search", "_Previous"));
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
gtk_button_set_image (GTK_BUTTON (button),
gtk_image_new_from_icon_name (PIKA_ICON_GO_PREVIOUS,
GTK_ICON_SIZE_BUTTON));
gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "win.search-previous");
gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
gtk_widget_show (button);
button = gtk_button_new_with_mnemonic (C_("search", "_Next"));
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
gtk_button_set_image (GTK_BUTTON (button),
gtk_image_new_from_icon_name (PIKA_ICON_GO_NEXT,
GTK_ICON_SIZE_BUTTON));
gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "win.search-next");
gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
gtk_widget_show (button);
button = gtk_button_new_with_mnemonic (C_("search", "_Close"));
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
gtk_widget_show (button);
g_signal_connect (button, "clicked",
G_CALLBACK (search_close_clicked),
self);
return hbox;
}
static void
browser_dialog_make_index_foreach (const gchar *help_id,
PikaHelpItem *item,
PikaHelpLocale *locale)
{
gchar *sort_key = item->title;
#if DEBUG_SORT_HELP_ITEMS
g_printerr ("%s: processing %s (parent %s)\n",
G_STRFUNC,
item->title ? item->title : "NULL",
item->parent ? item->parent : "NULL");
#endif
if (item->sort &&
g_regex_match_simple ("^[0-9]+([.][0-9]+)*$", item->sort, 0, 0))
{
sort_key = item->sort;
#if DEBUG_SORT_HELP_ITEMS
g_printerr ("%s: sort key = %s\n", G_STRFUNC, sort_key);
#endif
}
item->index = 0;
if (sort_key)
{
int max_tokens = PIKA_HELP_BROWSER_INDEX_MAX_DEPTH;
char **indices = g_strsplit (sort_key, ".", max_tokens + 1);
int i;
for (i = 0; i < max_tokens; i++)
{
gunichar c;
if (! indices[i])
{
/* make sure that all item->index's are comparable */
item->index <<= (8 * (max_tokens - i));
break;
}
item->index <<= 8; /* NOP if i = 0 */
c = g_utf8_get_char (indices[i]);
if (g_unichar_isdigit (c))
{
item->index += atoi (indices[i]);
}
else if (g_utf8_strlen (indices[i], -1) == 1)
{
item->index += (c & 0xFF);
}
}
g_strfreev (indices);
#if DEBUG_SORT_HELP_ITEMS
g_printerr ("%s: index = %lu\n", G_STRFUNC, item->index);
#endif
}
if (item->parent && strlen (item->parent))
{
PikaHelpItem *parent;
parent = g_hash_table_lookup (locale->help_id_mapping, item->parent);
if (parent)
{
parent->children = g_list_prepend (parent->children, item);
}
}
else
{
locale->toplevel_items = g_list_prepend (locale->toplevel_items, item);
}
}
static gint
help_item_compare (gconstpointer a,
gconstpointer b)
{
const PikaHelpItem *item_a = a;
const PikaHelpItem *item_b = b;
if (item_a->index > item_b->index)
return 1;
else if (item_a->index < item_b->index)
return -1;
return 0;
}
static void
add_child (PikaHelpBrowserDialog *self,
GtkTreeStore *store,
PikaHelpDomain *domain,
PikaHelpLocale *locale,
GtkTreeIter *parent,
PikaHelpItem *item,
int depth)
{
GtkTreeIter iter;
char *uri;
gtk_tree_store_append (store, &iter, parent);
gtk_tree_store_set (store, &iter,
0, item,
1, item->title,
-1);
uri = g_strconcat (domain->help_uri, "/",
locale->locale_id, "/",
item->ref,
NULL);
g_hash_table_insert (self->uri_hash_table,
uri,
gtk_tree_iter_copy (&iter));
if (depth + 1 == PIKA_HELP_BROWSER_INDEX_MAX_DEPTH)
return;
item->children = g_list_sort (item->children, help_item_compare);
for (GList *list = item->children; list; list = g_list_next (list))
{
PikaHelpItem *item = list->data;
add_child (self, store, domain, locale, &iter, item, depth + 1);
}
}