// -*- C++ -*-

/* 
 * GChemPaint bonds plugin
 * bondtool.cc
 *
 * Copyright (C) 2001-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 "bondtool.h"
#include "lib/settings.h"
#include "lib/document.h"
#include "lib/application.h"
#include "lib/atom.h"
#include "lib/bond.h"
#include "lib/theme.h"
#include "libgcpcanvas/gcp-canvas-group.h"
#include <glib/gi18n-lib.h>
#include <cmath>

gcpBondTool::gcpBondTool (gcpApplication *App, string ToolId, unsigned nPoints): gcpTool (App, ToolId)
{
	points = (nPoints)? gnome_canvas_points_new (nPoints): NULL;
	m_pOp = NULL;
}

gcpBondTool::~gcpBondTool ()
{
	if (points) gnome_canvas_points_free (points);
}

bool gcpBondTool::OnClicked ()
{
	if (Element::GetMaxBonds (m_pApp->GetCurZ()) < 1)
		return false;
	int i;
	m_pAtom = NULL;
	m_pItem = NULL;
	m_bChanged = false;
	m_dAngle = 0.;
	gcpBond* pBond;
	gcpDocument* pDoc = m_pView->GetDoc ();
	if (m_pObject)
	{
		TypeId Id = m_pObject->GetType ();
		switch (Id)
		{
		case BondType:
			pBond = (gcpBond*) m_pObject;
			m_pAtom = (gcpAtom*) pBond->GetAtom (0);
			m_pAtom->GetCoords (&m_x0, &m_y0, NULL);
			m_pAtom = (gcpAtom*) pBond->GetAtom (1);
			m_pAtom->GetCoords (&m_x1, &m_y1, NULL);
			m_x0 *= m_dZoomFactor;
			m_y0 *= m_dZoomFactor;
			m_x1 *= m_dZoomFactor;
			m_y1 *= m_dZoomFactor;
			points->coords[0] = m_x0;
			points->coords[1] = m_y0;
			m_bChanged = true;
			m_pOp = pDoc->GetNewOperation (GCP_MODIFY_OPERATION);
			m_pOp->AddObject (m_pObjectGroup, 0);
			UpdateBond ();
			return true;
		case AtomType:
			if (!((gcpAtom*)m_pObject)->AcceptNewBonds()) return false;
			((gcpAtom*)m_pObject)->GetCoords(&m_x0, &m_y0, NULL);
			m_x0 *= m_dZoomFactor;
			m_y0 *=  m_dZoomFactor;
			points->coords[0] = m_x0;
			points->coords[1] = m_y0;
			/* search  preferred orientation for new bond */
			i = ((gcpAtom*) m_pObject)->GetBondsNumber ();
			switch (i) {
				case 0:
					break;
				case 1: {
					map<Atom*, Bond*>::iterator i;
					gcpBond* bond = (gcpBond*) ((Atom*) m_pObject)->GetFirstBond (i);
					m_dAngle = bond->GetAngle2D ((gcpAtom*) m_pObject);
					m_dAngle += (m_nState & GDK_LOCK_MASK)?
						pDoc->GetBondAngle (): -pDoc->GetBondAngle ();
					break;
				}
				case 2: {
					double a1, a2;
					map<Atom*, Bond*>::iterator i;
					gcpBond* bond = (gcpBond*) ((Atom*) m_pObject)->GetFirstBond (i);
					a1 = bond->GetAngle2D ((gcpAtom*) m_pObject);
					bond = (gcpBond*) ((Atom*) m_pObject)->GetNextBond (i);
					a2 = bond->GetAngle2D ((gcpAtom*) m_pObject);
					m_dAngle = (a1 + a2) / 2.;
					a2 = fabs (a2 - m_dAngle);
					if (a2 < 90.)
						m_dAngle += 180.;
					if (m_dAngle > 360.)
						m_dAngle -= 360.;
					break;
				}
				default:
					break;
			}
			break;
		default:
			return false;
		}
	}
	else if (points)
	{
		points->coords[0] = m_x0;
		points->coords[1] = m_y0;
	}
	double a = m_dAngle * M_PI / 180.;
	m_x1 =  m_x0 + pDoc->GetBondLength () * m_dZoomFactor * cos (a);
	m_y1 =  m_y0 - pDoc->GetBondLength () * m_dZoomFactor * sin (a);
	GnomeCanvasItem* pItem = gnome_canvas_get_item_at(GNOME_CANVAS(m_pWidget), m_x1, m_y1);
	if (pItem == (GnomeCanvasItem*)m_pBackground) pItem = NULL;
	Object* pObject = NULL;
	if (pItem) pObject = (Object*)g_object_get_data(G_OBJECT(pItem), "object");
	m_pAtom = NULL;
	if (pObject && pObject != m_pObject)
	{
		if ((pObject->GetType() == BondType) || (pObject->GetType() == FragmentType))
		{
			m_pAtom = (gcpAtom*)pObject->GetAtomAt(m_x1 / m_dZoomFactor, m_y1 / m_dZoomFactor);
		}
		else if (pObject->GetType() == AtomType)
		{
			m_pAtom = (gcpAtom*)pObject;
		}
	}
	if (m_pAtom)
	{
		m_pAtom->GetCoords(&m_x1, &m_y1, NULL);
		m_x1 *= m_dZoomFactor;
		m_y1 *= m_dZoomFactor;
		m_x = m_x1 - m_x0;
		m_y = m_y1 - m_y0;
		m_dAngle = atan(-m_y/m_x) * 90 / 1.570796326794897;
		if (m_x < 0) m_dAngle += 180;
	}
	char tmp[32];
	snprintf(tmp, sizeof(tmp) - 1, _("Orientation: %g"), m_dAngle);
	m_pApp->SetStatusText(tmp);
	Draw();
	return true;
}

