/*
 * Copyright 2009 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of either or both of the following licenses:
 *
 * 1) the GNU Lesser General Public License version 3, as published by the
 * Free Software Foundation; and/or
 * 2) the GNU Lesser General Public License version 2.1, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the applicable version of the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of both the GNU Lesser General Public
 * License version 3 and version 2.1 along with this program.  If not, see
 * <http://www.gnu.org/licenses/>
 *
 * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */
/**
 * SECTION:ctk-text
 * @short_description: Creates widgets that display text using pango
 *
 * #CtkText is a widget that internally uses #ClutterText and Pango to display text
 */
#if HAVE_CONFIG_H
#include <config.h>
#endif

#include "ctk-text.h"

#include <math.h>

G_DEFINE_TYPE (CtkText, ctk_text, CLUTTER_TYPE_TEXT);

#define CTK_TEXT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  CTK_TYPE_TEXT, \
  CtkTextPrivate))

#define GFLOAT_FROM_PANGO_UNIT(x) (pango_units_to_double (x))
#define GFLOAT_TO_PANGO_UNIT(x) (pango_units_from_double (x))

struct _CtkTextPrivate
{
  GtkSettings    *settings;
  PangoAlignment  alignment;

  gboolean        font_overridden;
};

/* Globals */

/* Forwards */
static void on_gtk_font_changed          (GtkSettings *settings,
    GParamSpec  *pspec,
    ClutterText *text);
static void on_clutter_text_font_changed (CtkText *self,
    GParamSpec *pspec);

