/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * Copyright © 2012 Igalia S.L. * * 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 2, 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "config.h" #include "ephy-history-service.h" #include "ephy-overview-store.h" #include "ephy-removable-pixbuf-renderer.h" #include "ephy-snapshot-service.h" #include "ephy-widgets-type-builtins.h" /* Update thumbnails after one week. */ #define THUMBNAIL_UPDATE_THRESHOLD (60 * 60 * 24 * 7) #define EPHY_OVERVIEW_STORE_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_OVERVIEW_STORE, EphyOverviewStorePrivate)) struct _EphyOverviewStorePrivate { EphyHistoryService *history_service; GdkPixbuf *default_icon; GdkPixbuf *icon_frame; }; enum { PROP_0, PROP_HISTORY_SERVICE, PROP_DEFAULT_ICON, PROP_ICON_FRAME, }; G_DEFINE_TYPE (EphyOverviewStore, ephy_overview_store, GTK_TYPE_LIST_STORE) static void ephy_overview_store_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { EphyOverviewStore *store = EPHY_OVERVIEW_STORE (object); switch (prop_id) { case PROP_HISTORY_SERVICE: store->priv->history_service = g_value_dup_object (value); g_object_notify (object, "history-service"); break; case PROP_DEFAULT_ICON: ephy_overview_store_set_default_icon (store, g_value_get_object (value)); break; case PROP_ICON_FRAME: ephy_overview_store_set_icon_frame (store, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void ephy_overview_store_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { EphyOverviewStore *store = EPHY_OVERVIEW_STORE (object); switch (prop_id) { case PROP_HISTORY_SERVICE: g_value_set_object (value, store->priv->history_service); break; case PROP_DEFAULT_ICON: g_value_set_object (value, store->priv->default_icon); break; case PROP_ICON_FRAME: g_value_set_object (value, store->priv->icon_frame); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void ephy_overview_store_dispose (GObject *object) { EphyOverviewStorePrivate *priv = EPHY_OVERVIEW_STORE (object)->priv; if (priv->history_service) g_clear_object (&priv->history_service); if (priv->default_icon) g_clear_object (&priv->default_icon); if (priv->icon_frame) g_clear_object (&priv->icon_frame); G_OBJECT_CLASS (ephy_overview_store_parent_class)->dispose (object); } static void ephy_overview_store_class_init (EphyOverviewStoreClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = ephy_overview_store_set_property; object_class->get_property = ephy_overview_store_get_property; object_class->dispose = ephy_overview_store_dispose; g_object_class_install_property (object_class, PROP_HISTORY_SERVICE, g_param_spec_object ("history-service", "History service", "History Service", EPHY_TYPE_HISTORY_SERVICE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_object_class_install_property (object_class, PROP_DEFAULT_ICON, g_param_spec_object ("default-icon", "Default icon", "Default Icon", GDK_TYPE_PIXBUF, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_object_class_install_property (object_class, PROP_ICON_FRAME, g_param_spec_object ("icon-frame", "Icon frame", "Frame to display around icons", GDK_TYPE_PIXBUF, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_type_class_add_private (object_class, sizeof(EphyOverviewStorePrivate)); } static void ephy_overview_store_init (EphyOverviewStore *self) { GType types[EPHY_OVERVIEW_STORE_NCOLS]; types[EPHY_OVERVIEW_STORE_ID] = G_TYPE_STRING; types[EPHY_OVERVIEW_STORE_URI] = G_TYPE_STRING; types[EPHY_OVERVIEW_STORE_TITLE] = G_TYPE_STRING; types[EPHY_OVERVIEW_STORE_AUTHOR] = G_TYPE_STRING; types[EPHY_OVERVIEW_STORE_SNAPSHOT] = GDK_TYPE_PIXBUF; types[EPHY_OVERVIEW_STORE_LAST_VISIT] = G_TYPE_INT; types[EPHY_OVERVIEW_STORE_SELECTED] = G_TYPE_BOOLEAN; types[EPHY_OVERVIEW_STORE_SNAPSHOT_CANCELLABLE] = G_TYPE_CANCELLABLE; types[EPHY_OVERVIEW_STORE_SNAPSHOT_MTIME] = G_TYPE_INT; types[EPHY_OVERVIEW_STORE_CLOSE_BUTTON_RENDER_POLICY] = EPHY_TYPE_REMOVABLE_PIXBUF_RENDER_POLICY; gtk_list_store_set_column_types (GTK_LIST_STORE (self), EPHY_OVERVIEW_STORE_NCOLS, types); self->priv = EPHY_OVERVIEW_STORE_GET_PRIVATE (self); } typedef struct { GtkTreeRowReference *ref; char *url; WebKitWebView *webview; GCancellable *cancellable; time_t timestamp; } PeekContext; static void peek_context_free (PeekContext *ctx) { g_free (ctx->url); gtk_tree_row_reference_free (ctx->ref); if (ctx->webview) g_object_unref (ctx->webview); if (ctx->cancellable) g_object_unref (ctx->cancellable); g_slice_free (PeekContext, ctx); } static GdkPixbuf * ephy_overview_store_add_frame (EphyOverviewStore *store, GdkPixbuf *snapshot) { GdkPixbuf *framed; if (store->priv->icon_frame) { framed = gdk_pixbuf_copy (store->priv->icon_frame); gdk_pixbuf_copy_area (snapshot, 0, 0, gdk_pixbuf_get_width (snapshot), gdk_pixbuf_get_height (snapshot), framed, 10, 9); } else framed = g_object_ref (snapshot); return framed; } static void ephy_overview_store_set_snapshot_internal (EphyOverviewStore *store, GtkTreeIter *iter, GdkPixbuf *snapshot, int mtime) { GdkPixbuf *framed; framed = ephy_overview_store_add_frame (store, snapshot); gtk_list_store_set (GTK_LIST_STORE (store), iter, EPHY_OVERVIEW_STORE_SNAPSHOT, framed, EPHY_OVERVIEW_STORE_SNAPSHOT_MTIME, mtime, -1); g_object_unref (framed); } typedef struct { EphyHistoryURL *url; EphyHistoryService *history_service; } ThumbnailTimeContext; static void on_snapshot_saved_cb (EphySnapshotService *service, GAsyncResult *res, ThumbnailTimeContext *ctx) { ephy_history_service_set_url_thumbnail_time (ctx->history_service, ctx->url->url, ctx->url->thumbnail_time, NULL, NULL, NULL); ephy_history_url_free (ctx->url); g_slice_free (ThumbnailTimeContext, ctx); } void ephy_overview_store_set_snapshot (EphyOverviewStore *store, GtkTreeIter *iter, cairo_surface_t *snapshot) { GdkPixbuf *pixbuf; char *url; ThumbnailTimeContext *ctx; EphySnapshotService *snapshot_service; int mtime; mtime = time (NULL); pixbuf = ephy_snapshot_service_crop_snapshot (snapshot); ephy_overview_store_set_snapshot_internal (store, iter, pixbuf, mtime); gtk_tree_model_get (GTK_TREE_MODEL (store), iter, EPHY_OVERVIEW_STORE_URI, &url, -1); ctx = g_slice_new (ThumbnailTimeContext); ctx->url = ephy_history_url_new (url, NULL, 0, 0, 0); ctx->url->thumbnail_time = mtime; ctx->history_service = store->priv->history_service; g_free (url); snapshot_service = ephy_snapshot_service_get_default (); ephy_snapshot_service_save_snapshot_async (snapshot_service, pixbuf, ctx->url->url, ctx->url->thumbnail_time, NULL, (GAsyncReadyCallback) on_snapshot_saved_cb, ctx); g_object_unref (pixbuf); } static void ephy_overview_store_set_default_icon_internal (EphyOverviewStore *store, GtkTreeIter *iter, GdkPixbuf *default_icon) { gtk_list_store_set (GTK_LIST_STORE (store), iter, EPHY_OVERVIEW_STORE_SNAPSHOT, default_icon, EPHY_OVERVIEW_STORE_SNAPSHOT_MTIME, 0, -1); } static void set_snapshot (EphyOverviewStore *store, GdkPixbuf *snapshot, GtkTreeRowReference *ref, time_t timestamp) { GtkTreePath *path; GtkTreeIter iter; path = gtk_tree_row_reference_get_path (ref); gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); gtk_tree_path_free (path); if (snapshot) ephy_overview_store_set_snapshot_internal (store, &iter, snapshot, timestamp); else ephy_overview_store_set_default_icon_internal (store, &iter, store->priv->default_icon); gtk_list_store_set (GTK_LIST_STORE (store), &iter, EPHY_OVERVIEW_STORE_SNAPSHOT_CANCELLABLE, NULL, -1); } static void on_snapshot_retrieved_cb (GObject *object, GAsyncResult *res, PeekContext *ctx) { GdkPixbuf *snapshot; snapshot = ephy_snapshot_service_get_snapshot_finish (EPHY_SNAPSHOT_SERVICE (object), res, NULL); set_snapshot (EPHY_OVERVIEW_STORE (gtk_tree_row_reference_get_model (ctx->ref)), snapshot, ctx->ref, ctx->timestamp); if (snapshot) g_object_unref (snapshot); peek_context_free (ctx); } static void on_snapshot_retrieved_for_url_cb (GObject *object, GAsyncResult *res, PeekContext *ctx) { GdkPixbuf *snapshot; snapshot = ephy_snapshot_service_get_snapshot_for_url_finish (EPHY_SNAPSHOT_SERVICE (object), res, NULL); set_snapshot (EPHY_OVERVIEW_STORE (gtk_tree_row_reference_get_model (ctx->ref)), snapshot, ctx->ref, ctx->timestamp); if (snapshot) g_object_unref (snapshot); peek_context_free (ctx); } static void history_service_url_cb (gpointer service, gboolean success, EphyHistoryURL *url, PeekContext *ctx) { EphySnapshotService *snapshot_service; snapshot_service = ephy_snapshot_service_get_default (); ctx->timestamp = url->thumbnail_time; if (ctx->webview) ephy_snapshot_service_get_snapshot_async (snapshot_service, ctx->webview, ctx->timestamp, ctx->cancellable, (GAsyncReadyCallback) on_snapshot_retrieved_cb, ctx); else ephy_snapshot_service_get_snapshot_for_url_async (snapshot_service, ctx->url, ctx->timestamp, ctx->cancellable, (GAsyncReadyCallback) on_snapshot_retrieved_for_url_cb, ctx); ephy_history_url_free (url); } void ephy_overview_store_peek_snapshot (EphyOverviewStore *self, WebKitWebView *webview, GtkTreeIter *iter) { char *url; GtkTreePath *path; PeekContext *ctx; GCancellable *cancellable; gtk_tree_model_get (GTK_TREE_MODEL (self), iter, EPHY_OVERVIEW_STORE_URI, &url, EPHY_OVERVIEW_STORE_SNAPSHOT_CANCELLABLE, &cancellable, -1); if (cancellable) { g_cancellable_cancel (cancellable); g_object_unref (cancellable); } if (url == NULL || g_strcmp0 (url, "about:blank") == 0) { gtk_list_store_set (GTK_LIST_STORE (self), iter, EPHY_OVERVIEW_STORE_SNAPSHOT_CANCELLABLE, NULL, -1); return; } cancellable = g_cancellable_new (); gtk_list_store_set (GTK_LIST_STORE (self), iter, EPHY_OVERVIEW_STORE_SNAPSHOT_CANCELLABLE, cancellable, -1); ctx = g_slice_new (PeekContext); path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), iter); ctx->ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (self), path); ctx->url = url; ctx->webview = webview ? g_object_ref (webview) : NULL; ctx->cancellable = cancellable; ephy_history_service_get_url (self->priv->history_service, url, NULL, (EphyHistoryJobCallback)history_service_url_cb, ctx); gtk_tree_path_free (path); } static gboolean set_default_icon_helper (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GdkPixbuf *new_default_icon) { EphyOverviewStorePrivate *priv; GdkPixbuf *current_pixbuf; priv = EPHY_OVERVIEW_STORE (model)->priv; gtk_tree_model_get (model, iter, EPHY_OVERVIEW_STORE_SNAPSHOT, ¤t_pixbuf, -1); if (current_pixbuf == priv->default_icon || current_pixbuf == NULL) ephy_overview_store_set_default_icon_internal (EPHY_OVERVIEW_STORE (model), iter, new_default_icon); g_object_unref (current_pixbuf); return FALSE; } void ephy_overview_store_set_default_icon (EphyOverviewStore *store, GdkPixbuf *default_icon) { GdkPixbuf *new_default_icon; if (store->priv->default_icon) g_object_unref (store->priv->default_icon); new_default_icon = ephy_overview_store_add_frame (store, default_icon); gtk_tree_model_foreach (GTK_TREE_MODEL (store), (GtkTreeModelForeachFunc) set_default_icon_helper, new_default_icon); store->priv->default_icon = new_default_icon; g_object_notify (G_OBJECT (store), "default-icon"); } void ephy_overview_store_set_icon_frame (EphyOverviewStore *store, GdkPixbuf *icon_frame) { gboolean update_default = FALSE; GdkPixbuf *old_default_icon; if (store->priv->icon_frame == icon_frame) return; if (store->priv->icon_frame) g_object_unref (store->priv->icon_frame); else if (store->priv->default_icon) update_default = TRUE; store->priv->icon_frame = g_object_ref (icon_frame); if (update_default) { old_default_icon = g_object_ref (store->priv->default_icon); ephy_overview_store_set_default_icon (store, old_default_icon); g_object_unref (old_default_icon); } g_object_notify (G_OBJECT (store), "icon-frame"); } gboolean ephy_overview_store_needs_snapshot (EphyOverviewStore *store, GtkTreeIter *iter) { GdkPixbuf *icon; GCancellable *cancellable; gboolean needs_snapshot; int mtime, current_mtime; g_return_val_if_fail (EPHY_IS_OVERVIEW_STORE (store), FALSE); g_return_val_if_fail (iter != NULL, FALSE); current_mtime = time (NULL); gtk_tree_model_get (GTK_TREE_MODEL (store), iter, EPHY_OVERVIEW_STORE_SNAPSHOT, &icon, EPHY_OVERVIEW_STORE_SNAPSHOT_MTIME, &mtime, EPHY_OVERVIEW_STORE_SNAPSHOT_CANCELLABLE, &cancellable, -1); /* If the thumbnail is the default icon and there is no cancellable in the row, then this row needs a snapshot. */ needs_snapshot = (icon == store->priv->default_icon && cancellable == NULL) || current_mtime - mtime > THUMBNAIL_UPDATE_THRESHOLD; if (icon) g_object_unref (icon); if (cancellable) g_object_unref (cancellable); return needs_snapshot; } gboolean ephy_overview_store_remove (EphyOverviewStore *store, GtkTreeIter *iter) { GCancellable *cancellable; g_return_val_if_fail (EPHY_IS_OVERVIEW_STORE (store), FALSE); gtk_tree_model_get (GTK_TREE_MODEL (store), iter, EPHY_OVERVIEW_STORE_SNAPSHOT_CANCELLABLE, &cancellable, -1); if (cancellable) { g_cancellable_cancel (cancellable); g_object_unref (cancellable); } return gtk_list_store_remove (GTK_LIST_STORE (store), iter); } typedef struct { GtkTreeRowReference *ref; EphyOverviewStoreAnimRemoveFunc callback; gpointer user_data; } AnimRemoveContext; static gboolean animated_remove_func (AnimRemoveContext *ctx) { GtkTreeRowReference *ref; EphyOverviewStore *store; GtkTreePath *path; GtkTreeIter iter; GdkPixbuf *orig_pixbuf, *new_pixbuf; int width, height; gboolean valid; ref = ctx->ref; store = EPHY_OVERVIEW_STORE (gtk_tree_row_reference_get_model (ref)); path = gtk_tree_row_reference_get_path (ref); gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); gtk_tree_path_free (path); gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, EPHY_OVERVIEW_STORE_SNAPSHOT, &orig_pixbuf, -1); width = gdk_pixbuf_get_width (orig_pixbuf); height = gdk_pixbuf_get_height (orig_pixbuf); if (width > 10) { new_pixbuf = gdk_pixbuf_scale_simple (orig_pixbuf, width * 0.80, height * 0.80, GDK_INTERP_TILES); g_object_unref (orig_pixbuf); gtk_list_store_set (GTK_LIST_STORE (store), &iter, EPHY_OVERVIEW_STORE_SNAPSHOT, new_pixbuf, -1); g_object_unref (new_pixbuf); return TRUE; } g_object_unref (orig_pixbuf); valid = ephy_overview_store_remove (store, &iter); if (ctx->callback) ctx->callback (store, &iter, valid, ctx->user_data); gtk_tree_row_reference_free (ref); g_slice_free (AnimRemoveContext, ctx); return FALSE; } void ephy_overview_store_animated_remove (EphyOverviewStore *store, GtkTreeRowReference *ref, EphyOverviewStoreAnimRemoveFunc callback, gpointer user_data) { GtkTreePath *path; GtkTreeIter iter; AnimRemoveContext *ctx = g_slice_new0 (AnimRemoveContext); ctx->ref = ref; ctx->callback = callback; ctx->user_data = user_data; path = gtk_tree_row_reference_get_path (ref); gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); gtk_tree_path_free (path); gtk_list_store_set (GTK_LIST_STORE (store), &iter, EPHY_OVERVIEW_STORE_CLOSE_BUTTON_RENDER_POLICY, EPHY_REMOVABLE_PIXBUF_RENDER_NEVER, -1); g_timeout_add (40, (GSourceFunc) animated_remove_func, ctx); } gboolean ephy_overview_store_find_url (EphyOverviewStore *store, const char *url, GtkTreeIter *iter) { gboolean valid, found = FALSE; char *row_url; g_return_val_if_fail (EPHY_IS_OVERVIEW_STORE (store), FALSE); g_return_val_if_fail (url != NULL, FALSE); valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), iter); while (valid) { gtk_tree_model_get (GTK_TREE_MODEL (store), iter, EPHY_OVERVIEW_STORE_URI, &row_url, -1); found = g_strcmp0 (row_url, url) == 0; g_free (row_url); if (found) break; valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), iter); } return found; }