// -*- C++ -*-

/* 
 * GChemPaint library
 * reaction-arrow.cc 
 *
 * Copyright (C) 2004-2007 Jean Bréfort <jean.brefort@normalesup.org>
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#include "gchempaint-config.h"
#include "reaction-arrow.h"
#include "reaction.h"
#include "reaction-step.h"
#include "document.h"
#include "settings.h"
#include "theme.h"
#include "view.h"
#include "widgetdata.h"
#include "libgcpcanvas/gcp-canvas-group.h"
#include "libgcpcanvas/gcp-canvas-line.h"
#include <cmath>
#include <cstring>

gcpReactionArrow::gcpReactionArrow (gcpReaction* react, unsigned Type): gcpArrow (ReactionArrowType)
{
	SetId ((char*) "ra1");
	m_Type = Type;
	m_Start = m_End = NULL;
	if (react) react->AddChild (this);
	m_TypeChanged = false;
}


gcpReactionArrow::~gcpReactionArrow()
{
	if (IsLocked ())
		return;
	if (m_Start)
		m_Start->RemoveArrow (this);
	if (m_End)
		m_End->RemoveArrow (this);
}

xmlNodePtr gcpReactionArrow::Save (xmlDocPtr xml)
{
	xmlNodePtr parent, node;
	node = xmlNewDocNode (xml, NULL, (xmlChar*) "reaction-arrow", NULL);
	if (!node)
		return NULL;
	if (!gcpArrow::Save (xml, node)) {
		xmlFreeNode (node);
		return NULL;
	}
	xmlNewProp (node, (xmlChar*) "type", (xmlChar*) ((m_Type == gcpSimpleArrow)? "single": "double"));
	if (m_Type == gcpFullReversibleArrow)
		xmlNewProp (node, (xmlChar*) "heads", (xmlChar*) "full");
	if (m_Start)
		xmlNewProp (node, (xmlChar*) "start",  (xmlChar*) m_Start->GetId ());
	if (m_End)
		xmlNewProp (node, (xmlChar*) "end",  (xmlChar*) m_End->GetId ());
	gcpReaction* r = (gcpReaction*) GetReaction();
	if (!r) {
		//save the arrow as an object (this is NOT safe)
		parent = xmlNewDocNode (xml, NULL, (xmlChar*) "object", NULL);
		if (node && parent)
			xmlAddChild (parent, node);
		else {
			xmlFreeNode(node);
			return NULL;
		}
	}
	else
		parent = node;
	return parent;
}

bool gcpReactionArrow::Load (xmlNodePtr node)
{
	char *buf;
	Object *parent;
	if (gcpArrow::Load (node))
	{
		buf = (char*) xmlGetProp (node, (xmlChar*) "type");
		if (buf) {
			if (!strcmp (buf, "double")) {
				m_Type = gcpReversibleArrow;
				char *buf0 = (char*) xmlGetProp (node, (xmlChar*) "heads");
				if (buf0) {
					if (!strcmp (buf0, "full"))
						m_Type = gcpFullReversibleArrow;
					xmlFree (buf0);
				}
				m_TypeChanged = true;
			}
			xmlFree (buf);
		}
		parent = GetParent ();
		if (!parent)
			return true;
		buf = (char*) xmlGetProp (node, (xmlChar*) "start");
		if (buf) {
			m_Start = reinterpret_cast<gcpReactionStep*> (parent->GetDescendant (buf));
			xmlFree (buf);
			if (!m_Start)
				return false;
			m_Start->AddArrow (this);
		}
		buf = (char*) xmlGetProp (node, (xmlChar*) "end");
		if (buf) {
			m_End = reinterpret_cast<gcpReactionStep*> (parent->GetDescendant (buf));
			xmlFree (buf);
			if (!m_End)
				return false;
			m_End->AddArrow (this);
		}
		return true;
	}
	return false;
}

void gcpReactionArrow::Add (GtkWidget* w)
{
	gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data");
	gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme ();
	GnomeCanvasPoints *points = gnome_canvas_points_new (2);
	GnomeCanvasGroup* group = GNOME_CANVAS_GROUP(gnome_canvas_item_new (pData->Group, gnome_canvas_group_ext_get_type (), NULL));
	GnomeCanvasItem* item;
	switch(m_Type) {
		case gcpSimpleArrow:
			points->coords[0] = m_x * pTheme->GetZoomFactor ();
			points->coords[1] = m_y * pTheme->GetZoomFactor ();
			points->coords[2] = (m_x + m_width) * pTheme->GetZoomFactor ();
			points->coords[3] = (m_y + m_height) * pTheme->GetZoomFactor ();
			item = gnome_canvas_item_new (
										group,
										gnome_canvas_line_ext_get_type (),
										"points", points,
										"fill_color", (pData->IsSelected (this))? SelectColor: Color,
										"width_units", pTheme->GetArrowWidth (),
										"last_arrowhead", true,
										"arrow_shape_a", pTheme->GetArrowHeadA (),
										"arrow_shape_b", pTheme->GetArrowHeadB (),
										"arrow_shape_c", pTheme->GetArrowHeadC (),
										"last_arrowhead_style", (unsigned char) ARROW_HEAD_BOTH,
										NULL);
			g_object_set_data (G_OBJECT (item), "object", this);
			g_object_set_data (G_OBJECT (group), "arrow", item);
			g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), w);
			break;
		case gcpReversibleArrow: {
			double dAngle = atan (- m_height / m_width);
			if (m_width < 0)
				dAngle += M_PI;
			points->coords[0] = m_x * pTheme->GetZoomFactor () - pTheme->GetArrowDist ()  / 2 * sin (dAngle);
			points->coords[1] = m_y * pTheme->GetZoomFactor () - pTheme->GetArrowDist ()  / 2 * cos (dAngle);
			points->coords[2] = (m_x + m_width) * pTheme->GetZoomFactor () - pTheme->GetArrowDist ()  / 2 * sin (dAngle);
			points->coords[3] = (m_y + m_height) * pTheme->GetZoomFactor () - pTheme->GetArrowDist ()  / 2 * cos (dAngle);
			item = gnome_canvas_item_new (
								group,
								gnome_canvas_line_ext_get_type (),
								"points", points,
								"fill_color", (pData->IsSelected(this))? SelectColor: Color,
								"width_units", pTheme->GetArrowWidth (),
								"last_arrowhead", true,
								"arrow_shape_a", pTheme->GetArrowHeadA (),
								"arrow_shape_b", pTheme->GetArrowHeadB (),
								"arrow_shape_c", pTheme->GetArrowHeadC (),
								"last_arrowhead_style", (unsigned char) ARROW_HEAD_LEFT,
								NULL);
			g_object_set_data (G_OBJECT (item), "object", this);
			g_object_set_data (G_OBJECT (group), "direct", item);
			g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), w);
			points->coords[2] = m_x * pTheme->GetZoomFactor () + pTheme->GetArrowDist () / 2 * sin (dAngle);
			points->coords[3] = m_y * pTheme->GetZoomFactor () + pTheme->GetArrowDist ()  / 2 * cos (dAngle);
			points->coords[0] = (m_x + m_width) * pTheme->GetZoomFactor () + pTheme->GetArrowDist ()  / 2 * sin( dAngle);
			points->coords[1] = (m_y + m_height) * pTheme->GetZoomFactor () + pTheme->GetArrowDist ()  / 2 * cos (dAngle);
			item = gnome_canvas_item_new (
								group,
								gnome_canvas_line_ext_get_type (),
								"points", points,
								"fill_color", (pData->IsSelected (this))? SelectColor: Color,
								"width_units", pTheme->GetArrowWidth (),
								"last_arrowhead", true,
								"arrow_shape_a", pTheme->GetArrowHeadA (),
								"arrow_shape_b", pTheme->GetArrowHeadB (),
								"arrow_shape_c", pTheme->GetArrowHeadC (),
								"last_arrowhead_style", (unsigned char) ARROW_HEAD_LEFT,
								NULL);
			g_object_set_data (G_OBJECT (item), "object", this);
			g_object_set_data (G_OBJECT (group), "reverse", item);
			g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), w);
			break;
		}
		case gcpFullReversibleArrow: {
			double dAngle = atan (- m_height / m_width);
			if (m_width < 0)
				dAngle += M_PI;
			points->coords[0] = m_x * pTheme->GetZoomFactor () - pTheme->GetArrowDist ()  / 2 * sin (dAngle);
			points->coords[1] = m_y * pTheme->GetZoomFactor () - pTheme->GetArrowDist ()  / 2 * cos (dAngle);
			points->coords[2] = (m_x + m_width) * pTheme->GetZoomFactor () - pTheme->GetArrowDist ()  / 2 * sin (dAngle);
			points->coords[3] = (m_y + m_height) * pTheme->GetZoomFactor () - pTheme->GetArrowDist ()  / 2 * cos (dAngle);
			item = gnome_canvas_item_new(
								group,
								gnome_canvas_line_ext_get_type (),
								"points", points,
								"fill_color", (pData->IsSelected (this))? SelectColor: Color,
								"width_units", pTheme->GetArrowWidth (),
								"last_arrowhead", true,
								"arrow_shape_a", pTheme->GetArrowHeadA (),
								"arrow_shape_b", pTheme->GetArrowHeadB (),
								"arrow_shape_c", pTheme->GetArrowHeadC (),
								"last_arrowhead_style", (unsigned char) ARROW_HEAD_BOTH,
								NULL);
			g_object_set_data (G_OBJECT (item), "object", this);
			g_object_set_data (G_OBJECT (group), "direct", item);
			g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), w);
			points->coords[2] = m_x * pTheme->GetZoomFactor () + pTheme->GetArrowDist () / 2 * sin (dAngle);
			points->coords[3] = m_y * pTheme->GetZoomFactor () + pTheme->GetArrowDist ()  / 2 * cos (dAngle);
			points->coords[0] = (m_x + m_width) * pTheme->GetZoomFactor () + pTheme->GetArrowDist ()  / 2 * sin (dAngle);
			points->coords[1] = (m_y + m_height) * pTheme->GetZoomFactor () + pTheme->GetArrowDist ()  / 2 * cos (dAngle);
			item = gnome_canvas_item_new (
								group,
								gnome_canvas_line_ext_get_type (),
								"points", points,
								"fill_color", (pData->IsSelected (this))? SelectColor: Color,
								"width_units", pTheme->GetArrowWidth (),
								"last_arrowhead", true,
								"arrow_shape_a", pTheme->GetArrowHeadA (),
								"arrow_shape_b", pTheme->GetArrowHeadB (),
								"arrow_shape_c", pTheme->GetArrowHeadC (),
								"last_arrowhead_style", (unsigned char) ARROW_HEAD_BOTH,
								NULL);
			g_object_set_data (G_OBJECT (item), "object", this);
			g_object_set_data (G_OBJECT (group), "reverse", item);
			g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (on_event), w);
			break;
		}
	}
	pData->Items[this] = group;
	gnome_canvas_points_free (points);
}

void gcpReactionArrow::Update (GtkWidget* w)
{
	gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data");
	if (pData->Items[this] != NULL)
		return;
	gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme ();
	GnomeCanvasGroup* group = pData->Items[this];
	if (!group) {
		Add (w);
		m_TypeChanged = false;
		return;
	}
	if (m_TypeChanged) {
		gtk_object_destroy (GTK_OBJECT (group));
		Add (w);
		m_TypeChanged = false;
		return;
	}
	GnomeCanvasPoints *points = gnome_canvas_points_new (2);
	switch(m_Type) {
		case gcpSimpleArrow:
			points->coords[0] = m_x * pTheme->GetZoomFactor ();
			points->coords[1] = m_y * pTheme->GetZoomFactor ();
			points->coords[2] = (m_x + m_width) * pTheme->GetZoomFactor ();
			points->coords[3] = (m_y + m_height) * pTheme->GetZoomFactor ();
			g_object_set (G_OBJECT (g_object_get_data (G_OBJECT (group), "arrow")),
								"points", points,
								"width_units", pTheme->GetArrowWidth (),
								"arrow_shape_a", pTheme->GetArrowHeadA (),
								"arrow_shape_b", pTheme->GetArrowHeadB (),
								"arrow_shape_c", pTheme->GetArrowHeadC (),
								NULL);
			break;
		case gcpReversibleArrow:
		case gcpFullReversibleArrow:
			double dAngle = atan (- m_height / m_width);
			if (m_width < 0) dAngle += M_PI;
			points->coords[0] = m_x * pTheme->GetZoomFactor () - pTheme->GetArrowDist () / 2 * sin (dAngle);
			points->coords[1] = m_y * pTheme->GetZoomFactor () - pTheme->GetArrowDist () / 2 * cos (dAngle);
			points->coords[2] = (m_x + m_width) * pTheme->GetZoomFactor () - pTheme->GetArrowDist () / 2 * sin (dAngle);
			points->coords[3] = (m_y + m_height) * pTheme->GetZoomFactor () - pTheme->GetArrowDist () / 2 * cos (dAngle);
			g_object_set (G_OBJECT (g_object_get_data (G_OBJECT (group), "direct")),
								"points", points,
								"width_units", pTheme->GetArrowWidth (),
								"arrow_shape_a", pTheme->GetArrowHeadA (),
								"arrow_shape_b", pTheme->GetArrowHeadB (),
								"arrow_shape_c", pTheme->GetArrowHeadC (),
								NULL);
			points->coords[2] = m_x * pTheme->GetZoomFactor () + pTheme->GetArrowDist () / 2 * sin (dAngle);
			points->coords[3] = m_y * pTheme->GetZoomFactor () + pTheme->GetArrowDist () / 2 * cos (dAngle);
			points->coords[0] = (m_x + m_width) * pTheme->GetZoomFactor () + pTheme->GetArrowDist () / 2 * sin (dAngle);
			points->coords[1] = (m_y + m_height) * pTheme->GetZoomFactor () + pTheme->GetArrowDist () / 2 * cos (dAngle);
			g_object_set (G_OBJECT (g_object_get_data (G_OBJECT (group), "reverse")),
								"points", points,
								"width_units", pTheme->GetArrowWidth (),
								"arrow_shape_a", pTheme->GetArrowHeadA (),
								"arrow_shape_b", pTheme->GetArrowHeadB (),
								"arrow_shape_c", pTheme->GetArrowHeadC (),
								NULL);
			break;
	}
	gnome_canvas_points_free (points);
}

void gcpReactionArrow::RemoveStep (gcpReactionStep *Step)
{
	if (Step == m_Start)
		m_Start = NULL;
	else if (Step == m_End)
		m_End = NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1