void gcpBondTool::OnDrag()
{
	double x1, y1, x2, y2;
	gcpDocument* pDoc = m_pView->GetDoc ();
	gcpTheme *Theme = pDoc->GetTheme ();
	if ((m_pObject) && (m_pObject->GetType() == BondType))
	{
		if (((gcpBond*)m_pObject)->GetDist(m_x / m_dZoomFactor, m_y / m_dZoomFactor) < (Theme->GetPadding () + Theme->GetBondWidth () / 2) * m_dZoomFactor)
		{
			if (!m_bChanged)
			{
				gnome_canvas_item_show(m_pItem);
				m_bChanged = true;
			}
		}
		else if (m_bChanged)
		{
			gnome_canvas_item_hide(m_pItem);
			m_bChanged = false;
		}
	}
	else
	{
		if (m_pItem)
		{
			gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM(m_pItem), &x1, &y1, &x2, &y2);
			gtk_object_destroy(GTK_OBJECT(GNOME_CANVAS_ITEM(m_pItem)));
			gnome_canvas_request_redraw(GNOME_CANVAS(m_pWidget), (int)x1, (int)y1, (int)x2, (int)y2);
			m_pItem = NULL;
		}
	
		GnomeCanvasItem* pItem = gnome_canvas_get_item_at(GNOME_CANVAS(m_pWidget), m_x, m_y);
		if (pItem == (GnomeCanvasItem*)m_pBackground) pItem = NULL;
		Object* pObject = NULL;
		if (pItem) {
			pObject = (Object*) g_object_get_data(G_OBJECT (pItem), "object");
			if (pObject && (pObject == m_pObject || (pObject->GetType () == FragmentType && dynamic_cast<gcpFragment*> (pObject)->GetAtom () == m_pObject)))
				return;
		}
		double dAngle;
		m_pAtom = NULL;
		if (MergeAtoms && pObject)
		{
			if (pObject->GetType() == BondType)
			{
				m_pAtom = (gcpAtom*)pObject->GetAtomAt(m_x / m_dZoomFactor, m_y / m_dZoomFactor);
			}
			else if (pObject->GetType() == FragmentType)
			{
				m_pAtom = (gcpAtom*)pObject->GetAtomAt(m_x1 / m_dZoomFactor, m_y1 / m_dZoomFactor);
			}
			else if (pObject->GetType() == AtomType)
			{
				m_pAtom = (gcpAtom*)pObject;
			}
		}
		if (m_pAtom)
		{
			if ((Object*)m_pAtom == m_pObject) return;
			if (!m_pAtom->AcceptNewBonds()) return;
			m_pAtom->GetCoords(&m_x1, &m_y1, NULL);
			m_x1 *= m_dZoomFactor;
			m_y1 *= m_dZoomFactor;
			m_x = m_x1 - m_x0;
			m_y = m_y1 - m_y0;
			dAngle = atan(-m_y/m_x) * 90 / 1.570796326794897;
			if (isnan (dAngle))
				dAngle = m_dAngle;
			else if (m_x < 0.)
				dAngle += 180.;
		}
		else
		{
			m_x-= m_x0;
			m_y -= m_y0;
			if (m_x == 0)
			{
				if (m_y == 0) return;
				dAngle = (m_y < 0) ? 90 : 270;
			}
			else
			{
				dAngle = atan(-m_y/m_x) * 180 / M_PI;
				if (!(m_nState & GDK_CONTROL_MASK)) dAngle = rint(dAngle / 5) * 5;
				if (isnan (dAngle))
					dAngle = m_dAngle;
				else if (m_x < 0.)
					dAngle += 180.;
			}
			m_dAngle = dAngle;
			if (m_nState & GDK_SHIFT_MASK)
			{
				x1 = sqrt(square(m_x) + square(m_y));
				m_x1 = m_x0 + x1 * cos(m_dAngle / 180 * M_PI);
				m_y1 = m_y0 - x1 * sin(m_dAngle / 180 * M_PI);
			}
			else
			{
				m_x1 = m_x0 + pDoc->GetBondLength () * m_dZoomFactor * cos(m_dAngle / 180 * M_PI);
				m_y1 = m_y0 - pDoc->GetBondLength () * m_dZoomFactor * sin(m_dAngle / 180 * M_PI);
			}
		}
		char tmp[32];
		if (dAngle < 0) dAngle += 360;
		snprintf(tmp, sizeof(tmp) - 1, _("Orientation: %g"), dAngle);
		m_pApp->SetStatusText(tmp);
		Draw();
	}
}

