/* eLectrix - a pdf viewer
 * Copyright (C) 2010, 2011 Martin Linder <mali2297@users.sf.net>
 *
 * 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, see <http://www.gnu.org/licenses/gpl-2.0.html>.
 */
#include <poppler.h>
#include <time.h>
#include "e6x-common.h"
#include "e6x-util.h"
#include "e6x-pdf-document.h"

#define E6X_PDF_DOCUMENT_GET_PRIVATE(o) \
   (G_TYPE_INSTANCE_GET_PRIVATE ((o), E6X_TYPE_PDF_DOCUMENT, E6xPdfDocumentPrivate))

typedef struct _E6xAnnotMapping E6xAnnotMapping;

struct _E6xPdfDocumentPrivate
{
  PopplerDocument *popdoc;
  GList *links;
  GList *notes;
  struct
  {
    guint page_no;
    gchar *string;
    GList *rect_list;
  } search;
};

struct _E6xAnnotMapping
{
  PopplerRectangle area;
  gchar *text;
};

static const gchar *default_info[] =
{
  "format", N_("Format"), "",
  "title", N_("Title"), "",
  "author", N_("Author"), "",
  "subject", N_("Subject"), "",
  "keywords", N_("Keywords"), "",
  "creator", N_("Creator"), "",
  "producer", N_("Producer"), "",
  "creation-date", N_("Creation date"), "",
  "mod-date", N_("Modification date"), "",
  NULL
};

/* Standard GObject methods */
G_DEFINE_TYPE (E6xPdfDocument, e6x_pdf_document, E6X_TYPE_DOCUMENT)
static void e6x_pdf_document_finalize (GObject *object);
static void e6x_pdf_document_dispose (GObject *object);

/* Implementations of E6xDocument virtual methods */
static gboolean e6x_pdf_document_reload (E6xDocument *doc,
                                         GError **error);
static gboolean e6x_pdf_document_save_copy (E6xDocument *doc,
                                            const gchar *filename,
                                            GError **error);
static void e6x_pdf_document_get_page_size (E6xDocument *doc,
                                            gdouble *width,
                                            gdouble *height);
static cairo_surface_t *e6x_pdf_document_render_page (E6xDocument *doc);
static gchar *e6x_pdf_document_get_text (E6xDocument *doc,
                                         GdkRectangle rect);
static guint e6x_pdf_document_get_n_matches (E6xDocument *doc,
                                             guint page_no,
                                             const gchar *string);
static GdkRectangle *e6x_pdf_document_get_nth_match (E6xDocument *doc,
                                                     guint page_no,
                                                     const gchar *string,
                                                     guint match_no);
static gboolean e6x_pdf_document_is_at_link (E6xDocument *doc,
                                             GdkPoint pos);
static gboolean e6x_pdf_document_go_to_link (E6xDocument *doc,
                                             GdkPoint pos);
static gchar *e6x_pdf_document_get_tip (E6xDocument *doc,
                                        GdkPoint pos);
static gboolean e6x_pdf_document_go_to_bookmark (E6xDocument *doc,
                                                 GtkTreeIter *iter);
static gboolean e6x_pdf_document_save_att (E6xDocument *doc, 
                                           GtkTreeIter *iter,
                                           const gchar *filename,
                                           GError **error);

/* Helpers */
static void _fill_toc (E6xDocument *doc,
                       GtkTreeStore *treestore,
                       GtkTreeIter *treeparent,
                       PopplerIndexIter *idxiter);
static gboolean _clear_toc (GtkTreeModel *model,
                            GtkTreePath *path,
                            GtkTreeIter *iter,
                            gpointer data);
static gboolean  _clear_attlist (GtkTreeModel *attlist,
                                 GtkTreePath *path,
                                 GtkTreeIter *iter,
                                 gpointer data);
static void _set_search (E6xDocument *doc,
                         guint page_no,
                         const gchar *string);
static GList *_get_link (E6xDocument *doc,
                         GdkPoint pos);
static GList *_get_note (E6xDocument *doc,
                         GdkPoint pos);
static void _perform_action (E6xDocument *doc,
                             PopplerAction *action);
static gboolean _open_uri (E6xDocument *doc, 
                           const gchar *uri);
static guint _get_page_no_from_dest (E6xDocument *doc,
                                     PopplerDest *dest);