/* GObject stuff */
static void
ctk_text_finalized (GObject *object)
{
  CtkTextPrivate *priv = CTK_TEXT_GET_PRIVATE (object);

  if (priv->settings)
    {
      g_signal_handlers_disconnect_by_func (priv->settings,
                                            on_gtk_font_changed,
                                            object);
      priv->settings = NULL;
    }

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

static void
ctk_text_allocate (ClutterActor          *actor,
                   const ClutterActorBox *box,
                   ClutterAllocationFlags flags)
{
  CtkTextPrivate *priv = CTK_TEXT (actor)->priv;
  ClutterActorBox real_box;
  PangoLayout    *layout;
  PangoRectangle log_rect;
  gfloat         real_height;
  gfloat         text_height;

  layout = clutter_text_get_layout (CLUTTER_TEXT (actor));
  pango_layout_get_extents (layout, NULL, &log_rect);

  if (priv->alignment != PANGO_ALIGN_LEFT
      && pango_layout_get_line_count (layout) == 1)
    {
      gfloat         real_width;
      gfloat         text_width;

      text_width = GFLOAT_FROM_PANGO_UNIT (log_rect.width);
      real_width = box->x2 - box->x1;

      if (priv->alignment == PANGO_ALIGN_CENTER)
        {
          real_box.x1 = (real_width/2) - (text_width/2);
          real_box.x2 = real_box.x1 + text_width;
        }
      else //PANGO_ALIGN_RIGHT
        {
          real_box.x1 = box->x2 - text_width;
          real_box.x2 = box->x2;
        }
    }
  else
    {
      real_box.x1 = box->x1;
      real_box.x2 = box->x2;
    }

  real_height = box->y2 - box->y1;
  text_height = GFLOAT_FROM_PANGO_UNIT (log_rect.height);

  real_box.x1 = floor (real_box.x1);
  real_box.x2 = floor (real_box.x2);
  real_box.y1 = floor (box->y1 + (real_height-text_height)/2.0f);
  real_box.y2 = floor (real_box.y1 + text_height);

  CLUTTER_ACTOR_CLASS (ctk_text_parent_class)->allocate (actor,
      &real_box,
      flags);
}

static void
ctk_text_constructed (GObject *object)
{
  CtkTextPrivate *priv = CTK_TEXT (object)->priv;
  ClutterText    *text = CLUTTER_TEXT (object);
  ClutterColor    white = { 0xff, 0xff, 0xff, 0xff };
  gchar          *font = NULL;

  g_object_get (G_OBJECT (priv->settings), "gtk-font-name", &font, NULL);

  clutter_text_set_font_name (text, font);
  clutter_text_set_color (text, &white);

  g_free (font);
}

static void
ctk_text_focus_in (ClutterActor *actor)
{
  g_debug ("CtkText: focus-in");
}

static void
ctk_text_focus_out (ClutterActor *actor)
{
    //g_debug ("CtkText: focus-out");
}

static gboolean
ctk_text_key_press (ClutterActor *actor, ClutterKeyEvent *event)
{
  ClutterActor *parent = 0;
  int ctrl_key = 0;

  parent = clutter_actor_get_parent (actor);

  ctrl_key = event->modifier_state & CLUTTER_CONTROL_MASK;

  if (ctrl_key && ((clutter_keysym_to_unicode(event->keyval) == 'c') || (clutter_keysym_to_unicode(event->keyval) == 'C')))
  {
    /* Copy text */
    char* selection = 0;

    /* g_debug ("Ctrl+C\n"); */

    selection = clutter_text_get_selection (CLUTTER_TEXT (actor));

    if (g_strcmp0 (selection, "") == 0)
    {
      /* No text selected: Select the entire text and put it in the clipboard. */
      /* Warning: this behavior should be verified with the design team. */

      selection = (char*) clutter_text_get_text (CLUTTER_TEXT (actor));
      gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), selection, -1);
    }
    else
    {
      gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), selection, -1);
      if (g_strcmp0 (selection, "") != 0)
        g_free (selection);
    }
  }

  if (ctrl_key && ((clutter_keysym_to_unicode(event->keyval) == 'v') || (clutter_keysym_to_unicode(event->keyval) == 'V')))
  {
    /* Paste text */
    gchar* current_selection = 0;
    const char* clipboard_text = 0;
    gint cursor_position = 0;

    /* g_debug ("Ctrl+V\n"); */

    current_selection = clutter_text_get_selection (CLUTTER_TEXT (actor));
    if (g_strcmp0 (current_selection, "") != 0)
    {
      /* There is currently and active selection: delete the selected text, then paste the clipboard text; */
      clutter_text_delete_selection (CLUTTER_TEXT (actor));
      cursor_position = clutter_text_get_cursor_position (CLUTTER_TEXT (actor));
    }
    else
    {
      cursor_position = clutter_text_get_cursor_position (CLUTTER_TEXT (actor));
    }

    clipboard_text = gtk_clipboard_wait_for_text (gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
    clutter_text_insert_text (CLUTTER_TEXT (actor), clipboard_text, cursor_position);

    if (g_strcmp0 (current_selection, "") != 0)
      g_free (current_selection);
  }

  if (ctrl_key && ((clutter_keysym_to_unicode(event->keyval) == 'a') || (clutter_keysym_to_unicode(event->keyval) == 'A')))
  {
    /* Select all the text entry */
    /* g_debug ("Ctrl+A\n"); */
    clutter_text_set_selection (CLUTTER_TEXT (actor), 0, -1);
  }

  if (ctrl_key && ((clutter_keysym_to_unicode(event->keyval) == 'x') || (clutter_keysym_to_unicode(event->keyval) == 'X')))
  {
    /* Put the selected text into the clipboard then delete it from th text */
    /* g_debug ("Ctrl+X\n"); */

    char* selection = 0;
    selection = clutter_text_get_selection (CLUTTER_TEXT (actor));

    if (g_strcmp0 (selection, "") == 0)
    {
      /* No text selected: Do nothing */
    }
    else
    {
      g_debug ("Cutting %s and puting it in the clipboard \n", selection);
      gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), selection, -1);

      clutter_text_delete_selection (CLUTTER_TEXT (actor));

      if (g_strcmp0 (selection, "") != 0)
        g_free (selection);
    }

    clutter_text_delete_selection (CLUTTER_TEXT (actor));
  }

  /* Pass the event to ClutterText. Right now ClutterText does not implement
   * the Copy/Paste shortkeys. But in the future we may have to remove our implementation and
   * let ClutterText do his own.
   */

  if(CLUTTER_ACTOR_CLASS(ctk_text_parent_class)->key_press_event)
  {
    return CLUTTER_ACTOR_CLASS(ctk_text_parent_class)->key_press_event (actor, event);
  }
  return FALSE;
}

