// -*- C++ -*-

/* 
 * GChemPaint text plugin
 * texttool.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 "texttool.h"
#include "lib/text.h"
#include "lib/document.h"
#include "lib/application.h"
#include "lib/settings.h"
#include "lib/theme.h"
#include "lib/window.h"
#include <goffice/gtk/go-color-selector.h>
#include <gdk/gdkkeysyms.h>
#include <unistd.h>

static void on_get_data (GtkClipboard *clipboard, GtkSelectionData *selection_data,  guint info, gcpTextTool* tool)
{
	tool->OnGetData (clipboard, selection_data, info);
}

static void on_sel_changed (gcpTextTool *tool)
{
	tool->UpdateAttributeList ();
}

static bool filter_attribute (PangoAttribute *attribute, gcpTextTool *tool)
{
	int index = tool->GetIndex ();
	if (index < 0)
		return false;
	if ((index)? (attribute->start_index < (unsigned) index && attribute->end_index >= (unsigned) index):
		attribute->start_index == 0) {
			switch (attribute->klass->type) {
				case PANGO_ATTR_FAMILY:
					tool->SetFamilyName (((PangoAttrString*)attribute)->value);
					break;
				case PANGO_ATTR_STYLE:
					tool->SetStyle ((PangoStyle) ((PangoAttrInt*)attribute)->value);
					break;
				case PANGO_ATTR_WEIGHT:
					tool->SetWeight ((PangoWeight) ((PangoAttrInt*)attribute)->value);
					break;
				case PANGO_ATTR_VARIANT:
					tool->SetVariant ((PangoVariant) ((PangoAttrInt*)attribute)->value);
					break;
				case PANGO_ATTR_STRETCH:
					tool->SetStretch ((PangoStretch) ((PangoAttrInt*)attribute)->value);
					break;
				case PANGO_ATTR_SIZE:
					tool->SetSize (((PangoAttrInt*)attribute)->value);
					break;
				case PANGO_ATTR_UNDERLINE:
					tool->SetUnderline ((PangoUnderline) ((PangoAttrInt*)attribute)->value);
					break;
				case PANGO_ATTR_STRIKETHROUGH:
					tool->SetStrikethrough (((PangoAttrInt*)attribute)->value);
					break;
				case PANGO_ATTR_RISE:
					tool->SetRise (((PangoAttrInt*)attribute)->value);
					break;
				case PANGO_ATTR_FOREGROUND: {
					PangoColor color = ((PangoAttrColor*)attribute)->color;
					tool->SetColor (RGBA_TO_UINT(color.red >> 8, color.green >> 8, color.blue >> 8, 0xff));
					break;
				}
				default:
					break;
			}
		}
	return false;
}

gcpTextTool::gcpTextTool (gcpApplication* App, string Id):
	gcpTool (App, Id),
	m_FamilyList (NULL)
{
	m_Active = NULL;
	m_bUndo = true;
	m_CurNode = m_InitNode = NULL;
	m_Strikethrough = false;
	m_FontDesc = NULL;
	gcpTheme *pTheme = ThemeManager.GetTheme ("Default");
	m_FamilyName = pTheme->GetTextFontFamily ();
	m_Style = pTheme->GetFontStyle ();
	m_Weight = pTheme->GetFontWeight ();
	m_Stretch = pTheme->GetFontStretch ();
	m_Variant = pTheme->GetFontVariant ();
	m_Size = pTheme->GetFontSize ();
	m_Underline = PANGO_UNDERLINE_NONE;
	m_Rise = 0;
	m_Color = RGBA_BLACK;
	m_SelSignal = 0;
}

gcpTextTool::~gcpTextTool ()
{
	map<string, PangoFontFamily*>::iterator i, iend = m_Families.end ();
	for (i = m_Families.begin (); i != iend; i++) {
		g_object_unref ((*i).second);
	}
	map<string, PangoFontFace*>::iterator j, jend = m_Faces.end ();
	for (j = m_Faces.begin (); j != jend; j++) {
		g_object_unref ((*j).second);
	}
	m_FontDesc = NULL;
}

bool gcpTextTool::OnClicked ()
{
	if (m_Active && ((m_pObject == NULL) || (m_pObject->GetType () != TextType) ||
			(m_Active != g_object_get_data (G_OBJECT (m_pData->Items[m_pObject]), "text"))))
		Unselect ();
	bool create = false;
	if (!m_pObject) {
		gcpTheme *pTheme = m_pView->GetDoc ()->GetTheme ();
		gcpText *text = new gcpText(m_x0 / pTheme->GetZoomFactor (), m_y0 / pTheme->GetZoomFactor ());
		m_pView->GetDoc ()->AddObject (text);
		m_pView->GetDoc ()->AbortOperation ();
		m_pObject = text;
		create = true;
	}
	if (m_pObject) {
		if (m_pObject->GetType () != TextType)
			return false;
		m_pObject->SetSelected (m_pWidget, SelStateUpdating);
		m_Active = GNOME_CANVAS_PANGO (g_object_get_data (G_OBJECT (m_pData->Items[m_pObject]), "text"));
		m_pView->SetGnomeCanvasPangoActive (m_Active);
		g_object_set (G_OBJECT (m_Active), "editing", true, NULL);
		m_CurNode = ((gcpText*) m_pObject)->SaveSelected ();
		m_InitNode = ((gcpText*) m_pObject)->SaveSelected ();
		m_pView->GetDoc ()->GetWindow ()->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", false);
		if (m_SelSignal == 0)
			m_SelSignal = g_signal_connect_swapped (m_Active, "sel-changed", G_CALLBACK (on_sel_changed), this);
		if (create)
			BuildAttributeList ();
		else
			UpdateAttributeList ();
	}
	return true;
}

bool gcpTextTool::OnEvent (GdkEvent* event)
{
	if (m_Active) {
		if ((event->type == GDK_KEY_PRESS) || (event->type == GDK_KEY_RELEASE)) {
			if (event->key.state & GDK_CONTROL_MASK) {
				switch (event->key.keyval) {
				case GDK_Right:
				case GDK_Left:
				case GDK_Up:
				case GDK_Down:
				case GDK_End:
				case GDK_Home:
				case GDK_Delete:
				case GDK_KP_Delete:
				case GDK_BackSpace:
					break;
				case GDK_a:
					m_pView->OnSelectAll ();
					return true;
				case GDK_z:
					m_pView->GetDoc()->OnUndo ();
					return true;
				case GDK_Z:
					m_pView->GetDoc()->OnRedo ();
					return true;
				case GDK_c:
					CopySelection (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
					return true;
				case GDK_v:
					PasteSelection (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
					return true;
				case GDK_x:
					CutSelection (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
					return true;
				case GDK_i:
					m_Style = (m_Style == PANGO_STYLE_NORMAL)? PANGO_STYLE_ITALIC: PANGO_STYLE_NORMAL;
					SelectBestFontFace ();
					BuildAttributeList ();
					return true;
				case GDK_u:
					gtk_combo_box_set_active (m_UnderlineBox, ((m_Underline == PANGO_UNDERLINE_SINGLE)? PANGO_UNDERLINE_NONE: PANGO_UNDERLINE_SINGLE));
					return true;
				case GDK_b:
					m_Weight = (m_Weight == PANGO_WEIGHT_NORMAL)? PANGO_WEIGHT_BOLD: PANGO_WEIGHT_NORMAL;
					SelectBestFontFace ();
					BuildAttributeList ();
					return true;
				case GDK_k:
					gtk_toggle_button_set_active (m_StrikethroughBtn, !m_Strikethrough);
					return true;
				case GDK_plus:
				case GDK_dead_circumflex:
					if (m_Rise == 0) {
						m_Size = m_Size * 2 / 3;
						m_Rise = m_Size / PANGO_SCALE;
					} else if (m_Rise < 0)
						m_Rise = m_Size / PANGO_SCALE;
					else {
						m_Size = m_Size * 3 / 2;
						m_Rise = 0;
					}
					g_signal_handler_block (m_RiseButton, m_RiseSignal);
					gtk_spin_button_set_value (m_RiseButton, m_Rise);
					m_Rise *= PANGO_SCALE;
					g_signal_handler_unblock (m_RiseButton, m_RiseSignal);
					SetSizeFull (true);
					return true;
				case GDK_equal:
				case GDK_underscore:
					if (m_Rise == 0) {
						m_Size = m_Size * 2 / 3;
						m_Rise = - m_Size / PANGO_SCALE / 2;
					} else if (m_Rise > 0)
						m_Rise = - m_Size / PANGO_SCALE/ 2;
					else {
						m_Size = m_Size * 3 / 2;
						m_Rise = 0;
					}
					g_signal_handler_block (m_RiseButton, m_RiseSignal);
					gtk_spin_button_set_value (m_RiseButton, m_Rise);
					m_Rise *= PANGO_SCALE;
					g_signal_handler_unblock (m_RiseButton, m_RiseSignal);
					SetSizeFull (true);
					return true;
				case GDK_space: {
					GnomeCanvasPango* saved = m_Active;
					m_Active = NULL;
					UpdateAttributeList ();
					m_Active = saved;
					BuildAttributeList ();
					return true;
				}
				default:
					break;
				}
			}
			if (!g_utf8_validate (((GdkEventKey*) event)->string, -1, NULL)) {
				gsize r, w;
				gchar* newstr = g_locale_to_utf8 (((GdkEventKey*) event)->string, ((GdkEventKey*) event)->length, &r, &w, NULL);
				g_free (((GdkEventKey*) event)->string);
				((GdkEventKey*) event)->string = newstr;
				((GdkEventKey*) event)->length = w;
			}
			gnome_canvas_item_grab_focus ((GnomeCanvasItem*) m_Active);
			GnomeCanvasItemClass* klass = GNOME_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (m_Active));
			klass->event ((GnomeCanvasItem*) m_Active, event);
			return true;
		} else if (event->type == GDK_BUTTON_PRESS) {
			 switch (event->button.button) {
				case 2:
				return true;
			}
		} else if (event->type == GDK_MOTION_NOTIFY) {
			GnomeCanvasItemClass* klass = GNOME_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (m_Active));
			klass->event ((GnomeCanvasItem*) m_Active, event);
		}
	}
	return false;
}

void gcpTextTool::Activate ()
{
	if (!m_Active)
		UpdateAttributeList ();
}

bool gcpTextTool::Deactivate ()
{
	if (m_Active)
		Unselect ();
	return true;
}

bool gcpTextTool::NotifyViewChange ()
{
	return (m_Active)? Unselect (): true;
}

bool gcpTextTool::Unselect ()
{
	if (!m_Active)
		return true;
	if (m_SelSignal) {
		g_signal_handler_disconnect (m_Active, m_SelSignal);
		m_SelSignal = 0;
	}
	g_object_set (G_OBJECT (m_Active), "editing", false, NULL);
	m_pView->SetGnomeCanvasPangoActive (NULL);
	Object *pObj = (Object*) g_object_get_data (G_OBJECT (m_Active), "object");
	pObj->SetSelected (m_pWidget, SelStateUnselected);
	char const *text = pango_layout_get_text (gnome_canvas_pango_get_layout (m_Active));
	m_Active = NULL;
	while (!m_UndoList.empty ()) {
		xmlFree(m_UndoList.front ());
		m_UndoList.pop_front ();
	}
	while (!m_RedoList.empty ()) {
		xmlFree(m_RedoList.front ());
		m_RedoList.pop_front ();
	}
	xmlBufferPtr initbuf = xmlBufferCreate ();
	xmlBufferPtr endbuf = xmlBufferCreate ();
	xmlNodeDump (initbuf, m_pApp->GetXmlDoc (), m_InitNode, 0, 0);
	xmlNodeDump (endbuf, m_pApp->GetXmlDoc (), m_CurNode, 0, 0);
	if (strcmp ((char*) initbuf->content, (char*) endbuf->content))
	{
		char* initval = (char*) xmlNodeGetContent (m_InitNode);
		char* endval = (char*) xmlNodeGetContent (m_CurNode);
		gcpOperation *pOp = NULL;
		if ((initval && strlen (initval))) {
			if (endval && strlen (endval)) {
				pOp = m_pView->GetDoc ()->GetNewOperation (GCP_MODIFY_OPERATION);
				pOp->AddNode (m_InitNode, 0);
				pOp->AddNode (m_CurNode, 1);
				m_CurNode = m_InitNode = NULL;
			} else {
				pOp = m_pView->GetDoc ()->GetNewOperation (GCP_DELETE_OPERATION);
				pOp->AddNode (m_InitNode);
				m_InitNode = NULL;
			}
		} else if (endval && strlen (endval)) {
			pOp = m_pView->GetDoc ()->GetNewOperation (GCP_ADD_OPERATION);
			pOp->AddNode (m_CurNode);
			m_CurNode = NULL;
		}
		if (initval)
			xmlFree (initval);
		if (endval)
			xmlFree (endval);
		if (pOp)
			m_pView->GetDoc ()->PushOperation (pOp, m_bUndo);
		m_bUndo = true;
	}
	xmlBufferFree (initbuf);
	xmlBufferFree (endbuf);
	if (m_CurNode)
		xmlFree (m_CurNode);
	if (m_InitNode)
		xmlFree (m_InitNode);
	m_CurNode = m_InitNode = NULL;
	if (!*text) {
		Object* pMol = pObj->GetMolecule ();	//if pObj is a fragment
		if (pMol)
			pObj = pMol;
		m_pView->GetDoc ()->Remove (pObj);
		m_pView->GetDoc ()->AbortOperation ();
	}
	m_pView->GetDoc ()->GetWindow ()->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", m_pView->GetDoc ()->HasChildren ());
	return true;
}

bool gcpTextTool::DeleteSelection ()
{
	if (!m_Active)
		return false;
	unsigned start, end;

	gcpTextObject *text = dynamic_cast<gcpTextObject*> ((Object*) g_object_get_data (G_OBJECT (m_Active), "object"));
	if (!text)
		return false;
	text->GetSelectionBounds (start, end);
	gcp_pango_layout_replace_text (gnome_canvas_pango_get_layout (m_Active),
												start, end - start, "", NULL);
	gnome_canvas_pango_set_selection_bounds (m_Active, start, start);
	text->OnChanged (true);
	return true;
}

bool gcpTextTool::CopySelection (GtkClipboard *clipboard)
{
	if (!m_Active)
		return false;
	unsigned start, end;
	gcpText *text = (gcpText*) g_object_get_data (G_OBJECT (m_Active), "object");
	text->GetSelectionBounds (start, end);
	if (start == end)
		return false;
	m_pData->Copy (clipboard); //To clean the xmlDoc
	xmlDocPtr pDoc = m_pData->GetXmlDoc (clipboard);
	if (!pDoc)
		return false;
	pDoc->children = xmlNewDocNode (pDoc, NULL, (xmlChar*) "chemistry", NULL);
	xmlNsPtr ns = xmlNewNs (pDoc->children, (xmlChar*) "http://www.nongnu.org/gchempaint", (xmlChar*) "gcp");
	xmlSetNs (pDoc->children, ns);
	xmlNodePtr node = text->SaveSelection (pDoc);
	if (node)
		xmlAddChild (pDoc->children, node);
	else
		return false;
	gtk_clipboard_set_with_data (clipboard, targets, ClipboardFormats,
				(GtkClipboardGetFunc) on_get_data,
				(GtkClipboardClearFunc) on_clear_data, this);
	gtk_clipboard_request_contents (clipboard,
			gdk_atom_intern ("TARGETS", FALSE),
			(GtkClipboardReceivedFunc) on_receive_targets,
			m_pApp);
	return true;
}

bool gcpTextTool::CutSelection (GtkClipboard *clipboard)
{
	if (!CopySelection (clipboard))
		return false;
	return DeleteSelection ();
}

bool gcpTextTool::PasteSelection (GtkClipboard *clipboard)
{
	if (!m_Active)
		return false;
	guint *DataType = (clipboard == gtk_clipboard_get (GDK_SELECTION_CLIPBOARD))? &ClipboardDataType: &ClipboardDataType1;
	GdkAtom targets_atom  = gdk_atom_intern (targets[*DataType].target, FALSE);
	gtk_clipboard_request_contents (clipboard, targets_atom,  (GtkClipboardReceivedFunc) on_receive, m_pView);
	return true;
}

struct FragState {
	PangoAttrList *l;
	unsigned start;
};

static bool filter_fragment (PangoAttribute *attr, struct FragState *s)
{
	PangoAttribute *new_attr = pango_attribute_copy (attr);
	new_attr->start_index += s->start;
	new_attr->end_index += s->start;
	pango_attr_list_change (s->l, new_attr);
	return false;
}

bool gcpTextTool::OnReceive (GtkClipboard *clipboard, GtkSelectionData *data, int type)
{
	if (!m_Active)
		return false;
	guint *DataType = (clipboard == gtk_clipboard_get (GDK_SELECTION_CLIPBOARD))? &ClipboardDataType: &ClipboardDataType1;
	g_return_val_if_fail ((data->target == gdk_atom_intern (targets[*DataType].target, FALSE)), FALSE);
	gcpText *text = (gcpText*) g_object_get_data (G_OBJECT (m_Active), "object");
	unsigned start, end;
	text->GetSelectionBounds (start, end);
	PangoLayout *layout = gnome_canvas_pango_get_layout (m_Active);
	switch (*DataType) {
		case GCP_CLIPBOARD_NATIVE: {
			xmlDocPtr xml = xmlParseMemory ((const char*) data->data, data->length);
			xmlNodePtr node = xml->children;
			if ((strcmp((char*)node->name, "chemistry")) || (node->children->next)) {
				xmlFreeDoc (xml);
				return false;
			}
			node = node->children;
			if (!strcmp ((char*) node->name, "text")) {
				gcp_pango_layout_replace_text (layout, start, end - start, "", NULL);
				text->LoadSelection (node, start);
				xmlFreeDoc (xml);
				return true; // otherwise, we'd call OnChange(true) twice.
			} else if (!strcmp((char*)node->name, "fragment")) {
				gcpFragment* fragment = new gcpFragment ();
				gcpDocument *pDoc = m_pView->GetDoc ();
				gcpTheme *pTheme = pDoc->GetTheme ();
				pDoc->AddChild (fragment);
				fragment->Load (node);
				string buf = fragment->GetBuffer ();
				PangoAttrList *l = pango_attr_list_new ();
				PangoAttribute *attr = pango_attr_family_new (pTheme->GetFontFamily ());
				pango_attr_list_insert (l, attr);
				attr = pango_attr_size_new (pTheme->GetFontSize ());
				pango_attr_list_insert (l, attr);
				gcp_pango_layout_replace_text (layout, start, end - start, buf.c_str (), l);
				pango_attr_list_unref (l);
				l = fragment->GetAttrList ();
				struct FragState s;
				s.l = pango_layout_get_attributes (layout);
				s.start = start;
				pango_attr_list_filter (l, (PangoAttrFilterFunc) filter_fragment, &s);
				delete fragment;
				start += buf.length ();
				gnome_canvas_pango_set_selection_bounds (m_Active, start, start);
			} else {
				xmlFreeDoc (xml);
				return false;
			}
			xmlFreeDoc (xml);
			break;
		}
		case GCP_CLIPBOARD_UTF8_STRING: {
			PangoAttrList *l = pango_attr_list_new ();
			gcp_pango_layout_replace_text (layout, start, end - start, (char const *) data->data, l);
			pango_attr_list_unref (l);
			break;
		}
		case GCP_CLIPBOARD_STRING: {
			PangoAttrList *l = pango_attr_list_new ();
			if (!g_utf8_validate ((const char*) data->data, data->length, NULL)) {
				gsize r, w;
				gchar* newstr = g_locale_to_utf8 ((const char*) data->data, data->length, &r, &w, NULL);
				gcp_pango_layout_replace_text (layout, start, end - start, (char const *) data->data, l);
				g_free (newstr);
			} else
				gcp_pango_layout_replace_text (layout, start, end - start, (char const *) data->data, l);
			pango_attr_list_unref (l);
			break;
		}
	}
	text->OnChanged (true);
	return true;
}

bool gcpTextTool::OnUndo ()
{
	if (m_UndoList.empty ()) {
		if (m_pView->GetDoc()->CanUndo())
		{
			if (!m_RedoList.empty())
			{
				if (m_CurNode) xmlFree(m_CurNode);
				m_CurNode = m_RedoList.back();
				m_RedoList.pop_back();
			}
			m_bUndo = false;
			Unselect();
		}
		return false;
	}
	xmlNodePtr node = m_UndoList.front();
	gcpTextObject *text = (gcpTextObject*) g_object_get_data (G_OBJECT (m_Active), "object");
	text->LoadSelected (node);
	m_UndoList.pop_front ();
	gcpDocument *pDoc = m_pView->GetDoc ();
	gcpWindow *pWin = pDoc->GetWindow ();
	if (m_UndoList.empty() && !pDoc->CanUndo ())
		pWin->ActivateActionWidget ("/MainMenu/EditMenu/Undo", false);
	m_RedoList.push_front(m_CurNode);
	pWin->ActivateActionWidget ("/MainMenu/EditMenu/Redo", true);
	unsigned start, end;
	char* tmp = (char*) xmlGetProp (node, (xmlChar*) "start-sel");
	start = (int) strtoul (tmp, NULL, 10);
	xmlFree (tmp);
	tmp = (char*) xmlGetProp (node, (xmlChar*) "end-sel");
	end = (int) strtoul (tmp, NULL, 10);
	xmlFree (tmp);
	gnome_canvas_pango_set_selection_bounds (m_Active, start, end);
	m_CurNode = node;
	return true;
}

bool gcpTextTool::OnRedo ()
{
	if (m_RedoList.empty ())
		return false;
	xmlNodePtr node = m_RedoList.front ();
	gcpTextObject *text = (gcpTextObject*) g_object_get_data (G_OBJECT (m_Active), "object");
	text->LoadSelected (node);
	m_RedoList.pop_front ();
	gcpWindow *pWin = m_pView->GetDoc ()->GetWindow ();
	if (m_RedoList.empty ())
		pWin->ActivateActionWidget ("/MainMenu/EditMenu/Redo", false);
	m_UndoList.push_front (m_CurNode);
	pWin->ActivateActionWidget ("/MainMenu/EditMenu/Undo", true);
	unsigned start, end;
	char* tmp = (char*) xmlGetProp (node, (xmlChar*) "start-sel");
	start = (int) strtoul (tmp, NULL, 10);
	xmlFree (tmp);
	tmp = (char*) xmlGetProp (node, (xmlChar*) "end-sel");
	end = (int) strtoul (tmp, NULL, 10);
	xmlFree (tmp);
	gnome_canvas_pango_set_selection_bounds (m_Active, start, end);
	m_CurNode = node;
	return true;
}

void gcpTextTool::PushNode (xmlNodePtr node)
{
	gcpWindow *pWin = m_pView->GetDoc ()->GetWindow ();
	while (!m_RedoList.empty ()) {
		xmlFree (m_RedoList.front ());
		m_RedoList.pop_front ();
		pWin->ActivateActionWidget ("/MainMenu/EditMenu/Redo", false);
	}
	m_UndoList.push_front (m_CurNode);
	m_CurNode = node;
	pWin->ActivateActionWidget ("/MainMenu/EditMenu/Undo", true);
}

void gcpTextTool::OnGetData (GtkClipboard *clipboard, GtkSelectionData *selection_data,  guint info)
{
	xmlDocPtr pDoc = gcpWidgetData::GetXmlDoc (clipboard);
	guint *DataType = (clipboard == gtk_clipboard_get (GDK_SELECTION_CLIPBOARD))? &ClipboardDataType: &ClipboardDataType1;
	if (ClipboardData) {
		xmlFree (ClipboardData);
	} 
	*DataType = info;
	gint size;
	if (info) {
		gcpText *text = new gcpText ();
		text->Load (pDoc->children->children);
		PangoLayout *layout = text->GetLayout ();
		ClipboardData = xmlStrdup ((xmlChar*) pango_layout_get_text (layout));
		delete text;
		size = strlen ((char*) ClipboardData);
		gtk_selection_data_set_text (selection_data, (const gchar*) ClipboardData, size);
	} else {
		xmlDocDumpFormatMemory (pDoc, &ClipboardData, &size, info);
		gtk_selection_data_set (selection_data, gdk_atom_intern (GCHEMPAINT_ATOM_NAME, FALSE), 8,  (const guchar*) ClipboardData, size);
	}
	cleared = false;
	if (clipboard == gtk_clipboard_get (GDK_SELECTION_CLIPBOARD))
			m_pView->GetDoc ()->GetWindow ()->ActivateActionWidget ("/MainMenu/EditMenu/Paste", true);
}

void gcpTextTool::UpdateAttributeList ()
{
	if (!m_FamilyList)
		return;
	gcpTheme *pTheme = m_pApp->GetActiveDocument ()->GetTheme ();
	m_FamilyName = pTheme->GetTextFontFamily ();
	m_Style = pTheme->GetFontStyle ();
	m_Weight = pTheme->GetFontWeight ();
	m_Stretch = pTheme->GetFontStretch ();
	m_Variant = pTheme->GetFontVariant ();
	m_Size = pTheme->GetFontSize ();
	m_Rise = 0;
	m_Underline = PANGO_UNDERLINE_NONE;
	m_Strikethrough = false;
	m_Color = RGBA_BLACK;
	if (m_Active) {
		PangoLayout *layout;
		g_object_get (m_Active, "layout", &layout, NULL);
		PangoAttrList *l = pango_layout_get_attributes (layout);
		if (l)
			pango_attr_list_filter (l, (PangoAttrFilterFunc) filter_attribute, this);
	}
	// select the found face
	GtkTreeIter iter;
	char const *buf;
	gtk_tree_model_get_iter_first (GTK_TREE_MODEL (m_FamilyList), &iter);
	do {
		gtk_tree_model_get (GTK_TREE_MODEL (m_FamilyList), &iter, 0, &buf, -1);
		if (!strcmp (m_FamilyName, buf)) {
			m_Dirty = true;
			GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_FamilyList), &iter);
			gtk_tree_view_set_cursor (m_FamilyTree, path, NULL, FALSE);
			gtk_tree_path_free (path);
			break;
		}
	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (m_FamilyList), &iter));
	// Update other widgets
	SetSizeFull (true);
	g_signal_handler_block (m_UnderlineBox, m_UnderlineSignal);
	gtk_combo_box_set_active (m_UnderlineBox, m_Underline);
	g_signal_handler_unblock (m_UnderlineBox, m_UnderlineSignal);
	g_signal_handler_block (m_StrikethroughBtn, m_StrikethroughSignal);
	gtk_toggle_button_set_active (m_StrikethroughBtn, m_Strikethrough);
	g_signal_handler_unblock (m_StrikethroughBtn, m_StrikethroughSignal);
	g_signal_handler_block (m_RiseButton, m_RiseSignal);
	gtk_spin_button_set_value (m_RiseButton, m_Rise / PANGO_SCALE);
	g_signal_handler_unblock (m_RiseButton, m_RiseSignal);
	g_signal_handler_block (m_ColorSelector, m_ForeSignal);
	go_color_selector_set_color (m_ColorSelector, m_Color);
	g_signal_handler_unblock (m_ColorSelector, m_ForeSignal);
	BuildAttributeList ();
}

void gcpTextTool::BuildAttributeList ()
{
	if (!m_Active)
		return;
	PangoAttrList *l = pango_attr_list_new ();
	pango_attr_list_insert (l, pango_attr_family_new (m_FamilyName));
	pango_attr_list_insert (l, pango_attr_style_new (m_Style));
	pango_attr_list_insert (l, pango_attr_weight_new (m_Weight));
	pango_attr_list_insert (l, pango_attr_stretch_new (m_Stretch));
	pango_attr_list_insert (l, pango_attr_variant_new (m_Variant));
	pango_attr_list_insert (l, pango_attr_size_new (m_Size));
	pango_attr_list_insert (l, pango_attr_underline_new (m_Underline));
	pango_attr_list_insert (l, pango_attr_strikethrough_new (m_Strikethrough));
	pango_attr_list_insert (l, pango_attr_rise_new (m_Rise));
	pango_attr_list_insert (l, pango_attr_foreground_new (UINT_RGBA_R (m_Color) * 0x101, UINT_RGBA_G (m_Color) * 0x101, UINT_RGBA_B (m_Color) * 0x101));
	gnome_canvas_pango_set_insert_attrs (m_Active, l);
	m_Dirty = false;
	if (m_pView)
		gtk_window_present (m_pView->GetDoc ()->GetWindow ()->GetWindow ());
}

static void on_select_family (GtkTreeSelection *selection, gcpTextTool *tool)
{
	tool->OnSelectFamily (selection);
}

static void on_select_face (GtkTreeSelection *selection, gcpTextTool *tool)
{
	tool->OnSelectFace (selection);
}

static void on_select_size (GtkTreeSelection *selection, gcpTextTool *tool)
{
	int size;
	GtkTreeModel *model;
	GtkTreeIter iter;
	gtk_tree_selection_get_selected (selection, &model, &iter);
	gtk_tree_model_get (model, &iter, 0, &size, -1);
	tool->OnSelectSize (size * PANGO_SCALE);
}

static void on_size_activate (GtkEntry *entry, gcpTextTool *tool)
{
	tool->OnSizeChanged ();
}

static void on_size_focus_out (GtkEntry *entry, GdkEventFocus *event, gcpTextTool *tool)
{
	tool->OnSizeChanged ();
}

static void on_underline_changed (GtkComboBox *box, gcpTextTool *tool)
{
	tool->OnUnderlineChanged (gtk_combo_box_get_active (box));
}

static void on_strikethrough_toggled (GtkToggleButton *btn, gcpTextTool *tool)
{
	tool->OnStriketroughToggled (gtk_toggle_button_get_active (btn));
}

static void on_fore_color_changed (GOSelector *sel, gcpTextTool *tool)
{
	gboolean auto_color;
	tool->OnForeColorChanged (go_color_selector_get_color (sel, &auto_color));
}

static void on_rise_changed (GtkSpinButton *btn, gcpTextTool *tool)
{
	tool->OnPositionChanged (gtk_spin_button_get_value_as_int (btn));
}

/* These are what we use as the standard font sizes, for the size list.
 */
