Logo Search packages:      
Sourcecode: nautilus-cd-burner version File versions  Download package

burn-extension.c

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
 *
 * Copyright (C) 2003 Novell, Inc.
 * Copyright (C) 2003-2004 Red Hat, Inc.
 * Copyright (C) 2005 William Jon McCann <mccann@jhu.edu>
 *
 * 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 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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <libhal.h>
#include <libgnomevfs/gnome-vfs.h>
#include <libgnomevfs/gnome-vfs-utils.h>
#include <eel/eel-features.h>
#include <eel/eel-stock-dialogs.h>
#include <eel/eel-vfs-extensions.h>
#include <libnautilus-extension/nautilus-menu-provider.h>
#include <libnautilus-extension/nautilus-location-widget-provider.h>

#include "nautilus-burn-bar.h"

#define NAUTILUS_TYPE_BURN  (nautilus_burn_get_type ())
#define NAUTILUS_BURN(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), NAUTILUS_TYPE_BURN, NautilusBurn))
#define NAUTILUS_IS_BURN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAUTILUS_TYPE_BURN))

typedef struct _NautilusBurnPrivate NautilusBurnPrivate;

typedef struct
{
      GObject              parent_slot;
        NautilusBurnPrivate *priv;
} NautilusBurn;

typedef struct
{
      GObjectClass parent_slot;
} NautilusBurnClass;

#define NAUTILUS_BURN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NAUTILUS_TYPE_BURN, NautilusBurnPrivate))

struct _NautilusBurnPrivate
{
      GnomeVFSMonitorHandle *burn_monitor;
      guint                  empty : 1;

      guint                  start_monitor_id;
      guint                  empty_update_id;

      GSList                *widget_list;
};

static GType nautilus_burn_get_type      (void);
static void  nautilus_burn_register_type (GTypeModule *module);

static GObjectClass *parent_class;

#ifndef EEL_CHECK_VERSION
#define     EEL_CHECK_VERSION(major,minor,micro)      \
    (EEL_MAJOR_VERSION > (major) || \
     (EEL_MAJOR_VERSION == (major) && EEL_MINOR_VERSION > (minor)) || \
     (EEL_MAJOR_VERSION == (major) && EEL_MINOR_VERSION == (minor) && \
      EEL_MICRO_VERSION >= (micro)))
#endif

#undef DEBUG_ENABLE

#ifdef DEBUG_ENABLE
#ifdef G_HAVE_ISO_VARARGS
#  define DEBUG_PRINT(...) debug_print (__VA_ARGS__);
#elif defined(G_HAVE_GNUC_VARARGS)
#  define DEBUG_PRINT(args...) debug_print (args);
#endif
#else
#ifdef G_HAVE_ISO_VARARGS
#  define DEBUG_PRINT(...)
#elif defined(G_HAVE_GNUC_VARARGS)
#  define DEBUG_PRINT(args...)
#endif
#endif

#ifdef DEBUG_ENABLE
static FILE *debug_out = NULL;

static void
debug_init (void)
{
      const char path [50] = "burn_extension_debug_XXXXXX";
      int  fd = g_file_open_tmp (path, NULL, NULL);
      if (fd >= 0) {
            debug_out = fdopen (fd, "a");
      }
}

static void
debug_print (const char *format, ...)
{
      va_list args;

      va_start (args, format);
      vfprintf ((debug_out ? debug_out : stderr), format, args);
      vfprintf (stderr, format, args);
      va_end (args);
      if (debug_out)
            fflush (debug_out);
}
#endif

static void
launch_process (char **argv, GtkWindow *parent)
{
      GError *error;

      error = NULL;
      if (!g_spawn_async (NULL,
                      argv, NULL,
                      0,
                      NULL, NULL,
                      NULL,
                      &error)) {

#if EEL_CHECK_VERSION (2,13,3)
            eel_show_error_dialog (_("Unable to launch the cd burner application"),
                               error->message,
                               GTK_WINDOW (parent));
#else
            eel_show_error_dialog (_("Unable to launch the cd burner application"),
                               error->message,
                               "",
                               GTK_WINDOW (parent));
#endif

            g_error_free (error);
      }
}

