/* Copyright 2013 Endless Mobile, Inc. */

#include "config.h"
#include "eosapplication.h"
#include "eosattribution-private.h"

#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>

#include "eoswindow.h"

#define CSS_THEME_URI "resource:///com/endlessm/sdk/css/endless-widgets.css"
#define _CREDITS_DIALOG_DEFAULT_HEIGHT 450
#define _CREDITS_DIALOG_DEFAULT_WIDTH 750

/**
 * SECTION:application
 * @short_description: Start here with your application
 * @title: Applications
 *
 * The #EosApplication class is where you start when programming your
 * application.
 * You should create a class that extends #EosApplication.
 *
 * You also need to think up an application ID.
 * This takes the form of a reverse domain name, and it should be unique.
 * This ID is used to make sure that only one copy of your application is
 * running at any time; if a user tries to start a second copy, then the first
 * copy is brought to the front.
 *
 * To set up your application's data and window, override the
 * #GApplication::startup function, like this example do-nothing application,
 * <quote>Smoke Grinder</quote>:
 * |[
 * const Lang = imports.lang;
 * const Endless = imports.gi.Endless;
 *
 * const SmokeGrinder = new Lang.Class ({
 *     Name: 'SmokeGrinder',
 *     Extends: Endless.Application,
 *
 *     vfunc_startup: function() {
 *         this.parent();
 *         this._window = new Endless.Window({application: this});
 *         this._window.show_all();
 *     },
 * });
 *
 * let app = new SmokeGrinder({ application_id: "com.example.smokegrinder",
 *                              flags: 0 });
 * app.run(ARGV);
 * ]|
 *
 * You can specify attribution for images used in your application.
 * This is important if you use images that require you to credit the original
 * author, or Creative Commons-licenses.
 * The attribution takes the form of a JSON file, an array of objects with the
 * properties listed in <xref linkend="image-attribution-properties"/>.
 * See #EosApplication:image-attribution-file.
 *
 * <table id="image-attribution-properties">
 *   <thead>
 *     <tr>
 *       <th align="left">Property</th>
 *       <th align="left">Required?</th>
 *       <th align="left">Type</th>
 *       <th align="left">Description</th>
 *     </tr>
 *   </thead>
 *   <tr>
 *     <td><code>thumb_uri</code></td>
 *     <td>Yes</td>
 *     <td>string</td>
 *     <td>
 *       A URI to show in the dialog. (e.g. <code>resource:///com/endlessm/...</code>)
 *     </td>
 *   </tr>
 *   <tr>
 *     <td><code>uri</code></td>
 *     <td>No</td>
 *     <td>string</td>
 *     <td>
 *       URI where the original image is to be found: e.g., a Flickr link.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td><code>license</code></td>
 *     <td>*</td>
 *     <td>string</td>
 *     <td>
 *       Text identifying the license under which you are using this image.
 *       This field is not free-form; the allowed values are listed in <xref
 *       linkend="image-attribution-licenses"/>.
 *       If the license is not listed there, leave this field blank and clarify
 *       the license in the <code>comment</code> field.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td><code>license_uri</code></td>
 *     <td>*</td>
 *     <td>string</td>
 *     <td>
 *       URI linking to the text of the image license.
 *       If you use the <code>license</code> field, this field may be
 *       automatically filled in, so you can leave it blank.
 *       If you do specify a value, then your value will override any automatic
 *       value.
 *       Note that you will then lose any localization from the automatic value;
 *       localization of this field is planned for later.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td><code>credit</code></td>
 *     <td>*</td>
 *     <td>string</td>
 *     <td>
 *       The name or username of the author of the image.
 *       This is appropriate when the terms of use specify that the author is to
 *       be credited when the image is used.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td><code>credit_contact</code></td>
 *     <td>No</td>
 *     <td>string</td>
 *     <td>
 *       URI at which the author can be contacted.
 *       (If this is an e-mail address, prefix it with <code>mailto:</code> so
 *       that it is a valid URI.)
 *     </td>
 *   </tr>
 *   <tr>
 *     <td><code>copyright_holder</code></td>
 *     <td>*</td>
 *     <td>string</td>
 *     <td>
 *       Copyright holder of the image.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td><code>copyright_year</code></td>
 *     <td>No</td>
 *     <td>integer</td>
 *     <td>
 *       Copyright year of the image.
 *       This will be displayed along with the copyright holder.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td><code>permission</code></td>
 *     <td>No</td>
 *     <td>boolean</td>
 *     <td>
 *       Whether the image is used with permission.
 *       If this is specified, a string such as <quote>Used with
 *       permission</quote> may be displayed.
 *     </td>
 *   </tr>
 *   <tr>
 *     <td><code>comment</code></td>
 *     <td>No</td>
 *     <td>string</td>
 *     <td>
 *       Any other comments about the image license, terms of use, or source.
 *     </td>
 *   </tr>
 *   <tfoot>
 *     <tr>
 *       <td colspan="4">*At least one of these properties is required.</td>
 *     </tr>
 *   </tfoot>
 *   <caption>Allowed properties of the objects in the image attribution JSON
 *   file</caption>
 * </table>
 * <para></para>
 * <table id="image-attribution-licenses">
 *   <thead>
 *     <tr>
 *       <th align="left">String</th>
 *       <th align="left">Description</th>
 *     </tr>
 *   </thead>
 *   <tr>
 *     <td>Public domain</td>
 *     <td>Public domain</td>
 *   </tr>
 *   <tr>
 *     <td>CC0 1.0</td>
 *     <td><ulink url="http://creativecommons.org/publicdomain/zero/1.0/">CC0
 *       1.0 Universal (Public domain)</ulink></td>
 *   </tr>
 *   <tr>
 *     <td>CC BY 2.0</td>
 *     <td><ulink url="http://creativecommons.org/licenses/by/2.0/">Creative
 *       Commons Attribution 2.0</ulink></td>
 *   </tr>
 *   <tr>
 *     <td>CC BY 3.0</td>
 *     <td><ulink url="http://creativecommons.org/licenses/by/3.0/">Creative
 *       Commons Attribution 3.0</ulink></td>
 *   </tr>
 *   <tr>
 *     <td>CC BY-SA 2.0</td>
 *     <td><ulink url="http://creativecommons.org/licenses/by-sa/2.0/">Creative
 *       Commons Attribution-ShareAlike 2.0</ulink></td>
 *   </tr>
 *   <tr>
 *     <td>CC BY-SA 3.0</td>
 *     <td><ulink url="http://creativecommons.org/licenses/by-sa/3.0/">Creative
 *       Commons Attribution-ShareAlike 3.0</ulink></td>
 *   </tr>
 *   <tr>
 *     <td>CC BY-ND 2.0</td>
 *     <td><ulink url="http://creativecommons.org/licenses/by-nd/2.0/">Creative
 *       Commons Attribution-NoDerivs 2.0</ulink></td>
 *   </tr>
 *   <tr>
 *     <td>CC BY-ND 3.0</td>
 *     <td><ulink url="http://creativecommons.org/licenses/by-nd/3.0/">Creative
 *       Commons Attribution-NoDerivs 3.0</ulink></td>
 *   </tr>
 *   <caption>Allowed values for the <code>license</code> property in the image
 *   attribution JSON file</caption>
 * </table>
 */