static const guint16 font_sizes[] = {
  8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 26, 28,
  32, 36, 40, 48, 56, 64, 72
};

GtkWidget *gcpTextTool::GetPropertyPage ()
{
	GladeXML *xml = glade_xml_new (GLADEDIR"/fontsel.glade", "fontsel", GETTEXT_PACKAGE);
	PangoFontFamily **families;
	int i, nb;
	gcpTheme *pTheme = m_pApp->GetActiveDocument ()->GetTheme ();
	// Initialize faces list
	m_FaceList = gtk_list_store_new (1, G_TYPE_STRING);
	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (m_FaceList), 0, GTK_SORT_ASCENDING);
	m_FacesTree = (GtkTreeView *) glade_xml_get_widget (xml, "style");
	gtk_tree_view_set_model (m_FacesTree, GTK_TREE_MODEL (m_FaceList));
	GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
	GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "text", 0, NULL);
	gtk_tree_view_append_column (m_FacesTree, column);
	GtkTreeSelection *selection = gtk_tree_view_get_selection (m_FacesTree);
	m_FaceSel = selection;
	m_FaceSignal = g_signal_connect (m_FaceSel, "changed", G_CALLBACK (on_select_face), this);
	// Initialize sizes list
	m_SizeList = gtk_list_store_new (1, G_TYPE_INT);
	m_SizesTree = (GtkTreeView *) glade_xml_get_widget (xml, "size-list");
	gtk_tree_view_set_model (m_SizesTree, GTK_TREE_MODEL (m_SizeList));
	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "text", 0, NULL);
	gtk_tree_view_append_column (m_SizesTree, column);
	GtkTreeIter iter, selected;
	for (i = 0; i < (int) G_N_ELEMENTS (font_sizes); i++) {
		gtk_list_store_append (m_SizeList, &iter);
		gtk_list_store_set (m_SizeList, &iter,
				  0, font_sizes[i],
				  -1);
	}
	selection = gtk_tree_view_get_selection (m_SizesTree);
	m_SizeSel = selection;
	m_SizeSignal = g_signal_connect (m_SizeSel, "changed", G_CALLBACK (on_select_size), this);
	// Size entry
	m_SizeEntry = (GtkEntry*) glade_xml_get_widget (xml, "size-entry");
	g_signal_connect (m_SizeEntry, "activate", G_CALLBACK (on_size_activate), this);
	g_signal_connect_after (m_SizeEntry, "focus_out_event", G_CALLBACK (on_size_focus_out), this);
	SetSizeFull (true);
	PangoContext *pc = gtk_widget_get_pango_context (GTK_WIDGET (m_SizeEntry));
	pango_context_list_families (pc, &families, &nb);
	m_FamilyList = gtk_list_store_new (1, G_TYPE_STRING);
	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (m_FamilyList), 0, GTK_SORT_ASCENDING);
	m_FamilyTree = (GtkTreeView *) glade_xml_get_widget (xml, "family");
	gtk_tree_view_set_model (m_FamilyTree, GTK_TREE_MODEL (m_FamilyList));
	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "text", 0, NULL);
	gtk_tree_view_append_column (m_FamilyTree, column);
	GtkTreePath *path = NULL;
	string name;
	bool default_found = false;
	for (i = 0; i < nb; i++) {
		PangoFontFace **faces;
		int *sizes, n;
		pango_font_family_list_faces (families[i], &faces, &n);
		if (n <= 0)
			continue;
		pango_font_face_list_sizes (faces[0], &sizes, &n);
		if (n > 0) // Do not use bitmap fonts
			continue;
		name = pango_font_family_get_name (families[i]);
		m_Families[name] = (PangoFontFamily*) g_object_ref (families[i]);
		gtk_list_store_append (m_FamilyList, &iter);
		gtk_list_store_set (m_FamilyList, &iter,
				  0, name.c_str (),
				  -1);
		if (name == pTheme->GetTextFontFamily ()) {
			selected = iter;
			default_found = true;
		}
	}
	if (!default_found) {
		if	(!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (m_FamilyList), &iter))
			return NULL;
		selected = iter;
	}
	path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_FamilyList), &selected);
	selection = gtk_tree_view_get_selection (m_FamilyTree);
	m_FamilySel = selection;
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
	m_FamilySignal = g_signal_connect (G_OBJECT (selection), "changed", G_CALLBACK (on_select_family), this);
	// Select default font
	if (path) {
		gtk_tree_selection_select_path (selection, path);
		gtk_tree_view_scroll_to_cell (m_FamilyTree, path, column, FALSE, 0., 0.);
		gtk_tree_path_free (path);
	}
	m_UnderlineBox = GTK_COMBO_BOX (glade_xml_get_widget (xml, "underline"));
	gtk_combo_box_set_active (m_UnderlineBox, 0);
	m_UnderlineSignal = g_signal_connect (G_OBJECT (m_UnderlineBox), "changed", G_CALLBACK (on_underline_changed), this);
	m_StrikethroughBtn = GTK_TOGGLE_BUTTON (glade_xml_get_widget (xml, "strikethrough"));
	m_StrikethroughSignal = g_signal_connect (G_OBJECT (m_StrikethroughBtn), "toggled", G_CALLBACK (on_strikethrough_toggled), this);
	m_RiseButton = GTK_SPIN_BUTTON (glade_xml_get_widget (xml, "rise"));
	m_RiseSignal = g_signal_connect (G_OBJECT (m_RiseButton), "value-changed", G_CALLBACK (on_rise_changed), this);
	m_ColorSelector = GO_SELECTOR (go_color_selector_new (RGBA_BLACK, RGBA_BLACK, "fore"));
	go_color_selector_set_allow_alpha (m_ColorSelector, false);
	m_ForeSignal = g_signal_connect (G_OBJECT (m_ColorSelector), "activate", G_CALLBACK (on_fore_color_changed), this);
	gtk_widget_show (GTK_WIDGET (m_ColorSelector));
	gtk_table_attach (GTK_TABLE (glade_xml_get_widget (xml, "table2")), GTK_WIDGET (m_ColorSelector), 1, 2, 0, 1, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 0, 0);
	return glade_xml_get_widget (xml, "fontsel");
}

