// -*- C++ -*-

/* 
 * GChemPaint library
 * fragment.cc 
 *
 * Copyright (C) 2002-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 "fragment.h"
#include "fragment-atom.h"
#include "widgetdata.h"
#include "document.h"
#include "application.h"
#include "view.h"
#include "text.h"
#include "theme.h"
#include "settings.h"
#include "window.h"
#include "libgcpcanvas/gcp-canvas-group.h"
#include "libgcpcanvas/gcp-canvas-rect-ellipse.h"
#include "libgcpcanvas/gcp-canvas-bpath.h"
#include <gcu/element.h>
#include <pango/pango-attributes.h>
#include <glib/gi18n-lib.h>
#include <list>
#include <cmath>
#include <cstring>

static void on_fragment_changed (gcpFragment *fragment)
{
	fragment->OnChanged (true);
}

static void on_fragment_sel_changed (gcpFragment *fragment, struct GnomeCanvasPangoSelBounds *bounds)
{
	fragment->OnSelChanged (bounds);
}

gcpFragment::gcpFragment (): gcpTextObject (FragmentType)
{
	m_Atom = new gcpFragmentAtom (this, 0);
	m_BeginAtom = m_EndAtom = 0;
	m_StartSel = m_EndSel = 0;
	m_lbearing = 0;
	m_CHeight = 0.;
	SetId ((char*) "f1");
}

gcpFragment::gcpFragment (double x, double y): gcpTextObject (x, y, FragmentType)
{
	m_Atom = new gcpFragmentAtom (this, 0);
	m_Atom->SetCoords (x, y);
	m_BeginAtom = m_EndAtom = 0;
	m_lbearing = 0;
	m_CHeight = 0.;
	SetId ((char*) "f1");
}

gcpFragment::~gcpFragment ()
{
	if (m_Atom)
		delete m_Atom;
}

bool gcpFragment::OnChanged (bool save)
{
	if (m_bLoading)
		return false;
	gcpDocument* pDoc = (gcpDocument*) GetDocument ();
	if (!pDoc)
		return false;
	gcpView* pView = pDoc->GetView ();
	GtkWidget* pWidget = pView->GetWidget ();
	gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (pWidget), "data");
	GnomeCanvasGroup *group = pData->Items[this];
	if (!group) {
		pData->Items.erase (this);
		m_bLoading = false;
		return false;
	}
	GnomeCanvasPango *PangoItem = GNOME_CANVAS_PANGO (g_object_get_data (G_OBJECT (group), "fragment"));
	unsigned CurPos = gnome_canvas_pango_get_cur_index (PangoItem);
	AnalContent (m_StartSel, CurPos);
	m_bLoading = true;
	m_buf = pango_layout_get_text (m_Layout);
	if (m_buf.length ()) {
		PangoLayoutIter *iter = pango_layout_get_iter (m_Layout);
		m_ascent = pango_layout_iter_get_baseline (iter) / PANGO_SCALE;
		pango_layout_iter_free (iter);
	}
	/*main atom management*/
	if (!m_Atom->GetZ()) {
		int Z = GetElementAtPos (m_StartSel, CurPos);
		if (!Z && m_StartSel > m_BeginAtom)
			Z = GetElementAtPos (m_StartSel = m_BeginAtom, CurPos);
		if (Z) {
			m_Atom->SetZ (Z);
			m_BeginAtom = m_StartSel;
			m_EndAtom = CurPos;
		}
	} else if (m_EndSel <= m_BeginAtom) {
		int delta = CurPos - m_EndSel;
		m_BeginAtom += delta;
		m_EndAtom += delta;
	} else if ((m_EndAtom <= m_EndSel && m_EndAtom >= m_StartSel) ||
		(m_BeginAtom <= m_EndSel && m_BeginAtom >= m_StartSel) ||
		(m_BeginAtom + 3 >= CurPos)) {
		if (m_BeginAtom > m_StartSel)
			m_BeginAtom = m_StartSel;
		if (m_EndAtom > CurPos)
			m_EndAtom = CurPos;
		else if (m_EndAtom < m_BeginAtom + 3)
			m_EndAtom = m_BeginAtom + 3;
		int Z = GetElementAtPos (m_BeginAtom, m_EndAtom);
		m_Atom->SetZ (Z);
		if (!Z)
			m_EndAtom = CurPos;
	}
	PangoRectangle rect;
	pango_layout_index_to_pos (m_Layout, m_BeginAtom, &rect);
	m_lbearing = rect.x / PANGO_SCALE;
	pango_layout_index_to_pos (m_Layout, m_EndAtom, &rect);
	m_lbearing += rect.x / PANGO_SCALE;
	m_lbearing /=  2;
	pView->Update (this);
	m_bLoading = false;
	gcpWindow* pWin = pDoc->GetWindow ();
	if (m_Atom->GetZ () || ((m_buf.length () == 0) && (m_Atom->GetBondsNumber () == 0))) {
		if (!pDoc->GetReadOnly ()) {
			pWin->ActivateActionWidget ("/MainMenu/FileMenu/Save", true);
			pWin->ActivateActionWidget ("/MainToolbar/Save", true);
		}
		pWin->ActivateActionWidget ("/MainMenu/FileMenu/SaveAs", true);
		pWin->ActivateActionWidget ("/MainMenu/FileMenu/Print", true);
	} else {
		pWin->ActivateActionWidget ("/MainMenu/FileMenu/Save", false);
		pWin->ActivateActionWidget ("/MainMenu/FileMenu/SaveAs", false);
		pWin->ActivateActionWidget ("/MainMenu/FileMenu/Print", false);
		pWin->ActivateActionWidget ("/MainToolbar/Save", false);
	}
	pango_layout_get_extents (m_Layout, NULL, &rect);
	m_length = rect.width / PANGO_SCALE;
	m_height = rect.height / PANGO_SCALE;
	pView->Update (this);
	EmitSignal (OnChangedSignal);
	m_StartSel = m_EndSel = CurPos;
	if (m_buf.length () == 0) {
		m_BeginAtom = m_EndAtom = 0;
	}
	if (save) {
		gcpTool* FragmentTool = dynamic_cast<gcpApplication*> (pDoc->GetApplication ())->GetTool ("Fragment");
		if (!FragmentTool)
			return  true;
		xmlNodePtr node = SaveSelected ();
		if (node)
			FragmentTool->PushNode (node);
	}
	return true;
}

