/* * Web view members and methods for normal schemes (http, https, file, about) * * 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 "schemes.h" #include #include #include #include "../application.h" #include "../config.h" #include "../notebook.h" #include "../tab-page.h" #include "../web-view.h" static void finalize(MqWebViewScheme *scheme) { memset(&scheme->normal, 0, sizeof(scheme->normal)); } static gboolean match_uri(const gchar *uri) { /* This is a catch-all scheme handler, so match any schemes not handled * by another handler. */ return !g_str_has_prefix(uri, "view-source:"); } static gchar * rewrite_uri(MqWebView G_GNUC_UNUSED *web_view, MqWebViewScheme G_GNUC_UNUSED *scheme, const gchar *uri) { if (g_str_has_prefix(uri, "about:")) { return g_strconcat("mq-about:", uri + strlen("about:"), NULL); } else { return g_strdup(uri); } } static gchar * display_uri(MqWebView G_GNUC_UNUSED *web_view, MqWebViewScheme G_GNUC_UNUSED *scheme, const gchar *uri) { if (g_str_has_prefix(uri, "mq-about:")) { return g_strconcat("about:", uri + strlen("mq-about:"), NULL); } else { return g_strdup(uri); } } #define WKCMA(ACTION) \ WEBKIT_CONTEXT_MENU_ACTION_##ACTION static void menu_open_link_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { MqWebViewNormalScheme *scheme; scheme = &mq_web_view_get_scheme(web_view)->normal; mq_web_view_load_uri(web_view, webkit_hit_test_result_get_link_uri( scheme->hit_test_result)); } static void menu_open_link_tab_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { MqWebViewNormalScheme *scheme; scheme = &mq_web_view_get_scheme(web_view)->normal; mq_notebook_insert_child( MQ_NOTEBOOK(gtk_widget_get_parent(GTK_WIDGET( mq_web_view_get_tab_page(web_view)))), webkit_hit_test_result_get_link_uri(scheme->hit_test_result), mq_web_view_get_tab_page(web_view), !mq_config_get_boolean(mq_web_view_get_config(web_view), "tabs.background")); } static void 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(mq_web_view_get_scheme( web_view)->normal.hit_test_result), NULL }; mq_application_add_window( mq_tab_page_get_application(mq_web_view_get_tab_page(web_view)), uris); } static void menu_open_image_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { MqWebViewNormalScheme *scheme; scheme = &mq_web_view_get_scheme(web_view)->normal; mq_web_view_load_uri(web_view, webkit_hit_test_result_get_image_uri( scheme->hit_test_result)); } static void menu_open_image_tab_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { MqWebViewNormalScheme *scheme; scheme = &mq_web_view_get_scheme(web_view)->normal; mq_notebook_insert_child( MQ_NOTEBOOK(gtk_widget_get_parent(GTK_WIDGET( mq_web_view_get_tab_page(web_view)))), webkit_hit_test_result_get_image_uri(scheme->hit_test_result), mq_web_view_get_tab_page(web_view), !mq_config_get_boolean(mq_web_view_get_config(web_view), "tabs.background")); } static void menu_open_image_win_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { const gchar *uris[2] = { webkit_hit_test_result_get_image_uri(mq_web_view_get_scheme( web_view)->normal.hit_test_result), NULL }; mq_application_add_window( mq_tab_page_get_application(mq_web_view_get_tab_page(web_view)), uris); } static void menu_open_video_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { MqWebViewNormalScheme *scheme; scheme = &mq_web_view_get_scheme(web_view)->normal; mq_web_view_load_uri(web_view, webkit_hit_test_result_get_media_uri( scheme->hit_test_result)); } static void menu_open_video_tab_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { MqWebViewNormalScheme *scheme; scheme = &mq_web_view_get_scheme(web_view)->normal; mq_notebook_insert_child( MQ_NOTEBOOK(gtk_widget_get_parent(GTK_WIDGET( mq_web_view_get_tab_page(web_view)))), webkit_hit_test_result_get_media_uri(scheme->hit_test_result), mq_web_view_get_tab_page(web_view), !mq_config_get_boolean(mq_web_view_get_config(web_view), "tabs.background")); } static void menu_open_video_win_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { const gchar *uris[2] = { webkit_hit_test_result_get_media_uri(mq_web_view_get_scheme( web_view)->normal.hit_test_result), NULL }; mq_application_add_window( mq_tab_page_get_application(mq_web_view_get_tab_page(web_view)), uris); } static void menu_open_audio_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { MqWebViewNormalScheme *scheme; scheme = &mq_web_view_get_scheme(web_view)->normal; mq_web_view_load_uri(web_view, webkit_hit_test_result_get_media_uri( scheme->hit_test_result)); } static void menu_open_audio_tab_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { MqWebViewNormalScheme *scheme; scheme = &mq_web_view_get_scheme(web_view)->normal; mq_notebook_insert_child( MQ_NOTEBOOK(gtk_widget_get_parent(GTK_WIDGET( mq_web_view_get_tab_page(web_view)))), webkit_hit_test_result_get_media_uri(scheme->hit_test_result), mq_web_view_get_tab_page(web_view), !mq_config_get_boolean(mq_web_view_get_config(web_view), "tabs.background")); } static void menu_open_audio_win_activate_cb(GtkAction G_GNUC_UNUSED *action, MqWebView *web_view) { const gchar *uris[2] = { webkit_hit_test_result_get_media_uri(mq_web_view_get_scheme( web_view)->normal.hit_test_result), NULL }; mq_application_add_window( mq_tab_page_get_application(mq_web_view_get_tab_page(web_view)), uris); } #define ITEM_DECLS \ GtkAction *action; \ WebKitContextMenuItem *menu_item; #define ITEM_DECLS_NO_CUSTOM \ WebKitContextMenuItem *menu_item; #define NEW_CUSTOM_ITEM(NAME, LABEL) \ G_STMT_START { \ /* Don't blame me; blame WebKitGTK+ for using GtkAction. */ \ G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ action = gtk_action_new(#NAME, (LABEL), NULL, NULL); \ G_GNUC_END_IGNORE_DEPRECATIONS \ g_signal_connect(action, "activate", \ G_CALLBACK(menu_##NAME##_activate_cb), web_view); \ menu_item = webkit_context_menu_item_new(action); \ webkit_context_menu_append(context_menu, menu_item); \ } G_STMT_END #define NEW_STOCK_ITEM(STOCK) \ G_STMT_START { \ menu_item = webkit_context_menu_item_new_from_stock_action( \ WEBKIT_CONTEXT_MENU_ACTION_##STOCK); \ webkit_context_menu_append(context_menu, menu_item); \ } G_STMT_END #define NEW_SEPARATOR_ITEM() \ G_STMT_START { \ webkit_context_menu_append(context_menu, \ webkit_context_menu_item_new_separator()); \ } G_STMT_END #define RESTORE_ITEMS(ITEMS) \ G_STMT_START { \ for (; ITEMS; ITEMS = ITEMS->next) { \ webkit_context_menu_append(context_menu, ITEMS->data); \ g_object_unref(ITEMS->data); \ } \ } G_STMT_END static void context_menu_link_cb(WebKitContextMenu *context_menu, MqWebView *web_view) { ITEM_DECLS NEW_CUSTOM_ITEM(open_link, "_Open Link"); NEW_CUSTOM_ITEM(open_link_tab, "Open Link in New _Tab"); NEW_CUSTOM_ITEM(open_link_win, "Open Link in New _Window"); NEW_SEPARATOR_ITEM(); /* --- */ NEW_STOCK_ITEM(DOWNLOAD_LINK_TO_DISK); /* _Download Linked File */ NEW_STOCK_ITEM(COPY_LINK_TO_CLIPBOARD); /* Copy Link Loc_ation */ } static void context_menu_image_cb(WebKitContextMenu *context_menu, MqWebView *web_view) { ITEM_DECLS NEW_CUSTOM_ITEM(open_image, "Open _Image"); NEW_CUSTOM_ITEM(open_image_tab, "Open Image in New Tab"); NEW_CUSTOM_ITEM(open_image_win, "Open Image in New Window"); NEW_SEPARATOR_ITEM(); /* --- */ NEW_STOCK_ITEM(DOWNLOAD_IMAGE_TO_DISK); /* Sa_ve Image As */ NEW_STOCK_ITEM(COPY_IMAGE_TO_CLIPBOARD); /* Cop_y Image */ NEW_STOCK_ITEM(COPY_IMAGE_URL_TO_CLIPBOARD); /* Copy Image _Address */ } static void context_menu_media_cb(WebKitContextMenu *context_menu, GList *media_ctrl_items, GList *media_toggle_items, gboolean is_video, MqWebView *web_view) { ITEM_DECLS /* _Play/_Pause, _Mute */ RESTORE_ITEMS(media_ctrl_items); /* _Toggle Media Controls, Toggle Media _Loop Playback, Switch Video to * _Fullscreen */ RESTORE_ITEMS(media_toggle_items); NEW_SEPARATOR_ITEM(); /* --- */ if (is_video) { NEW_CUSTOM_ITEM(open_video, "_Open Video"); NEW_CUSTOM_ITEM(open_video_tab, "Open Video in New Tab"); NEW_CUSTOM_ITEM(open_video_win, "Open Video in New Window"); NEW_SEPARATOR_ITEM(); /* --- */ NEW_STOCK_ITEM(DOWNLOAD_VIDEO_TO_DISK); /* Download _Video */ /* Cop_y Video Link Location */ NEW_STOCK_ITEM(COPY_VIDEO_LINK_TO_CLIPBOARD); } else { NEW_CUSTOM_ITEM(open_audio, "_Open Audio"); NEW_CUSTOM_ITEM(open_audio_tab, "Open Audio in New Tab"); NEW_CUSTOM_ITEM(open_audio_win, "Open Audio in New Window"); NEW_SEPARATOR_ITEM(); /* --- */ NEW_STOCK_ITEM(DOWNLOAD_AUDIO_TO_DISK); /* Download _Audio */ /* Cop_y Audio Link Location */ NEW_STOCK_ITEM(COPY_AUDIO_LINK_TO_CLIPBOARD); } } static void context_menu_editable_cb(WebKitContextMenu *context_menu, GList *spell_repl_items, GList *spell_ctrl_items, GList *edit_items, GList *input_items, MqWebView G_GNUC_UNUSED *web_view) { ITEM_DECLS_NO_CUSTOM RESTORE_ITEMS(spell_repl_items); /* Spelling suggestions */ NEW_SEPARATOR_ITEM(); /* --- */ RESTORE_ITEMS(spell_ctrl_items); /* _Ignore Spelling, _Learn Spelling */ NEW_SEPARATOR_ITEM(); /* --- */ RESTORE_ITEMS(edit_items); /* Cu_t, _Copy, _Paste, _Delete */ NEW_SEPARATOR_ITEM(); /* --- */ NEW_STOCK_ITEM(SELECT_ALL); /* Select _All */ NEW_SEPARATOR_ITEM(); /* --- */ RESTORE_ITEMS(input_items); /* _Insert Unicode Character */ } static void context_menu_document_cb(WebKitContextMenu *context_menu, GList *nav_items, MqWebView G_GNUC_UNUSED *web_view) { ITEM_DECLS_NO_CUSTOM RESTORE_ITEMS(nav_items); /* _Back, _Forward, _Stop, _Reload */ NEW_SEPARATOR_ITEM(); /* --- */ NEW_STOCK_ITEM(SELECT_ALL); /* Select _All */ NEW_SEPARATOR_ITEM(); /* --- */ /* TODO: View Page Source */ } #define PRESERVE_ITEM(ITEMS) \ G_STMT_START { \ g_object_ref(items->data); \ ITEMS = g_list_prepend(ITEMS, items->data); \ } G_STMT_END static gboolean context_menu(MqWebView *web_view, MqWebViewScheme G_GNUC_UNUSED *scheme, WebKitContextMenu *context_menu, GdkEvent G_GNUC_UNUSED *event, WebKitHitTestResult *hit_test_result) { GList *items; GList *nav_items; GList *edit_items; GList *input_items; GList *spell_repl_items; GList *spell_ctrl_items; GList *media_ctrl_items; GList *media_toggle_items; gboolean is_selection; gboolean is_video; WebKitContextMenuAction stock_action; WebKitHitTestResultContext context; gboolean context_handled; WebKitContextMenuItem *menu_item; /* 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 * guesses). */ items = webkit_context_menu_get_items(context_menu); nav_items = NULL; edit_items = NULL; input_items = NULL; spell_repl_items = NULL; spell_ctrl_items = NULL; media_ctrl_items = NULL; media_toggle_items = NULL; is_selection = FALSE; is_video = FALSE; for (; items; items = items->next) { stock_action = webkit_context_menu_item_get_stock_action( items->data); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch" switch (stock_action) { case WKCMA(GO_BACK): case WKCMA(GO_FORWARD): case WKCMA(STOP): case WKCMA(RELOAD): PRESERVE_ITEM(nav_items); break; case WKCMA(COPY): PRESERVE_ITEM(edit_items); is_selection = TRUE; break; case WKCMA(CUT): case WKCMA(PASTE): case WKCMA(DELETE): PRESERVE_ITEM(edit_items); break; case WKCMA(INPUT_METHODS): case WKCMA(UNICODE): PRESERVE_ITEM(input_items); break; case WKCMA(SPELLING_GUESS): case WKCMA(NO_GUESSES_FOUND): PRESERVE_ITEM(spell_repl_items); break; case WKCMA(IGNORE_SPELLING): case WKCMA(LEARN_SPELLING): case WKCMA(IGNORE_GRAMMAR): PRESERVE_ITEM(spell_ctrl_items); break; case WKCMA(OPEN_VIDEO_IN_NEW_WINDOW): case WKCMA(COPY_VIDEO_LINK_TO_CLIPBOARD): case WKCMA(DOWNLOAD_VIDEO_TO_DISK): is_video = TRUE; break; case WKCMA(OPEN_AUDIO_IN_NEW_WINDOW): case WKCMA(COPY_AUDIO_LINK_TO_CLIPBOARD): case WKCMA(DOWNLOAD_AUDIO_TO_DISK): is_video = FALSE; break; case WKCMA(TOGGLE_MEDIA_CONTROLS): case WKCMA(TOGGLE_MEDIA_LOOP): case WKCMA(ENTER_VIDEO_FULLSCREEN): PRESERVE_ITEM(media_toggle_items); break; case WKCMA(MEDIA_PLAY): case WKCMA(MEDIA_PAUSE): case WKCMA(MEDIA_MUTE): PRESERVE_ITEM(media_ctrl_items); break; } #pragma GCC diagnostic pop } nav_items = g_list_reverse(nav_items); edit_items = g_list_reverse(edit_items); input_items = g_list_reverse(input_items); spell_repl_items = g_list_reverse(spell_repl_items); spell_ctrl_items = g_list_reverse(spell_ctrl_items); media_ctrl_items = g_list_reverse(media_ctrl_items); media_toggle_items = g_list_reverse(media_toggle_items); /* Clear the menu. */ webkit_context_menu_remove_all(context_menu); /* 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 (scheme->normal.hit_test_result) { g_object_unref(scheme->normal.hit_test_result); } scheme->normal.hit_test_result = hit_test_result; g_object_ref(scheme->normal.hit_test_result); context_handled = FALSE; /* Build the context menu. */ if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) { 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, 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, 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, web_view); context_handled = TRUE; } if (!context_handled && context & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) { if (is_selection) { RESTORE_ITEMS(edit_items); /* _Copy */ context_handled = TRUE; } else { context_menu_document_cb(context_menu, nav_items, web_view); context_handled = TRUE; } } if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) { if (context_handled) { NEW_SEPARATOR_ITEM(); } NEW_STOCK_ITEM(INSPECT_ELEMENT); /* Inspect Element */ } /* Propagate the event further and show the context menu. */ return FALSE; } static void save_html_replace_cb(GFile *file, GAsyncResult *result, guchar *data) { g_file_replace_contents_finish(file, result, NULL, NULL); /* TODO: Error handling? */ g_free(data); } static void save_html_get_data_cb(WebKitWebResource *resource, GAsyncResult *result, MqWebView *web_view) { MqWebViewNormalScheme *scheme; guchar *data; gsize length; scheme = &mq_web_view_get_scheme(web_view)->normal; data = webkit_web_resource_get_data_finish(resource, result, &length, NULL); /* TODO: Error handling? */ g_file_replace_contents_async(scheme->save_file, (gchar *) data, length, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL, (GAsyncReadyCallback) save_html_replace_cb, data); } static void save_mhtml_cb(WebKitWebView *web_view, GAsyncResult *result) { webkit_web_view_save_to_file_finish(web_view, result, NULL); } static gchar * get_extension(gchar *filename) { gchar *extension; extension = strrchr(filename, '.'); if (extension) { return extension; } else { return filename + strlen(filename); } } static gboolean extension_is_html(const gchar *extension) { return extension[0] == '.' && extension[1] == 'h' && extension[2] == 't' && extension[3] == 'm' && (extension[4] == '\0' || (extension[4] == 'l' && extension[5] == '\0')); } static gboolean extension_is_mhtml(const gchar *extension) { return extension[0] == '.' && extension[1] == 'm' && extension[2] == 'h' && extension[3] == 't' && (extension[4] == '\0' || (extension[4] == 'm' && (extension[5] == '\0' || (extension[5] == 'l' && extension[6] == '\0')))); } static void save_html(MqWebView *web_view, GFile *file) { mq_web_view_get_scheme(web_view)->normal.save_file = file; webkit_web_resource_get_data( webkit_web_view_get_main_resource(WEBKIT_WEB_VIEW(web_view)), NULL, (GAsyncReadyCallback) save_html_get_data_cb, web_view); } static void save_mhtml(MqWebView *web_view, GFile *file) { webkit_web_view_save_to_file(WEBKIT_WEB_VIEW(web_view), file, WEBKIT_SAVE_MODE_MHTML, NULL, (GAsyncReadyCallback) save_mhtml_cb, NULL); } static gchar * get_clean_title(MqWebView *web_view) { const gchar *title; title = webkit_web_view_get_title(WEBKIT_WEB_VIEW(web_view)); if (!title || title[0] == '\0') { title = mq_web_view_get_uri(web_view); } if (!title || title[0] == '\0') { title = "page"; } return g_strdelimit(g_strdup(title), #ifdef G_OS_WIN32 "/\\:*\"?<>|", #else "/", #endif '_'); } static void save_type_changed_cb(GtkComboBox *combo_box, GtkFileChooser *chooser) { gchar *name; gchar *extension; const gchar *active_id; gchar *new_name; name = gtk_file_chooser_get_current_name(chooser); extension = get_extension(name); active_id = gtk_combo_box_get_active_id(combo_box); if (g_strcmp0(active_id, "html") == 0) { if (!extension_is_html(extension)) { /* Extension is not "htm" or "html". */ extension[0] = '\0'; /* Remove extension. */ new_name = g_strconcat(name, ".html", NULL); gtk_file_chooser_set_current_name(chooser, new_name); g_free(new_name); } } else if (g_strcmp0(active_id, "mhtml") == 0) { if (!extension_is_mhtml(extension)) { /* Extension is not "mht", "mhtm", or "mhtml". */ extension[0] = '\0'; /* Remove extension. */ new_name = g_strconcat(name, ".mhtml", NULL); gtk_file_chooser_set_current_name(chooser, new_name); g_free(new_name); } } g_free(name); } static void save_response_cb(GtkWidget *dialog, gint response_id, MqWebView *web_view) { MqWebViewNormalScheme *scheme; gchar *dir; gchar *filename; GFile *file; gchar *extension; const gchar *active_id; scheme = &mq_web_view_get_scheme(web_view)->normal; if (response_id == GTK_RESPONSE_ACCEPT) { dir = gtk_file_chooser_get_current_folder( GTK_FILE_CHOOSER(dialog)); if (dir) { mq_config_set_string(mq_web_view_get_config(web_view), "directories.downloads", dir); g_free(dir); mq_config_save(mq_web_view_get_config(web_view)); } filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog)); file = g_file_new_for_path(filename); active_id = gtk_combo_box_get_active_id( scheme->save_type_combo_box); if (g_strcmp0(active_id, "detect") == 0) { extension = get_extension(filename); if (extension_is_html(extension)) { save_html(web_view, file); } else { save_mhtml(web_view, file); } } else if (g_strcmp0(active_id, "html") == 0) { save_html(web_view, file); } else if (g_strcmp0(active_id, "mhtml") == 0) { save_mhtml(web_view, file); } g_free(filename); } gtk_widget_destroy(dialog); } static void save_file(MqWebView *web_view, MqWebViewScheme *scheme) { GtkWidget *dialog; GtkFileChooser *chooser; gchar *dir; gchar *title; gchar *filename; GtkWidget *type_combo_box; GtkWidget *type_box; dialog = gtk_file_chooser_dialog_new("Save File", GTK_WINDOW(mq_tab_page_get_window( mq_web_view_get_tab_page(web_view))), GTK_FILE_CHOOSER_ACTION_SAVE, "_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL); chooser = GTK_FILE_CHOOSER(dialog); gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); dir = mq_config_get_string(mq_web_view_get_config(web_view), "directories.downloads"); gtk_file_chooser_set_current_folder(chooser, dir); g_free(dir); title = get_clean_title(web_view); filename = g_strconcat(title, ".mhtml", NULL); g_free(title); gtk_file_chooser_set_current_name(chooser, filename); g_free(filename); mq_web_view_add_html_mhtml_file_chooser_filters(chooser); type_combo_box = gtk_combo_box_text_new(); gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(type_combo_box), "detect", "By extension"); gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(type_combo_box), "html", "HTML document only"); gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(type_combo_box), "mhtml", "MHTML archive with associated resources"); gtk_combo_box_set_active(GTK_COMBO_BOX(type_combo_box), 0); g_signal_connect(type_combo_box, "changed", G_CALLBACK(save_type_changed_cb), chooser); scheme->normal.save_type_combo_box = GTK_COMBO_BOX(type_combo_box); type_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(type_box), gtk_label_new("Save as file type:"), FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(type_box), type_combo_box, FALSE, FALSE, 0); gtk_file_chooser_set_extra_widget(chooser, type_box); g_signal_connect(dialog, "response", G_CALLBACK(save_response_cb), web_view); gtk_widget_show_all(dialog); } MqWebViewSchemeMethods mq_web_view_normal_scheme_methods = { .match_uri = match_uri, .finalize = finalize, .rewrite_uri = rewrite_uri, .display_uri = display_uri, .context_menu = context_menu, .save_file = save_file, };