typedef struct {
  GOnce  init_config_dir_once;
  GFile *config_dir;

  GFile *image_attribution_file;

  EosWindow *main_application_window;
} EosApplicationPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (EosApplication, eos_application, GTK_TYPE_APPLICATION)

enum
{
  PROP_0,
  PROP_CONFIG_DIR,
  PROP_IMAGE_ATTRIBUTION_FILE,
  NPROPS
};

static GParamSpec *eos_application_props[NPROPS] = { NULL, };

/* Signal handler for attribution widget requesting to show uri */
static void
on_attribution_show_uri (EosAttribution *attribution,
                         const gchar    *uri,
                         EosApplication *self)
{
  GError *error = NULL;
  if (!gtk_show_uri (NULL, uri, GDK_CURRENT_TIME, &error))
    {
      g_critical ("Error showing URI %s: %s", uri, error->message);
      g_error_free (error);
    }
}

/* Signal handler for app.image-quit::activate action */
static void
on_quit_activate (GSimpleAction  *action,
                  GVariant       *parameter,
                  gpointer       data)
{
  EosApplication *self = EOS_APPLICATION (data);
  g_application_quit (G_APPLICATION(self));
}

/* Signal handler for app.image-credits::activate action */
static void
on_image_credits_activate (GSimpleAction  *action,
                           GVariant       *parameter,
                           gpointer       data)
{
  EosApplication *self = EOS_APPLICATION (data);
  EosApplicationPrivate *priv = eos_application_get_instance_private (self);
  GtkWidget *dialog, *attribution, *content;
  GError *error = NULL;

  attribution = eos_attribution_new_sync (priv->image_attribution_file, NULL,
                                          &error);
  if (attribution == NULL)
    {
      g_warning ("Error loading image attribution file: %s", error->message);
      return;
    }
  gtk_widget_set_hexpand (attribution, TRUE);
  gtk_widget_set_vexpand (attribution, TRUE);
  g_signal_connect (attribution, "show-uri",
                    G_CALLBACK (on_attribution_show_uri), self);
  gtk_widget_show_all (attribution);

  dialog = g_object_new (GTK_TYPE_DIALOG,
                         "default-height", _CREDITS_DIALOG_DEFAULT_HEIGHT,
                         "default-width", _CREDITS_DIALOG_DEFAULT_WIDTH,
                         "destroy-with-parent", TRUE,
                         "modal", TRUE,
                         "title", _("Image credits"),
                         "transient-for", GTK_WINDOW (priv->main_application_window),
                         "use-header-bar", TRUE,
                         NULL);
  content = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
  gtk_container_add (GTK_CONTAINER (content), attribution);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
}