void gcpBondTool::OnRelease()
{
	double x1, y1, x2, y2;
	gcpDocument* pDoc = m_pView->GetDoc();
	if (m_pItem)
	{
		gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM(m_pItem), &x1, &y1, &x2, &y2);
		gtk_object_destroy(GTK_OBJECT(GNOME_CANVAS_ITEM(m_pItem)));
		gnome_canvas_request_redraw(GNOME_CANVAS(m_pWidget), (int)x1, (int)y1, (int)x2, (int)y2);
		m_pItem = NULL;
	}
	else 
	{
		if (m_pOp) pDoc->AbortOperation();
		m_pOp = NULL;
		return;
	}
	if ((m_pObject) && (m_pObject->GetType() == BondType)) {
		FinalizeBond ();
		gcpAtom* pAtom = (gcpAtom*) ((gcpBond*) m_pObject)->GetAtom (0);
		pAtom->Update ();
		m_pView->Update (pAtom);
		pAtom = (gcpAtom*) ((gcpBond*) m_pObject)->GetAtom (1);
		pAtom->Update ();
		m_pView->Update (pAtom);
		m_pOp->AddObject (m_pObjectGroup, 1);
		pDoc->FinishOperation ();
		m_pOp = NULL;
		m_pObject->EmitSignal (OnChangedSignal);
		return;
	} else {
		if (m_pOp)
			pDoc->AbortOperation();
		m_pOp = NULL;
	}
	m_pApp->ClearStatus();
	GnomeCanvasItem* pItem = gnome_canvas_get_item_at(GNOME_CANVAS(m_pWidget), m_x1, m_y1);
	if (pItem == (GnomeCanvasItem*)m_pBackground) pItem = NULL;
	Object* pObject = NULL;
	if (pItem) pObject = (Object*)g_object_get_data(G_OBJECT(pItem), "object");
	m_pAtom = NULL;
	if (MergeAtoms && pObject)
	{
		if (pObject->GetType() == BondType)
		{
			m_pAtom = (gcpAtom*)pObject->GetAtomAt(m_x1 / m_dZoomFactor, m_y1 / m_dZoomFactor);
		}
		else if (pObject->GetType() == FragmentType)
		{
			m_pAtom = (gcpAtom*)pObject->GetAtomAt(m_x1 / m_dZoomFactor, m_y1 / m_dZoomFactor);
		}
		else if (pObject->GetType() == AtomType)
		{
			m_pAtom = (gcpAtom*)pObject;
		}
	}
	gcpAtom* pAtom;
	gcpBond* pBond;
	if (!m_pObject) {
		//Add an atom at (x0, y0)
		pAtom = new gcpAtom (m_pApp->GetCurZ(), m_x0 / m_dZoomFactor, m_y0 / m_dZoomFactor, 0);
		pDoc->AddAtom (pAtom);
		m_pObject = pAtom;
	} else {
		pObject = m_pObject->GetGroup ();
		if (pObject)
			ModifiedObjects.insert (pObject->GetId ());
	}
	if (m_pObject->GetType() == AtomType)
	{
		if (m_pAtom) {
			if (m_pObject == m_pAtom) {
				ModifiedObjects.clear ();
				return;
			}
			pObject = m_pObject->GetGroup ();
			if (!pObject)
				throw runtime_error (_("Invalid document tree, please file a bug report"));
			ModifiedObjects.insert (pObject->GetId ());
			pAtom = m_pAtom;
		} else {
			pAtom = new gcpAtom(m_pApp->GetCurZ(), m_x1 / m_dZoomFactor, m_y1 / m_dZoomFactor, 0);
			pDoc->AddAtom(pAtom);
		}
		pBond = (gcpBond*)pAtom->GetBond((gcpAtom*)m_pObject);
		if (pBond)
		{
			m_pOp = pDoc-> GetNewOperation(GCP_MODIFY_OPERATION);
			m_pOp->AddObject(pBond->GetGroup (), 0);
			if (pBond->GetType() == NormalBondType)	pBond->IncOrder();
			m_pObject = pBond;
			m_bChanged = true;
			FinalizeBond(); 
			gcpAtom* pAtom = (gcpAtom*) ((gcpBond*)m_pObject)->GetAtom(0);
			pAtom->Update ();
			m_pView->Update(pAtom);
			pAtom = (gcpAtom*) ((gcpBond*)m_pObject)->GetAtom(1);
			pAtom->Update ();
			m_pView->Update(pAtom);
			m_pView->Update(pBond);
			m_pOp->AddObject(pBond->GetGroup (), 1);
			pDoc->FinishOperation();
			m_pOp = NULL;
		}
		else
		{
			// Push modified objects in Operation
			if (ModifiedObjects.size ()) {
				m_pOp = pDoc-> GetNewOperation(GCP_MODIFY_OPERATION);
				set<string>::iterator it, end = ModifiedObjects.end();
				for (it = ModifiedObjects.begin(); it != end; it++)
					m_pOp->AddObject (pDoc->GetDescendant ((*it).c_str ()), 0);
			}
			pBond = new gcpBond((gcpAtom*)m_pObject, pAtom, 1);
			SetType(pBond);
			pDoc->AddBond(pBond);
			if (m_pOp) {
				set<string>::iterator it, end = ModifiedObjects.end();
				for (it = ModifiedObjects.begin(); it != end; it++) {
					pObject = pDoc->GetDescendant ((*it).c_str ());
					if (pObject)
						m_pOp->AddObject (pObject, 1);
				}
			} else {
				m_pOp = pDoc-> GetNewOperation(GCP_ADD_OPERATION);
				m_pOp->AddObject (pBond->GetMolecule ());
			}
			pDoc->FinishOperation();
		}
	}
	ModifiedObjects.clear ();
}

