/* this file is part of atril, a mate document viewer * * Copyright (C) 2014 Avishkar Gupta * Based on ev-view.c, also a part of atril, a mate document viewer. * * Atril 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 2 of the License, or * (at your option) any later version. * * Atril 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #if ENABLE_EPUB #include #include #include #include #include #include "ev-web-view.h" #include "ev-document-model.h" #include "ev-document.h" #include "ev-jobs.h" struct _EvWebView { WebKitWebView web_view; EvDocument *document; EvDocumentModel *model; GepubDoc *gepub_doc; gint current_page; gboolean inverted_stylesheet; gboolean fullscreen; WebKitFindController *findcontroller; WebKitFindOptions findoptions; gdouble zoom_level; gchar *hlink; gchar *search_string; }; struct _EvWebViewClass { WebKitWebViewClass base_class; }; /*** Callbacks ***/ static void ev_web_view_change_page (EvWebView *webview, gint new_page); static void ev_web_view_page_changed_cb (EvDocumentModel *model, gint old_page, gint new_page, EvWebView *webview); /*** GObject ***/ static void ev_web_view_dispose (GObject *object); static void ev_web_view_finalize (GObject *object); G_DEFINE_TYPE (EvWebView, ev_web_view, WEBKIT_TYPE_WEB_VIEW) static void web_view_update_range_and_current_page (EvWebView *webview) { g_return_if_fail(EV_IS_WEB_VIEW(webview)); if (ev_document_get_n_pages (webview->document) <= 0) return; ev_document_model_set_page(webview->model, 0); webview->current_page = 0; EvPage *webpage = ev_document_get_page (webview->document, 0); if (webpage->backend_page) webkit_web_view_load_uri (WEBKIT_WEB_VIEW (webview), (gchar*)webpage->backend_page); g_object_unref (webpage); } static void ev_web_view_dispose (GObject *object) { EvWebView *webview = EV_WEB_VIEW (object); if (webview->document) { g_object_unref(webview->document); webview->document = NULL ; } if (webview->model) { g_object_unref(webview->model); webview->model = NULL; } if (webview->hlink) { g_free(webview->hlink); webview->hlink = NULL; } if (webview->search_string) { g_free(webview->search_string); webview->search_string = NULL; } G_OBJECT_CLASS (ev_web_view_parent_class)->dispose (object); } static void ev_web_view_class_init (EvWebViewClass *klass) { G_OBJECT_CLASS(klass)->finalize = ev_web_view_finalize; G_OBJECT_CLASS(klass)->dispose = ev_web_view_dispose; } static void epub_uri_scheme_request_cb (WebKitURISchemeRequest *request, gpointer user_data) { WebKitWebView *wv = webkit_uri_scheme_request_get_web_view (request); EvWebView *webview = EV_WEB_VIEW (wv); const gchar *path = webkit_uri_scheme_request_get_path (request); if (!webview->document || !path) { GError *error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Resource not found: %s", path ? path : "(null)"); webkit_uri_scheme_request_finish_error (request, error); g_error_free (error); return; } while (*path == '/') path++; GBytes *resource = ev_document_get_resource (webview->document, path); gchar *mime = ev_document_get_resource_mime (webview->document, path); if (!resource) { GError *error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Resource not found: %s", path); webkit_uri_scheme_request_finish_error (request, error); g_error_free (error); g_free (mime); return; } gsize size = g_bytes_get_size (resource); GInputStream *stream = g_memory_input_stream_new_from_bytes (resource); webkit_uri_scheme_request_finish (request, stream, size, mime ? mime : "application/octet-stream"); g_object_unref (stream); g_bytes_unref (resource); g_free (mime); } static void ev_web_view_init (EvWebView *webview) { static gboolean scheme_registered = FALSE; gtk_widget_set_can_focus (GTK_WIDGET (webview), TRUE); gtk_widget_set_has_window (GTK_WIDGET (webview), TRUE); if (!scheme_registered) { WebKitWebContext *context = webkit_web_context_get_default (); webkit_web_context_register_uri_scheme (context, "epub", epub_uri_scheme_request_cb, NULL, NULL); scheme_registered = TRUE; } webview->current_page = 0; webview->fullscreen = FALSE; webview->inverted_stylesheet = FALSE; webview->hlink = NULL; webview->search_string = NULL; } static void ev_web_view_finalize (GObject *object) { G_OBJECT_CLASS(ev_web_view_parent_class)->finalize(object); } /*** Callbacks ***/ static void ev_web_view_change_page (EvWebView *webview, gint new_page) { g_return_if_fail(EV_IS_WEB_VIEW(webview)); webview->current_page = new_page; ev_document_model_set_page(webview->model,new_page); webkit_find_controller_search_finish(webview->findcontroller); if (webview->hlink) { webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),(gchar*)webview->hlink); g_free(webview->hlink); webview->hlink = NULL; } else { EvPage *page = ev_document_get_page (webview->document, new_page); if (page->backend_page) webkit_web_view_load_uri (WEBKIT_WEB_VIEW (webview), (gchar*)page->backend_page); g_object_unref (page); } } static void ev_web_view_page_changed_cb (EvDocumentModel *model, gint old_page, gint new_page, EvWebView *webview) { if (!webview->document) return; if (webview->current_page != new_page) { ev_web_view_change_page (webview, new_page); } else { webkit_web_view_reload (WEBKIT_WEB_VIEW(webview)); } } GtkWidget* ev_web_view_new (void) { GtkWidget *webview; webview = g_object_new (EV_TYPE_WEB_VIEW, NULL); EV_WEB_VIEW(webview)->findcontroller = webkit_web_view_get_find_controller (WEBKIT_WEB_VIEW(webview)); EV_WEB_VIEW(webview)->findoptions = webkit_find_controller_get_options (EV_WEB_VIEW(webview)->findcontroller); EV_WEB_VIEW(webview)->zoom_level = 1.0; EV_WEB_VIEW(webview)->findoptions |= WEBKIT_FIND_OPTIONS_NONE; return webview; } static void ev_web_view_document_changed_cb (EvDocumentModel *model, GParamSpec *pspec, EvWebView *webview) { g_return_if_fail(EV_IS_WEB_VIEW(webview)); EvDocument *document = ev_document_model_get_document (model); if (document != webview->document) { if (webview->document ) g_object_unref(webview->document); webview->document = document ; if(webview->document) { g_object_ref(webview->document); webview->gepub_doc = ev_document_get_doc_handle (webview->document); } webview->inverted_stylesheet = FALSE; gint current_page = ev_document_model_get_page(model); ev_web_view_change_page (webview, current_page); } } static const gchar *night_mode_css = "body { color: #fff !important; background-color: #000 !important; }" "* { color: inherit !important; background-color: inherit !important; }" "img { filter: none !important; }"; static void ev_web_view_inverted_colors_changed_cb (EvDocumentModel *model, GParamSpec *pspec, EvWebView *webview) { EvDocument *document = ev_document_model_get_document (model); if (!document || !document->iswebdocument) return; WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (webview)); if (ev_document_model_get_inverted_colors (model)) { WebKitUserStyleSheet *sheet = webkit_user_style_sheet_new ( night_mode_css, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL); webkit_user_content_manager_add_style_sheet (ucm, sheet); webkit_user_style_sheet_unref (sheet); webview->inverted_stylesheet = TRUE; } else { webkit_user_content_manager_remove_all_style_sheets (ucm); webview->inverted_stylesheet = FALSE; } webkit_web_view_reload (WEBKIT_WEB_VIEW (webview)); } void ev_web_view_set_model (EvWebView *webview, EvDocumentModel *model) { g_return_if_fail (EV_IS_WEB_VIEW (webview)); g_return_if_fail (EV_IS_DOCUMENT_MODEL (model)); if (model == webview->model) return; if (webview->model) { g_signal_handlers_disconnect_by_func (webview->model, ev_web_view_document_changed_cb, webview); g_signal_handlers_disconnect_by_func (webview->model, ev_web_view_page_changed_cb, webview); g_object_unref (webview->model); } webview->model = g_object_ref (model); /* Initialize webview from model */ webview->fullscreen = ev_document_model_get_fullscreen (webview->model); webview->document = ev_document_model_get_document(webview->model); ev_web_view_document_changed_cb (webview->model, NULL, webview); g_signal_connect (webview->model, "notify::document", G_CALLBACK (ev_web_view_document_changed_cb), webview); g_signal_connect (webview->model, "notify::inverted-colors", G_CALLBACK (ev_web_view_inverted_colors_changed_cb), webview); g_signal_connect (webview->model,"page-changed", G_CALLBACK(ev_web_view_page_changed_cb), webview); } void ev_web_view_reload_page (EvWebView *webview, gint page) { webkit_web_view_reload (WEBKIT_WEB_VIEW(webview)); } void ev_web_view_reload (EvWebView *webview) { web_view_update_range_and_current_page (webview); } gboolean ev_web_view_next_page (EvWebView *webview) { int page, n_pages; g_return_val_if_fail (EV_IS_WEB_VIEW (webview), FALSE); if (!webview->document) return FALSE; page = ev_document_model_get_page (webview->model); n_pages = ev_document_get_n_pages (webview->document); page = page + 1; if (page < n_pages) { ev_document_model_set_page (webview->model, page); webview->current_page = page; return TRUE; } else if (page == n_pages) { ev_document_model_set_page (webview->model, page - 1); return TRUE; } else { return FALSE; } } gboolean ev_web_view_previous_page (EvWebView *webview) { int page; g_return_val_if_fail (EV_IS_WEB_VIEW (webview), FALSE); if (!webview->document) return FALSE; page = ev_document_model_get_page (webview->model); page = page - 1 ; if (page >= 0) { ev_document_model_set_page (webview->model, page); webview->current_page = page; return TRUE; } else if (page == -1) { ev_document_model_set_page (webview->model, 0); return TRUE; } else { return FALSE; } } void ev_web_view_handle_link(EvWebView *webview,EvLink *link) { EvLinkAction *action = NULL; EvLinkDest *dest = NULL; EvLinkDestType dest_type ; action = ev_link_get_action(link); if (action == NULL) return; dest = ev_link_action_get_dest(action); if (dest == NULL) return; dest_type = ev_link_dest_get_dest_type(dest); switch(dest_type) { case EV_LINK_DEST_TYPE_PAGE: { ev_document_model_set_page(webview->model,ev_link_dest_get_page(dest)); break; } case EV_LINK_DEST_TYPE_PAGE_LABEL: { const gchar *text = ev_link_dest_get_page_label (dest); gint page = atoi(text); if (page <= ev_document_get_n_pages(webview->document) && page > 0) { ev_document_model_set_page(webview->model,page-1); } break; } case EV_LINK_DEST_TYPE_HLINK: { const gchar *uri = ev_link_dest_get_named_dest(dest); ev_document_model_set_page(webview->model,ev_link_dest_get_page(dest)); webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),uri); break; default:return; } } } /* Searching */ static void ev_web_view_find_restart (EvWebView *webview) { if (!webview->search_string || !webview->search_string[0]) return; webkit_find_controller_search (webview->findcontroller, webview->search_string, webview->findoptions, G_MAXUINT); } void ev_web_view_find_changed (EvWebView *webview, const gchar *text, gboolean case_sensitive) { g_free (webview->search_string); webview->search_string = g_strdup (text); webview->findoptions = WEBKIT_FIND_OPTIONS_NONE; if (!case_sensitive) webview->findoptions |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE; webview->findoptions |= WEBKIT_FIND_OPTIONS_WRAP_AROUND; ev_web_view_find_restart (webview); } void ev_web_view_find_next (EvWebView *webview) { webkit_find_controller_search_next (webview->findcontroller); } void ev_web_view_find_previous (EvWebView *webview) { webkit_find_controller_search_previous (webview->findcontroller); } void ev_web_view_find_search_changed (EvWebView *webview) { webkit_find_controller_search_finish (webview->findcontroller); } void ev_web_view_find_cancel (EvWebView *webview) { webkit_find_controller_search_finish (webview->findcontroller); g_free (webview->search_string); webview->search_string = NULL; } /* Selection and copying*/ void ev_web_view_select_all(EvWebView *webview) { webkit_web_view_execute_editing_command(WEBKIT_WEB_VIEW(webview), WEBKIT_EDITING_COMMAND_SELECT_ALL); } static void copy_text_cb(WebKitWebView *webview, GAsyncResult *res, gpointer data) { gboolean okay_to_copy = webkit_web_view_can_execute_editing_command_finish (WEBKIT_WEB_VIEW(webview), res, NULL); if (okay_to_copy) { webkit_web_view_execute_editing_command (WEBKIT_WEB_VIEW(webview), WEBKIT_EDITING_COMMAND_COPY); } } void ev_web_view_copy(EvWebView *webview) { webkit_web_view_can_execute_editing_command(WEBKIT_WEB_VIEW(webview), WEBKIT_EDITING_COMMAND_COPY, NULL, (GAsyncReadyCallback)copy_text_cb, NULL); } /*Zoom control*/ gboolean ev_web_view_zoom_in(EvWebView *webview) { webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW(webview), (webview->zoom_level+= 0.1)); return TRUE; } gboolean ev_web_view_zoom_out(EvWebView *webview) { if (webview->zoom_level == 1) return FALSE; webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW(webview), (webview->zoom_level -= 0.1)); return TRUE; } gboolean ev_web_view_zoom_reset(EvWebView *webview) { if (webview->zoom_level == 1) return FALSE; webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW(webview), 1.0); return TRUE; } /** * ev_web_view_disconnect_handlers * @webview : #EvWebView instance * * This function call will disconnect all model signal handlers from the webview, to ensure smooth operation of the Atril-view. * Equivalent to function ev_view_disconnect_handlers in ev-view.c */ void ev_web_view_disconnect_handlers(EvWebView *webview) { g_signal_handlers_disconnect_by_func(webview->model, ev_web_view_document_changed_cb, webview); g_signal_handlers_disconnect_by_func(webview->model, ev_web_view_inverted_colors_changed_cb, webview); g_signal_handlers_disconnect_by_func(webview->model, ev_web_view_page_changed_cb, webview); } #endif