void gcpTextTool::OnSelectFamily (GtkTreeSelection *selection)
{
	GtkTreeModel *model;
	GtkTreeIter iter, selected;
	char const *name;
	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
		return;
	gtk_tree_model_get (model, &iter, 0, &m_FamilyName, -1);
	PangoFontFamily *family = m_Families[m_FamilyName];
	PangoFontFace **faces;
	int i, besti, nb;
	g_signal_handler_block (m_FaceSel, m_FaceSignal);
	pango_font_family_list_faces (family, &faces, &nb);
	gtk_list_store_clear (m_FaceList);
	map<string, PangoFontFace*>::iterator j, jend = m_Faces.end ();
	for (j = m_Faces.begin (); j != jend; j++) {
		g_object_unref ((*j).second);
	}
	m_Faces.clear ();
	PangoFontDescription *desc;
	int distance, best;
	PangoStyle Style;
	PangoWeight Weight;
	PangoVariant Variant;
	PangoStretch Stretch;

	best = 32000; // This should be enough
	for (i = 0; i < nb; i++) {
		name = pango_font_face_get_face_name (faces[i]);
		desc = pango_font_face_describe (faces[i]);
		m_Faces[name] = (PangoFontFace*) g_object_ref (faces[i]);
		gtk_list_store_append (m_FaceList, &iter);
		gtk_list_store_set (m_FaceList, &iter,
				  0, name,
				  -1);
		// Try to select the best available face
		Style = pango_font_description_get_style (desc);
		Weight = pango_font_description_get_weight (desc);
		Variant = pango_font_description_get_variant (desc);
		Stretch = pango_font_description_get_stretch (desc);
		distance = abs (Weight - m_Weight)
						+ abs ((Style? Style + 2: 0) - (m_Style? m_Style + 2: 0)) * 1000
						+ abs (Variant - m_Variant) * 10 + abs (Stretch - m_Stretch);
		if (distance < best) {
			best = distance;
			selected = iter;
			besti = i;
		}
		// TODO: write this code
		pango_font_description_free (desc);
	}
	g_signal_handler_unblock (m_FaceSel, m_FaceSignal);
	GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_FaceList), &selected);
	if (path) {
		gtk_tree_selection_select_path (GTK_TREE_SELECTION (m_FaceSel), path);
		gtk_tree_path_free (path);
	} else {
		//TODO: choose a face when default is not available
	}
	if (m_Active) {
		PangoAttrList *l = pango_attr_list_new ();
		pango_attr_list_insert (l, pango_attr_family_new (m_FamilyName));
		pango_attr_list_insert (l, pango_attr_style_new (m_Style));
		pango_attr_list_insert (l, pango_attr_weight_new (m_Weight));
		pango_attr_list_insert (l, pango_attr_stretch_new (m_Stretch));
		pango_attr_list_insert (l, pango_attr_variant_new (m_Variant));
		gnome_canvas_pango_apply_attrs_to_selection (m_Active, l);
		pango_attr_list_unref (l);
	}
}