static gboolean
ctk_text_key_release (ClutterActor *actor, ClutterKeyEvent *event)
{
  ClutterActor *parent = 0;
  parent = clutter_actor_get_parent (actor);

  if(CLUTTER_ACTOR_CLASS(ctk_text_parent_class)->key_release_event)
  {
    return CLUTTER_ACTOR_CLASS(ctk_text_parent_class)->key_release_event(actor, event);
  }
  return FALSE;
}

static void
ctk_text_class_init (CtkTextClass *klass)
{
  GObjectClass      *obj_class = G_OBJECT_CLASS (klass);
  ClutterActorClass *act_class = CLUTTER_ACTOR_CLASS (klass);

  obj_class->finalize     = ctk_text_finalized;
  obj_class->constructed  = ctk_text_constructed;
  act_class->allocate     = ctk_text_allocate;

  act_class->key_focus_in = ctk_text_focus_in;
  act_class->key_focus_out = ctk_text_focus_out;
  act_class->key_press_event = ctk_text_key_press;
  act_class->key_release_event = ctk_text_key_release;

  g_type_class_add_private (obj_class, sizeof (CtkTextPrivate));
}

static void
ctk_text_init (CtkText *text)
{
  CtkTextPrivate *priv;

  priv = text->priv = CTK_TEXT_GET_PRIVATE (text);

  priv->font_overridden = FALSE;
  priv->alignment = PANGO_ALIGN_LEFT;

  priv->settings = gtk_settings_get_default ();
  g_signal_connect (priv->settings, "notify::gtk-font-name",
                    G_CALLBACK (on_gtk_font_changed), text);

  g_signal_connect (text, "notify::font-name",
                    G_CALLBACK (on_clutter_text_font_changed), NULL);
}

/**
 * ctk_text_new:
 * @text: string containing the text to display
 *
 * Builds a CtkText object containing @text
 * Defaults to enabling Pango markup
 *
 * returns: a CtkText object
 */
ClutterActor *
ctk_text_new (const gchar *text)
{
  return g_object_new (CTK_TYPE_TEXT,
                       "text", text ? text : "",
                       "use-markup", TRUE,
                       NULL);
}

/*
 * Private methods
 */
static void
on_gtk_font_changed (GtkSettings *settings,
                     GParamSpec  *pspec,
                     ClutterText *self)
{
  gchar *font_name = NULL;

  g_return_if_fail (CTK_IS_TEXT (self));

  /* If the developer has chosen their own font, then we don't auto-update
   * the text font with the system default
   */
  if (CTK_TEXT (self)->priv->font_overridden)
    return;

  g_object_get (G_OBJECT (settings), "gtk-font-name", &font_name, NULL);

  clutter_text_set_font_name (self, font_name);

  g_free (font_name);
}

static void
on_clutter_text_font_changed (CtkText *self,
                              GParamSpec *pspec)
{
  CtkTextPrivate *priv;
  gchar          *sys_font;
  const gchar    *text_font;

  g_return_if_fail (CTK_IS_TEXT (self));
  priv = self->priv;

  g_object_get (G_OBJECT (priv->settings), "gtk-font-name", &sys_font, NULL);
  text_font = clutter_text_get_font_name (CLUTTER_TEXT (self));

  if (g_strcmp0 (sys_font, text_font) == 0)
    priv->font_overridden = FALSE;
  else
    priv->font_overridden = TRUE;

  g_free (sys_font);
}

/*
 * Public methods
 */

/**
 * ctk_text_set_alignment:
 * @self: A #CtkText object
 * @alignment: A #PangoAlignment object
 *
 * Sets the alignment of the text, see #PangoAlignment for more info
 */
void
ctk_text_set_alignment (CtkText        *self,
                        PangoAlignment  alignment)
{
  CtkTextPrivate *priv;

  g_return_if_fail (CTK_IS_TEXT (self));
  priv = self->priv;

  if (priv->alignment == alignment)
    return;

  priv->alignment = alignment;

  if (CLUTTER_ACTOR_IS_VISIBLE (self))
    clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
}

/**
 * ctk_text_get_alignment:
 * @self: A #CtkText object
 *
 * Retrives the #PangoAlignment that is currently used by @self
 *
 * Returns: a #PangoAlignment
 */
PangoAlignment
ctk_text_get_alignment (CtkText *self)
{
  g_return_val_if_fail (CTK_IS_TEXT (self), PANGO_ALIGN_LEFT);

  return self->priv->alignment;
}