void gcpFragment::Add (GtkWidget* w)
{
	gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data");
	gcpTheme *pTheme = pData->View->GetDoc ()->GetTheme ();
	gcpView* pView = pData->View;
	if (m_ascent <= 0) {
		PangoContext* pc = pData->View->GetPangoContext ();
		m_Layout = pango_layout_new (pc);
		PangoAttrList *l = pango_attr_list_new ();
		pango_layout_set_attributes (m_Layout, l);
		pango_layout_set_font_description (m_Layout, pView->GetPangoFontDesc ());
		pango_layout_set_text (m_Layout, "l", 1);
		PangoLayoutIter* iter = pango_layout_get_iter (m_Layout);
		m_ascent = pango_layout_iter_get_baseline (iter) / PANGO_SCALE;
		pango_layout_iter_free (iter);
		pango_layout_set_text (m_Layout, "C", 1);
		PangoRectangle rect;
		pango_layout_get_extents (m_Layout, &rect, NULL);
		m_CHeight =  double (rect.height / PANGO_SCALE) / 2.0;
		pango_layout_set_text (m_Layout, m_buf.c_str (), -1);
		if (m_AttrList) {
			pango_layout_set_attributes (m_Layout, m_AttrList);
			pango_attr_list_unref (m_AttrList);
			m_AttrList = NULL;
		}
		if (m_buf.length () > 0) {
			m_buf.clear ();
			pango_layout_index_to_pos (m_Layout, m_BeginAtom, &rect);
			m_lbearing = rect.x / PANGO_SCALE;
			pango_layout_index_to_pos (m_Layout, m_EndAtom, &rect);
			m_lbearing += rect.x / PANGO_SCALE;
			m_lbearing /=  2;
			iter = pango_layout_get_iter (m_Layout);
			m_ascent = pango_layout_iter_get_baseline (iter) / PANGO_SCALE;
			pango_layout_iter_free (iter);
		}
		pango_layout_get_extents (m_Layout, NULL, &rect);
		m_length = rect.width / PANGO_SCALE;
		m_height = rect.height / PANGO_SCALE;
	}
	GnomeCanvasGroup* group = GNOME_CANVAS_GROUP(gnome_canvas_item_new(pData->Group, gnome_canvas_group_ext_get_type(), NULL)), *chgp;
	GnomeCanvasItem* item = gnome_canvas_item_new(
						group,
						gnome_canvas_rect_ext_get_type(),
						"x1", m_x * pTheme->GetZoomFactor () - pTheme->GetPadding () - m_lbearing,
						"y1", m_y * pTheme->GetZoomFactor () - pTheme->GetPadding () - m_ascent + m_CHeight,
						"x2", m_x * pTheme->GetZoomFactor () + m_length + pTheme->GetPadding () - m_lbearing,
						"y2", m_y * pTheme->GetZoomFactor () + m_height + pTheme->GetPadding () - m_ascent + m_CHeight,
						"fill_color", "white",
						"outline_color", "white",
						NULL);
	g_object_set_data(G_OBJECT(group), "rect", item);
	g_signal_connect(G_OBJECT(item), "event", G_CALLBACK(on_event), w);
	g_object_set_data(G_OBJECT(item), "object", this);
	item = gnome_canvas_item_new (
						group,
						gnome_canvas_pango_get_type (),
						"layout", m_Layout,
						"x", m_x * pTheme->GetZoomFactor () - m_lbearing,
						"y", m_y * pTheme->GetZoomFactor () - m_ascent + m_CHeight,
						"editing", false,
						NULL);
	g_object_set_data (G_OBJECT (group), "fragment", item);
	g_object_set_data (G_OBJECT (item), "object", this);
	g_signal_connect (G_OBJECT(item), "event", G_CALLBACK (on_event), w);
	g_signal_connect_swapped (G_OBJECT (item), "changed", G_CALLBACK (on_fragment_changed), this);
	g_signal_connect_swapped (G_OBJECT (item), "sel-changed", G_CALLBACK (on_fragment_sel_changed), this);
	/* add charge */
	int charge = m_Atom->GetCharge ();
	if (charge) {
		double x, y, Angle, Dist;
		unsigned char Pos = m_Atom->gcpAtom::GetChargePosition (&Angle, &Dist);
		int align = GetChargePosition(m_Atom, Pos, 0., x, y);
		if (Dist != 0.) {
			x = m_x + Dist * cos (Angle);
			y = m_y - Dist * sin (Angle);
		}
		x *= pTheme->GetZoomFactor ();
		y *= pTheme->GetZoomFactor ();
		switch (align) {
		case -2:
			x -= pTheme->GetChargeSignSize () / 2.;
			y += pTheme->GetChargeSignSize () / 2.;
			break;
			case -1:
				x -= pTheme->GetChargeSignSize () + pTheme->GetPadding ();
				break;
			case 0:
			case -3:
				x -= pTheme->GetChargeSignSize () / 2.;
				break;
			case 1:
				x += pTheme->GetPadding ();
				break;
		case 2:
			x -= pTheme->GetChargeSignSize () / 2.;
			y -= pTheme->GetChargeSignSize () / 2.;
			break;
		}
		y -= pTheme->GetChargeSignSize () / 2.;
		chgp = (GnomeCanvasGroup*) gnome_canvas_item_new (
					group,
					gnome_canvas_group_ext_get_type(),
					NULL);
		g_object_set_data (G_OBJECT (group), "charge", chgp);
		item = gnome_canvas_item_new (
					chgp,
					gnome_canvas_ellipse_ext_get_type (),
					"x1", x,
					"y1", y,
					"x2", x + pTheme->GetChargeSignSize (),
					"y2", y + pTheme->GetChargeSignSize (),
					"outline_color", (pData->IsSelected(this))? SelectColor: Color,
					"width_units", 0.5,
					NULL
				);
		g_object_set_data (G_OBJECT (group), "circle", item);
		ArtBpath *path = art_new (ArtBpath, 5);
		path[0].code = ART_MOVETO_OPEN;
		path[0].x3 = x + 1.;
		path[1].code = ART_LINETO;
		path[1].x3 = x + pTheme->GetChargeSignSize () - 1.;
		path[0].y3 = path[1].y3 = y + pTheme->GetChargeSignSize () / 2.;
		if (charge > 0) {
			path[2].code = ART_MOVETO_OPEN;
			path[2].y3 = y + 1.;
			path[3].code = ART_LINETO;
			path[3].y3 = y + pTheme->GetChargeSignSize () - 1.;
			path[2].x3 = path[3].x3 = x + pTheme->GetChargeSignSize () / 2.;
			path[4].code = ART_END;
		} else
			path[2].code = ART_END;
		GnomeCanvasPathDef *cpd = gnome_canvas_path_def_new_from_bpath (path);
		item = gnome_canvas_item_new (
					chgp,
					gnome_canvas_bpath_ext_get_type (),
					"bpath", cpd,
					"outline_color", (pData->IsSelected(this))? SelectColor: Color,
					"width_units", 1.,
					NULL
				);
		gnome_canvas_path_def_unref (cpd);
		g_object_set_data (G_OBJECT (group), "sign", item);
	}
	pData->Items[this] = group;
}