static void
eos_application_get_property (GObject    *object,
                              guint       property_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
  EosApplication *self = EOS_APPLICATION (object);

  switch (property_id)
    {
    case PROP_CONFIG_DIR:
      g_value_set_object (value, eos_application_get_config_dir (self));
      break;

    case PROP_IMAGE_ATTRIBUTION_FILE:
      g_value_set_object (value, eos_application_get_image_attribution_file (self));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
eos_application_set_property (GObject      *object,
                              guint         property_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
  EosApplication *self = EOS_APPLICATION (object);

  switch (property_id)
    {
    case PROP_IMAGE_ATTRIBUTION_FILE:
      eos_application_set_image_attribution_file (self,
                                                  g_value_get_object (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
eos_application_finalize (GObject *object)
{
  EosApplication *self = EOS_APPLICATION (object);
  EosApplicationPrivate *priv = eos_application_get_instance_private (self);
  g_clear_object (&priv->config_dir);
  g_clear_object (&priv->image_attribution_file);

  G_OBJECT_CLASS (eos_application_parent_class)->finalize (object);
}

static void
eos_application_activate (GApplication *application)
{
  EosApplication *self = EOS_APPLICATION (application);
  EosApplicationPrivate *priv = eos_application_get_instance_private (self);

  G_APPLICATION_CLASS (eos_application_parent_class)->activate (application);

  /* Raise the main application window if it is iconified. This behavior will
  be default in GTK at some future point, in which case the following
  paragraph can be removed. */
  if (priv->main_application_window)
    {
      gtk_window_present (GTK_WINDOW (priv->main_application_window));
    }

  /* TODO: Should it be required to override activate() as in GApplication? */
}

static gpointer
ensure_config_dir_exists_and_is_writable (EosApplication *self)
{
  EosApplicationPrivate *priv = eos_application_get_instance_private (self);
  const gchar *xdg_path = g_get_user_config_dir ();
  const gchar *app_id = g_application_get_application_id (G_APPLICATION (self));
  GFile *xdg_dir = g_file_new_for_path (xdg_path);
  GFile *config_dir = g_file_get_child (xdg_dir, app_id);
  gchar *config_path = g_file_get_path (config_dir); /* For error reporting */

  g_object_unref (xdg_dir);

  GError *error = NULL;
  if (!g_file_make_directory_with_parents (config_dir, NULL, &error))
    {
      if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_EXISTS)
        {
          g_clear_error (&error); /* Ignore G_IO_ERROR_EXISTS */
        }
      else
        {
          g_error ("There was an error creating the user config directory %s: "
                   "%s",
                   config_path, error->message);
        }
    }

  GFileInfo *info = g_file_query_info (config_dir,
                                       G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
                                       G_FILE_QUERY_INFO_NONE,
                                       NULL,
                                       &error);
  if (info == NULL)
    {
      g_error ("Checking the user config directory %s failed. This means "
               "something strange is going on in your home directory: %s",
               config_path, error->message);
    }
  if (!g_file_info_get_attribute_boolean(info,
                                         G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
    {
      g_error ("Your user config directory %s is not writable. This means "
               "something strange is going on in your home directory.",
               config_path);
    }

  g_object_unref (info);
  g_free (config_path);

  priv->config_dir = config_dir;
  return NULL;
}

static void
eos_application_startup (GApplication *application)
{
  EosApplication *self = EOS_APPLICATION (application);
  EosApplicationPrivate *priv = eos_application_get_instance_private (self);

  G_APPLICATION_CLASS (eos_application_parent_class)->startup (application);

  /* Set up the hotkey for the quit action */
  static const gchar * const quit_accels[] = { "<Primary>q", NULL };
  gtk_application_set_accels_for_action (GTK_APPLICATION (application),
                                         "app.quit",
                                         quit_accels);

  /* Set up the hotkey for the image credit dialog */
  static const gchar * const credits_accels[] = { "<Primary><Shift>a", NULL };
  gtk_application_set_accels_for_action (GTK_APPLICATION (application),
                                         "app.image-credits",
                                         credits_accels);

  GtkCssProvider *provider = gtk_css_provider_new ();

  /* Reset CSS for SDK applications and apply our own theme on top of it. This
  is so that we do not interfere with existing, complicated Adwaita theming.
  */
  GFile *css_file = g_file_new_for_uri (CSS_THEME_URI);
  gtk_css_provider_load_from_file (provider, css_file, NULL);
  g_object_unref (css_file);

  gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
                                             GTK_STYLE_PROVIDER (provider),
                                             GTK_STYLE_PROVIDER_PRIORITY_SETTINGS);
  g_debug ("Initialized theme\n");

  g_object_unref (provider);

  g_once (&priv->init_config_dir_once,
          (GThreadFunc)ensure_config_dir_exists_and_is_writable, self);
}

static void
eos_application_window_added (GtkApplication *application,
                              GtkWindow *window)
{
  EosApplication *self = EOS_APPLICATION (application);
  EosApplicationPrivate *priv = eos_application_get_instance_private (self);

  GTK_APPLICATION_CLASS (eos_application_parent_class)->window_added (
    application, window);

  /* If the new window is an EosWindow, then it is our main application window;
  it should be raised when the application is activated */
  if (EOS_IS_WINDOW (window))
    {
      if (priv->main_application_window != NULL)
        {
          g_error ("You should not add more than one application window.");
        }
      g_object_ref (window);
      priv->main_application_window = EOS_WINDOW (window);
    }
}

static void
eos_application_window_removed (GtkApplication *application,
                                GtkWindow *window)
{
  EosApplication *self = EOS_APPLICATION (application);
  EosApplicationPrivate *priv = eos_application_get_instance_private (self);

  GTK_APPLICATION_CLASS (eos_application_parent_class)->window_removed (
    application, window);

  if (EOS_IS_WINDOW (window))
    {
      if (priv->main_application_window == NULL)
        {
          g_warning ("EosWindow is being removed from EosApplication, although "
                     "none was added.");
          return;
        }
      if (priv->main_application_window != EOS_WINDOW (window))
        g_warning ("A different EosWindow is being removed from EosApplication "
                   "than the one that was added.");
      g_object_unref (window);
      priv->main_application_window = NULL;
    }
}

static void
on_app_id_set (EosApplication *self)
{
  const gchar *id = g_application_get_application_id (G_APPLICATION (self));
  g_set_prgname (id);

  /* Just in case, since g_set_prgname() does not always update the GDK
  program class, under mysterious circumstances */
  gchar *capitalized_id = g_strdup (id);
  if (capitalized_id != NULL && capitalized_id[0] != '\0')
    capitalized_id[0] = g_ascii_toupper (capitalized_id[0]);
  gdk_set_program_class (capitalized_id);
}

static void
eos_application_class_init (EosApplicationClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass);
  GtkApplicationClass *gtk_application_class = GTK_APPLICATION_CLASS (klass);

  object_class->get_property = eos_application_get_property;
  object_class->set_property = eos_application_set_property;
  object_class->finalize = eos_application_finalize;
  g_application_class->activate = eos_application_activate;
  g_application_class->startup = eos_application_startup;
  gtk_application_class->window_added = eos_application_window_added;
  gtk_application_class->window_removed = eos_application_window_removed;

  /**
   * EosApplication:config-dir:
   *
   * A directory appropriate for storing per-user configuration information for
   * this application.
   * Accessing this property guarantees that the directory exists and is
   * writable.
   * See also eos_application_get_config_dir() for more information.
   */
  eos_application_props[PROP_CONFIG_DIR] =
    g_param_spec_object ("config-dir", "Config dir",
                         "User configuration directory for this application",
                         G_TYPE_FILE,
                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  /**
   * EosApplication:image-attribution-file:
   *
   * A #GFile handle to a file for storing attribution information for the
   * images included in this application's resource file.
   *
   * This attribution file must be a JSON file.
   * Here is an example of the required format:
   * |[
   * [
   *   {
   *     "resource_path": "/com/example/smokegrinder/image1.jpg",
   *     "license": "Public domain",
   *     "uri": "http://www.photos.com/photos/12345",
   *     "comment": "No known copyright restrictions"
   *   },
   *   {
   *     "resource_path": "/com/example/smokegrinder/image2.jpg",
   *     "license_uri": "http://example.com/image-license",
   *     "uri": "http://www.photos.com/photos/54321",
   *     "credit": "Edward X. Ample",
   *     "credit_contact": "http://www.photos.com/users/example"
   *   },
   *   {
   *     "resource_path": "/com/example/smokegrinder/image3.jpg",
   *     "copyright_holder": "Jane Q. Hacker",
   *     "copyright_year": 2014,
   *     "permission": true
   *   }
   * ]
   * ]|
   *
   * The JSON object is an array of objects that each contain information about
   * one image.
   * The only required property is <code>resource_path</code>, which is the path
   * to the image in the resource file.
   *
   * The recognized properties are shown in <xref
   * linkend="image-attribution-properties"/>.
   *
   * Nothing is guaranteed about how the application uses this information.
   * It can display it to the user or make it available to other programs.
   *
   * <note><para>
   *   Currently, pressing <keycombo><keycap>Control</keycap>
   *   <keycap>Shift</keycap><keycap>A</keycap></keycombo> brings up a credits
   *   dialog.
   *   This is liable to change in future versions.
   * </para></note>
   *
   * Since: 0.2
   */
  eos_application_props[PROP_IMAGE_ATTRIBUTION_FILE] =
    g_param_spec_object ("image-attribution-file", "Image attribution file",
                         "File with attribution information for images in this application",
                         G_TYPE_FILE,
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (object_class, NPROPS,
                                     eos_application_props);
}

static void
set_image_credits_action_enabled (EosApplication *self,
                                  gboolean        enabled)
{
  GAction *action = g_action_map_lookup_action (G_ACTION_MAP (self),
                                                "image-credits");
  g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
  /* action map owns action */
}

static void
eos_application_init (EosApplication *self)
{
  EosApplicationPrivate *priv = eos_application_get_instance_private (self);
  priv->init_config_dir_once = (GOnce)G_ONCE_INIT;

  /* Set up app actions */
  static const GActionEntry actions[] = {
    { "quit", on_quit_activate, NULL, NULL, NULL },
    { "image-credits", on_image_credits_activate, NULL, NULL, NULL }
  };

  g_action_map_add_action_entries (G_ACTION_MAP (self),
                                   actions, G_N_ELEMENTS (actions),
                                   self);

  set_image_credits_action_enabled (self, FALSE);

  g_signal_connect (self, "notify::application-id",
                    G_CALLBACK (on_app_id_set), self);
}

/* Public API */

/**
 * eos_application_new:
 * @application_id: a unique identifier for the application, for example a
 * reverse domain name.
 * @flags: flags to apply to the application; see #GApplicationFlags.
 *
 * Create a new application. For the application ID, use a reverse domain name,
 * such as <code>com.endlessm.weather</code>. See g_application_id_is_valid()
 * for the full rules for application IDs.
 *
 * Returns: a pointer to the application.
 */
EosApplication *
eos_application_new (const gchar      *application_id,
                     GApplicationFlags flags)
{
  return g_object_new (EOS_TYPE_APPLICATION,
                       "application-id", application_id,
                       "flags", flags,
                       NULL);
}

/**
 * eos_application_get_config_dir:
 * @self: the application
 *
 * Gets a #GFile pointing to the application-specific user configuration
 * directory.
 * This directory is located in <code>XDG_USER_CONFIG_DIR</code>, which usually
 * expands to <filename class="directory">~/.config</filename>.
 * The directory name is the same as the application's unique ID (see
 * #GApplication:application-id.)
 *
 * You should use this directory to store configuration data specific to your
 * application and specific to one user, such as cookies.
 *
 * Calling this function will also ensure that the directory exists and is
 * writable.
 * If it does not exist, it will be created.
 * If it cannot be created, or it exists but is not writable, the program will
 * abort.
 *
 * Returns: (transfer none): A #GFile pointing to the user config directory.
 */
GFile *
eos_application_get_config_dir (EosApplication *self)
{
  EosApplicationPrivate *priv = eos_application_get_instance_private (self);
  g_once (&priv->init_config_dir_once,
          (GThreadFunc)ensure_config_dir_exists_and_is_writable, self);
  return priv->config_dir;
}

/**
 * eos_application_get_image_attribution_file:
 * @self: the application
 *
 * Gets a #GFile pointing to a JSON file containing credits for images included
 * in the app's resources.
 * See #EosApplication:image-attribution-file.
 *
 * Returns: (transfer none) (allow-none): A #GFile pointing to the image
 * attribution file, or %NULL if one has not been set.
 *
 * Since: 0.2
 */
GFile *
eos_application_get_image_attribution_file (EosApplication *self)
{
  g_return_val_if_fail (self != NULL && EOS_IS_APPLICATION (self), NULL);

  EosApplicationPrivate *priv = eos_application_get_instance_private (self);
  return priv->image_attribution_file;
}

/**
 * eos_application_set_image_attribution_file:
 * @self: the application
 * @file: (allow-none): a #GFile pointing to a file in the proper format, or
 * %NULL to unset.
 *
 * You can provide attribution and credit for images included in the application
 * by giving this function a JSON file with image credits.
 * See #EosApplication:image-attribution-file for the JSON file's required
 * format.
 *
 * Since: 0.2
 */
void
eos_application_set_image_attribution_file (EosApplication *self,
                                            GFile          *file)
{
  g_return_if_fail (self != NULL && EOS_IS_APPLICATION (self));
  g_return_if_fail (file == NULL || G_IS_FILE (file));

  EosApplicationPrivate *priv = eos_application_get_instance_private (self);

  if (priv->image_attribution_file == file ||
      (priv->image_attribution_file != NULL && file != NULL &&
       g_file_equal (file, priv->image_attribution_file)))
    return;

  if (priv->image_attribution_file == NULL || file == NULL)
    {
      gboolean enabled = (file != NULL);
      set_image_credits_action_enabled (self, enabled);
    }

  g_clear_object (&priv->image_attribution_file);
  if (file != NULL)
    g_object_ref (file);
  priv->image_attribution_file = file;

  g_object_notify_by_pspec (G_OBJECT (self),
                            eos_application_props[PROP_IMAGE_ATTRIBUTION_FILE]);
}