static guint _get_page_no_from_named_dest (E6xDocument *doc,
                                           const gchar *name);
static GdkPoint _surface_to_pdf_point (E6xDocument *doc,
                                       GdkPoint point);
static GdkPoint _pdf_to_surface_point (E6xDocument *doc,
                                       GdkPoint point);
static GList *_get_annot_mapping (PopplerPage *page);
static void _free_annot_mapping (E6xAnnotMapping *map);
static gint _compare_annot_mapping (E6xAnnotMapping *map1, 
                                    E6xAnnotMapping *map2);

GObject *
e6x_pdf_document_new (const gchar *filename,
                      const gchar *passwd,
                      GError **error)
{
  GObject *object = g_object_new (E6X_TYPE_PDF_DOCUMENT, NULL);
  E6xDocument *doc = E6X_DOCUMENT (object);
  gboolean is_success = FALSE;

  doc->filename = g_strdup (filename);
  doc->passwd = g_strdup (passwd);
  is_success = e6x_document_reload (doc, error);

  if (is_success == FALSE)
  {
    g_object_unref (object);
    object = NULL;
  }

  return object;
}


static void
e6x_pdf_document_class_init (E6xPdfDocumentClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  E6xDocumentClass *doc_class = E6X_DOCUMENT_CLASS (klass);

  g_type_class_add_private (klass, sizeof (E6xPdfDocumentPrivate));

  object_class->dispose = e6x_pdf_document_dispose;
  object_class->finalize = e6x_pdf_document_finalize;

  doc_class->reload = &e6x_pdf_document_reload;
  doc_class->save_copy = &e6x_pdf_document_save_copy;
  doc_class->get_page_size = &e6x_pdf_document_get_page_size;
  doc_class->render_page = &e6x_pdf_document_render_page;
  doc_class->get_text = &e6x_pdf_document_get_text;
  doc_class->get_n_matches = &e6x_pdf_document_get_n_matches;
  doc_class->get_nth_match = &e6x_pdf_document_get_nth_match;
  doc_class->is_at_link = &e6x_pdf_document_is_at_link;
  doc_class->go_to_link = &e6x_pdf_document_go_to_link;
  doc_class->get_tip = &e6x_pdf_document_get_tip;
  doc_class->go_to_bookmark = &e6x_pdf_document_go_to_bookmark;
  doc_class->save_att = &e6x_pdf_document_save_att;
}


static void
e6x_pdf_document_init (E6xPdfDocument *doc)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT_GET_PRIVATE (doc);

  priv->popdoc = NULL;
  priv->links = NULL;
  priv->notes = NULL;

  doc->priv = priv;
}


static void
e6x_pdf_document_finalize (GObject *object)
{
  G_OBJECT_CLASS (e6x_pdf_document_parent_class)->finalize (object);
}


static void
e6x_pdf_document_dispose (GObject *object)
{
  E6xDocument *doc = E6X_DOCUMENT (object);
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (object)->priv;

  _set_search (E6X_DOCUMENT (object), 0, NULL);

  if (priv->popdoc != NULL)
  {
    g_object_unref (priv->popdoc);
    priv->popdoc = NULL;
  }
  if (priv->links != NULL)
  {
    poppler_page_free_link_mapping (priv->links);
    priv->links = NULL;
  }
  if (priv->notes != NULL)
  { 
    g_list_foreach (priv->notes, (GFunc) &_free_annot_mapping, NULL);
    g_list_free (priv->notes);
    priv->notes = NULL;
  }
  if (doc->toc != NULL)
  {
    gtk_tree_model_foreach (doc->toc, &_clear_toc, NULL);
    gtk_tree_store_clear (GTK_TREE_STORE (doc->toc));
    g_object_unref (doc->toc);
    doc->toc = NULL;
  }
  if (doc->attlist != NULL)
  {
    gtk_tree_model_foreach (doc->attlist, &_clear_attlist, NULL);
    gtk_list_store_clear (GTK_LIST_STORE (doc->attlist));
    g_object_unref (doc->attlist);
    doc->attlist = NULL;
  }

  G_OBJECT_CLASS (e6x_pdf_document_parent_class)->dispose (object);
}