void gcpFragment::SetSelected (GtkWidget* w, int state)
{
	gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data");
	GnomeCanvasGroup* group = pData->Items[this];
	gchar *chargecolor, *color;
	switch (state) {	
	case SelStateUnselected:
		color = (char*) "white";
		chargecolor = (char*) "black";
		break;
	case SelStateSelected:
		chargecolor = color = SelectColor;
		break;
	case SelStateUpdating:
		chargecolor = color = AddColor;
		break;
	case SelStateErasing:
		chargecolor = color = DeleteColor;
		break;
	default:
		color = (char*) "white";
		chargecolor = (char*) "black";
		break;
	}
	g_object_set (G_OBJECT (g_object_get_data (G_OBJECT (group), "rect")),
				"fill_color", color, NULL);
	gpointer item;
	if ((item = g_object_get_data (G_OBJECT (group), "circle")))
		g_object_set (item, "outline_color", chargecolor, NULL);
	if ((item = g_object_get_data (G_OBJECT (group), "sign")))
		g_object_set (item, "outline_color", chargecolor, NULL);
}

void gcpFragment::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];
	g_object_set (G_OBJECT (g_object_get_data (G_OBJECT (group), "fragment")),
						"x", m_x * pTheme->GetZoomFactor () - m_lbearing,
						"y", m_y * pTheme->GetZoomFactor () - m_ascent + m_CHeight,
						"width", m_length,
						"height", m_height,
						NULL);
	g_object_set (G_OBJECT (g_object_get_data (G_OBJECT (group), "rect")),
						"x1", m_x * pTheme->GetZoomFactor () - pTheme->GetPadding () - m_lbearing,
						"y1", m_y * pTheme->GetZoomFactor () - pTheme->GetPadding () - m_ascent + m_CHeight,
						"x2", m_x * pTheme->GetZoomFactor () + m_length + pTheme->GetPadding () - m_lbearing,
						"y2", m_y * pTheme->GetZoomFactor () + m_height + pTheme->GetPadding () - m_ascent + m_CHeight,
						NULL);
	void* item = g_object_get_data (G_OBJECT (group), "charge");
	int charge = m_Atom->GetCharge ();
	if (charge) {
		double x, y, Angle, Dist;
		unsigned char Pos = m_Atom->gcpAtom::GetChargePosition (&Angle, &Dist);
		if (item) {
			int align = GetChargePosition (m_Atom, Pos, Angle, x, y);
			if (Dist != 0.) {
				x = m_x + Dist * cos (Angle);
				y = m_y - Dist * sin (Angle);
			}
			x *= pTheme->GetZoomFactor ();
			y *= pTheme->GetZoomFactor ();
			switch (align) {
			case -2:
				x -= pTheme->GetChargeSignSize () / 2.;
				y += pTheme->GetChargeSignSize () / 2.;
				break;
			case -1:
				x -= pTheme->GetChargeSignSize () + pTheme->GetPadding ();
				break;
			case 0:
			case -3:
				x -= pTheme->GetChargeSignSize () / 2.;
				break;
			case 1:
				x += pTheme->GetPadding ();
				break;
			case 2:
				x -= pTheme->GetChargeSignSize () / 2.;
				y -= pTheme->GetChargeSignSize () / 2.;
				break;
			}
			y -= pTheme->GetChargeSignSize () / 2.;
			item = g_object_get_data (G_OBJECT (group), "circle");
			g_object_set (G_OBJECT (item),
						"x1", x,
						"y1", y,
						"x2", x + pTheme->GetChargeSignSize (),
						"y2", y + pTheme->GetChargeSignSize (),
						NULL);
			item = g_object_get_data (G_OBJECT (group), "sign");
			ArtBpath *path = art_new (ArtBpath, 5);
			path[0].code = ART_MOVETO_OPEN;
			path[0].x3 = x + 1.;
			path[1].code = ART_LINETO;
			path[1].x3 = x + pTheme->GetChargeSignSize () - 1.;
			path[0].y3 = path[1].y3 = y + pTheme->GetChargeSignSize () / 2.;
			if (charge > 0) {
				path[2].code = ART_MOVETO_OPEN;
				path[2].y3 = y + 1.;
				path[3].code = ART_LINETO;
				path[3].y3 = y + pTheme->GetChargeSignSize () - 1.;
				path[2].x3 = path[3].x3 = x + pTheme->GetChargeSignSize () / 2.;
				path[4].code = ART_END;
			} else
				path[2].code = ART_END;
			GnomeCanvasPathDef *cpd = gnome_canvas_path_def_new_from_bpath (path);
			g_object_set (G_OBJECT (item),
						"bpath", cpd,
						NULL
					);
			gnome_canvas_path_def_unref (cpd);
		} else {
			GnomeCanvasGroup *chgp;
			int align = GetChargePosition (m_Atom, Pos, Angle, x, y);
			x *= pTheme->GetZoomFactor ();
			if (Dist != 0.) {
				x = m_x + Dist * cos (Angle);
				y = m_y - Dist * sin (Angle);
			}
			y *= pTheme->GetZoomFactor ();
			switch (align) {
			case -2:
				x -= pTheme->GetChargeSignSize () / 2.;
				y += pTheme->GetChargeSignSize () / 2.;
				break;
			case -1:
				x -= pTheme->GetChargeSignSize () + pTheme->GetPadding ();
				break;
			case 0:
			case -3:
				x -= pTheme->GetChargeSignSize () / 2.;
				break;
			case 1:
				x += pTheme->GetPadding ();
				break;
			case 2:
				x -= pTheme->GetChargeSignSize () / 2.;
				y -= pTheme->GetChargeSignSize () / 2.;
				break;
			}
			y -= pTheme->GetChargeSignSize () / 2.;
			chgp = (GnomeCanvasGroup*) gnome_canvas_item_new (
						group,
						gnome_canvas_group_ext_get_type (),
						NULL);
			g_object_set_data (G_OBJECT (group), "charge", chgp);
			item = gnome_canvas_item_new (
						chgp,
						gnome_canvas_ellipse_ext_get_type (),
						"x1", x,
						"y1", y,
						"x2", x + pTheme->GetChargeSignSize (),
						"y2", y + pTheme->GetChargeSignSize (),
						"outline_color", (pData->IsSelected (this))? SelectColor: Color,
						"width_units", 0.5,
						NULL
					);
			g_object_set_data (G_OBJECT (group), "circle", item);
			ArtBpath *path = art_new (ArtBpath, 5);
			path[0].code = ART_MOVETO_OPEN;
			path[0].x3 = x + 1.;
			path[1].code = ART_LINETO;
			path[1].x3 = x + pTheme->GetChargeSignSize () - 1.;
			path[0].y3 = path[1].y3 = y + pTheme->GetChargeSignSize () / 2.;
			if (charge > 0) {
				path[2].code = ART_MOVETO_OPEN;
				path[2].y3 = y + 1.;
				path[3].code = ART_LINETO;
				path[3].y3 = y + pTheme->GetChargeSignSize () - 1.;
				path[2].x3 = path[3].x3 = x + pTheme->GetChargeSignSize () / 2.;
				path[4].code = ART_END;
			} else
				path[2].code = ART_END;
			GnomeCanvasPathDef *cpd = gnome_canvas_path_def_new_from_bpath (path);
			item = gnome_canvas_item_new (
						chgp,
						gnome_canvas_bpath_ext_get_type (),
						"bpath", cpd,
						"outline_color", (pData->IsSelected (this))? SelectColor: Color,
						"width_units", 1.,
						NULL
					);
			gnome_canvas_path_def_unref (cpd);
			g_object_set_data (G_OBJECT (group), "sign", item);
		}
	} else {
		if (item) {
			gtk_object_destroy (GTK_OBJECT (item));
			g_object_set_data ((GObject*) group, "charge", NULL);
		}
	}
}

