/* 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 * * pikahelp.c * Copyright (C) 1999-2004 Michael Natterer * Henrik Brix Andersen * * 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 . */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "libpikawidgets/pikawidgets.h" #include "widgets-types.h" #include "config/pikaguiconfig.h" #include "core/pika.h" #include "core/pikaparamspecs.h" #include "core/pikaprogress.h" #include "core/pika-utils.h" #include "pdb/pikapdb.h" #include "pdb/pikaprocedure.h" #include "plug-in/pikaplugin.h" #include "plug-in/pikapluginmanager-help-domain.h" #include "plug-in/pikatemporaryprocedure.h" #include "pikahelp.h" #include "pikahelp-ids.h" #include "pikalanguagecombobox.h" #include "pikalanguagestore-parser.h" #include "pikamessagebox.h" #include "pikamessagedialog.h" #include "pikamessagedialog.h" #include "pikawidgets-utils.h" #include "pika-log.h" #include "pika-intl.h" typedef struct _PikaIdleHelp PikaIdleHelp; struct _PikaIdleHelp { Pika *pika; PikaProgress *progress; gchar *help_domain; gchar *help_locales; gchar *help_id; GtkDialog *query_dialog; }; /* local function prototypes */ static gboolean pika_idle_help (PikaIdleHelp *idle_help); static void pika_idle_help_free (PikaIdleHelp *idle_help); static gboolean pika_help_browser (Pika *pika, PikaProgress *progress); static void pika_help_browser_error (Pika *pika, PikaProgress *progress, const gchar *title, const gchar *primary, const gchar *text); static void pika_help_call (Pika *pika, PikaProgress *progress, const gchar *procedure_name, const gchar *help_domain, const gchar *help_locales, const gchar *help_id); static void pika_help_get_help_domains (Pika *pika, gchar ***domain_names, gchar ***domain_uris); static gchar * pika_help_get_default_domain_uri (Pika *pika); static gchar * pika_help_get_locales (Pika *pika); static GFile * pika_help_get_user_manual_basedir (void); static void pika_help_query_alt_user_manual (PikaIdleHelp *idle_help); static void pika_help_language_combo_changed (GtkComboBox *combo, PikaIdleHelp *idle_help); /* public functions */ void pika_help_show (Pika *pika, PikaProgress *progress, const gchar *help_domain, const gchar *help_id) { PikaGuiConfig *config; g_return_if_fail (PIKA_IS_PIKA (pika)); g_return_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress)); config = PIKA_GUI_CONFIG (pika->config); if (config->use_help) { PikaIdleHelp *idle_help = g_slice_new0 (PikaIdleHelp); idle_help->pika = pika; idle_help->progress = progress; if (help_domain && strlen (help_domain)) idle_help->help_domain = g_strdup (help_domain); idle_help->help_locales = pika_help_get_locales (pika); if (help_id && strlen (help_id)) idle_help->help_id = g_strdup (help_id); PIKA_LOG (HELP, "request for help-id '%s' from help-domain '%s'", help_id ? help_id : "(null)", help_domain ? help_domain : "(null)"); g_idle_add ((GSourceFunc) pika_idle_help, idle_help); } } gboolean pika_help_browser_is_installed (Pika *pika) { g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE); if (pika_pdb_lookup_procedure (pika->pdb, "extension-pika-help-browser")) return TRUE; return FALSE; } gboolean pika_help_user_manual_is_installed (Pika *pika) { GFile *basedir; gboolean found = FALSE; g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE); /* if PIKA2_HELP_URI is set, assume that the manual can be found there */ if (g_getenv ("PIKA2_HELP_URI")) return TRUE; basedir = pika_help_get_user_manual_basedir (); if (g_file_query_file_type (basedir, G_FILE_QUERY_INFO_NONE, NULL) == G_FILE_TYPE_DIRECTORY) { gchar *locales = pika_help_get_locales (pika); const gchar *s = locales; const gchar *p; for (p = strchr (s, ':'); p && !found; p = strchr (s, ':')) { gchar *locale = g_strndup (s, p - s); GFile *file1 = g_file_get_child (basedir, locale); GFile *file2 = g_file_get_child (file1, "pika-help.xml"); found = (g_file_query_file_type (file2, G_FILE_QUERY_INFO_NONE, NULL) == G_FILE_TYPE_REGULAR); g_object_unref (file1); g_object_unref (file2); g_free (locale); s = p + 1; } g_free (locales); if (! found) { GFile *file1 = g_file_get_child (basedir, "en"); GFile *file2 = g_file_get_child (file1, "pika-help.xml"); found = (g_file_query_file_type (file2, G_FILE_QUERY_INFO_NONE, NULL) == G_FILE_TYPE_REGULAR); g_object_unref (file1); g_object_unref (file2); } } g_object_unref (basedir); return found; } void pika_help_user_manual_changed (Pika *pika) { PikaProcedure *procedure; g_return_if_fail (PIKA_IS_PIKA (pika)); /* Check if a help parser is running */ procedure = pika_pdb_lookup_procedure (pika->pdb, "extension-pika-help-temp"); if (PIKA_IS_TEMPORARY_PROCEDURE (procedure)) { pika_plug_in_close (PIKA_TEMPORARY_PROCEDURE (procedure)->plug_in, TRUE); } } GList * pika_help_get_installed_languages (void) { GList *manuals = NULL; GFile *basedir; /* if PIKA2_HELP_URI is set, assume that the manual can be found there */ if (g_getenv ("PIKA2_HELP_URI")) basedir = g_file_new_for_uri (g_getenv ("PIKA2_HELP_URI")); else basedir = pika_help_get_user_manual_basedir (); if (g_file_query_file_type (basedir, G_FILE_QUERY_INFO_NONE, NULL) == G_FILE_TYPE_DIRECTORY) { GFileEnumerator *enumerator; enumerator = g_file_enumerate_children (basedir, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (enumerator) { GFileInfo *info; while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL))) { if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { GFile *locale_dir; GFile *file; locale_dir = g_file_enumerator_get_child (enumerator, info); file = g_file_get_child (locale_dir, "pika-help.xml"); if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL) == G_FILE_TYPE_REGULAR) { manuals = g_list_prepend (manuals, g_strdup (g_file_info_get_name (info))); } g_object_unref (locale_dir); g_object_unref (file); } g_object_unref (info); } g_object_unref (enumerator); } } g_object_unref (basedir); return manuals; } /* private functions */ static gboolean pika_idle_help (PikaIdleHelp *idle_help) { PikaGuiConfig *config = PIKA_GUI_CONFIG (idle_help->pika->config); const gchar *procedure_name = NULL; if (! idle_help->help_domain && ! config->user_manual_online && ! pika_help_user_manual_is_installed (idle_help->pika)) { /* The user manual is not installed locally, propose alternative * manuals (other installed languages or online version). */ pika_help_query_alt_user_manual (idle_help); return FALSE; } if (config->help_browser == PIKA_HELP_BROWSER_PIKA) { if (pika_help_browser (idle_help->pika, idle_help->progress)) procedure_name = "extension-pika-help-browser-temp"; } if (config->help_browser == PIKA_HELP_BROWSER_WEB_BROWSER) { /* FIXME: should check for procedure availability */ procedure_name = "plug-in-web-browser"; } if (procedure_name) pika_help_call (idle_help->pika, idle_help->progress, procedure_name, idle_help->help_domain, idle_help->help_locales, idle_help->help_id); pika_idle_help_free (idle_help); return FALSE; } static void pika_idle_help_free (PikaIdleHelp *idle_help) { g_free (idle_help->help_domain); g_free (idle_help->help_locales); g_free (idle_help->help_id); g_slice_free (PikaIdleHelp, idle_help); } static gboolean pika_help_browser (Pika *pika, PikaProgress *progress) { static gboolean busy = FALSE; PikaProcedure *procedure; if (busy) return TRUE; busy = TRUE; /* Check if a help browser is already running */ procedure = pika_pdb_lookup_procedure (pika->pdb, "extension-pika-help-browser-temp"); if (! procedure) { PikaValueArray *args = NULL; gchar **help_domains = NULL; gchar **help_uris = NULL; GError *error = NULL; procedure = pika_pdb_lookup_procedure (pika->pdb, "extension-pika-help-browser"); if (! procedure) { pika_help_browser_error (pika, progress, _("Help browser is missing"), _("The PIKA help browser is not available."), _("The PIKA help browser plug-in appears " "to be missing from your installation. " "You may instead use the web browser " "for reading the help pages.")); busy = FALSE; return FALSE; } pika_help_get_help_domains (pika, &help_domains, &help_uris); args = pika_procedure_get_arguments (procedure); pika_value_array_truncate (args, 3); g_value_set_enum (pika_value_array_index (args, 0), PIKA_RUN_INTERACTIVE); g_value_take_boxed (pika_value_array_index (args, 1), help_domains); g_value_take_boxed (pika_value_array_index (args, 2), help_uris); pika_procedure_execute_async (procedure, pika, pika_get_user_context (pika), NULL, args, NULL, &error); pika_value_array_unref (args); if (error) { pika_message_literal (pika, G_OBJECT (progress), PIKA_MESSAGE_ERROR, error->message); g_error_free (error); } } /* Check if the help browser started properly */ procedure = pika_pdb_lookup_procedure (pika->pdb, "extension-pika-help-browser-temp"); if (! procedure) { pika_help_browser_error (pika, progress, _("Help browser doesn't start"), _("Could not start the PIKA help browser " "plug-in."), _("You may instead use the web browser " "for reading the help pages.")); busy = FALSE; return FALSE; } busy = FALSE; return TRUE; } static void pika_help_browser_error (Pika *pika, PikaProgress *progress, const gchar *title, const gchar *primary, const gchar *text) { GtkWidget *dialog; dialog = pika_message_dialog_new (title, PIKA_ICON_HELP_USER_MANUAL, NULL, 0, NULL, NULL, _("_Cancel"), GTK_RESPONSE_CANCEL, _("Use _Web Browser"), GTK_RESPONSE_OK, NULL); pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); if (progress) { guint32 window_id = pika_progress_get_window_id (progress); if (window_id) pika_window_set_transient_for (GTK_WINDOW (dialog), window_id); } pika_message_box_set_primary_text (PIKA_MESSAGE_DIALOG (dialog)->box, "%s", primary); pika_message_box_set_text (PIKA_MESSAGE_DIALOG (dialog)->box, "%s", text); if (pika_dialog_run (PIKA_DIALOG (dialog)) == GTK_RESPONSE_OK) { g_object_set (pika->config, "help-browser", PIKA_HELP_BROWSER_WEB_BROWSER, NULL); } gtk_widget_destroy (dialog); } static void pika_help_call (Pika *pika, PikaProgress *progress, const gchar *procedure_name, const gchar *help_domain, const gchar *help_locales, const gchar *help_id) { PikaProcedure *procedure; /* Special case the help browser */ if (! strcmp (procedure_name, "extension-pika-help-browser-temp")) { PikaValueArray *return_vals; GError *error = NULL; PIKA_LOG (HELP, "Calling help via %s: %s %s %s", procedure_name, help_domain ? help_domain : "(null)", help_locales ? help_locales : "(null)", help_id ? help_id : "(null)"); return_vals = pika_pdb_execute_procedure_by_name (pika->pdb, pika_get_user_context (pika), progress, &error, procedure_name, G_TYPE_STRING, help_domain, G_TYPE_STRING, help_locales, G_TYPE_STRING, help_id, G_TYPE_NONE); pika_value_array_unref (return_vals); if (error) { pika_message_literal (pika, NULL, PIKA_MESSAGE_ERROR, error->message); g_error_free (error); } return; } /* Check if a help parser is already running */ procedure = pika_pdb_lookup_procedure (pika->pdb, "extension-pika-help-temp"); if (! procedure) { PikaValueArray *args = NULL; gchar **help_domains = NULL; gchar **help_uris = NULL; GError *error = NULL; procedure = pika_pdb_lookup_procedure (pika->pdb, "extension-pika-help"); if (! procedure) /* FIXME: error msg */ return; pika_help_get_help_domains (pika, &help_domains, &help_uris); args = pika_procedure_get_arguments (procedure); pika_value_array_truncate (args, 2); g_value_take_boxed (pika_value_array_index (args, 0), help_domains); g_value_take_boxed (pika_value_array_index (args, 1), help_uris); pika_procedure_execute_async (procedure, pika, pika_get_user_context (pika), progress, args, NULL, &error); pika_value_array_unref (args); if (error) { pika_message_literal (pika, NULL, PIKA_MESSAGE_ERROR, error->message); g_error_free (error); } } /* Check if the help parser started properly */ procedure = pika_pdb_lookup_procedure (pika->pdb, "extension-pika-help-temp"); if (procedure) { PikaValueArray *return_vals; GError *error = NULL; PIKA_LOG (HELP, "Calling help via %s: %s %s %s", procedure_name, help_domain ? help_domain : "(null)", help_locales ? help_locales : "(null)", help_id ? help_id : "(null)"); return_vals = pika_pdb_execute_procedure_by_name (pika->pdb, pika_get_user_context (pika), progress, &error, "extension-pika-help-temp", G_TYPE_STRING, procedure_name, G_TYPE_STRING, help_domain, G_TYPE_STRING, help_locales, G_TYPE_STRING, help_id, G_TYPE_NONE); pika_value_array_unref (return_vals); if (error) { pika_message_literal (pika, NULL, PIKA_MESSAGE_ERROR, error->message); g_error_free (error); } } } static void pika_help_get_help_domains (Pika *pika, gchar ***domain_names, gchar ***domain_uris) { gchar **plug_in_domains = NULL; gchar **plug_in_uris = NULL; gint i, n_domains; n_domains = pika_plug_in_manager_get_help_domains (pika->plug_in_manager, &plug_in_domains, &plug_in_uris); *domain_names = g_new0 (gchar *, n_domains + 2); *domain_uris = g_new0 (gchar *, n_domains + 2); (*domain_names)[0] = g_strdup ("https://heckin.technology/AlderconeStudio/PIKApp/help"); (*domain_uris)[0] = pika_help_get_default_domain_uri (pika); for (i = 0; i < n_domains; i++) { (*domain_names)[i + 1] = plug_in_domains[i]; (*domain_uris)[i + 1] = plug_in_uris[i]; } g_free (plug_in_domains); g_free (plug_in_uris); } static gchar * pika_help_get_default_domain_uri (Pika *pika) { PikaGuiConfig *config = PIKA_GUI_CONFIG (pika->config); GFile *dir; gchar *uri; if (g_getenv ("PIKA2_HELP_URI")) return g_strdup (g_getenv ("PIKA2_HELP_URI")); if (config->user_manual_online) return g_strdup (config->user_manual_online_uri); dir = pika_help_get_user_manual_basedir (); uri = g_file_get_uri (dir); g_object_unref (dir); return uri; } static gchar * pika_help_get_locales (Pika *pika) { PikaGuiConfig *config = PIKA_GUI_CONFIG (pika->config); gchar **names; gchar *locales = NULL; GList *locales_list = NULL; GList *iter; gint i; if (config->help_locales && strlen (config->help_locales)) return g_strdup (config->help_locales); /* Process locales. */ names = (gchar **) g_get_language_names (); for (i = 0; names[i]; i++) { gchar *locale = g_strdup (names[i]); gchar *c; /* We don't care about encoding in context of our help system. */ c = strchr (locale, '.'); if (c) *c = '\0'; /* We don't care about variants either. */ c = strchr (locale, '@'); if (c) *c = '\0'; /* Apparently some systems (i.e. Windows) would return a value as * IETF language tag, which is a different format from POSIX * locale; especially it would separate the lang and the region * with an hyphen instead of an underscore. * Actually the difference is much deeper, and IETF language tags * can have extended language subtags, a script subtag, variants, * moreover using different codes. * We'd actually need to look into this in details (TODO). * this dirty hack should do for easy translation at least (like * "en-GB" -> "en_GB). * Cf. bug 777754. */ c = strchr (locale, '-'); if (c) *c = '_'; if (locale && *locale && ! g_list_find_custom (locales_list, locale, (GCompareFunc) g_strcmp0)) { gchar *base; /* Adding this locale. */ locales_list = g_list_prepend (locales_list, locale); /* Adding the base language as well. */ base = strdup (locale); c = strchr (base, '_'); if (c) *c = '\0'; if (base && *base && ! g_list_find_custom (locales_list, base, (GCompareFunc) g_strcmp0)) { locales_list = g_list_prepend (locales_list, base); } else { g_free (base); } } else { g_free (locale); } } locales_list = g_list_reverse (locales_list); /* Finally generate the colon-separated value. */ if (locales_list) { locales = g_strdup (locales_list->data); for (iter = locales_list->next; iter; iter = iter->next) { gchar *temp = locales; locales = g_strconcat (temp, ":", iter->data, NULL); g_free (temp); } } g_list_free_full (locales_list, g_free); return locales; } static GFile * pika_help_get_user_manual_basedir (void) { return pika_data_directory_file ("help", NULL); } static void pika_help_query_online_response (GtkWidget *dialog, gint response, PikaIdleHelp *idle_help) { gtk_widget_destroy (dialog); if (response == GTK_RESPONSE_ACCEPT) { g_object_set (idle_help->pika->config, "user-manual-online", TRUE, NULL); } if (response != GTK_RESPONSE_YES) { g_object_set (idle_help->pika->config, "help-locales", "", NULL); } if (response == GTK_RESPONSE_ACCEPT || response == GTK_RESPONSE_YES) { pika_help_show (idle_help->pika, idle_help->progress, idle_help->help_domain, idle_help->help_id); } pika_idle_help_free (idle_help); } static void pika_help_query_alt_user_manual (PikaIdleHelp *idle_help) { GtkWidget *dialog; GList *manuals; dialog = pika_message_dialog_new (_("PIKA user manual is missing"), PIKA_ICON_HELP_USER_MANUAL, NULL, 0, NULL, NULL, _("_Cancel"), GTK_RESPONSE_CANCEL, NULL); idle_help->query_dialog = GTK_DIALOG (dialog); if (idle_help->progress) { guint32 window_id = pika_progress_get_window_id (idle_help->progress); if (window_id) pika_window_set_transient_for (GTK_WINDOW (dialog), window_id); } pika_message_box_set_primary_text (PIKA_MESSAGE_DIALOG (dialog)->box, _("The PIKA user manual is not installed " "in your language.")); /* Add a list of available manuals instead, if any. */ manuals = pika_help_get_installed_languages (); if (manuals != NULL) { GtkWidget *lang_combo; /* Add an additional button. */ gtk_dialog_add_button (GTK_DIALOG (dialog), _("Read Selected _Language"), GTK_RESPONSE_YES); /* And a dropdown list of available manuals. */ lang_combo = pika_language_combo_box_new (TRUE, _("Available manuals...")); gtk_combo_box_set_active (GTK_COMBO_BOX (lang_combo), 0); gtk_dialog_set_response_sensitive (idle_help->query_dialog, GTK_RESPONSE_YES, FALSE); g_signal_connect (lang_combo, "changed", G_CALLBACK (pika_help_language_combo_changed), idle_help); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), lang_combo, TRUE, TRUE, 0); gtk_widget_show (lang_combo); pika_message_box_set_text (PIKA_MESSAGE_DIALOG (dialog)->box, _("You may either select a manual in another " "language or read the online version.")); } else { pika_message_box_set_text (PIKA_MESSAGE_DIALOG (dialog)->box, _("You may either install the additional help " "package or change your preferences to use " "the online version.")); } gtk_dialog_add_button (GTK_DIALOG (dialog), _("Read _Online"), GTK_RESPONSE_ACCEPT); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); if (manuals != NULL) { pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_YES, GTK_RESPONSE_CANCEL, -1); g_list_free_full (manuals, g_free); } else { pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_CANCEL, -1); } g_signal_connect (dialog, "response", G_CALLBACK (pika_help_query_online_response), idle_help); gtk_widget_show (dialog); } static void pika_help_language_combo_changed (GtkComboBox *combo, PikaIdleHelp *idle_help) { gchar *help_locales = NULL; gchar *code; code = pika_language_combo_box_get_code (PIKA_LANGUAGE_COMBO_BOX (combo)); if (code && g_strcmp0 ("", code) != 0) { help_locales = g_strdup_printf ("%s:", code); gtk_dialog_set_response_sensitive (idle_help->query_dialog, GTK_RESPONSE_YES, TRUE); } else { gtk_dialog_set_response_sensitive (idle_help->query_dialog, GTK_RESPONSE_YES, FALSE); } g_object_set (idle_help->pika->config, "help-locales", help_locales? help_locales : "", NULL); g_free (code); if (help_locales) g_free (help_locales); }