static gboolean
e6x_pdf_document_reload (E6xDocument *doc,
                         GError **error)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (doc)->priv;
  gchar *uri = NULL, **p = NULL;
  PopplerIndexIter *idxiter = NULL;

  if (doc->attlist != NULL)
  {
    gtk_tree_model_foreach (doc->attlist, &_clear_attlist, NULL);
    gtk_list_store_clear (GTK_LIST_STORE (doc->attlist));
    g_object_unref (doc->attlist);
    doc->attlist = NULL;
  }
  if (doc->toc != NULL)
  {
    gtk_tree_model_foreach (doc->toc, &_clear_toc, NULL);
    gtk_tree_store_clear (GTK_TREE_STORE (doc->toc));
    g_object_unref (doc->toc);
    doc->toc = NULL;
  }
  if (doc->info != NULL)
  {
    g_strfreev (doc->info);
    doc->info = NULL;
  }
  if (priv->popdoc != NULL)
  {
    g_object_unref (priv->popdoc);
    priv->popdoc = NULL;
  }
  if (G_UNLIKELY (doc->filename == NULL))
  {
    return FALSE;
  }

  uri = g_filename_to_uri (doc->filename, NULL, error);

  if (G_UNLIKELY (uri == NULL))
  {
    g_free (doc->filename);
    g_free (doc->passwd);
    doc->filename = NULL;
    doc->passwd = NULL;
    return FALSE;
  }

  priv->popdoc = poppler_document_new_from_file (uri, doc->passwd, error);
  g_free (uri);

  if (G_UNLIKELY (priv->popdoc == NULL))
  {
    g_free (doc->filename);
    doc->filename = NULL;
    return FALSE;
  }

  doc->n_pages = poppler_document_get_n_pages (priv->popdoc);
  doc->page_no = CLAMP (doc->page_no, 1, doc->n_pages);

  doc->info = g_strdupv ((gchar **) default_info);
  g_assert (g_strv_length (doc->info) % 3 == 0);
  for (p = doc->info; *p != NULL; p += 3)
  {
    gchar *s = NULL;

    if (g_str_has_suffix (*p, "-date") == TRUE)
    {
      time_t t = 0;

      g_object_get (priv->popdoc, *p, &t, NULL);
      if (t > 0)
        s = e6x_util_dattostr (t);
    }
    else
    {
      g_object_get (priv->popdoc, *p, &s, NULL);
    }
    if (s != NULL)
    {
      g_free (*(p + 2));
      *(p + 2) = s;
    }
  }

  if (*(doc->info[5]) != '\0')
  {
    doc->title = g_strdup (doc->info[5]);
  }
  else
  {
    doc->title = g_path_get_basename (doc->filename);
  }

  idxiter = poppler_index_iter_new (priv->popdoc);
  if (idxiter != NULL)
  {
    doc->toc = GTK_TREE_MODEL (gtk_tree_store_new (E6X_TOC_NCOLS, 
                                                   G_TYPE_STRING,
                                                   G_TYPE_STRING,
                                                   G_TYPE_POINTER));
    _fill_toc (doc, GTK_TREE_STORE (doc->toc), NULL, idxiter);
    poppler_index_iter_free (idxiter);
    e6x_document_set_show_toc (doc, TRUE);
  }
  
  if (poppler_document_has_attachments (priv->popdoc))
  {
    GList *list = NULL, *l = NULL;
    
    list = poppler_document_get_attachments (priv->popdoc);
    doc->attlist = GTK_TREE_MODEL (gtk_list_store_new (E6X_ATT_NCOLS, 
                                                       G_TYPE_STRING,
                                                       G_TYPE_STRING,
                                                       G_TYPE_STRING,
                                                       G_TYPE_STRING,
                                                       G_TYPE_STRING,
                                                       G_TYPE_POINTER));
    
    for (l = list; l != NULL; l = g_list_next (l))
    {
      PopplerAttachment *att = POPPLER_ATTACHMENT (l->data);
      GtkTreeIter iter;
      time_t t = 0;
      gchar *ctime_s = NULL, *mtime_s = NULL, *size_s = NULL;
      
      t = att->ctime;
      if (t > 0)
        ctime_s = e6x_util_dattostr (t);
      t = att->mtime;
      if (t > 0)
        mtime_s = e6x_util_dattostr (t);
      
      size_s = g_format_size_for_display (att->size);
      
      gtk_list_store_append (GTK_LIST_STORE (doc->attlist), &iter);
      gtk_list_store_set (GTK_LIST_STORE (doc->attlist), &iter,
                          E6X_ATT_COL_NAME, att->name,
                          E6X_ATT_COL_DESC, att->description,
                          E6X_ATT_COL_CTIME, ctime_s,
                          E6X_ATT_COL_MTIME, mtime_s,
                          E6X_ATT_COL_SIZE, size_s,
                          E6X_ATT_COL_POINTER, att,
                          -1);
      
      g_free (size_s);
      g_free (mtime_s);
      g_free (ctime_s);
    }
    
    g_list_free (list);
  }  

  return TRUE;
}