xmlNodePtr gcpFragment::Save (xmlDocPtr xml)
{
	m_buf = pango_layout_get_text (m_Layout);
	if (m_RealSave && !Validate ())
		return NULL;
	xmlNodePtr node = xmlNewDocNode (xml, NULL, (xmlChar*) "fragment", NULL);
	if (m_buf.length ()) {
		if (!m_Atom->GetBondsNumber () || m_Atom->GetZ ()) {
			if (!node)
				return NULL;
			if (!SavePortion (xml, node, 0, m_BeginAtom)) {
				xmlFreeNode (node);
				return NULL;
			}
			if (m_Atom->GetZ  ()) {
				xmlNodePtr child = m_Atom->Save(xml);
				if (!child) {
					xmlFreeNode(node);
					return NULL;
				}
				xmlAddChild(node, child);
			}
			if (!SavePortion(xml, node, m_EndAtom, m_buf.length ())) {
				xmlFreeNode(node);
				return NULL;
			}
		}
	}
	return (SaveNode (xml, node))? node: NULL;
}

struct FilterStruct {
	unsigned start, end;
	list<PangoAttribute*> pal;
};

bool filter_func (PangoAttribute *attribute, struct FilterStruct *s)
{
	if (attribute->klass->type == PANGO_ATTR_RISE && ((PangoAttrInt*) attribute)->value > 0 &&
		s->start <= attribute->start_index && s->end >= attribute->end_index) {
		list<PangoAttribute*>::iterator i, iend = s->pal.end ();
		for (i = s->pal.begin (); i != iend; i++)
			if ((*i)->start_index > attribute->end_index)
				break;
		s->pal.insert (i, attribute);
	}
	return FALSE;
}

