// -*- C++ -*-

/* 
 * GChemPaint library
 * widgetdata.cc
 *
 * Copyright (C) 2002-2006 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 "widgetdata.h"
#include "view.h"
#include "settings.h"
#include "document.h"
#include "application.h"
#include "operation.h"
#include "theme.h"
#include <cstring>

static xmlDocPtr pXmlDoc = NULL, pXmlDoc1 = NULL;
xmlChar* ClipboardData = NULL;
guint ClipboardDataType, ClipboardDataType1;
bool cleared = true;
char *ClipboardTextData = NULL;

GtkTargetEntry const export_targets[] = {
	{(char *) GCHEMPAINT_ATOM_NAME,  0, 0},
	{(char *) "image/svg",  0, 1},
	{(char *) "image/svg+xml",  0, 2},
	{(char *) "image/png",  0, 3},
	{(char *) "image/jpeg",  0, 4},
	{(char *) "image/bmp",  0, 5},
	{(char *) "UTF8_STRING", 0, 6},
	{(char *) "STRING", 0, 7}
};

void on_receive_targets (GtkClipboard *clipboard, GtkSelectionData *selection_data, gcpApplication *App)
{
	GtkClipboard* sel_clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
	guint *DataType = (clipboard == sel_clipboard)? &ClipboardDataType: &ClipboardDataType1;
	if (selection_data->target == gdk_atom_intern ("TARGETS", FALSE)) {
		static char const *formats [] =
		{
			GCHEMPAINT_ATOM_NAME,
			"image/svg",
			"image/svg+xml",
			"image/png",
			"image/jpeg",
			"image/bmp",
			"UTF8_STRING",
			"STRING",
			NULL
		};

		GdkAtom const *targets = (GdkAtom *) selection_data->data;
		unsigned const atom_count = (selection_data->length / sizeof (GdkAtom));
		unsigned i, j;
		/* Nothing on clipboard? */
		if (selection_data->length < 0) {
			if (clipboard == sel_clipboard)
				App->ActivateWindowsActionWidget ("/MainMenu/EditMenu/Paste", false);
			return;
		}

		gchar* name;
		*DataType = 7;
		for ( j = 0; j < atom_count ; j++) {
			name = gdk_atom_name (targets [j]);
			for (i = 0; i < *DataType; i++)
				if (!strcmp (name, formats[i])) {
					*DataType = i;
					break;
				}
			g_free (name);
		}
	}
	if (clipboard == sel_clipboard && App != NULL)
		App->ActivateWindowsActionWidget ("/MainMenu/EditMenu/Paste",
			ClipboardDataType == 0 || ClipboardDataType == 5 || ClipboardDataType == 6);
}

static void on_get_data(GtkClipboard *clipboard, GtkSelectionData *selection_data,  guint info, gcpApplication *App)
{
	xmlDocPtr pDoc = (clipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))? pXmlDoc: pXmlDoc1;
	guint *DataType = (clipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))? &ClipboardDataType: &ClipboardDataType1;
	g_return_if_fail(pDoc);
	if (ClipboardData) {
		xmlFree (ClipboardData);
	} 
	ClipboardData = NULL;
	g_free (ClipboardTextData);
	ClipboardTextData = NULL;
	*DataType = info;
	int size;
	switch (info) {
	case 0:
		xmlDocDumpFormatMemory (pDoc, &ClipboardData, &size, info);
		gtk_selection_data_set(selection_data, gdk_atom_intern (GCHEMPAINT_ATOM_NAME, FALSE), 8, (const guchar*) ClipboardData, size);
		break;
	case 1:
	case 2: {
		gcpDocument *Doc = new gcpDocument (NULL, true);
		gcpView *pView = Doc->GetView ();
		pView->CreateNewWidget (); // force canvas creation
		Doc->ParseXMLTree (pDoc);
		xmlDocPtr doc = pView->BuildSVG ();
		xmlDocDumpFormatMemory (doc, &ClipboardData, &size, info);
		gtk_selection_data_set (selection_data, gdk_atom_intern (export_targets[info].target, FALSE), 8, (const guchar*) ClipboardData, size);
		xmlFreeDoc (doc);
		delete Doc;
		break;
	}
	case 3: {
		gcpDocument *Doc = new gcpDocument (NULL, true);
		gcpView *pView = Doc->GetView ();
		gsize size;
		pView->CreateNewWidget (); // force canvas creation
		Doc->ParseXMLTree (pDoc);
		GdkPixbuf *pixbuf = pView->BuildPixbuf (-1); // copy with zoom == 1
		gdk_pixbuf_save_to_buffer (pixbuf, &ClipboardTextData, &size, "png", NULL, NULL);
		gtk_selection_data_set (selection_data, gdk_atom_intern (export_targets[info].target, FALSE), 8, (const guchar*) ClipboardTextData, size);
		g_object_unref (pixbuf);
		delete Doc;
		break;
	}
	case 4: {
		gcpDocument *Doc = new gcpDocument (NULL, true);
		gcpView *pView = Doc->GetView ();
		gsize size;
		pView->CreateNewWidget (); // force canvas creation
		Doc->ParseXMLTree (pDoc);
		GdkPixbuf *pixbuf = pView->BuildPixbuf (-1); // copy with zoom == 1
		gdk_pixbuf_save_to_buffer (pixbuf, &ClipboardTextData, &size, "jpg", NULL, NULL);
		gtk_selection_data_set (selection_data, gdk_atom_intern (export_targets[info].target, FALSE), 8, (const guchar*) ClipboardTextData, size);
		g_object_unref (pixbuf);
		delete Doc;
		break;
	}
	case 5: {
		gcpDocument *Doc = new gcpDocument (NULL, true);
		gcpView *pView = Doc->GetView ();
		gsize size;
		pView->CreateNewWidget (); // force canvas creation
		Doc->ParseXMLTree (pDoc);
		GdkPixbuf *pixbuf = pView->BuildPixbuf (-1); // copy with zoom == 1
		gdk_pixbuf_save_to_buffer (pixbuf, &ClipboardTextData, &size, "bmp", NULL, NULL);
		gtk_selection_data_set (selection_data, gdk_atom_intern (export_targets[info].target, FALSE), 8, (const guchar*) ClipboardTextData, size);
		g_object_unref (pixbuf);
		delete Doc;
		break;
	}
	default:
		xmlDocDumpFormatMemory (pDoc, &ClipboardData, &size, info);
		gtk_selection_data_set_text (selection_data, (const gchar*) ClipboardData, size);
		break;
	}
	cleared = false;
	if (clipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
		App->ActivateWindowsActionWidget ("/MainMenu/EditMenu/Paste", true);
}