void gcpTextTool::OnSelectFace (GtkTreeSelection *selection)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	char const *name;
	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
		return;
	gtk_tree_model_get (model, &iter, 0, &name, -1);
	PangoFontFace *face = m_Faces[name];
	PangoFontDescription *desc = pango_font_face_describe (face);
	m_Style = pango_font_description_get_style (desc);
	m_Weight = pango_font_description_get_weight (desc);
	m_Variant = pango_font_description_get_variant (desc);
	m_Stretch = pango_font_description_get_stretch (desc);
	pango_font_description_free (desc);
	BuildAttributeList ();
	if (m_Active) {
		PangoAttrList *l = pango_attr_list_new ();
		pango_attr_list_insert (l, pango_attr_style_new (m_Style));
		pango_attr_list_insert (l, pango_attr_weight_new (m_Weight));
		pango_attr_list_insert (l, pango_attr_stretch_new (m_Stretch));
		pango_attr_list_insert (l, pango_attr_variant_new (m_Variant));
		gnome_canvas_pango_apply_attrs_to_selection (m_Active, l);
		pango_attr_list_unref (l);
	}
}

void gcpTextTool::OnSelectSize (int size)
{
	m_Size = size;
	SetSizeFull (false);
}

void gcpTextTool::OnSizeChanged ()
{
	char const *text = gtk_entry_get_text (m_SizeEntry);
	m_Size = (int) (MAX (0.1, atof (text) * PANGO_SCALE + 0.5));
	SetSizeFull (true);
}