bool gcpFragment::SavePortion (xmlDocPtr xml, xmlNodePtr node, unsigned start, unsigned end)
{
	xmlNodePtr child;
	struct FilterStruct s;
	s.start = start;
	s.end = end;
	int charge;
	if (m_AttrList == NULL)
		m_AttrList = pango_layout_get_attributes (m_Layout);
	pango_attr_list_filter (m_AttrList, (PangoAttrFilterFunc) filter_func, &s);
	list<PangoAttribute*>::iterator i, iend = s.pal.end ();
	string str;
	char *err;
	for (i = s.pal.begin (); i != iend; i++) {
		if (start < (*i)->start_index) {
			str.assign (m_buf, start, (*i)->start_index - start);
			xmlNodeAddContent (node, (const xmlChar*) str.c_str ());
		}
		str.assign (m_buf, (*i)->start_index, (*i)->end_index - (*i)->start_index);
		child = xmlNewDocNode (xml, NULL, (xmlChar*) "charge", NULL);
		if (!child)
			return false;
		charge = strtol (str.c_str (), &err, 10);
		if (err && strcmp (err, "+") && strcmp (err, "-")) {
			if (!m_RealSave) {
				return  false;
				xmlFreeNode (child);
			}
			gcpDocument *pDoc = (gcpDocument*) GetDocument ();
			GtkWidget* w = gtk_message_dialog_new (
											pDoc->GetWindow ()->GetWindow (),
											GTK_DIALOG_DESTROY_WITH_PARENT,
											GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
											_("Invalid charge."));
			gtk_window_set_icon_name (GTK_WINDOW (w), "gchempaint");
			gtk_dialog_run (GTK_DIALOG (w));
			gtk_widget_destroy (w);
			return false;
		} else {
			if (!charge)
				charge = 1;
			if (*err == '-')
				charge = - charge;
			char *buf = g_strdup_printf ("%d", charge);
			xmlNewProp (child, (xmlChar*) "value", (xmlChar*) buf);
			g_free (buf);
			xmlAddChild (node, child);
		}
		start = (*i)->end_index;
	}
	if (start < end) {
		str.assign (m_buf, start, end - start);
		xmlNodeAddContent (node, (const xmlChar*) str.c_str ());
	}
	return true;
}

xmlNodePtr gcpFragment::SaveSelection (xmlDocPtr xml)
{
	xmlNodePtr node = xmlNewDocNode (xml, NULL, (xmlChar*) "fragment", NULL);
	if (!node)
		return NULL;
	SavePortion (xml, node, m_StartSel, m_EndSel);
	return (gcpTextObject::SaveNode (xml, node))? node: NULL;
}

bool gcpFragment::Load (xmlNodePtr node)
{
	gcpDocument* pDoc = (gcpDocument*) GetDocument ();
	gcpTheme *pTheme = pDoc->GetTheme ();
	if (!gcpTextObject::Load (node))
		return false;
	if (m_AttrList != NULL)
		pango_attr_list_unref (m_AttrList);
	m_AttrList = pango_attr_list_new ();
	m_bLoading = true;
	m_buf.clear (); // just in case
	xmlNodePtr child = node->children;
	char* tmp;
	PangoAttribute *attr;
	int size = pTheme->GetFontSize ();
	while (child) {
		if (!strcmp ((const char*) child->name, "text")) {
			tmp = (char*) xmlNodeGetContent (child);
			m_buf += tmp;
			xmlFree (tmp);
		} else if (!strcmp ((const char*) child->name, "atom")) {
			if (!m_Atom->Load (child))
				return false;
			m_BeginAtom = m_buf.length ();
			m_buf += m_Atom->GetSymbol();
			m_Atom->SetCoords (m_x, m_y);
			m_EndAtom = m_buf.length ();
		} else if (!strcmp ((const char*) child->name, "charge")) {
			int start = m_buf.length (), end;
			tmp = (char*) xmlGetProp (child, (xmlChar*) "value");
			int charge = atoi (tmp);
			xmlFree (tmp);
			if (abs(charge) > 1)
				tmp = g_strdup_printf ("%d%c", abs (charge), (charge > 0)? '+': '-');
			else if (charge == 1)
				tmp = g_strdup ("+");
			else if (charge == -1)
				tmp = g_strdup ("-");
			else
				tmp = g_strdup ("");//should not occur!
			m_buf += tmp;
			end = m_buf.length ();
			attr = pango_attr_size_new (size * 2 / 3);
			attr->start_index = start;
			attr->end_index = end;
			pango_attr_list_insert (m_AttrList, attr);
			attr = pango_attr_rise_new (2 * size / 3);
			attr->start_index = start;
			attr->end_index = end;
			pango_attr_list_insert (m_AttrList, attr);
		}
		child = child->next;
	}
	if (m_Layout) {
		pango_layout_set_text (m_Layout, m_buf.c_str (), -1);
		pango_layout_set_attributes (m_Layout, m_AttrList);
	}
	AnalContent ();
	m_bLoading = false;
	return true;
}

void gcpFragment::AnalContent ()
{
	if (!m_Atom->GetParent ())
		AddChild (m_Atom);
	unsigned end = (m_Layout)? strlen (pango_layout_get_text (m_Layout)): m_buf.length ();
	AnalContent(0, end);
}

