/*
 * Copyright (c) 2003, 2004 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "sgtk-util.h"
#include "st-link.h"
#include "st-action.h"
#include "st-dialog-api.h"
#include "st-stock.h"
#include "st-util-api.h"

/*** type definitions ********************************************************/

enum {
  ACTIVATE,
  LAST_SIGNAL
};

struct _STLinkPrivate
{
  GtkWidget		*label;
  GtkWidget		*menu;

  char			*text;
  char			*uri;
  gboolean		in_link;
};

/*** variable declarations ***************************************************/

static GObjectClass *parent_class = NULL;
static unsigned int link_signals[LAST_SIGNAL] = { 0 };

/*** function declarations ***************************************************/

static void st_link_class_init (STLinkClass *class);
static void st_link_init (STLink *link);
static void st_link_finalize (GObject *object);

static void st_link_update_text (STLink *link);
static void st_link_update_cursor (STLink *link);

static void st_link_activate (STLink *link);
static gboolean st_link_expose_event_h (GtkWidget *widget,
					GdkEventExpose *event,
					gpointer user_data);
static gboolean st_link_enter_notify_event_h (STLink *link,
					      GdkEventCrossing *event,
					      gpointer user_data);
static gboolean st_link_leave_notify_event_h (STLink *link,
					      GdkEventCrossing *event,
					      gpointer user_data);
static void st_link_open_activate_h (GtkMenuItem *item,
				     gpointer user_data);
static void st_link_copy_address_activate_h (GtkMenuItem *item,
					     gpointer user_data);
static gboolean st_link_button_release_event_h (STLink *link,
						GdkEventButton *event,
						gpointer user_data);

static void st_link_open (STLink *link);
static void st_link_copy_address (STLink *link);

/*** implementation **********************************************************/

GType
st_link_get_type (void)
{
  static GType link_type = 0;
  
  if (! link_type)
    {
      static const GTypeInfo link_info = {
	sizeof(STLinkClass),
	NULL,
	NULL,
	(GClassInitFunc) st_link_class_init,
	NULL,
	NULL,
	sizeof(STLink),
	0,
	(GInstanceInitFunc) st_link_init,
      };
      
      link_type = g_type_register_static(GTK_TYPE_EVENT_BOX,
					 "STLink",
					 &link_info,
					 0);
    }

  return link_type;
}

static void
st_link_class_init (STLinkClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS(class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);

  parent_class = g_type_class_peek_parent(class);

  g_type_class_add_private(class, sizeof(STLinkPrivate));

  object_class->finalize = st_link_finalize;

  link_signals[ACTIVATE] = g_signal_new ("activate",
					 G_OBJECT_CLASS_TYPE(object_class),
					 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
					 G_STRUCT_OFFSET(STLinkClass, activate),
					 NULL,
					 NULL,
					 g_cclosure_marshal_VOID__VOID,
					 G_TYPE_NONE,
					 0);
  widget_class->activate_signal = link_signals[ACTIVATE];

  class->activate = st_link_activate;
}

static void
st_link_init (STLink *link)
{
  GtkWidget *item;

  link->priv = G_TYPE_INSTANCE_GET_PRIVATE(link, ST_TYPE_LINK, STLinkPrivate);

  gtk_event_box_set_visible_window(GTK_EVENT_BOX(link), FALSE);

  link->priv->menu = gtk_menu_new();

  item = gtk_image_menu_item_new_from_stock(ST_STOCK_OPEN_LINK, NULL);
  gtk_widget_show(item);
  gtk_menu_shell_append(GTK_MENU_SHELL(link->priv->menu), item);
  g_signal_connect(item, "activate", G_CALLBACK(st_link_open_activate_h), link);

  item = gtk_image_menu_item_new_from_stock(ST_STOCK_COPY_LINK_ADDRESS, NULL);
  gtk_widget_show(item);
  gtk_menu_shell_append(GTK_MENU_SHELL(link->priv->menu), item);
  g_signal_connect(item, "activate", G_CALLBACK(st_link_copy_address_activate_h), link);
  
  link->priv->label = gtk_label_new(NULL);
  gtk_container_add(GTK_CONTAINER(link), link->priv->label);
  gtk_widget_show(link->priv->label);

  sgtk_widget_set_popup_menu(GTK_WIDGET(link), GTK_MENU(link->priv->menu));

  g_object_connect(link,
		   "signal::expose-event", st_link_expose_event_h, NULL,
		   "signal::enter-notify-event", st_link_enter_notify_event_h, NULL,
		   "signal::leave-notify-event", st_link_leave_notify_event_h, NULL,
		   "signal::button-release-event", st_link_button_release_event_h, NULL,
		   NULL);
}

static void
st_link_finalize (GObject *object)
{
  STLink *link = ST_LINK(object);

  gtk_widget_destroy(link->priv->menu);
  g_free(link->priv->text);
  g_free(link->priv->uri);

  parent_class->finalize(object);
}

static void
st_link_update_text (STLink *link)
{
  g_return_if_fail(ST_IS_LINK(link));

  if (link->priv->text)
    {
      char *markup;
      
      if (link->priv->uri)
	markup = g_strdup_printf("<span underline=\"single\">%s</span>", link->priv->text);
      else
	markup = g_strdup(link->priv->text);
      
      gtk_label_set_markup(GTK_LABEL(link->priv->label), markup);
      g_free(markup);
    }
  else
    gtk_label_set_text(GTK_LABEL(link->priv->label), NULL);

  if (link->priv->uri)
    {
      char *tip;

      tip = g_strdup_printf(_("Visit %s"), link->priv->uri);
      st_set_tooltip(GTK_WIDGET(link), tip);
      g_free(tip);
    }
  else
    st_set_tooltip(GTK_WIDGET(link), NULL);
}