static void
launch_ncb_on_window (GtkWindow *window)
{
      char *argv [2];

      argv [0] = g_build_filename (BINDIR, "nautilus-cd-burner", NULL);
      argv [1] = NULL;

      launch_process (argv, window);

      g_free (argv [0]);
}

static void
write_activate_cb (NautilusMenuItem *item,
               gpointer          user_data)
{
      launch_ncb_on_window (GTK_WINDOW (user_data));
}

static void
write_iso_activate_cb (NautilusMenuItem *item,
                   gpointer          user_data)
{
      NautilusFileInfo *file;
      char             *argv [3];
      char             *uri;
      char             *image_name;

      file = g_object_get_data (G_OBJECT (item), "file");

      uri = nautilus_file_info_get_uri (file);
      image_name = gnome_vfs_get_local_path_from_uri (uri);
      if (!image_name) {
            g_warning ("Can not get local path for URI %s", uri);
            g_free (uri);
            return;
      }

      g_free (uri);

      argv [0] = g_build_filename (BINDIR, "nautilus-cd-burner", NULL);
      argv [1] = g_strdup_printf ("--source-iso=%s", image_name);
      argv [2] = NULL;

      launch_process (argv, GTK_WINDOW (user_data));

      g_free (argv [1]);
      g_free (argv [0]);
      g_free (image_name);
}

static void
write_cue_activate_cb (NautilusMenuItem *item,
                   gpointer          user_data)
{
      NautilusFileInfo *file;
      char             *argv [3];
      char             *uri;
      char             *image_name;

      file = g_object_get_data (G_OBJECT (item), "file");

      uri = nautilus_file_info_get_uri (file);
      image_name = gnome_vfs_get_local_path_from_uri (uri);
      if (!image_name) {
            g_warning ("Can not get local path for URI %s", uri);
            g_free (uri);
            return;
      }

      g_free (uri);

      argv [0] = g_build_filename (BINDIR, "nautilus-cd-burner", NULL);
      argv [1] = g_strdup_printf ("--source-cue=%s", image_name);
      argv [2] = NULL;

      launch_process (argv, GTK_WINDOW (user_data));

      g_free (argv [1]);
      g_free (argv [0]);
      g_free (image_name);
}

static void
copy_disc_activate_cb (NautilusMenuItem *item,
                   gpointer          user_data)
{
      char             *argv [3];
      char             *device_path;

      device_path = g_object_get_data (G_OBJECT (item), "drive_device_path");

      if (!device_path) {
            g_warning ("Drive device path not specified");
            return;
      }

      argv [0] = g_build_filename (BINDIR, "nautilus-cd-burner", NULL);
      argv [1] = g_strdup_printf ("--source-device=%s", device_path);
      argv [2] = NULL;

      launch_process (argv, GTK_WINDOW (user_data));

      g_free (argv [1]);
      g_free (argv [0]);
}


static LibHalContext *
get_hal_context (void)
{
      static LibHalContext *ctx = NULL;
      DBusError         error;
      DBusConnection         *dbus_conn;

      if (ctx == NULL) {
            ctx = libhal_ctx_new ();
            if (ctx == NULL) {
                  g_warning ("Could not create a HAL context");
            } else {
                  dbus_error_init (&error);
                  dbus_conn = dbus_bus_get (DBUS_BUS_SYSTEM, &error);

                  if (dbus_error_is_set (&error)) {
                        g_warning ("Could not connect to system bus: %s", error.message);
                        dbus_error_free (&error);
                        return NULL;
                  }

                  libhal_ctx_set_dbus_connection (ctx, dbus_conn);

                  if (! libhal_ctx_init (ctx, &error)) {
                        g_warning ("Could not initalize the HAL context: %s",
                                 error.message);

                        if (dbus_error_is_set (&error))
                              dbus_error_free (&error);

                        libhal_ctx_free (ctx);
                        ctx = NULL;
                  }
            }
      }

      return ctx;
}