typedef struct
{
	unsigned index, end;
	bool result;
} ChargeFindStruct;

static bool search_for_charge (PangoAttribute *attr, ChargeFindStruct *s)
{
	if (attr->start_index <= s->index && attr->end_index >= s->index
		&& attr->klass->type == PANGO_ATTR_RISE &&
		((PangoAttrInt*) attr)->value > 0) {
			s->result = true;
			s->index = attr->start_index;
			s->end = attr->end_index;
		}
	return false;
}

void gcpFragment::AnalContent (unsigned start, unsigned &end)
{
	gcpDocument* pDoc = (gcpDocument*) GetDocument ();
	if (!pDoc)
		return;
	gcpTheme *pTheme = pDoc->GetTheme ();
	char const *text;
	PangoAttrList *l;
	if (m_Layout) {
		text = pango_layout_get_text (m_Layout);
		l = pango_layout_get_attributes (m_Layout);
	} else {
		text = m_buf.c_str ();
		l = pango_attr_list_ref (m_AttrList);
	}
	bool Charge = false;
	unsigned start_tag, end_tag, next;
	char c;
	ChargeFindStruct s;
	s.index = s.end = 0;
	if (start > 0) {
		// search if a charge is at preceeding character
		s.result = false;
		s.index = start;
		pango_attr_list_filter (l, (PangoAttrFilterFunc) search_for_charge, &s);
		Charge = s.result;
	} else if (text[start] == '+' || text[start] == '-')
		Charge = true;
	else
		Charge = false;
	next = start;
	start_tag = end_tag = start;
	while (start < end) {
		c = text[start];
		if ((c >= '0') && (c <= '9')) {
			s.result = false;
			s.index = start;
			pango_attr_list_filter (l, (PangoAttrFilterFunc) search_for_charge, &s);
			Charge = s.result;
			next = start + 1;
			// add new size and rise attributes
			int size = pTheme->GetFontSize ();
			PangoAttribute *attr = pango_attr_size_new (size * 2 / 3);
			attr->start_index = start;
			attr->end_index = next;
			pango_attr_list_change (l, attr);
			if (!Charge)
				attr = pango_attr_rise_new (-size / 3);
			else {
				if (text[start - 1] == '+' || text[start - 1] == '-') {
					// move character before sign
					char *new_t = g_strdup (text);
					new_t[start] = new_t[start - 1];
					new_t[start - 1] = c;
					if (m_Layout) {
						pango_layout_set_text (m_Layout, new_t, -1);
						text = pango_layout_get_text (m_Layout);
					} else
						m_buf = new_t;
						text = m_buf.c_str ();
				}
				attr = pango_attr_rise_new (size * 2 / 3);
			}
			attr->start_index = start;
			attr->end_index = next;
				pango_attr_list_change (l, attr);
			start = next - 1;
		} else if ((c == '+') || (c == '-')) {
			if (!m_bLoading) {
				//do not allow both local and global charges
				if (m_Atom->GetCharge())
					m_Atom->SetCharge(0);
				next = start + 1;
				if (!Charge) {
					Charge = true;
					int size = pTheme->GetFontSize ();
					PangoAttribute *attr = pango_attr_size_new (size * 2 / 3);
					attr->start_index = start;
					attr->end_index = next;
					pango_attr_list_change (l, attr);
					attr = pango_attr_rise_new (2 * size / 3);
					attr->start_index = start;
					attr->end_index = next;
					pango_attr_list_change (l, attr);
				} else {
					string old_charge (m_buf, s.index, s.end - s.index);
					char *nextch = NULL;
					int charge = strtol (old_charge.c_str (), &nextch, 10);
					if (charge == 0)
						charge = 1;
					if (*nextch == 0) {
						// no sign, just add it
						int size = pTheme->GetFontSize ();
						PangoAttribute *attr = pango_attr_size_new (size * 2 / 3);
						attr->start_index = start;
						attr->end_index = next;
						pango_attr_list_change (l, attr);
						attr = pango_attr_rise_new (2 * size / 3);
						attr->start_index = start;
						attr->end_index = next;
						pango_attr_list_change (l, attr);
					} else {
						if (*nextch == '-')
							charge = - charge;
						if (c == '+')
							charge++;
						else
							charge--;
						char *buf;
						if (abs(charge) > 1)
							buf = g_strdup_printf ("%d%c", abs (charge), (charge > 0)? '+': '-');
						else if (charge == 1)
							buf = g_strdup ("+");
						else if (charge == -1)
							buf = g_strdup ("-");
						else
							buf = g_strdup ("");
						int size = pTheme->GetFontSize ();
						PangoAttrList *l = pango_attr_list_new ();
						PangoAttribute *attr = pango_attr_size_new (size * 2 / 3);
						pango_attr_list_insert (l, attr);
						attr = pango_attr_rise_new (2 * size / 3);
						pango_attr_list_insert (l, attr);
						gcp_pango_layout_replace_text (m_Layout, s.index, s.end - s.index + 1, buf, l);
						pango_attr_list_unref (l);
						m_StartSel = m_EndSel = s.index + strlen (buf);
						end += m_StartSel - start - 1;
						GnomeCanvasPango* text = pDoc->GetView ()->GetActiveRichText ();
						gnome_canvas_pango_set_selection_bounds (text, m_StartSel, m_EndSel);
						g_free (buf);
					}
				}
			}
		} else
			Charge = false;
		start++;
	}
}