void gcpBondTool::Draw()
{
	double x1, y1, x2, y2;
	gcpTheme *pTheme = m_pView->GetDoc ()->GetTheme ();
	points->coords[2] = m_x1;
	points->coords[3] = m_y1;
	m_pItem = gnome_canvas_item_new(
								m_pGroup,
								gnome_canvas_line_get_type(),
								"points", points,
								"fill_color", AddColor,
								"width_units", pTheme->GetBondWidth (),
								NULL);
	gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM(m_pItem), &x1, &y1, &x2, &y2);
	gnome_canvas_request_redraw(GNOME_CANVAS(m_pWidget), (int)x1, (int)y1, (int)x2, (int)y2);
}

void gcpBondTool::UpdateBond()
{
	double x1, y1, x2, y2;
	int i = 1;
	gcpTheme *pTheme = m_pView->GetDoc ()->GetTheme ();
	BondOrder = ((gcpBond*)m_pObject)->GetOrder();
	if (((gcpBond*)m_pObject)->GetType() == NormalBondType) ((gcpBond*)m_pObject)->IncOrder();
	m_pItem = gnome_canvas_item_new(m_pGroup, gnome_canvas_group_ext_get_type(), NULL);
	while (((gcpBond*)m_pObject)->GetLine2DCoords(i++, &x1, &y1, &x2, &y2))
	{
		points->coords[0] = x1 * m_dZoomFactor;
		points->coords[1] = y1 * m_dZoomFactor;
		points->coords[2] = x2 * m_dZoomFactor;
		points->coords[3] = y2 * m_dZoomFactor;
		gnome_canvas_item_new(
						GNOME_CANVAS_GROUP(m_pItem),
						gnome_canvas_line_get_type(),
						"points", points,
						"fill_color", AddColor,
						"width_units", pTheme->GetBondWidth (),
						NULL);
	}
	gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM(m_pItem), &x1, &y1, &x2, &y2);
	gnome_canvas_request_redraw(GNOME_CANVAS(m_pWidget), (int)x1, (int)y1, (int)x2, (int)y2);
}