static gboolean
e6x_pdf_document_save_copy (E6xDocument *doc,
                            const gchar *filename,
                            GError **error)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (doc)->priv;
  gchar *uri = NULL;
  gboolean retval = FALSE;

  uri = g_filename_to_uri (filename, NULL, error);
  if (G_LIKELY (uri != NULL))
  {
    retval = poppler_document_save_a_copy (priv->popdoc, uri, error);
    g_free (uri);
  }

  return retval;
}


static void
e6x_pdf_document_get_page_size (E6xDocument *doc,
                                gdouble *width,
                                gdouble *height)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (doc)->priv;
  PopplerPage *page = NULL;

  g_return_if_fail (priv->popdoc != NULL);

  page = poppler_document_get_page (priv->popdoc, doc->page_no - 1);
  g_return_if_fail (page != NULL);

  poppler_page_get_size (page, width, height);

  g_object_unref (page);
}


static cairo_surface_t *
e6x_pdf_document_render_page (E6xDocument *doc)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (doc)->priv;
  PopplerPage *page = NULL;
  gdouble page_width = 0.0, page_height = 0.0;
  gint surface_width = 0, surface_height = 0;
  cairo_surface_t *surface = NULL;
  cairo_t *cr = NULL;

  g_return_val_if_fail (priv->popdoc != NULL, NULL);

  page = poppler_document_get_page (priv->popdoc, doc->page_no - 1);
  g_return_val_if_fail (page != NULL, NULL);

  poppler_page_get_size (page, &page_width, &page_height);

  if (priv->links != NULL)
    poppler_page_free_link_mapping (priv->links);
  priv->links = poppler_page_get_link_mapping (page);

  if (priv->notes != NULL)
  {
    g_list_foreach (priv->notes, (GFunc) &_free_annot_mapping, NULL);
    g_list_free (priv->notes);
  }
  priv->notes = _get_annot_mapping (page);
  
  if (doc->angle == 90 || doc->angle == 270)
  {
    surface_width = page_height * doc->scale;
    surface_height = page_width * doc->scale;
  }
  else
  {
    surface_width = page_width * doc->scale;
    surface_height = page_height * doc->scale;
  }

  surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
                                        surface_width,
                                        surface_height);
  cr = cairo_create (surface);
  cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
  cairo_rectangle (cr, 0.0, 0.0, surface_width, surface_height);
  cairo_fill (cr);

  if (doc->angle != 0)
  {
    switch (doc->angle)
    {
    case 90:
      cairo_translate (cr, surface_width, 0);
      break;
    case 180:
      cairo_translate (cr, surface_width, surface_height);
      break;
    case 270:
      cairo_translate (cr, 0, surface_height);
      break;
    default:
      g_warn_if_reached ();
      break;
    }
    cairo_rotate (cr, (doc->angle / 90) * G_PI_2);
  }
  if (doc->scale != 1.0)
  {
    cairo_scale (cr, doc->scale, doc->scale);
  }

  poppler_page_render (page, cr);

  cairo_destroy (cr);
  g_object_unref (page);

  return surface;
}