void gcpTextTool::SetSizeFull (bool update_list)
{
	char *buf = g_strdup_printf ("%.1f", (double) m_Size / PANGO_SCALE);
	gtk_entry_set_text (m_SizeEntry, buf);
	g_free (buf);
	if (update_list) {
		GtkTreeIter iter;
		bool found = false;
		GtkTreeSelection *selection = gtk_tree_view_get_selection (m_SizesTree);
		g_signal_handler_block (selection, m_SizeSignal);
		
		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (m_SizeList), &iter);
		for (unsigned i = 0; i < G_N_ELEMENTS (font_sizes) && !found; i++) {
			if (font_sizes[i] * PANGO_SCALE == m_Size) {
				GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_SizeList), &iter);
				gtk_tree_view_set_cursor (m_SizesTree, path, NULL, FALSE);
				gtk_tree_path_free (path);
				found = true;
			}

			gtk_tree_model_iter_next (GTK_TREE_MODEL (m_SizeList), &iter);
		}
		
		if (!found)
			gtk_tree_selection_unselect_all (selection);
		g_signal_handler_unblock (selection, m_SizeSignal);
	}
	BuildAttributeList ();
	if (m_Active) {
		PangoAttrList *l = pango_attr_list_new ();
		pango_attr_list_insert (l, pango_attr_size_new (m_Size));
		gnome_canvas_pango_apply_attrs_to_selection (m_Active, l);
		pango_attr_list_unref (l);
	}
}