void gcpBondTool::FinalizeBond()
{
	if (m_bChanged)
	{
		gcpBond* pBond = (gcpBond*)m_pObject;
		if (pBond->GetType () == NormalBondType) {
			pBond->Revert ();
			m_pView->Update (m_pObject);
		} else {
			pBond->SetType (NormalBondType);
			m_pView->Remove (m_pObject);
			m_pView->AddObject (m_pObject);
		}
	}
	else ((gcpBond*)m_pObject)->SetOrder(BondOrder);
	m_pView->Update(((gcpBond*)m_pObject)->GetAtom(0));
	m_pView->Update(((gcpBond*)m_pObject)->GetAtom(1));
}

void gcpBondTool::SetType(gcpBond* pBond)
{
	pBond->SetType(NormalBondType);
}

static void on_length_changed (GtkSpinButton *btn, gcpBondTool *tool)
{
	tool->SetLength (gtk_spin_button_get_value (btn));
}

static void on_angle_changed (GtkSpinButton *btn, gcpBondTool *tool)
{
	tool->SetAngle (gtk_spin_button_get_value (btn));
}

static void on_merge_toggled (GtkToggleButton *btn)
{
	MergeAtoms = gtk_toggle_button_get_active (btn);
}

void gcpBondTool::SetAngle (double angle)
{
	m_pApp->GetActiveDocument ()->SetBondAngle (angle);
}

void gcpBondTool::SetLength (double length)
{
	m_pApp->GetActiveDocument ()->SetBondLength (length);
}

GtkWidget *gcpBondTool::GetPropertyPage ()
{
	GladeXML *xml = glade_xml_new (GLADEDIR"/bond.glade", "bond", GETTEXT_PACKAGE);
	m_LengthBtn = GTK_SPIN_BUTTON (glade_xml_get_widget (xml, "bond-length"));
	g_signal_connect (m_LengthBtn, "value-changed", G_CALLBACK (on_length_changed), this);
	m_AngleBtn = GTK_SPIN_BUTTON (glade_xml_get_widget (xml, "bond-angle"));
	g_signal_connect (m_AngleBtn, "value-changed", G_CALLBACK (on_angle_changed), this);
	m_MergeBtn = GTK_TOGGLE_BUTTON (glade_xml_get_widget (xml, "merge"));
	g_signal_connect (m_MergeBtn, "toggled", G_CALLBACK (on_merge_toggled), NULL);
	return glade_xml_get_widget (xml, "bond");
}