void on_clear_data(GtkClipboard *clipboard, gcpApplication *App)
{
	if (ClipboardData)
	{
		xmlFree(ClipboardData);
		ClipboardData = NULL;
	g_free (ClipboardTextData);
	ClipboardTextData = NULL;
	}
	cleared =true;
	gtk_clipboard_request_contents(clipboard, gdk_atom_intern ("TARGETS", FALSE),  (GtkClipboardReceivedFunc)on_receive_targets, App);
}

bool gcpWidgetData::IsSelected(Object *obj)
{
	std::list<Object*>::iterator i;
	Object* pGroup = obj->GetGroup ();
	for (i = SelectedObjects.begin(); i != SelectedObjects.end(); i++)
		if ((*i == obj) || (*i == pGroup)) return true;
	return false;
}

void gcpWidgetData::Unselect(Object* obj)
{
	SelectedObjects.remove(obj);
	obj->SetSelected(Canvas, SelStateUnselected);
	View->Update(obj);
}

void gcpWidgetData::UnselectAll()
{
	Object* obj;
	while (!SelectedObjects.empty())
	{
		obj = SelectedObjects.front();
		SelectedObjects.pop_front();
		obj->SetSelected(Canvas, SelStateUnselected);
		View->Update(obj);//FIXME: is it really useful
	}
}

void gcpWidgetData::SetSelected(Object* obj)
{
	if (!IsSelected (obj)) {
		SelectedObjects.push_front(obj);
		obj->SetSelected(Canvas, SelStateSelected);
	}
}

void gcpWidgetData::MoveSelectedItems(double dx, double dy)
{
	std::list<Object*>::iterator i;
	for (i = SelectedObjects.begin(); i != SelectedObjects.end(); i++) MoveItems(*i, dx, dy);
}

void gcpWidgetData::MoveItems(Object* obj, double dx, double dy)
{
	Object* pObject;
	GnomeCanvasGroup* group;
	if ((group = Items[obj])) gnome_canvas_item_move((GnomeCanvasItem*)group, dx, dy);
	else
		Items.erase(obj);
	std::map<std::string, Object*>::iterator i;
	pObject = obj->GetFirstChild(i);
	while (pObject)
	{
		MoveItems(pObject, dx, dy);
		pObject = obj->GetNextChild(i);
	}
}

void gcpWidgetData::MoveSelection(double dx, double dy)
{
	std::list<Object*>::iterator i;
	gcpDocument* pDoc = View->GetDoc();
	gcpOperation* pOp = pDoc-> GetNewOperation(GCP_MODIFY_OPERATION);
	gcpTheme *pTheme = pDoc->GetTheme ();
	for (i = SelectedObjects.begin(); i != SelectedObjects.end(); i++)
	{
		pOp->AddObject(*i,0);
		(*i)->Move(dx / pTheme->GetZoomFactor (), dy / pTheme->GetZoomFactor ());
		View->Update(*i);
		pOp->AddObject(*i,1);
	}
	pDoc->FinishOperation();
}