void gcpTextTool::SelectBestFontFace ()
{
	PangoFontDescription *desc;
	int distance, best;
	PangoStyle Style;
	PangoWeight Weight;
	PangoVariant Variant;
	PangoStretch Stretch;
	map <string, PangoFontFace*>::iterator i, iend = m_Faces.end ();
	char const *name = NULL, *buf;
	GtkTreeIter iter;

	best = 32000; // This should be enough
	for (i = m_Faces.begin (); i != iend; i++) {
		desc = pango_font_face_describe ((*i).second);
		// Try to select the best available face
		Style = pango_font_description_get_style (desc);
		Weight = pango_font_description_get_weight (desc);
		Variant = pango_font_description_get_variant (desc);
		Stretch = pango_font_description_get_stretch (desc);
		distance = abs (Weight - m_Weight)
						+ abs ((Style? Style + 2: 0) - (m_Style? m_Style + 2: 0)) * 1000
						+ abs (Variant - m_Variant) * 10 + abs (Stretch - m_Stretch);
		if (distance < best) {
			best = distance;
			name = (*i).first.c_str ();
		}
		pango_font_description_free (desc);
	}
	// select the found face
	gtk_tree_model_get_iter_first (GTK_TREE_MODEL (m_FaceList), &iter);
	do {
		gtk_tree_model_get (GTK_TREE_MODEL (m_FaceList), &iter, 0, &buf, -1);
		if (!strcmp (name, buf)) {
			m_Dirty = true;
			GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_FaceList), &iter);
			gtk_tree_view_set_cursor (m_FacesTree, path, NULL, FALSE);
			gtk_tree_path_free (path);
			if (m_Dirty)
				OnSelectFace ((GtkTreeSelection*) m_FaceSel);
			break;
		}
	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (m_FaceList), &iter));
}