void gcpBondTool::Activate ()
{
	gcpDocument *pDoc = m_pApp->GetActiveDocument ();
	gtk_spin_button_set_value (m_LengthBtn, pDoc->GetBondLength ());
	gtk_spin_button_set_value (m_AngleBtn, pDoc->GetBondAngle ());
	gtk_toggle_button_set_active (m_MergeBtn, MergeAtoms);
}

gcpUpBondTool::gcpUpBondTool(gcpApplication *App): gcpBondTool(App, "UpBond", 3)
{
}

gcpUpBondTool::~gcpUpBondTool()
{
}

void gcpUpBondTool::Draw()
{
	double dx, dy, x1, y1, x2, y2;
	gcpTheme *Theme = m_pView->GetDoc ()->GetTheme ();
	x1 = sqrt(square(m_x1 - m_x0) + square(m_y1 - m_y0));
	if (x1 == 0) return;
	dx = (m_y0 - m_y1) / x1 * Theme->GetStereoBondWidth () / 2;
	dy = (m_x1 - m_x0) / x1 * Theme->GetStereoBondWidth () / 2;
	points->coords[2] = m_x1 + dx;
	points->coords[3] = m_y1 + dy;
	points->coords[4] = m_x1 - dx;
	points->coords[5] = m_y1 - dy;
	m_pItem = gnome_canvas_item_new(
								m_pGroup,
								gnome_canvas_polygon_get_type(),
								"points", points,
								"fill_color", AddColor,
								NULL);
	gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM(m_pItem), &x1, &y1, &x2, &y2);
	gnome_canvas_request_redraw(GNOME_CANVAS(m_pWidget), (int)x1, (int)y1, (int)x2, (int)y2);
}

void gcpUpBondTool::UpdateBond()
{
	if (((gcpBond*)m_pObject)->GetType() == UpBondType)
	{
		m_x = m_x0;
		m_x0 = m_x1;
		m_x1 = m_x;
		m_y = m_y0;
		m_y0 = m_y1;
		m_y1 = m_y;
		points->coords[0] = m_x0;
		points->coords[1] = m_y0;
	}
	Draw();
}

void gcpUpBondTool::FinalizeBond()
{
	if (m_bChanged)
	{
		gcpBond* pBond = (gcpBond*)m_pObject;
		if (pBond->GetType () == UpBondType) {
			pBond->Revert ();
			m_pView->Update (m_pObject);
		} else {
			pBond->SetType (UpBondType);
			m_pView->Remove (m_pObject);
			m_pView->AddObject (m_pObject);
		}
	}
}

void gcpUpBondTool::SetType(gcpBond* pBond)//FIXME: Is it really useful?
{
	pBond->SetType(UpBondType);
}

gcpDownBondTool::gcpDownBondTool(gcpApplication *App): gcpBondTool(App, "DownBond", 4)
{
}

gcpDownBondTool::~gcpDownBondTool()
{
}