/*!
Must return NULL if active tool is FragmentTool because this tool needs a fragment, not an atom
TODO: use x and y to figure the best atom in the fragment
*/
Object* gcpFragment::GetAtomAt (double x, double y, double z)
{
	gcpDocument* pDoc = (gcpDocument*) GetDocument ();
	gcpTheme *pTheme = pDoc->GetTheme ();
	gcpApplication* pApp = pDoc->GetApplication ();
	if (pApp->GetActiveTool () == pApp->GetTool ("Fragment"))
		return NULL;
	if (m_Atom->GetBondsNumber () || m_Atom->GetCharge ())
		return m_Atom;
	if (!pDoc)
		return NULL;
	double x0, y0;
	x0 = (x - m_x) * pTheme->GetZoomFactor () + m_lbearing;
	y0 = (y - m_y) * pTheme->GetZoomFactor () + m_ascent;
	if ((x0 < 0.) || (x0 > m_length) || (y0 < 0.) || (y0 > m_height))
		return NULL;
	int index, trailing;
	pango_layout_xy_to_index (m_Layout, (int) (x0 * PANGO_SCALE), (int) (y0 * PANGO_SCALE), &index, &trailing);
	char c = m_buf[index];
	if ((c >= 'a') && (c <= 'z')) {
		index--;
		c = m_buf[index];
	}
	if ((c >= 'a') && (c <= 'z')) {
		index--;
		c = m_buf[index];
	}
	int Z = GetElementAtPos((unsigned) index, (unsigned&) trailing);
	if (!Z)
		return NULL;
	m_bLoading = true;
	m_Atom->SetZ (Z);
	m_bLoading = false;
	m_BeginAtom =index;
	m_EndAtom = trailing;
	m_x -= m_lbearing / pTheme->GetZoomFactor () ;
	PangoRectangle rect;
	pango_layout_index_to_pos (m_Layout, index, &rect);
	m_lbearing = rect.x / PANGO_SCALE;
	pango_layout_index_to_pos (m_Layout, trailing, &rect);
	m_lbearing += rect.x / PANGO_SCALE;
	m_lbearing /=  2;
	m_x += m_lbearing / pTheme->GetZoomFactor ();
	m_Atom->SetCoords(m_x, m_y);
	
	return m_Atom;
}

void gcpFragment::Move (double x, double y, double z)
{
	gcpTextObject::Move (x, y, z);
	m_Atom->Move (x, y, z);
}

void gcpFragment::OnChangeAtom ()
{
	if (m_bLoading)
		return;
	gcpDocument *pDoc = (gcpDocument*) GetDocument ();
	if (!pDoc) return;
	char const *sym = m_Atom->GetSymbol ();
	gcp_pango_layout_replace_text (m_Layout, m_BeginAtom, m_EndAtom - m_BeginAtom, sym, pDoc->GetPangoAttrList ());
	m_EndAtom = m_BeginAtom + strlen (sym);
	OnChanged (false);
}

int gcpFragment::GetElementAtPos (unsigned start, unsigned &end)
{
	int Z;
	char text[4];
	memset (text, 0, 4);
	strncpy (text, pango_layout_get_text (m_Layout) + start, 3);
	for (unsigned i = strlen (text); i > 0; i--) {
		text[i] = 0;
		if ((Z = Element::Z (text))) {
			end = start + i;
			return Z;
		}
	}
	return 0;
}

int gcpFragment::GetChargePosition (gcpFragmentAtom *pAtom, unsigned char &Pos, double Angle, double &x, double &y)
{
	if ((pAtom != m_Atom) || (m_Atom->GetZ() == 0))
		return 0;
	double width, height;
	gcpDocument* pDoc = (gcpDocument*) GetDocument ();
	gcpTheme *pTheme = pDoc->GetTheme ();
	if (!pDoc)
		return 0;
	GtkWidget* pWidget = pDoc->GetView ()->GetWidget ();
	gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (pWidget), "data");
	GnomeCanvasGroup *item = pData->Items[this];
	if (!item)
		return 0;
	GnomeCanvasPango* text = (GnomeCanvasPango*) g_object_get_data (G_OBJECT (item), "fragment");
	if (!GNOME_IS_CANVAS_PANGO (text))
		return 0;
	struct FilterStruct s;
	s.start = 0;
	s.end = m_buf.length ();
	if (m_AttrList == NULL)
		m_AttrList = pango_layout_get_attributes (m_Layout);
	pango_attr_list_filter (m_AttrList, (PangoAttrFilterFunc) filter_func, &s);
	if (s.pal.size () > 0)
		return 0; //localized charges are prohibited if a global charge already exists
	// Get atom bounds
	int result = 0xff;
	PangoRectangle rect;
	pango_layout_index_to_pos (m_Layout, m_BeginAtom, &rect);
	x = rect.x / PANGO_SCALE;
	if (m_BeginAtom != 0)
		result &= 0x6d;
	pango_layout_index_to_pos (m_Layout, m_EndAtom, &rect);
	width = rect.x / PANGO_SCALE - x;
	if (m_EndAtom < m_buf.length ())
		result &= 0xb6;
	width /= pTheme->GetZoomFactor ();
	height = m_height / pTheme->GetZoomFactor ();
	if (m_Atom->GetBondsNumber())
	{
		map<Atom*, Bond*>::iterator i;
		gcpBond* pBond = (gcpBond*)m_Atom->GetFirstBond(i);
		double angle = pBond->GetAngle2D(m_Atom) + 180.0;
		if ((result & CHARGE_NE) && (angle >= 180.0) && (angle <= 270.0)) result -= CHARGE_NE;
		if ((result & CHARGE_NW) && (((angle >= 270.0) && (angle <= 360.0)) || (fabs(angle) < 0.1))) result -= CHARGE_NW;
		if ((result & CHARGE_N) && (angle >= 225.0) && (angle <= 315.0)) result -= CHARGE_N;
		if ((result & CHARGE_SE) && (angle >= 90.0) && (angle <= 180.0)) result -= CHARGE_SE;
		if ((result & CHARGE_SW) && (((angle >= 0.0) && (angle <= 90.0)) || (fabs(angle - 360.0) < 0.1))) result -= CHARGE_SW;
		if ((result & CHARGE_S) && (angle >= 45.0) && (angle <= 135.0)) result -= CHARGE_S;
		if ((result & CHARGE_E) && (angle <= 225.0) && (angle >= 135.0)) result -= CHARGE_E;
		if ((result & CHARGE_W) && ((angle >= 315.0) || (angle <= 45.0))) result -= CHARGE_W;
	}
	if (Pos == 0xff) {
		if (result) {
			if (result & CHARGE_NE)
				Pos = CHARGE_NE;
			else if (result & CHARGE_NW)
				Pos = CHARGE_NW;
			else if (result & CHARGE_N)
				Pos = CHARGE_N;
			else if (result & CHARGE_SE)
				Pos = CHARGE_SE;
			else if (result & CHARGE_SW)
				Pos = CHARGE_SW;
			else if (result & CHARGE_S)
				Pos = CHARGE_S;
			else if (result & CHARGE_E)
				Pos = CHARGE_E;
			else if (result & CHARGE_W)
				Pos = CHARGE_W;
		} else
			return 0;
	} else if (Pos) {
		if (!(Pos & result))
			return 0;
	} else
		return 0;

	switch (Pos) {
	case CHARGE_NE:
		x = m_x + width / 2.0;
		y = m_y - height / 2.0;
		return 1;
	case  CHARGE_NW:
		x = m_x - width / 2.0;
		y = m_y - height / 2.0;
		return -1;
	case  CHARGE_N:
		x = m_x;
		y = m_y - height / 2.0;
		return 2;
	case CHARGE_SE:
		x = m_x + width / 2.0;
		y = m_y + height / 2.0;
		return 1;
	case CHARGE_SW:
		x = m_x - width / 2.0;
		y = m_y + height / 2.0;
		return -1;
	case CHARGE_S:
		x = m_x;
		y = m_y + height / 2.0;
		return -2;
	case CHARGE_E:
		x = m_x + width / 2.0;
		y = m_y;
		return 1;
	case CHARGE_W:
		x = m_x - width / 2.0;
		y = m_y;
		return -1;
	}
	return 0;
}