static void
st_link_update_cursor (STLink *link)
{
  g_return_if_fail(ST_IS_LINK(link));

  /* GtkWidget->window is only available if the widget is realized. */
  if (GTK_WIDGET_REALIZED(GTK_WIDGET(link)))
    {
      if (link->priv->uri && link->priv->in_link)
	{
	  GdkCursor *cursor;
	  
	  /* set the proper cursor */
	  cursor = gdk_cursor_new(GDK_HAND2);
	  gdk_window_set_cursor(GTK_WIDGET(link)->window, cursor);
	  gdk_cursor_unref(cursor);
	}
      else
	gdk_window_set_cursor(GTK_WIDGET(link)->window, NULL);
    }
}

static void
st_link_activate (STLink *link)
{
  if (link->priv->uri)
    st_link_open(link);
}

static gboolean
st_link_expose_event_h (GtkWidget *widget,
			GdkEventExpose *event,
			gpointer user_data)
{
  if (GTK_WIDGET_DRAWABLE(widget) && GTK_WIDGET_HAS_FOCUS(widget))
    {
      int border_width = GTK_CONTAINER(widget)->border_width;

      gtk_paint_focus(widget->style,
		      widget->window,
		      GTK_WIDGET_STATE(widget),
		      &event->area,
		      widget,
		      "link",
		      widget->allocation.x + border_width,
		      widget->allocation.y + border_width,
		      widget->allocation.width - border_width * 2,
		      widget->allocation.height - border_width * 2);
    }
  
  return FALSE;			/* propagate event */
}

static gboolean
st_link_enter_notify_event_h (STLink *link,
			      GdkEventCrossing *event,
			      gpointer user_data)
{
  GtkWidget *event_widget;

  event_widget = gtk_get_event_widget((GdkEvent *) event);
  if (event_widget == (GtkWidget *) link && event->detail != GDK_NOTIFY_INFERIOR)
    link->priv->in_link = TRUE;

  st_link_update_cursor(link);

  return FALSE;
}

static gboolean
st_link_leave_notify_event_h (STLink *link,
			      GdkEventCrossing *event,
			      gpointer user_data)
{
  GtkWidget *event_widget;

  event_widget = gtk_get_event_widget((GdkEvent *) event);
  if (event_widget == (GtkWidget *) link && event->detail != GDK_NOTIFY_INFERIOR)
    link->priv->in_link = FALSE;

  st_link_update_cursor(link);

  return FALSE;
}

static void
st_link_open_activate_h (GtkMenuItem *item, gpointer user_data)
{
  STLink *link = user_data;
  st_link_open(link);
}

static void
st_link_copy_address_activate_h (GtkMenuItem *item, gpointer user_data)
{
  STLink *link = user_data;
  st_link_copy_address(link);
}

static gboolean
st_link_button_release_event_h (STLink *link,
				GdkEventButton *event,
				gpointer user_data)
{
  if (event->button == 1 && link->priv->in_link && link->priv->uri)
    {
      st_link_open(link);

      return TRUE;
    }

  return FALSE;
}

static void
st_link_open (STLink *link)
{
  GError *err = NULL;

  g_return_if_fail(ST_IS_LINK(link));
  g_return_if_fail(link->priv->uri != NULL);

  if (! st_action_run("view-web", link->priv->uri, &err))
    {
      char *normalized;
      
      normalized = st_dialog_normalize(err->message);
      
      st_error_dialog(_("Unable to visit the link"), "%s", normalized);
      
      g_free(normalized);
      g_error_free(err);
    }
}

static void
st_link_copy_address (STLink *link)
{
  GtkClipboard *clipboard;

  g_return_if_fail(ST_IS_LINK(link));
  g_return_if_fail(link->priv->uri != NULL);

  clipboard = gtk_clipboard_get(gdk_atom_intern("PRIMARY", TRUE));
  gtk_clipboard_set_text(clipboard, link->priv->uri, -1);
}

GtkWidget *
st_link_new (void)
{
  return g_object_new(ST_TYPE_LINK, NULL);
}

/**
 * st_link_set_text:
 * @link: a link.
 * @text: the link label (can contain Pango markup).
 *
 * Sets the text label of @link.
 **/
void
st_link_set_text (STLink *link, const char *text)
{
  g_return_if_fail(ST_IS_LINK(link));
  
  g_free(link->priv->text);
  link->priv->text = g_strdup(text);

  st_link_update_text(link);
}

void
st_link_set_uri (STLink *link, const char *uri)
{
  g_return_if_fail(ST_IS_LINK(link));
  
  g_free(link->priv->uri);
  link->priv->uri = g_strdup(uri);

  st_link_update_text(link);
  st_link_update_cursor(link);

  if (link->priv->uri)
    {
      GTK_WIDGET_SET_FLAGS(link, GTK_CAN_FOCUS);
      sgtk_widget_set_popup_menu(GTK_WIDGET(link), GTK_MENU(link->priv->menu));
    }
  else
    {
      GTK_WIDGET_UNSET_FLAGS(link, GTK_CAN_FOCUS);
      sgtk_widget_set_popup_menu(GTK_WIDGET(link), NULL);
    }
}