static gchar *
e6x_pdf_document_get_text (E6xDocument *doc,
                           GdkRectangle rect)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (doc)->priv;
  PopplerPage *page = poppler_document_get_page (priv->popdoc,
                                                 doc->page_no - 1);
  gchar *string = NULL;
  GdkPoint corner1 = {rect.x, rect.y};
  GdkPoint corner2 = {rect.x + rect.width, rect.y + rect.height};

  corner1  = _surface_to_pdf_point (doc, corner1);
  corner2 = _surface_to_pdf_point (doc, corner2);

  if (ABS (corner1.x - corner2.x) > 2 && ABS (corner1.y - corner2.y) > 2)
  {
    PopplerRectangle poppler_rect 
      = { corner1.x, corner1.y, corner2.x, corner2.y };
    
#if POPPLER_CHECK_VERSION (0,16,0)
    /* Y-coords are not consistent in poppler, we need here to change them */
    gdouble page_height = 0.0;
    
    poppler_page_get_size (page, NULL, &page_height);
    poppler_rect.y1 = page_height - poppler_rect.y1; 
    poppler_rect.y2 = page_height - poppler_rect.y2; 

    string = poppler_page_get_selected_text (page,
                                             POPPLER_SELECTION_GLYPH,
                                             &poppler_rect);
#else
    string = poppler_page_get_text (page,
                                    POPPLER_SELECTION_GLYPH,
                                    &poppler_rect);
#endif
  }

  return string;
}


static guint
e6x_pdf_document_get_n_matches (E6xDocument *doc,
                                guint page_no,
                                const gchar *string)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (doc)->priv;

  _set_search (doc, page_no, string);

  return g_list_length (priv->search.rect_list);
}


static GdkRectangle *
e6x_pdf_document_get_nth_match (E6xDocument *doc,
                                guint page_no,
                                const gchar *string,
                                guint match_no)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (doc)->priv;
  GdkRectangle *rect = NULL;
  PopplerRectangle *pop_rect = NULL;

  _set_search (doc, page_no, string);

  pop_rect = g_list_nth_data (priv->search.rect_list, match_no);

  if (pop_rect != NULL)
  {
    GdkPoint corner1 = {pop_rect->x1, pop_rect->y1};
    GdkPoint corner2 = {pop_rect->x2, pop_rect->y2};

    corner1 = _pdf_to_surface_point (doc, corner1);
    corner2 = _pdf_to_surface_point (doc, corner2);

    rect = g_new0 (GdkRectangle, 1);
    rect->x = corner1.x;
    rect->y = corner1.y;
    rect->width = corner2.x - corner1.x;
    rect->height = corner2.y - corner1.y;
  }

  return rect;
}


static gboolean
e6x_pdf_document_is_at_link (E6xDocument *doc, GdkPoint pos)
{
  return (_get_link (doc, pos) != NULL);
}


static gboolean
e6x_pdf_document_go_to_link (E6xDocument *doc, GdkPoint pos)
{
  GList *link = NULL;
  PopplerAction *action = NULL;

  link = _get_link (doc, pos);
  if (link == NULL)
    return FALSE;

  action = ((PopplerLinkMapping *) link->data)->action;
  _perform_action (doc, action);

  return TRUE;
}


static gchar *
e6x_pdf_document_get_tip (E6xDocument *doc,
                          GdkPoint pos)
{
  GList *listitem = NULL;
 
  listitem = _get_link (doc, pos);
  if (listitem != NULL)
  {
    PopplerAction *action = NULL;
    gchar *str = NULL;
    guint page_no = 0;

    action = ((PopplerLinkMapping *) listitem->data)->action;

    str = g_strdup (action->any.title);

    if (str == NULL)
    {
      switch (action->type)
      {
      case POPPLER_ACTION_GOTO_DEST:
        page_no = _get_page_no_from_dest (doc, action->goto_dest.dest);
        str = g_strdup_printf (_("Link to Page %d"), 
                               page_no);
        break;
      case POPPLER_ACTION_NAMED:
        page_no = _get_page_no_from_named_dest (doc, action->named.named_dest);
        str = g_strdup_printf (_("Link to Page %d"), 
                               page_no);
        break;
      case POPPLER_ACTION_URI:
        str = g_strdup_printf (_("Link to URI %s"), 
                               action->uri.uri);
        break;
      case POPPLER_ACTION_GOTO_REMOTE:
        str = g_strdup_printf (_("Link to File %s"), 
                               action->goto_remote.file_name);
        break;
      case POPPLER_ACTION_MOVIE:
        str = g_strdup_printf (_("Link to File %s"),
                               poppler_movie_get_filename (action->movie.movie));
        break;
      case POPPLER_ACTION_LAUNCH:
        str = g_strdup_printf (_("Link to File %s (unsupported)"), 
                               action->launch.file_name);
        break;
      default:
        str = g_strdup (_("Unknown link"));
        break;
      }
    }
    
    return str;
  }
  
  listitem = _get_note (doc, pos);
  if (listitem != NULL)
    return g_strdup (((E6xAnnotMapping *) listitem->data)->text);
  
  return NULL;
}