bool gcpFragment::Validate ()
{
	char const *charge;
	char *err;
	if ((m_buf.length () == 0)
		&& m_Atom->GetBondsNumber () == 0)
		return true;
	if (m_Atom->GetZ() == 0) {
		gcpDocument *pDoc = dynamic_cast<gcpDocument*> (GetDocument ());
		GtkWidget* pWidget = pDoc->GetView ()->GetWidget ();
		gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (pWidget), "data");
		GnomeCanvasGroup *item = pData->Items[this];
		GnomeCanvasPango* text = GNOME_CANVAS_PANGO (g_object_get_data (G_OBJECT (item), "fragment"));
		gnome_canvas_pango_set_selection_bounds (text, m_BeginAtom, (m_EndAtom == m_BeginAtom)? m_EndAtom + 1: m_EndAtom);
		GtkWidget* w = gtk_message_dialog_new (
										GTK_WINDOW (pDoc->GetWindow ()->GetWindow ()),
										GTK_DIALOG_DESTROY_WITH_PARENT,
										GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
										_("Invalid symbol."));
		gtk_dialog_run (GTK_DIALOG (w));
		gtk_widget_destroy (w);
		return false;
	}
	//now scan for charges and validate
	struct FilterStruct s;
	s.start = 0;
	s.end = m_buf.length ();
	if (m_AttrList == NULL)
		m_AttrList = pango_layout_get_attributes (m_Layout);
	pango_attr_list_filter (m_AttrList, (PangoAttrFilterFunc) filter_func, &s);
	list<PangoAttribute*>::iterator i, iend = s.pal.end ();
	for (i = s.pal.begin (); i != iend; i++) {
		charge = m_buf.c_str () + (*i)->start_index;
		strtol (charge, &err, 10);
		if (*err != '+' && *err != '-' && err - m_buf.c_str () != (int) (*i)->end_index) {
			gcpDocument *pDoc = dynamic_cast<gcpDocument*> (GetDocument ());
			GtkWidget* pWidget = pDoc->GetView ()->GetWidget ();
			gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (pWidget), "data");
			GnomeCanvasGroup *item = pData->Items[this];
			GnomeCanvasPango* text = GNOME_CANVAS_PANGO (g_object_get_data (G_OBJECT (item), "fragment"));
			gnome_canvas_pango_set_selection_bounds (text, (*i)->start_index, (*i)->end_index);
			GtkWidget* w = gtk_message_dialog_new (
											GTK_WINDOW (pDoc->GetWindow ()->GetWindow ()),
											GTK_DIALOG_DESTROY_WITH_PARENT,
											GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
											_("Invalid charge."));
			gtk_dialog_run(GTK_DIALOG(w));
			gtk_widget_destroy(w);
			return false;
		}
	}
	return true;
}

void gcpFragment::Transform2D (Matrix2D& m, double x, double y)
{
	m_x -= x;
	m_y -= y;
	m.Transform (m_x, m_y);
	m_x += x;
	m_y += y;
	m_Atom->SetCoords (m_x, m_y);
}
	
double gcpFragment::GetYAlign ()
{
	return m_y;
}

int gcpFragment::GetAvailablePosition (double& x, double& y)
{
	return 0;
}

bool gcpFragment::GetPosition (double angle, double& x, double& y)
{
	return false;
}


syntax highlighted by Code2HTML, v. 0.9.1