void gcpDownBondTool::Draw()
{
	double dx, dy, dx1, dy1, length;
	gcpTheme *pTheme = m_pView->GetDoc ()->GetTheme ();
	m_pItem = gnome_canvas_item_new(m_pGroup, gnome_canvas_group_ext_get_type(), NULL);
	length = sqrt(square(m_x1 - m_x0) + square(m_y1 - m_y0));
	if (length == 0.0) return;
	int n = int(floor(length / (pTheme->GetHashDist () + pTheme->GetHashWidth ())));
	dx1 = (m_x1 - m_x0) / length * pTheme->GetHashWidth ();
	dy1 = (m_y1 - m_y0) / length * pTheme->GetHashWidth ();
	dx = (m_y0 - m_y1) / length * pTheme->GetStereoBondWidth () / 2;
	dy = (m_x1 - m_x0) / length * pTheme->GetStereoBondWidth () / 2;
	points->coords[0] = m_x0 + dx;
	points->coords[1] = m_y0 + dy;
	points->coords[2] = m_x0 - dx;
	points->coords[3] = m_y0 - dy;
	dx *= (1 - pTheme->GetHashWidth () / length);
	dy *= (1 - pTheme->GetHashWidth () / length);
	points->coords[4] = m_x0 + dx1 - dx;
	points->coords[5] = m_y0 + dy1 - dy;
	points->coords[6] = m_x0 + dx1 + dx;
	points->coords[7] = m_y0 + dy1 + dy;
	dx = (m_x1 - m_x0) / length * (pTheme->GetHashDist () + pTheme->GetHashWidth ())
		- (m_y0 - m_y1) / length * pTheme->GetStereoBondWidth () / 2 * (pTheme->GetHashDist () + pTheme->GetHashWidth ()) / length;
	dy = (m_y1 - m_y0) / length * (pTheme->GetHashDist () + pTheme->GetHashWidth ())
		- (m_x1 - m_x0) / length * pTheme->GetStereoBondWidth () / 2 *  (pTheme->GetHashDist () + pTheme->GetHashWidth ()) / length;
	dx1 = (m_x1 - m_x0) / length * (pTheme->GetHashDist () + pTheme->GetHashWidth ())
		+ (m_y0 - m_y1) / length * pTheme->GetStereoBondWidth () / 2 *  (pTheme->GetHashDist () + pTheme->GetHashWidth ()) / length;
	dy1 = (m_y1 - m_y0) / length * (pTheme->GetHashDist () + pTheme->GetHashWidth ())
		+ (m_x1 - m_x0) / length * pTheme->GetStereoBondWidth () / 2 *  (pTheme->GetHashDist () + pTheme->GetHashWidth ()) / length;
	gnome_canvas_item_new(
						GNOME_CANVAS_GROUP(m_pItem),
						gnome_canvas_polygon_get_type(),
						"points", points,
						"fill_color", AddColor,
						NULL);
	for (int i = 1; i < n; i++)
	{
		points->coords[0] += dx;
		points->coords[1] += dy;
		points->coords[2] += dx1;
		points->coords[3] += dy1;
		points->coords[6] += dx;
		points->coords[7] += dy;
		points->coords[4] += dx1;
		points->coords[5] += dy1;
		gnome_canvas_item_new(
						GNOME_CANVAS_GROUP(m_pItem),
						gnome_canvas_polygon_get_type(),
						"points", points,
						"fill_color", AddColor,
						NULL);
	}
	gnome_canvas_item_get_bounds(m_pItem, &dx, &dy, &dx1, &dy1);
	gnome_canvas_request_redraw(GNOME_CANVAS(m_pWidget), (int)dx, (int)dy, (int)dx1, (int)dy1);
}

void gcpDownBondTool::UpdateBond()
{
	if (((gcpBond*)m_pObject)->GetType() == DownBondType)
	{
		m_x = m_x0;
		m_x0 = m_x1;
		m_x1 = m_x;
		m_y = m_y0;
		m_y0 = m_y1;
		m_y1 = m_y;
	}
	Draw();
}

void gcpDownBondTool::FinalizeBond()
{
	if (m_bChanged)
	{
		gcpBond* pBond = (gcpBond*)m_pObject;
		if (pBond->GetType () == DownBondType) {
			pBond->Revert ();
			m_pView->Update (m_pObject);
		} else {
			pBond->SetType (DownBondType);
			m_pView->Remove (m_pObject);
			m_pView->AddObject (m_pObject);
		}
	}
}

void gcpDownBondTool::SetType(gcpBond* pBond)
{
	pBond->SetType(DownBondType);
}

gcpForeBondTool::gcpForeBondTool(gcpApplication *App): gcpBondTool(App, "ForeBond", 4)
{
}

gcpForeBondTool::~gcpForeBondTool()
{
}