static gboolean
e6x_pdf_document_go_to_bookmark (E6xDocument *doc,
                                 GtkTreeIter *iter)
{
  PopplerAction *action = NULL;

  gtk_tree_model_get (doc->toc, iter, E6X_TOC_COL_POINTER, &action, -1);

  if (G_UNLIKELY (action == NULL))
    return FALSE;

  _perform_action (doc, action);

  return TRUE;
}


static gboolean 
e6x_pdf_document_save_att (E6xDocument *doc, 
                           GtkTreeIter *iter,
                           const gchar *filename,
                           GError **error)
{
  PopplerAttachment *att = NULL;

  gtk_tree_model_get (doc->attlist, iter, E6X_ATT_COL_POINTER, &att, -1);

  if (G_UNLIKELY (att == NULL))
    return FALSE;
  
  /* Suspect that poppler_attachment_save unrefs attachment */ 
  g_object_ref (att); 
  return poppler_attachment_save (att, filename, error);
}


static void
_fill_toc (E6xDocument *doc,
           GtkTreeStore *treestore,
           GtkTreeIter *treeparent,
           PopplerIndexIter *idxiter)
{
  PopplerIndexIter *idxchild = NULL;
  PopplerAction *action = NULL;
  GtkTreeIter treeiter;

  if (G_UNLIKELY (idxiter == NULL))
    return;

  do
  {
    action = poppler_index_iter_get_action (idxiter);
    if (G_LIKELY (action != NULL))
    {
      guint page_no = 0;
      gchar *page_str = NULL;

      if (action->type == POPPLER_ACTION_GOTO_DEST)
        page_no = _get_page_no_from_dest (doc, action->goto_dest.dest);
      else if (action->type == POPPLER_ACTION_NAMED)
        page_no = _get_page_no_from_named_dest (doc, action->named.named_dest);
      if (page_no > 0)
        page_str = g_strdup_printf ("%d", page_no);
      else
        page_str = g_strdup ("");
      
      gtk_tree_store_append (treestore, &treeiter, treeparent);
      gtk_tree_store_set (treestore, &treeiter,
                          E6X_TOC_COL_TITLE, action->any.title,
                          E6X_TOC_COL_PAGENO, page_str,
                          E6X_TOC_COL_POINTER, action,
                          -1);
      
      g_free (page_str);
    }

    idxchild = poppler_index_iter_get_child (idxiter);
    if (idxchild != NULL)
      _fill_toc (doc, treestore, &treeiter, idxchild);
    poppler_index_iter_free (idxchild);
  }
  while (poppler_index_iter_next (idxiter) == TRUE);
}


static gboolean 
_clear_toc (GtkTreeModel *toc,
            GtkTreePath *path,
            GtkTreeIter *iter,
            gpointer data)
{
  PopplerAction *action = NULL;

  gtk_tree_model_get (toc, iter, E6X_TOC_COL_POINTER, &action, -1);
  if (action != NULL)
    poppler_action_free (action);
  gtk_tree_store_set (GTK_TREE_STORE (toc), iter,
                      E6X_TOC_COL_TITLE, NULL,
                      E6X_TOC_COL_PAGENO, NULL,
                      E6X_TOC_COL_POINTER, NULL, -1);

  return FALSE;
}


static gboolean 
_clear_attlist (GtkTreeModel *attlist,
                GtkTreePath *path,
                GtkTreeIter *iter,
                gpointer data)
{
  PopplerAttachment *att = NULL;

  gtk_tree_model_get (attlist, iter, E6X_ATT_COL_POINTER, &att, -1);
  if (att != NULL)
    g_object_unref (att);
  gtk_list_store_set (GTK_LIST_STORE (attlist), iter,
                      E6X_ATT_COL_NAME, NULL,
                      E6X_ATT_COL_DESC, NULL,
                      E6X_ATT_COL_CTIME, NULL,
                      E6X_ATT_COL_MTIME, NULL,
                      E6X_ATT_COL_SIZE, NULL,
                      E6X_ATT_COL_POINTER, NULL, -1);

  return FALSE;
}