void gcpWidgetData::RotateSelection(double dx, double dy, double angle)
{
	gcpTheme *pTheme = View->GetDoc ()->GetTheme ();
	std::list<Object*>::iterator i;
	Matrix2D m(angle);
	for (i = SelectedObjects.begin(); i != SelectedObjects.end(); i++) {
		(*i)->Transform2D (m, dx / pTheme->GetZoomFactor (), dy / pTheme->GetZoomFactor ());
		View->Update(*i);
	}
}

void gcpWidgetData::Copy(GtkClipboard* clipboard)
{
	/*First build the XML tree from current selection*/
	xmlDocPtr *pDoc = (clipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))? &pXmlDoc: &pXmlDoc1;
	if (*pDoc) xmlFreeDoc(*pDoc);
	*pDoc = xmlNewDoc((xmlChar*)"1.0");
	if (!*pDoc) return;
	if (SelectedObjects.empty()) return;
	xmlDocSetRootElement (*pDoc, xmlNewDocNode(*pDoc, NULL, (xmlChar*)"chemistry", NULL));
	xmlNsPtr ns = xmlNewNs ((*pDoc)->children, (xmlChar*) "http://www.nongnu.org/gchempaint", (xmlChar*) "gcp");
	xmlSetNs ((*pDoc)->children, ns);
//FIXME: implement exception handling
	std::list<Object*>::iterator i;
	xmlNodePtr child;
	for (i = SelectedObjects.begin(); i != SelectedObjects.end(); i++)
		if ((child = (*i)->Save (pXmlDoc)))
			xmlAddChild ((*pDoc)->children, child);
	gcpApplication* App = View->GetDoc()->GetApplication();
	gtk_clipboard_set_with_data (clipboard, export_targets, ClipboardFormats, (GtkClipboardGetFunc) on_get_data, (GtkClipboardClearFunc) on_clear_data, App);
	gtk_clipboard_request_contents (clipboard, gdk_atom_intern ("TARGETS", FALSE),  (GtkClipboardReceivedFunc) on_receive_targets, App);
}

void gcpWidgetData::GetObjectBounds(Object* obj, ArtDRect &rect)
{
	Object* pObject;
	GnomeCanvasGroup* group;
	double x1, y1, x2,  y2;
	if ((group = Items[obj]))
	{
		gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM(group), &x1, &y1, &x2, &y2);
		if (rect.x0 < -9.)
		{
			rect.x0 = x1;
			rect.y0 = y1;
			rect.x1 = x2;
			rect.y1 = y2;
		}
		else
		{
			if (rect.x0 > x1) rect.x0 = x1;
			if (rect.y0 > y1) rect.y0 = y1;
			if (rect.x1 < x2) rect.x1 = x2;
			if (rect.y1 < y2) rect.y1 = y2;
		}
	}
	else
		Items.erase(obj);
	std::map<std::string, Object*>::iterator i;
	pObject = obj->GetFirstChild(i);
	while (pObject)
	{
		GetObjectBounds(pObject, rect);
		pObject = obj->GetNextChild(i);
	}
}

void gcpWidgetData::GetSelectionBounds(ArtDRect &rect)
{
	std::list<Object*>::iterator i, j;
	rect.x0 = -10.;
	for (i = SelectedObjects.begin(); i != SelectedObjects.end(); i++)
		GetObjectBounds(*i, rect);
}

void gcpWidgetData::GetObjectBounds(Object* obj, ArtDRect *rect)
{
	rect->x0 = -10.;
	GetObjectBounds(obj, *rect);
}

void gcpWidgetData::SelectAll()
{
	std::map<Object*, GnomeCanvasGroup*>::iterator i;
	Object *pObject;
	for (i = Items.begin(); i != Items.end(); i++)
	{
		pObject = (*i).first->GetGroup();
		if (pObject) {
			if (!IsSelected(pObject))
				SetSelected(pObject);
		}
		else if (!IsSelected((*i).first))
			SetSelected((*i).first);
	}
}

xmlDocPtr gcpWidgetData::GetXmlDoc(GtkClipboard* clipboard)
{
	return (clipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))? pXmlDoc: pXmlDoc1;
}

void gcpWidgetData::ShowSelection(bool state)
{
	std::list<Object*>::iterator i, j;
	for (i = SelectedObjects.begin(); i != SelectedObjects.end(); i++)
		(*i)->SetSelected(Canvas, (state)? SelStateSelected: SelStateUnselected);
}


syntax highlighted by Code2HTML, v. 0.9.1