static gboolean
volume_is_blank (GnomeVFSVolume *volume)
{
      LibHalContext *ctx;
      char          *udi;
      gboolean       is_blank;

      ctx = get_hal_context ();
      if (ctx == NULL) {
            return FALSE;
      }

      is_blank = FALSE;

      udi = gnome_vfs_volume_get_hal_udi (volume);
      if (udi != NULL) {
            is_blank = libhal_device_get_property_bool (ctx,
                                              udi,
                                              "volume.disc.is_blank",
                                              NULL);
      }

      g_free (udi);

      return is_blank;
}

static GnomeVFSVolume *
drive_get_first_volume (GnomeVFSDrive *drive)
{
      GnomeVFSVolume *volume;
      GList          *volumes;

      volumes = gnome_vfs_drive_get_mounted_volumes (drive);

      volume = g_list_nth_data (volumes, 0);

      if (volume != NULL) {
            gnome_vfs_volume_ref (volume);
      }

      gnome_vfs_drive_volume_list_free (volumes);

      return volume;
}

static GList *
nautilus_burn_get_file_items (NautilusMenuProvider *provider,
                        GtkWidget            *window,
                        GList                *selection)
{
      GList            *items = NULL;
      NautilusMenuItem *item;
      NautilusFileInfo *file;
      GnomeVFSFileInfo *info;
      GnomeVFSVolume   *volume;
      GnomeVFSDrive    *drive;
      char             *mime_type;
      gboolean          is_local;
      gboolean          is_iso;
      gboolean          is_cue;

      if (!selection || selection->next != NULL) {
            return NULL;
      }

      file = NAUTILUS_FILE_INFO (selection->data);

      if (nautilus_file_info_is_gone (file)) {
            return NULL;
      }

      info = nautilus_file_info_get_vfs_file_info (file);

      if (! info) {
            return NULL;
      }

      is_local = GNOME_VFS_FILE_INFO_LOCAL (info);
      gnome_vfs_file_info_unref (info);

      mime_type = nautilus_file_info_get_mime_type (file);
      if (! mime_type) {
            return NULL;
      }

      is_iso = (strcmp (mime_type, "application/x-iso-image") == 0)
            || (strcmp (mime_type, "application/x-cd-image") == 0);
      is_cue = strcmp (mime_type, "application/x-cue") == 0
            || (strcmp (mime_type, "application/x-cdrdao-toc") == 0);

      if (is_iso && is_local) {
            item = nautilus_menu_item_new ("NautilusBurn::write_iso",
                                     _("_Write to Disc..."),
                                     _("Write disc image to a CD or DVD disc"),
                                     "nautilus-cd-burner");
            g_object_set_data (G_OBJECT (item), "file", file);
            g_object_set_data (G_OBJECT (item), "window", window);
            g_signal_connect (item, "activate",
                          G_CALLBACK (write_iso_activate_cb), NULL);
            items = g_list_append (items, item);
      } else if (is_cue && is_local) {
            item = nautilus_menu_item_new ("NautilusBurn::write_cue",
                                     _("_Write to Disc..."),
                                     _("Write disc image cuesheet to a CD or DVD disc"),
                                     "nautilus-cd-burner");
            g_object_set_data (G_OBJECT (item), "file", file);
            g_object_set_data (G_OBJECT (item), "window", window);
            g_signal_connect (item, "activate",
                          G_CALLBACK (write_cue_activate_cb), NULL);
            items = g_list_append (items, item);
      }

      /*
       * We handle two cases here.  The selection is:
       *  A) a volume
       *  B) a drive
       *
       * This is because there is very little distinction between
       * the two for CD/DVD media
       */

      drive = nautilus_file_info_get_drive (file);
      volume = nautilus_file_info_get_volume (file);

      /* nautilus-file-info doesn't increment refcount */
      if (drive != NULL) {
            gnome_vfs_drive_ref (drive);
      }
      /* nautilus-file-info doesn't increment refcount */
      if (volume != NULL) {
            gnome_vfs_volume_ref (volume);
      }

      if (drive == NULL && volume != NULL) {
            /* case A */
            drive = gnome_vfs_volume_get_drive (volume);
      } else if (volume == NULL && drive != NULL) {
            /* case B */
            volume = drive_get_first_volume (drive);
      }

      if (drive != NULL
          && volume != NULL
          && (gnome_vfs_drive_get_device_type (drive) == GNOME_VFS_DEVICE_TYPE_CDROM)
          && !volume_is_blank (volume)) {
            char *device_path;

            device_path = gnome_vfs_drive_get_device_path (drive);

            item = nautilus_menu_item_new ("NautilusBurn::copy_disc",
                                     _("_Copy Disc..."),
                                     _("Create a copy of this CD or DVD disc"),
                                     "nautilus-cd-burner");
            g_object_set_data (G_OBJECT (item), "file", file);
            g_object_set_data (G_OBJECT (item), "window", window);
            g_object_set_data_full (G_OBJECT (item), "drive_device_path", device_path, g_free);
            g_signal_connect (item, "activate",
                          G_CALLBACK (copy_disc_activate_cb), NULL);
            items = g_list_append (items, item);
      }

      if (drive != NULL) {
            gnome_vfs_drive_unref (drive);
      }
      if (volume != NULL) {
            gnome_vfs_volume_unref (volume);
      }

      g_free (mime_type);

      return items;
}