static void
_set_search (E6xDocument *doc,
             guint page_no,
             const gchar *string)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (doc)->priv;

  if (page_no == priv->search.page_no
      && g_strcmp0 (string, priv->search.string) == 0)
    return;

  if (priv->search.string != NULL)
  {
    g_free (priv->search.string);
    priv->search.string = NULL;
  }
  if (priv->search.rect_list != NULL)
  {
    poppler_page_selection_region_free (priv->search.rect_list);
    priv->search.rect_list = NULL;
  }

  priv->search.page_no = page_no;
  priv->search.string = g_strdup (string);

  if (priv->search.string != NULL)
  {
    PopplerPage *page;

    page = poppler_document_get_page (priv->popdoc, priv->search.page_no - 1);
    if (G_LIKELY (page != NULL))
    {
      priv->search.rect_list = poppler_page_find_text (page,
                                                       priv->search.string);
      g_object_unref (page);
    }
  }
}


static GList *
_get_link (E6xDocument *doc, GdkPoint pos)
{
  E6xPdfDocumentPrivate *priv =  E6X_PDF_DOCUMENT (doc)->priv;
  GList *l = NULL;

  pos = _surface_to_pdf_point (doc, pos);

  for (l = priv->links; l != NULL; l = g_list_next (l))
  {
    PopplerRectangle area = ((PopplerLinkMapping *) l->data)->area;

    if (pos.x >= area.x1 && pos.x < area.x2 && 
        pos.y >= area.y1 && pos.y < area.y2)
      break;
  }

  return l;
}


static GList *
_get_note (E6xDocument *doc, GdkPoint pos)
{
  E6xPdfDocumentPrivate *priv =  E6X_PDF_DOCUMENT (doc)->priv;
  GList *l = NULL;

  pos = _surface_to_pdf_point (doc, pos);

  for (l = priv->notes; l != NULL; l = g_list_next (l))
  {
    PopplerRectangle area = ((E6xAnnotMapping *) l->data)->area;

    if (pos.x >= area.x1 && pos.x < area.x2 && 
        pos.y >= area.y1 && pos.y < area.y2)
      break;
  }

  return l;
}


static void
_perform_action (E6xDocument *doc, PopplerAction *action)
{
  guint page_no = 0;

  switch (action->type)
  {
  case POPPLER_ACTION_GOTO_DEST:
    page_no = _get_page_no_from_dest (doc, action->goto_dest.dest);
    e6x_document_set_page_no (doc, page_no);
    break;
  case POPPLER_ACTION_NAMED:
    page_no = _get_page_no_from_named_dest (doc, action->named.named_dest);
    e6x_document_set_page_no (doc, page_no);
    break;
  case POPPLER_ACTION_URI:
    _open_uri (doc, action->uri.uri);
    break;
  case POPPLER_ACTION_GOTO_REMOTE:
    _open_uri (doc, action->goto_remote.file_name);
    break;
  case POPPLER_ACTION_MOVIE:
    _open_uri (doc, poppler_movie_get_filename (action->movie.movie));
    break;
  case POPPLER_ACTION_LAUNCH:
    /* Fall through */;
  case POPPLER_ACTION_NONE:
    /* Fall through */;
  case POPPLER_ACTION_UNKNOWN:
    /* Fall through */;
  default:
    g_message ("Unsupported action");
    break;
  }
  
  return;
}


static gboolean 
_open_uri (E6xDocument *doc, const gchar *uri)
{
  gboolean retval = FALSE;
  gchar *dirname = NULL;
  gchar *new_uri = NULL;
  gchar *scheme = NULL;
  
  dirname = g_path_get_dirname (doc->filename);
  scheme = g_uri_parse_scheme (uri);
  if (scheme == NULL && 
      g_path_is_absolute (uri) == FALSE)
    new_uri = g_build_filename (dirname, uri, NULL);
  else
    new_uri = g_strdup (uri);
  
  retval = e6x_util_open_uri (new_uri, NULL, NULL);
  
  g_free (dirname);
  g_free (scheme);
  g_free (new_uri);

  return retval;
}