int gcpTextTool::GetIndex ()
{
	return gnome_canvas_pango_get_cur_index (m_Active);
}

void gcpTextTool::OnUnderlineChanged (unsigned underline)
{
	m_Underline = (PangoUnderline) underline;
	BuildAttributeList ();
	if (m_Active) {
		PangoAttrList *l = pango_attr_list_new ();
		pango_attr_list_insert (l, pango_attr_underline_new (m_Underline));
		gnome_canvas_pango_apply_attrs_to_selection (m_Active, l);
		pango_attr_list_unref (l);
	}
}

void gcpTextTool::OnStriketroughToggled (bool strikethrough)
{
	m_Strikethrough = strikethrough;
	BuildAttributeList ();
	if (m_Active) {
		PangoAttrList *l = pango_attr_list_new ();
		pango_attr_list_insert (l, pango_attr_strikethrough_new (m_Strikethrough));
		gnome_canvas_pango_apply_attrs_to_selection (m_Active, l);
		pango_attr_list_unref (l);
	}
}

void gcpTextTool::OnPositionChanged (int position)
{
	m_Rise = position * PANGO_SCALE;
	BuildAttributeList ();
}

void gcpTextTool::OnForeColorChanged (GOColor color)
{
	m_Color = color;
	BuildAttributeList ();
	if (m_Active) {
		PangoAttrList *l = pango_attr_list_new ();
		pango_attr_list_insert (l, pango_attr_foreground_new (UINT_RGBA_R (m_Color) * 0x101, UINT_RGBA_G (m_Color) * 0x101, UINT_RGBA_B (m_Color) * 0x101));
		gnome_canvas_pango_apply_attrs_to_selection (m_Active, l);
		pango_attr_list_unref (l);
	}
}


syntax highlighted by Code2HTML, v. 0.9.1