static GList *
nautilus_burn_get_background_items (NautilusMenuProvider *provider,
                            GtkWidget            *window,
                            NautilusFileInfo     *current_folder)
{
      GList *items;
      char  *scheme;

      items = NULL;

      scheme = nautilus_file_info_get_uri_scheme (current_folder);

      if (!strcmp (scheme, "burn")) {
            NautilusMenuItem *item;

            item = nautilus_menu_item_new ("NautilusBurn::write_menu",
                                     _("_Write to Disc..."),
                                     _("Write contents to a CD or DVD disc"),
                                     "nautilus-cd-burner");
            g_signal_connect (item, "activate",
                          G_CALLBACK (write_activate_cb), window);
            items = g_list_append (items, item);

            g_object_set (item, "sensitive", ! NAUTILUS_BURN (provider)->priv->empty, NULL);
      }

      g_free (scheme);

      return items;
}

static GList *
nautilus_burn_get_toolbar_items (NautilusMenuProvider *provider,
                         GtkWidget            *window,
                         NautilusFileInfo     *current_folder)
{
      GList *items;

      items = NULL;

      return items;
}

static void
nautilus_burn_menu_provider_iface_init (NautilusMenuProviderIface *iface)
{
      iface->get_file_items = nautilus_burn_get_file_items;
      iface->get_background_items = nautilus_burn_get_background_items;
      iface->get_toolbar_items = nautilus_burn_get_toolbar_items;
}

static void
bar_activated_cb (NautilusBurnBar *bar,
              gpointer         data)
{
      launch_ncb_on_window (GTK_WINDOW (data));
}

static gboolean
dir_is_empty (const char *uri)
{
      GnomeVFSFileInfo  *info;
      GnomeVFSDirectoryHandle *handle;
      GnomeVFSResult           result;
      gboolean           found_file;

      result = gnome_vfs_directory_open (&handle, uri, GNOME_VFS_FILE_INFO_DEFAULT);

      if (result != GNOME_VFS_OK) {
            DEBUG_PRINT ("Could not open burn uri %s: %s\n",
                       uri,
                       gnome_vfs_result_to_string (result));
            return TRUE;
      }

      info = gnome_vfs_file_info_new ();

      found_file = FALSE;

      while (TRUE) {
            result = gnome_vfs_directory_read_next (handle, info);
            if (result != GNOME_VFS_OK) {
                  break;
            }

            /* Skip "." and "..".  */
            if (info->name != NULL
                && info->name [0] == '.'
                && (info->name [1] == 0
                  || (info->name [1] == '.' && info->name [2] == 0))) {
                  gnome_vfs_file_info_clear (info);
                  continue;
            }

            found_file = TRUE;
            break;
      }

      gnome_vfs_directory_close (handle);
      gnome_vfs_file_info_unref (info);

      return !found_file;
}