static guint
_get_page_no_from_dest (E6xDocument *doc,
                        PopplerDest *dest)
{
  if (dest->type == POPPLER_DEST_NAMED)
    return _get_page_no_from_named_dest (doc, dest->named_dest);
  else
    return dest->page_num;
}


static guint
_get_page_no_from_named_dest (E6xDocument *doc,
                              const gchar *name)
{
  E6xPdfDocumentPrivate *priv = E6X_PDF_DOCUMENT (doc)->priv;
  PopplerDest *dest = NULL;
  guint page_no = 0;

  dest = poppler_document_find_dest (priv->popdoc, name);

  if (dest != NULL)
  {
    page_no = _get_page_no_from_dest (doc, dest);
    poppler_dest_free (dest);
  }

  return page_no;
}


static GdkPoint
_surface_to_pdf_point (E6xDocument *doc, GdkPoint point)
{
  GdkPoint new_point = {0, 0};
  gdouble page_width = 0.0, page_height = 0.0;

  e6x_document_get_page_size (doc, &page_width, &page_height);

  switch (doc->angle)
  {
  case 0:
    new_point.x = point.x / doc->scale;
    new_point.y = page_height - point.y / doc->scale;
    break;
  case 90:
    new_point.x = point.y / doc->scale;
    new_point.y = point.x / doc->scale;
    break;
  case 180:
    new_point.x = page_width - point.x / doc->scale;
    new_point.y = point.y / doc->scale;
    break;
  case 270:
    new_point.x = page_width - point.y / doc->scale;
    new_point.y = page_height - point.x / doc->scale;
    break;
  default:
    g_warn_if_reached ();
    break;
  }

  return new_point;
}


static GdkPoint
_pdf_to_surface_point (E6xDocument *doc, GdkPoint point)
{
  GdkPoint new_point = {0, 0};
  gdouble page_width = 0.0, page_height = 0.0;

  e6x_document_get_page_size (doc, &page_width, &page_height);

  switch (doc->angle)
  {
  case 0:
    new_point.x = point.x * doc->scale;
    new_point.y = (page_height - point.y) * doc->scale;
    break;
  case 90:
    new_point.x = point.y * doc->scale;
    new_point.y = point.x * doc->scale;
    break;
  case 180:
    new_point.x = (page_width - point.x) * doc->scale;
    new_point.y = point.y * doc->scale;
    break;
  case 270:
    new_point.x = (page_height - point.y) * doc->scale;
    new_point.y = (page_width - point.x) * doc->scale;
    break;
  default:
    g_warn_if_reached ();
    break;
  }

  return new_point;
}


static GList *
_get_annot_mapping (PopplerPage *page)
{
  GList *e6xannots = NULL;
  GList *popannots = NULL;
  GList *l = NULL;
  
  popannots = poppler_page_get_annot_mapping (page);
  for (l = popannots; l != NULL; l = g_list_next (l))
  {
    E6xAnnotMapping *e6xmap = g_new0 (E6xAnnotMapping, 1); 
    PopplerAnnotMapping *popmap = ((PopplerAnnotMapping *) l->data);

    e6xmap->area = popmap->area;
    e6xmap->text = poppler_annot_get_contents (popmap->annot);
    
    if (e6xmap->text == NULL)
      _free_annot_mapping (e6xmap);
    else      
      e6xannots = g_list_prepend (e6xannots, e6xmap);
  }
  e6xannots = g_list_sort (e6xannots, (GCompareFunc) &_compare_annot_mapping);
  poppler_page_free_annot_mapping (popannots);
  
  return e6xannots; 
}


static void
_free_annot_mapping (E6xAnnotMapping *map)
{  
  g_free (map->text);
  g_free (map);
}


static gint
_compare_annot_mapping (E6xAnnotMapping *map1, E6xAnnotMapping *map2)
{
  PopplerRectangle area1 = map1->area;
  PopplerRectangle area2 = map2->area;
  
  /* If map1 is contained in map2, return -1 */
  if (area2.x1 <= area1.x1 && area2.x2 >= area1.x1 &&
      area2.y1 <= area1.y1 && area2.y2 >= area1.y2)
    return -1;
  
  /* If map2 is contained in map1, return 1 */
  if (area1.x1 <= area2.x1 && area1.x2 >= area2.x1 &&
      area1.y1 <= area2.y1 && area1.y2 >= area2.y2)
    return 1;
    
  return 0;
}