void gcpForeBondTool::Draw()
{
	double dx, dy, x1, y1, x2, y2;
	gcpTheme *pTheme = m_pView->GetDoc ()->GetTheme ();
	x1 = sqrt(square(m_x1 - m_x0) + square(m_y1 - m_y0));
	if (x1 == 0) return;
	dx = (m_y0 - m_y1) / x1 * pTheme->GetStereoBondWidth () / 2;
	dy = (m_x1 - m_x0) / x1 * pTheme->GetStereoBondWidth () / 2;
	points->coords[0] = m_x0 + dx;
	points->coords[1] = m_y0 + dy;
	points->coords[2] = m_x1 + dx;
	points->coords[3] = m_y1 + dy;
	points->coords[4] = m_x1 - dx;
	points->coords[5] = m_y1 - dy;
	points->coords[6] = m_x0 - dx;
	points->coords[7] = m_y0 - dy;
	m_pItem = gnome_canvas_item_new(
								m_pGroup,
								gnome_canvas_polygon_get_type(),
								"points", points,
								"fill_color", AddColor,
								NULL);
	gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM(m_pItem), &x1, &y1, &x2, &y2);
	gnome_canvas_request_redraw(GNOME_CANVAS(m_pWidget), (int)x1, (int)y1, (int)x2, (int)y2);
}

void gcpForeBondTool::UpdateBond ()
{
	Draw();
}

void gcpForeBondTool::FinalizeBond ()
{
	if (m_bChanged)
	{
		gcpBond* pBond = (gcpBond*) m_pObject;
		if (pBond->GetType () != ForeBondType) {
			pBond->SetType (ForeBondType);
			m_pView->Remove (m_pObject);
			m_pView->AddObject (m_pObject);
		}
	}
}

void gcpForeBondTool::SetType (gcpBond* pBond)
{
	pBond->SetType (ForeBondType);
}

gcpSquiggleBondTool::gcpSquiggleBondTool(gcpApplication *App): gcpBondTool(App, "SquiggleBond", 4)
{
}

gcpSquiggleBondTool::~gcpSquiggleBondTool()
{
}

void gcpSquiggleBondTool::Draw()
{
	GnomeCanvasPathDef *path_def;
	gcpTheme *pTheme = m_pView->GetDoc ()->GetTheme ();
	path_def = gnome_canvas_path_def_new();
	gnome_canvas_path_def_moveto(path_def, m_x0, m_y0);
	double x = m_x0, y = m_y0, dx, dy, length, x1, x2, y1, y2;
	length = sqrt(square(m_x1 - m_x0) + square(m_y1 - m_y0));
	int n = (int)length / 3, s = 1;
	dx = (m_x1 - m_x0) / n;
	dy = (m_y1 - m_y0) / n;
	for (int i = 1; i < n; i++)
	{
		x1 = x + dx / 3 + dy /1.5 * s;
		y1 = y + dy / 3 - dx /1.5 * s;
		x2 = x + dx / 1.5 + dy /1.5 * s;
		y2 = y + dy / 1.5 - dx /1.5 * s;
		x += dx;
		y += dy;
		s *= -1;
		gnome_canvas_path_def_curveto(path_def, x1, y1, x2, y2, x, y);
	}
	x1 = x + dx / 3 + dy /1.5 * s;
	y1 = y + dy / 3 - dx /1.5 * s;
	x2 = x + dx / 1.5 + dy /1.5 * s;
	y2 = y + dy / 1.5 - dx /1.5 * s;
	gnome_canvas_path_def_curveto(path_def, x1, y1, x2, y2, m_x1, m_y1);
	m_pItem = gnome_canvas_item_new(
								m_pGroup,
								gnome_canvas_bpath_get_type(),
								"outline_color", AddColor,
								"width_units", pTheme->GetBondWidth (),
								"bpath", path_def,
								NULL);
	gnome_canvas_path_def_unref(path_def);
}

void gcpSquiggleBondTool::UpdateBond()
{
	Draw();
}

void gcpSquiggleBondTool::FinalizeBond()
{
	if (m_bChanged)
	{
		gcpBond* pBond = (gcpBond*)m_pObject;
		if (pBond->GetType () == UndeterminedBondType) {
			pBond->Revert ();
			m_pView->Update (m_pObject);
		} else {
			pBond->SetType (UndeterminedBondType);
			m_pView->Remove (m_pObject);
			m_pView->AddObject (m_pObject);
		}
	}
}

void gcpSquiggleBondTool::SetType(gcpBond* pBond)
{
	pBond->SetType(UndeterminedBondType);
}


syntax highlighted by Code2HTML, v. 0.9.1