static void
destroyed_callback (GtkWidget    *widget,
                NautilusBurn *burn)
{
      burn->priv->widget_list = g_slist_remove (burn->priv->widget_list, widget);
}

static void
sense_widget (NautilusBurn *burn,
            GtkWidget    *widget)
{
      gtk_widget_set_sensitive (widget, !burn->priv->empty);

      burn->priv->widget_list = g_slist_prepend (burn->priv->widget_list, widget);

      g_signal_connect (widget, "destroy",
                    G_CALLBACK (destroyed_callback),
                    burn);
}

static GtkWidget *
nautilus_burn_get_location_widget (NautilusLocationWidgetProvider *iface,
                           const char                     *uri,
                           GtkWidget                      *window)
{
      if (g_str_has_prefix (uri, "burn:")) {
            GtkWidget    *bar;
            NautilusBurn *burn;

            DEBUG_PRINT ("Get location widget for burn\n");

            burn = NAUTILUS_BURN (iface);

            bar = nautilus_burn_bar_new ();

            sense_widget (burn, nautilus_burn_bar_get_button (NAUTILUS_BURN_BAR (bar)));

            g_signal_connect (bar, "activate",
                          G_CALLBACK (bar_activated_cb),
                          window);

            gtk_widget_show (bar);

            return bar;
      }

      return NULL;
}

static void
nautilus_burn_location_widget_provider_iface_init (NautilusLocationWidgetProviderIface *iface)
{
      iface->get_widget = nautilus_burn_get_location_widget;
}

static void
update_widget_sensitivity (GtkWidget    *widget,
                     NautilusBurn *burn)
{
      gtk_widget_set_sensitive (widget, !burn->priv->empty);
}

static gboolean
update_empty_idle (NautilusBurn *burn)
{
      gboolean is_empty;

      burn->priv->empty_update_id = 0;

      is_empty = dir_is_empty ("burn:///");

      DEBUG_PRINT ("Dir is %s\n", is_empty ? "empty" : "not empty");

      if (burn->priv->empty != is_empty) {
            burn->priv->empty = is_empty;
            /* update bar */
            g_slist_foreach (burn->priv->widget_list, (GFunc)update_widget_sensitivity, burn);

            /* Trigger update for menu items */
            nautilus_menu_provider_emit_items_updated_signal (NAUTILUS_MENU_PROVIDER (burn));
      }

      return FALSE;
}

static void
queue_update_empty (NautilusBurn *burn)
{
      if (burn->priv->empty_update_id != 0) {
            g_source_remove (burn->priv->empty_update_id);
      }

      burn->priv->empty_update_id = g_idle_add ((GSourceFunc)update_empty_idle, burn);
}

static void
burn_monitor_cb (GnomeVFSMonitorHandle    *handle,
             const gchar              *text_uri,
             const gchar              *info_uri,
             GnomeVFSMonitorEventType  event_type,
             gpointer                  data)
{
      NautilusBurn *burn;

      burn = NAUTILUS_BURN (data);

      DEBUG_PRINT ("Monitor callback type %d: %s: %s\n", event_type, text_uri, info_uri);

      /* only queue the action if it has a chance of changing the state */
      if (event_type == GNOME_VFS_MONITOR_EVENT_CREATED) {
            if (burn->priv->empty) {
                  queue_update_empty (burn);
            }
      } else if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED) {
            if (! burn->priv->empty) {
                  queue_update_empty (burn);
            }
      }
}

static gboolean
start_monitor (NautilusBurn *burn)
{
      GnomeVFSResult    result;
      char             *directory;

      directory = g_strdup ("burn:///");

      DEBUG_PRINT ("Starting monitor for %s\n", directory);

      result = gnome_vfs_monitor_add (&burn->priv->burn_monitor,
                              directory,
                              GNOME_VFS_MONITOR_DIRECTORY,
                              burn_monitor_cb,
                              burn);

      if (result != GNOME_VFS_OK) {
            DEBUG_PRINT ("Could not monitor directory %d: %s\n",
                       directory,
                       gnome_vfs_result_to_string (result));

            burn->priv->burn_monitor = NULL;
      }

      burn->priv->empty = dir_is_empty (directory);
      DEBUG_PRINT ("Init burn extension, empty: %d\n", burn->priv->empty);

      g_free (directory);

      burn->priv->start_monitor_id = 0;

      return FALSE;
}

static void
nautilus_burn_instance_init (NautilusBurn *burn)
{
      burn->priv = NAUTILUS_BURN_GET_PRIVATE (burn);

#ifdef DEBUG_ENABLE
      debug_init ();
#endif

      burn->priv->start_monitor_id = g_timeout_add (1000,
                                          (GSourceFunc)start_monitor,
                                          burn);
}

static void
nautilus_burn_finalize (GObject *object)
{
      NautilusBurn *burn;

      g_return_if_fail (object != NULL);
      g_return_if_fail (NAUTILUS_IS_BURN (object));

      DEBUG_PRINT ("Finalizing burn extension\n");

      burn = NAUTILUS_BURN (object);

      g_return_if_fail (burn->priv != NULL);

      if (burn->priv->empty_update_id > 0) {
            g_source_remove (burn->priv->empty_update_id);
      }

      if (burn->priv->start_monitor_id > 0) {
            g_source_remove (burn->priv->start_monitor_id);
      }

      if (burn->priv->burn_monitor != NULL) {
            gnome_vfs_monitor_cancel (burn->priv->burn_monitor);
      }

      if (burn->priv->widget_list != NULL) {
            g_slist_free (burn->priv->widget_list);
      }

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

static void
nautilus_burn_class_init (NautilusBurnClass *klass)
{
      GObjectClass *object_class = G_OBJECT_CLASS (klass);

      parent_class = g_type_class_peek_parent (klass);

      object_class->finalize = nautilus_burn_finalize;

      g_type_class_add_private (klass, sizeof (NautilusBurnPrivate));
}

static GType burn_type = 0;

static GType
nautilus_burn_get_type (void)
{
      return burn_type;
}

static void
nautilus_burn_register_type (GTypeModule *module)
{
      static const GTypeInfo info = {
            sizeof (NautilusBurnClass),
            (GBaseInitFunc) NULL,
            (GBaseFinalizeFunc) NULL,
            (GClassInitFunc) nautilus_burn_class_init,
            NULL,
            NULL,
            sizeof (NautilusBurn),
            0,
            (GInstanceInitFunc) nautilus_burn_instance_init,
      };

      static const GInterfaceInfo menu_provider_iface_info = {
            (GInterfaceInitFunc) nautilus_burn_menu_provider_iface_init,
            NULL,
            NULL
      };
      static const GInterfaceInfo location_widget_provider_iface_info = {
            (GInterfaceInitFunc) nautilus_burn_location_widget_provider_iface_init,
            NULL,
            NULL
      };

      burn_type = g_type_module_register_type (module,
                                     G_TYPE_OBJECT,
                                     "NautilusBurn",
                                     &info, 0);

      g_type_module_add_interface (module,
                             burn_type,
                             NAUTILUS_TYPE_MENU_PROVIDER,
                             &menu_provider_iface_info);
      g_type_module_add_interface (module,
                             burn_type,
                             NAUTILUS_TYPE_LOCATION_WIDGET_PROVIDER,
                             &location_widget_provider_iface_info);
}

void
nautilus_module_initialize (GTypeModule *module)
{
      nautilus_burn_register_type (module);
      bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
      bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
}

void
nautilus_module_shutdown (void)
{
      LibHalContext *ctx;
      ctx = get_hal_context ();
      if (ctx != NULL) {
            libhal_ctx_free (ctx);
      }
}

void
nautilus_module_list_types (const GType **types,
                      int          *num_types)
{
      static GType type_list [1];

      type_list[0] = NAUTILUS_TYPE_BURN;

      *types = type_list;
      *num_types = 1;
}

Generated by  Doxygen 1.6.0   Back to index