// -*- C++ -*-

/* 
 * GChemPaint library
 * view.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 "widgetdata.h"
#include "view.h"
#include "settings.h"
#include "document.h"
#include "application.h"
#include "tool.h"
#include "tools.h"
#include "text.h"
#include "theme.h"
#include "window.h"
#include "libgcpcanvas/gprintable.h"
#include "libgcpcanvas/gcp-canvas-group.h"
#include <libgnomevfs/gnome-vfs-file-info.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <unistd.h>
#include <pango/pango-context.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n-lib.h>
#include <libgnomeprint/gnome-print-job.h>
#include <cmath>
#include <clocale>
#ifdef HAVE_FSTREAM
#	include <fstream>
#else
#	include <fstream.h>
#endif
#include <map>
#include <string>
#include <cstring>

/*
Derivation of a new widget from gnome_canvas with an event for updating canvas size
*/

typedef struct _GnomeCanvasGCP           GnomeCanvasGCP;
typedef struct _GnomeCanvasGCPClass      GnomeCanvasGCPClass;

#define GNOME_TYPE_CANVAS_GCP            (gnome_canvas_gcp_get_type ())
#define GNOME_CANVAS_GCP(obj)            (GTK_CHECK_CAST ((obj), GNOME_TYPE_CANVAS_GCP, GnomeCanvasGCP))
#define GNOME_CANVAS_CLASS_GCP(klass)    (GTK_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_GCP, GnomeCanvasGCPClass))
#define GNOME_IS_CANVAS_GCP(obj)         (GTK_CHECK_TYPE ((obj), GNOME_TYPE_CANVAS_GCP))
#define GNOME_IS_CANVAS_GCP_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_GCP))
#define GNOME_CANVAS_GCP_GET_CLASS(obj)  (GTK_CHECK_GET_CLASS ((obj), GNOME_TYPE_CANVAS_GCP, GnomeCanvasGCPClass))

enum {
  UPDATE_BOUNDS,
  LAST_SIGNAL
};

struct _GnomeCanvasGCP {
	GnomeCanvas canvas;
};

struct _GnomeCanvasGCPClass {
	GnomeCanvasClass parent_class;

	void (* update_bounds) (GnomeCanvasGCP *canvas);
};

GType gnome_canvas_gcp_get_type (void) G_GNUC_CONST;
static void gnome_canvas_gcp_class_init(GnomeCanvasGCPClass *Class);
static void gnome_canvas_gcp_init(GnomeCanvasGCP *canvas);
static void gnome_canvas_gcp_update_bounds(GnomeCanvasGCP *canvas);
static GtkWidget *gnome_canvas_gcp_new (void);

static guint gnome_canvas_gcp_signals[LAST_SIGNAL] = { 0 };

GType
gnome_canvas_gcp_get_type (void)
{
	static GType canvas_gcp_type;

	if (!canvas_gcp_type) {
		static const GTypeInfo object_info = {
			sizeof(GnomeCanvasGCPClass),
			(GBaseInitFunc)NULL,
			(GBaseFinalizeFunc)NULL,
			(GClassInitFunc)gnome_canvas_gcp_class_init,
			(GClassFinalizeFunc)NULL,
			NULL,			/* class_data */
			sizeof(GnomeCanvasGCP),
			0,			/* n_preallocs */
			(GInstanceInitFunc)gnome_canvas_gcp_init,
			NULL			/* value_table */
		};

		canvas_gcp_type = g_type_register_static(GNOME_TYPE_CANVAS, "GnomeCanvasGCP",
						      &object_info, (GTypeFlags)0);
	}

	return canvas_gcp_type;
}

GtkWidget *gnome_canvas_gcp_new(void)
{
	return GTK_WIDGET(g_object_new(GNOME_TYPE_CANVAS_GCP, "aa", TRUE, NULL));
}

void gnome_canvas_gcp_class_init(GnomeCanvasGCPClass *Class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (Class);
	gnome_canvas_gcp_signals[UPDATE_BOUNDS] =
	g_signal_new ("update_bounds",
				  G_TYPE_FROM_CLASS(gobject_class),
				  G_SIGNAL_RUN_LAST,
				  G_STRUCT_OFFSET(GnomeCanvasGCPClass, update_bounds),
				  NULL, NULL,
				  g_cclosure_marshal_VOID__VOID,
				  G_TYPE_NONE, 0
				  );
	Class->update_bounds = gnome_canvas_gcp_update_bounds;
}

void gnome_canvas_gcp_init(GnomeCanvasGCP *canvas)
{
}

void gnome_canvas_gcp_update_bounds(GnomeCanvasGCP *canvas)
{
	while (gtk_events_pending()) gtk_main_iteration();
	gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(canvas), "data");
	double x1, y1, x2, y2;
	gnome_canvas_item_get_bounds(GNOME_CANVAS_ITEM(pData->Group), &x1, &y1, &x2, &y2);
	gcpView *pView = (gcpView*)g_object_get_data(G_OBJECT(canvas), "view");
	pView->UpdateSize(x1, y1, x2, y2);
}

bool on_event(GnomeCanvasItem *item, GdkEvent *event, GtkWidget* widget)
{
	gcpView* pView = (gcpView*) g_object_get_data(G_OBJECT(widget), "view");
	return pView->OnEvent(item, event, widget);
}

static bool on_destroy(GtkWidget *widget, gcpView * pView)
{
	pView->OnDestroy(widget);
	return true;
}

static bool on_size (GtkWidget *widget, GtkAllocation *alloc, gcpView * pView)
{
	pView->OnSize (widget, alloc->width, alloc->height);
	return true;
}

void on_receive(GtkClipboard *clipboard, GtkSelectionData *selection_data, gcpView * pView)
{
	pView->OnReceive(clipboard, selection_data);
}

using namespace std;

gcpView::gcpView (gcpDocument *pDoc, bool Embedded)
{
	m_pDoc = pDoc;
	gcpTheme *pTheme = pDoc->GetTheme ();
	m_PangoFontDesc = pango_font_description_new ();
	pango_font_description_set_family (m_PangoFontDesc, pTheme->GetFontFamily ());
	pango_font_description_set_style (m_PangoFontDesc, pTheme->GetFontStyle ());
	pango_font_description_set_weight (m_PangoFontDesc, pTheme->GetFontWeight ());
	pango_font_description_set_variant (m_PangoFontDesc, pTheme->GetFontVariant ());
	pango_font_description_set_stretch (m_PangoFontDesc, pTheme->GetFontStretch ());
	pango_font_description_set_size (m_PangoFontDesc, pTheme->GetFontSize ());
	m_sFontName = pango_font_description_to_string (m_PangoFontDesc);
	m_PangoSmallFontDesc = pango_font_description_new ();
	pango_font_description_set_family (m_PangoSmallFontDesc, pTheme->GetFontFamily ());
	pango_font_description_set_style (m_PangoSmallFontDesc, pTheme->GetFontStyle ());
	pango_font_description_set_weight (m_PangoSmallFontDesc, pTheme->GetFontWeight ());
	pango_font_description_set_variant (m_PangoSmallFontDesc, pTheme->GetFontVariant ());
	pango_font_description_set_stretch (m_PangoSmallFontDesc, pTheme->GetFontStretch ());
	pango_font_description_set_size (m_PangoSmallFontDesc, pTheme->GetFontSize () * 2 / 3);
	m_sSmallFontName = pango_font_description_to_string (m_PangoSmallFontDesc);
	m_width = 400;
	m_height = 300;
	m_ActiveRichText = NULL;
	m_bEmbedded = Embedded;
	m_UIManager = gtk_ui_manager_new ();
	m_Dragging = false;
	m_pWidget = NULL;
	m_PangoContext = NULL;
	m_CurObject = NULL;
}

gcpView::~gcpView ()
{
	if (m_PangoContext)
		g_object_unref (G_OBJECT (m_PangoContext));
	if (m_sFontName)
		g_free (m_sFontName);
	if (m_sSmallFontName)
		g_free (m_sSmallFontName);
	pango_font_description_free (m_PangoFontDesc);
	pango_font_description_free (m_PangoSmallFontDesc);
	g_object_unref (m_UIManager);
}

bool gcpView::OnEvent(GnomeCanvasItem *item, GdkEvent *event, GtkWidget* widget)
{
	gcpApplication *App = m_pDoc->GetApplication ();
	gcpTheme *pTheme = m_pDoc->GetTheme ();
	gcpTool* pActiveTool = App? App->GetActiveTool (): NULL;
	if ((!m_pDoc->GetEditable ()) || (!pActiveTool))
		return true;
	m_CurObject = (item) ? (Object*) g_object_get_data (G_OBJECT (item), "object") : NULL;
	if (item == (GnomeCanvasItem*)m_ActiveRichText)
	{
		GnomeCanvasItemClass* klass = GNOME_CANVAS_ITEM_CLASS(((GTypeInstance*)item)->g_class);
		return klass->event(item, event);
	}
	else if (pActiveTool->OnEvent(event)) return true;
	m_pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(widget), "data");
	m_pWidget = widget;
	double x, y;
	x = event->button.x;
	y = event->button.y;
	gnome_canvas_item_w2i(GNOME_CANVAS_ITEM(m_pData->Group), &x, &y);
	if (event->type == GDK_BUTTON_PRESS)
	{
		if (item == m_pData->Background)
		{
			item = NULL;
			std::map<Object*, GnomeCanvasGroup*>::iterator i = m_pData->Items.begin();
			gcpBond* pBond;
			while (i != m_pData->Items.end())
			{
				if ((*i).first->GetType() == BondType)
				{
					pBond = (gcpBond*)(*i).first;
					if (pBond->GetDist(x / pTheme->GetZoomFactor (), y / pTheme->GetZoomFactor ()) < (pTheme->GetPadding () + pTheme->GetBondWidth () / 2) / pTheme->GetZoomFactor ())
					{
						item = GNOME_CANVAS_ITEM((*i).second);
						m_CurObject = pBond;
						break;
					}
				}
				i++;
			}
		}
	}
	Object *pAtom;
	if (m_CurObject && ((pAtom = m_CurObject->GetAtomAt (x / pTheme->GetZoomFactor (), y / pTheme->GetZoomFactor ()))))
			m_CurObject = pAtom;
	switch (event->type)
	{
	case GDK_BUTTON_PRESS:
		switch (event->button.button)
		{
			case 1:
			{
				if (m_Dragging) break;
				bool result = pActiveTool->OnClicked(this, m_CurObject, x, y, event->button.state);
				if (item && (item == (GnomeCanvasItem*)m_ActiveRichText))
				{
					GnomeCanvasItemClass* klass = GNOME_CANVAS_ITEM_CLASS(((GTypeInstance*)item)->g_class);
					return klass->event(item, event);
				}
				m_Dragging = result;
				return true;
			}
			case 2:
			{
				m_lastx = x;
				m_lasty = y;
				GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
				OnPasteSelection (m_pWidget, clipboard);
				return true;
			}
			case 3: {
				bool result;
				g_object_unref (m_UIManager);
			 	m_UIManager = gtk_ui_manager_new ();
				result = pActiveTool->OnRightButtonClicked(this, m_CurObject, event->button.x, event->button.y, m_UIManager);
				if (m_CurObject)
					result |= m_CurObject->BuildContextualMenu (m_UIManager, m_CurObject, x / GetZoomFactor (), y / GetZoomFactor ());
				if (result) {
					GtkWidget *w = gtk_ui_manager_get_widget (m_UIManager, "/popup");
					gtk_menu_popup (GTK_MENU (w), NULL, NULL, NULL, NULL, 3,  gtk_get_current_event_time ());
					return true;
				}
			}
		}
		break;
	case GDK_MOTION_NOTIFY:
		if (!m_Dragging) break;
		pActiveTool->OnDrag(x, y, event->button.state);
		return true;
	case GDK_BUTTON_RELEASE:
	switch (event->button.button)
		{
		case 1:
			if (!m_Dragging) break;
			m_Dragging = false;
			pActiveTool->OnRelease(x, y, event->button.state);
			m_pDoc->GetApplication()->ClearStatus();
			return true;
		}
		break;
	default:
		break;
	}
	return false;
}

void gcpView::AddObject(Object* pObject)
{
	std::list<GtkWidget*>::iterator i;
	for (i = m_Widgets.begin(); i != m_Widgets.end(); i++)
		pObject->Add(*i);
}

GtkWidget* gcpView::CreateNewWidget()
{
	gtk_widget_push_colormap(gdk_rgb_get_colormap());
	m_pWidget = gnome_canvas_gcp_new();
	gtk_widget_pop_colormap();
	GtkWidget* pWidget = (m_Widgets.size() > 0) ? m_Widgets.front() : NULL;
	if (m_pWidget)
	{
		g_object_set_data(G_OBJECT(m_pWidget), "view", this);
		g_object_set_data(G_OBJECT(m_pWidget), "doc", m_pDoc);
		m_pData = new gcpWidgetData();
		m_pData->Canvas = m_pWidget;
		g_object_set_data(G_OBJECT(m_pWidget), "data", m_pData);
		m_pData->View = this;
		gnome_canvas_set_pixels_per_unit(GNOME_CANVAS(m_pWidget), 1);
		gnome_canvas_set_scroll_region(GNOME_CANVAS(m_pWidget), 0, 0, m_width, m_height);
		m_pData->Zoom = 1.0;
		m_pData->Background = gnome_canvas_item_new(
									gnome_canvas_root(GNOME_CANVAS(m_pWidget)),
									gnome_canvas_rect_get_type(),
									"x1", 0.0,
									"y1", 0.0,
									"x2", (double) m_width,
									"y2", (double) m_height,
									"fill_color", "white",
									NULL);
		m_pData->Group = GNOME_CANVAS_GROUP (gnome_canvas_item_new (
									gnome_canvas_root(GNOME_CANVAS(m_pWidget)),
									gnome_canvas_group_ext_get_type (),
									NULL));
		if (m_pDoc->GetEditable ())
			g_signal_connect(G_OBJECT(m_pData->Background), "event", G_CALLBACK(on_event), m_pWidget);
		g_signal_connect(G_OBJECT(m_pWidget), "destroy", G_CALLBACK(on_destroy), this);
		g_signal_connect(G_OBJECT(m_pWidget), "size_allocate", G_CALLBACK(on_size), this);
		g_signal_connect(G_OBJECT(m_pWidget), "realize", G_CALLBACK(gnome_canvas_gcp_update_bounds), this);
		gtk_widget_show(m_pWidget);
		m_Widgets.push_back(m_pWidget);
		if (pWidget)
		{
			gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(pWidget), "data");
			std::map<Object*, GnomeCanvasGroup*>::iterator i, iend = pData->Items.end();
			for (i = pData->Items.begin(); i != iend; i++)
				if ((*i).first->GetType () != BondType)
				 (*i).first->Add(m_pWidget);
			for (i = pData->Items.begin(); i != iend; i++)
				if ((*i).first->GetType () == BondType)
				 (*i).first->Add(m_pWidget);
		}
		else
		{
			m_PangoContext = gtk_widget_create_pango_context(m_pWidget);
			g_object_ref(G_OBJECT(m_PangoContext));
			UpdateFont();
		}
	}
	return m_pWidget;
}

void gcpView::OnDestroy(GtkWidget* widget)
{
	if (m_bEmbedded)
	{
		delete (gcpWidgetData*)g_object_get_data(G_OBJECT(widget), "data");
		m_Widgets.remove(widget);
	}
	else delete m_pDoc;
}

GnomeCanvasItem* gcpView::GetCanvasItem (GtkWidget* widget, Object* Object)
{
	gcpWidgetData* pData = reinterpret_cast<gcpWidgetData*> (g_object_get_data (G_OBJECT (widget), "data"));
	if ((!pData) || (pData->View != this))
		return NULL;
	GnomeCanvasItem *result = reinterpret_cast<GnomeCanvasItem*> (pData->Items[Object]);
	if (!result)
		pData->Items.erase (Object);
	return result;
}

void gcpView::Print(GnomePrintContext *pc, gdouble width, gdouble height)
{
	g_return_if_fail (G_IS_PRINTABLE (m_pData->Group));
	gnome_print_gsave(pc);
	double matrix [6] = {.75, 0., 0., -.75, 0., height};
	if (!m_bEmbedded)	//FIXME: add a print setup dialog and use real margins values
	{
		matrix[4] += 30;
		matrix[5] -= 30;
	}
	m_pData->ShowSelection(false);
	Object* pObj = NULL;
	if (m_ActiveRichText)
	{
		pObj = (Object*) g_object_get_data(G_OBJECT(m_ActiveRichText), "object");
		if (pObj) pObj->SetSelected(m_pWidget, SelStateUnselected);
	}
	gnome_print_concat (pc, matrix);
	GPrintable* printable = G_PRINTABLE (m_pData->Group);
	(* G_PRINTABLE_GET_IFACE (printable)->print) (G_PRINTABLE (printable), pc); 
	gnome_print_grestore(pc);
	m_pData->ShowSelection(true);
	if (pObj) pObj->SetSelected(m_pWidget, SelStateUpdating);
}

void gcpView::Update(Object* pObject)
{
	std::list<GtkWidget*>::iterator i;
	for (i = m_Widgets.begin(); i != m_Widgets.end(); i++)
	{
		pObject->Update(*i);
	}
}

GnomeCanvasItem* gcpView::GetBackground()
{
	return m_pData->Background;
}

double gcpView::GetZoomFactor()
{
	return m_pDoc->GetTheme ()->GetZoomFactor ();
}

void gcpView::UpdateFont()
{
	pango_context_set_font_description(m_PangoContext, m_PangoFontDesc);
	PangoLayout* pl = pango_layout_new(m_PangoContext);
	PangoRectangle rect;
	pango_layout_set_text(pl, "lj", 2);
	pango_layout_get_extents(pl, &rect, NULL);
	m_dFontHeight = rect.height / PANGO_SCALE;
	g_object_unref(G_OBJECT(pl));
	pl = pango_layout_new(m_PangoContext);
	pango_layout_set_text(pl, "C", 1);
	pango_layout_get_extents(pl, &rect, NULL);
	m_BaseLineOffset =  (double(rect.height / PANGO_SCALE) / 2.0) / m_pDoc->GetTheme ()->GetZoomFactor ();
	g_object_unref(G_OBJECT(pl));
}
	
void gcpView::Remove(Object* pObject)
{
	std::list<GtkWidget*>::iterator i;
	for (i = m_Widgets.begin(); i != m_Widgets.end(); i++)
	{
		gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(*i), "data");
		Object* pObj = pObject->GetMolecule();
		if (pObj) pData->SelectedObjects.remove(pObj);
		else pData->SelectedObjects.remove(pObject);
		if (pData->Items[pObject]) gtk_object_destroy(GTK_OBJECT(pData->Items[pObject]));
		pData->Items.erase(pObject);
	}
}

void gcpView::OnDeleteSelection (GtkWidget* w)
{
	m_pWidget = w;
	gcpApplication*	pApp = m_pDoc->GetApplication ();
	gcpTool* pActiveTool = pApp->GetActiveTool ();
	if (!pActiveTool->DeleteSelection ())
	{
		m_pData = (gcpWidgetData*) g_object_get_data (G_OBJECT(w), "data");
		gcpWidgetData* pData;
		std::list<GtkWidget*>::iterator j;
		for (j = m_Widgets.begin (); j != m_Widgets.end (); j++) {
			if (*j == m_pWidget)
				continue;
			pData = (gcpWidgetData*) g_object_get_data(G_OBJECT(*j), "data");
			pData->UnselectAll ();
		}
		Object *pObject, *Group;
		set<string> ModifiedObjects;
		bool modify = false;
		list<Object *>::iterator i, iend = m_pData->SelectedObjects.end ();
		// first search if we are deleting top objects or not
		for (i = m_pData->SelectedObjects.begin (); i != iend; i++)
			if ((*i)->GetGroup ()) {
				modify = true;
				break;
			}
		gcpOperation *Op = m_pDoc->GetNewOperation (((modify)?
								GCP_MODIFY_OPERATION: GCP_DELETE_OPERATION));
		while (!m_pData->SelectedObjects.empty ()) {
			pObject = m_pData->SelectedObjects.front ();
			Group = pObject->GetGroup ();
			if (Group && 
				(ModifiedObjects.find (Group->GetId ()) == ModifiedObjects.end ())) {
				Op->AddObject (Group);
				ModifiedObjects.insert (Group->GetId ());
			} else
				Op->AddObject (pObject);
			m_pData->SelectedObjects.front ()->Lock ();
			m_pDoc->Remove (m_pData->SelectedObjects.front ());
		}
		m_pData->ClearSelection ();
		set<string>::iterator k, kend = ModifiedObjects.end();
		for (k = ModifiedObjects.begin(); k != kend; k++) {
			pObject = m_pDoc->GetDescendant ((*k).c_str ());
			if (pObject)
				Op->AddObject (pObject, 1);
		}
	}
	m_pDoc->FinishOperation ();
	gcpWindow *Win = m_pDoc->GetWindow ();
	Win->ActivateActionWidget ("/MainMenu/EditMenu/Copy", false);
	Win->ActivateActionWidget ("/MainMenu/EditMenu/Cut", false);
	Win->ActivateActionWidget ("/MainMenu/EditMenu/Erase", false);
}

GtkTargetEntry const 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 gcpView::OnCopySelection(GtkWidget* w, GtkClipboard* clipboard)
{
	gcpApplication*	pApp = m_pDoc->GetApplication();
	gcpTool* pActiveTool = pApp->GetActiveTool();
	m_pWidget = w;
	m_pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(w), "data");
	if (!pActiveTool->CopySelection(clipboard)) m_pData->Copy(clipboard);
}

void gcpView::OnReceive(GtkClipboard* clipboard, GtkSelectionData* selection_data)
{
	if ((selection_data->length <= 0) || !selection_data->data) return;
	gcpApplication*	pApp = m_pDoc->GetApplication();
	gcpTool* pActiveTool = pApp->GetActiveTool();
	guint *DataType = (clipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))? &ClipboardDataType: &ClipboardDataType1;
	g_return_if_fail((selection_data->target == gdk_atom_intern (targets[*DataType].target, FALSE)));
	if (pActiveTool->OnReceive(clipboard, selection_data, *DataType)) return;
//	if (ToolsDlg) ToolsDlg->Select(SelectId);
	else if (pActiveTool/* && pActiveTool->Activate(false)*/)
	{
		pApp->ActivateTool("Select", true);
		pActiveTool = pApp->GetActiveTool();
//		if (pActiveTool) pActiveTool->Activate(true);
	}
	if (!pActiveTool || (pActiveTool != pApp->GetTool("Select"))) return;
	xmlDocPtr xml;
	m_pData->UnselectAll();
	switch (*DataType) {
		case GCP_CLIPBOARD_NATIVE:
			xml = xmlParseMemory ((const char*) selection_data->data, selection_data->length);
			m_pDoc->AddData (xml->children->children);
			xmlFreeDoc (xml);
			break;
		case GCP_CLIPBOARD_UTF8_STRING: {
				gcpText* text = new gcpText ();
				text->SetText ((char const*) selection_data->data);
				text->OnChanged (true);
				m_pDoc->AddObject (text);
				m_pData->SetSelected (text);
			}
			break;
		case GCP_CLIPBOARD_STRING: {
				gcpText* text = new gcpText ();
				if (!g_utf8_validate ((const char*) selection_data->data, selection_data->length, NULL)) {
					gsize r, w;
					gchar* newstr = g_locale_to_utf8 ((const char*) selection_data->data, selection_data->length, &r, &w, NULL);
					text->SetText (newstr);
					g_free (newstr);
				} else
					text->SetText ((char const*) selection_data->data);
				text->OnChanged (true);
				m_pDoc->AddObject (text);
				m_pData->SetSelected (text);
			}
			break;
	}
	ArtDRect rect;
	double dx, dy;
	while(gtk_events_pending()) gtk_main_iteration();
	m_pDoc->AbortOperation();
	m_pData->GetSelectionBounds(rect);
	if (clipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
	{
		//center the pasted data at the center of the visible area
		if (m_bEmbedded)
		{
			dx = m_pWidget->allocation.width / 2. - (rect.x0 + rect.x1) / 2.;
			dy = m_pWidget->allocation.height / 2. - (rect.y0 + rect.y1) / 2.;
		}
		else
		{
			GtkAdjustment *horiz, *vert;
			GtkWidget* parent = gtk_widget_get_parent(m_pWidget);
			horiz = gtk_viewport_get_hadjustment(GTK_VIEWPORT(parent));
			vert = gtk_viewport_get_vadjustment(GTK_VIEWPORT(parent));
			dx = horiz->value + horiz->page_size / 2.  - (rect.x0 + rect.x1) / 2.;
			dy = vert->value + vert->page_size / 2.  - (rect.y0 + rect.y1) / 2.;
		}
	}
	else //center the pasted data at the mouse click position
	{
		dx = m_lastx - (rect.x0 + rect.x1) / 2.;
		dy = m_lasty - (rect.y0 + rect.y1) / 2.;
	}
	m_pData->MoveSelection(dx, dy);
	gcpTool *Tool = pApp->GetTool("Select");
	if (Tool) Tool->AddSelection(m_pData);
	m_pDoc->PopOperation();
	gcpOperation* pOp = m_pDoc->GetNewOperation(GCP_ADD_OPERATION);
	std::list<Object*>::iterator i;
	for (i = m_pData->SelectedObjects.begin(); i != m_pData->SelectedObjects.end(); i++)
		pOp->AddObject(*i);
	m_pDoc->FinishOperation();
	gnome_canvas_gcp_update_bounds(GNOME_CANVAS_GCP(m_pData->Canvas));
}

void gcpView::OnPasteSelection(GtkWidget* w, GtkClipboard* clipboard)
{
	gcpApplication*	pApp = m_pDoc->GetApplication();
	gcpTool* pActiveTool = pApp->GetActiveTool();
	if (pActiveTool->PasteSelection(clipboard)) return;
	m_pWidget = w;
	m_pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(w), "data");
	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, this);
}

void gcpView::OnCutSelection(GtkWidget* w, GtkClipboard* clipboard)
{
	gcpApplication*	pApp = m_pDoc->GetApplication();
	gcpTool* pActiveTool = pApp->GetActiveTool();
	if (!pActiveTool->CutSelection(clipboard))
	{
		OnCopySelection(w, clipboard);
		OnDeleteSelection(w);
	}
	gcpWindow *Win = m_pDoc->GetWindow ();
	Win->ActivateActionWidget ("/MainMenu/EditMenu/Copy", false);
	Win->ActivateActionWidget ("/MainMenu/EditMenu/Cut", false);
	Win->ActivateActionWidget ("/MainMenu/EditMenu/Erase", false);
}

static void do_set_symbol (GtkAction *action, Object *obj)
{
	gcpDocument* pDoc = static_cast<gcpDocument*> (obj->GetDocument ());
	gcpApplication *App = static_cast<gcpApplication*> (pDoc->GetApplication ());
	gcpTools *tools = static_cast<gcpTools*> (App->GetDialog ("tools"));
	int Z = Element::Z (gtk_action_get_name(action));
	tools->SetElement (Z);
	if (obj->GetType () == AtomType) {
		Atom *atom = static_cast<Atom*> (obj);
		if (atom->GetZ () == Z)
			return;
		Object *group = obj->GetGroup ();
		gcpOperation *op = pDoc->GetNewOperation (GCP_MODIFY_OPERATION);
		op->AddObject (group);
		atom->SetZ (Z);
		pDoc->GetView ()->Update (obj);
		op->AddObject (group, 1);
		pDoc->FinishOperation ();
	}
}

bool gcpView::OnKeyPress(GtkWidget* w, GdkEventKey* event)
{
	gcpApplication*	pApp = m_pDoc->GetApplication();
	gcpTool* pActiveTool = pApp->GetActiveTool();
	if (pActiveTool->OnEvent ((GdkEvent*) event))
		return true;
	switch (event->keyval) {
	case GDK_Delete:
	case GDK_Clear:
	case GDK_BackSpace:
		OnDeleteSelection(w);
		return true;
	case GDK_Shift_L:
	case GDK_Shift_R:
		if (pActiveTool)
			pActiveTool->OnKeyPressed (GDK_SHIFT_MASK);
		return true;
	case GDK_Control_L:
	case GDK_Control_R:
		if (pActiveTool)
			pActiveTool->OnKeyPressed (GDK_CONTROL_MASK);
		return true;
	case GDK_Alt_L:
	case GDK_Alt_R:
		if (pActiveTool)
			pActiveTool->OnKeyPressed (GDK_MOD1_MASK);//FIXME: might be not portable
		return true;
	default: {
			if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) != 0 || event->keyval > 127)
				break;
			// Now try to get the atom at the cursor
			gcpAtom *atom = dynamic_cast<gcpAtom*> (m_CurObject);
			unsigned min_bonds = (atom)? atom->GetTotalBondsNumber (): 0;
			int Z = 0;
			switch (event->keyval) {
			case GDK_a:
				Z = 13;
				break;
			case GDK_b:
				Z = 5;
				break;
			case GDK_c:
				Z = 6;
				break;
			case GDK_d:
				Z = 11;
				break;
			case GDK_e:
				Z = 34;
				break;
			case GDK_f:
				Z = 9;
				break;
			case GDK_g:
				Z = 32;
				break;
			case GDK_h:
				Z = 1;
				break;
			case GDK_i:
				Z = 53;
				break;
			case GDK_j:
				Z = 22;
				break;
			case GDK_k:
				Z = 19;
				break;
			case GDK_l:
				Z = 3;
				break;
			case GDK_m:
				Z = 12;
				break;
			case GDK_n:
				Z = 7;
				break;
			case GDK_o:
				Z = 8;
				break;
			case GDK_p:
				Z = 15;
				break;
			case GDK_q:
				Z = 14;
				break;
			case GDK_r:
				Z = 35;
				break;
			case GDK_s:
				Z = 16;
				break;
			case GDK_t:
				Z = 78;
				break;
			case GDK_u:
				Z = 29;
				break;
			case GDK_v:
				Z = 23;
				break;
			case GDK_w:
				Z = 74;
				break;
			case GDK_x:
				Z = 17;
				break;
			case GDK_y:
				Z = 39;
				break;
			case GDK_z:
				Z = 40;
				break;
			}
			if (Z) {
				if (!atom) {
					gcpTools *tools = static_cast<gcpTools*> (pApp->GetDialog ("tools"));
					tools->SetElement (Z);
				} else if (atom->GetZ () != Z && Element::GetElement (Z)->GetMaxBonds () >= min_bonds) {
					Object *group = atom->GetGroup ();
					gcpOperation *op = m_pDoc->GetNewOperation (GCP_MODIFY_OPERATION);
					op->AddObject (group);
					atom->SetZ (Z);
					Update (atom);
					op->AddObject (group, 1);
					m_pDoc->FinishOperation ();
				}
				return true;
			}
			// build a contextual menu of all known symbols starting with the pressed key.
			map<string, Element*> entries;
			string symbol;
			unsigned key = gdk_keyval_to_upper (event->keyval);
			for (int i = 1; i <= 128; i++) {
				Element *elt = Element::GetElement (i);
				if (!elt || elt->GetMaxBonds () < min_bonds)
					continue;
				symbol = elt->GetSymbol ();
				if ((unsigned char) symbol[0] == key) {
					entries[symbol] = elt;
				}
			}
			if (entries.empty ())
				break;
			map<string, Element*>::iterator i, end = entries.end ();
			g_object_unref (m_UIManager);
			m_UIManager = gtk_ui_manager_new ();
			GtkActionGroup *group = gtk_action_group_new ("element");;
			GtkAction *action;
			string ui;
			for (i = entries.begin (); i != end; i++) {
				symbol = (*i).first;
				symbol.insert (((symbol.length () > 1)? 1: 0), "_");
				action = GTK_ACTION (gtk_action_new ((*i).second->GetSymbol (), symbol.c_str (), (*i).second->GetName (), NULL));
				g_signal_connect (action, "activate", G_CALLBACK (do_set_symbol), (atom)? static_cast<Object*> (atom): static_cast<Object*> (m_pDoc));
				gtk_action_group_add_action (group, action);
				g_object_unref (action);
				ui = string ("<ui><popup><menuitem action='") + (*i).second->GetSymbol () + "'/></popup></ui>";
				gtk_ui_manager_add_ui_from_string (m_UIManager, ui.c_str (), -1, NULL);
			}
			gtk_ui_manager_insert_action_group (m_UIManager, group, 0);
			g_object_unref (group);
			GtkWidget *w = gtk_ui_manager_get_widget (m_UIManager, "/popup");
			gtk_menu_popup (GTK_MENU (w), NULL, NULL, NULL, NULL, 3,  gtk_get_current_event_time ());
			break;
		}
	}
	return false;
}

bool gcpView::OnKeyRelease(GtkWidget* w, GdkEventKey* event)
{
	gcpApplication*	pApp = m_pDoc->GetApplication();
	gcpTool* pActiveTool = pApp->GetActiveTool();
	switch(event->keyval)
	{
		case GDK_Shift_L:
		case GDK_Shift_R:
			if (pActiveTool) pActiveTool->OnKeyReleased(GDK_SHIFT_MASK);
			return true;
		case GDK_Control_L:
		case GDK_Control_R:
			if (pActiveTool) pActiveTool->OnKeyReleased(GDK_CONTROL_MASK);
			return true;
		case GDK_Alt_L:
		case GDK_Alt_R:
			if (pActiveTool) pActiveTool->OnKeyReleased(GDK_MOD1_MASK);//FIXME: might be not portable
			return true;
		default:
			break;
	}
	return false;
}
	
bool gcpView::OnSize (GtkWidget *w, int width, int height)
{
	gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (w), "data");
	gnome_canvas_set_scroll_region (GNOME_CANVAS (w), 0, 0, (double) width / pData->Zoom, (double) height / pData->Zoom);
	if (pData->Background)
		g_object_set (G_OBJECT (pData->Background), "x2", (double) width / pData->Zoom, "y2", (double) height / pData->Zoom, NULL);
	return true;
}

void gcpView::UpdateSize(double x1, double y1, double x2, double y2)
{
	if (x1 < 0.0) x2 -= x1;
	if (y1 < 0.0) y2 -= y1;
	list<GtkWidget*>::iterator i;
	if ((x2 != m_width) || (y2 != m_height))
		for (i = m_Widgets.begin(); i != m_Widgets.end(); i++) {
			gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(*i), "data");
			gtk_widget_set_size_request(*i,(int)ceil(x2 * pData->Zoom), (int)ceil(y2 * pData->Zoom));
		}
	if ((x1 < 0.0) || (y1 < 0.0))
	{
		x1 = - x1;
		y1 = - y1;
		gcpTheme* pTheme = m_pDoc->GetTheme ();
		m_pDoc->Move(x1 / pTheme->GetZoomFactor (), y1 / pTheme->GetZoomFactor ());
		Update(m_pDoc);
	}
}

void gcpView::SetGnomeCanvasPangoActive (GnomeCanvasPango* item)
{
	m_ActiveRichText = item;
	m_Dragging = false;
}

bool gcpView::PrepareUnselect()
{
	gcpApplication*	pApp = m_pDoc->GetApplication();
	gcpTool* pActiveTool = pApp->GetActiveTool();
	return (pActiveTool)? pActiveTool->NotifyViewChange(): true;
}

void gcpView::OnSelectAll()
{
	gcpApplication*	pApp = m_pDoc->GetApplication();
	gcpTool* pActiveTool = pApp->GetTool("Select");
	if (pActiveTool) pApp->ActivateTool("Select", true);
	m_pData->SelectAll();
	if (pActiveTool)
		pActiveTool->AddSelection(m_pData);
}

static gboolean do_save_image (const gchar *buf, gsize count, GError **error, gpointer data)
{
	GnomeVFSHandle *handle = (GnomeVFSHandle*) data;
	GnomeVFSFileSize written = 0;
	GnomeVFSResult res;
	while (count) {
		res = gnome_vfs_write (handle, buf, count, &written);
		if (res != GNOME_VFS_OK) {
			g_set_error (error, g_quark_from_static_string ("gchempaint"), res, gnome_vfs_result_to_string (res));
			return false;
		}
		count -= written;
	}
	return true;
}

void gcpView::ExportImage (string const &filename, const char* type, int resolution)
{
	ArtDRect rect;
	m_pData->GetObjectBounds (m_pDoc, &rect);
	m_pData->ShowSelection (false);
	int w = (int) (ceil (rect.x1) - floor (rect.x0)), h = (int) (ceil (rect.y1) - floor (rect.y0));
	if (!strcmp (type, "eps")) {
		GnomePrintConfig* config = gnome_print_config_default();
		GnomePrintContext *pc;
		GnomePrintJob *gpj = gnome_print_job_new (config);
		pc = gnome_print_job_get_context (gpj);
		gnome_print_beginpage(pc, (const guchar*)"");
		gdouble width, height;
		gnome_print_config_get_page_size(config, &width, &height);
		Print(pc, width, height);
		gnome_print_showpage(pc);
		g_object_unref(pc);
		gnome_print_job_close(gpj);
		char *tmpname = g_strdup ("/tmp/gcp2epsXXXXXX");
		int f = g_mkstemp (tmpname);
		close (f);
		double hp, mt, ml;
		GnomePrintUnit const *unit;
		GnomePrintUnit const *inches = gnome_print_unit_get_by_abbreviation ((const guchar *) "in");
		gnome_print_config_get_length (config, (const guchar*) "Settings.Output.Media.PhysicalSize.Height", &hp, &unit);
		gnome_print_convert_distance (&hp, unit, inches);
		hp *= 72;
/*		gnome_print_config_get_length (config, (const guchar*) "Settings.Document.Page.Margins.Left", &ml, &unit);
		gnome_print_convert_distance (&ml, unit, inches);
		ml *= 72;
		gnome_print_config_get_length (config, (const guchar*) "Settings.Document.Page.Margins.Top", &mt, &unit);
		gnome_print_convert_distance (&mt, unit, inches);
		mt *= 72;	*/
		ml = mt = 30.; // see gcpView::Print !
		gnome_print_config_set_boolean (config, (const guchar*) "Settings.Output.Job.PrintToFile", true);
		gnome_print_config_set (config, (const guchar*) GNOME_PRINT_KEY_OUTPUT_FILENAME, (const guchar*) tmpname);
		gnome_print_job_print (gpj);
		g_object_unref (gpj);
		gnome_print_config_unref(config);
		char buf[256];
		ifstream *fin = new ifstream (tmpname);
		ostringstream fout (filename.c_str ());
		fout << "%!PS-Adobe-3.0 EPSF-3.0" << endl;
		fout << "%%BoundingBox: " << (int) (rect.x0 * .75 + ml) << " " << (int) (hp - mt - rect.y1 * .75)  <<  " "  << (int) (rect.x1 * .75 + ml) << " " << (int) (hp - mt - rect.y0 * .75) << endl;
		fout << "%%HiResBoundingBox: " << rect.x0 * .75 + ml << " " << hp - mt - rect.y1 * .75 <<  " "  << rect.x1 * .75 + ml << " " << hp - mt - rect.y0 * .75 << endl;
		fin->getline (buf, 256);
		while (!fin->eof ()) {
			fin->getline (buf, 256);
			if (strlen (buf) >= 255)
				exit (-1); /* This is violent but should not occur */
			if (!strncmp (buf, "%%", 2)) {
				if (!strncmp (buf + 2, "Orientation", strlen ("Orientation")))
					continue;
				else if (!strncmp (buf + 2, "Pages", strlen ("Pages")))
					continue;
				else if (!strncmp (buf + 2, "BoundingBox", strlen ("BoundingBox")))
					continue;
				else if (!strncmp (buf + 2, "BeginDefaults", strlen ("BeginDefaults")))
					continue;
				else if (!strncmp (buf + 2, "EndDefaults", strlen ("EndDefaults")))
					continue;
				else if (!strncmp (buf + 2, "BeginSetup", strlen ("BeginSetup")))
					continue;
				else if (!strncmp (buf + 2, "EndSetup", strlen ("EndSetup")))
					continue;
				else if (!strncmp (buf + 2, "PageMedia", strlen ("PageMedia")))
					continue;
				else if (!strncmp (buf + 2, "EndProlog", strlen ("EndProlog")))
					continue;
				else if (!strncmp (buf + 2, "BeginResource", strlen ("BeginResource")))
					continue;
				else if (!strncmp (buf + 2, "EndResource", strlen ("EndResource")))
					continue;
				else if (!strncmp (buf + 2, "Page: ", strlen ("Page: ")))
					continue;
				else if (!strncmp (buf + 2, "%%", 2))
					continue;
				else if (!strncmp (buf + 2, "PageResources", strlen ("PageResources")))
					continue;
				else if (!strncmp (buf + 2, "EndComments", strlen ("EndComments"))) {
					 fout << buf << endl;
					if (!filename.compare (filename.length () - 5, 5, ".epsi")) {
						gnome_canvas_update_now (GNOME_CANVAS (m_pWidget));
						GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, w, h);
						GnomeCanvasBuf cbuf;
						int i, j, mask, lines, bytes;
						cbuf.buf = gdk_pixbuf_get_pixels (pixbuf);
						cbuf.rect.x0 = (int) floor (rect.x0);
						cbuf.rect.x1 = (int) ceil (rect.x1);
						cbuf.rect.y0 = (int) floor (rect.y0);
						cbuf.rect.y1 = (int) ceil (rect.y1);
						cbuf.buf_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
						cbuf.bg_color = 0xffffff;
						cbuf.is_buf = 1;
						(* GNOME_CANVAS_ITEM_GET_CLASS (m_pData->Group)->render) (GNOME_CANVAS_ITEM (m_pData->Group), &cbuf);
						/* use 8 bits depth, no more than 250 bytes per line*/
						lines = (w + 249) / 250 * h;
						fout << "%%BeginPreview: " << w << " " << h << " " << 8 << " " << lines << endl;
						fout << hex;
						for (j = 0; j < h; j++) {
							bytes = 0;
							for (i = 0; i < 3 * w; i+= 3) {
								if (bytes == 0)
									fout << "%";
								mask = 0xff - (cbuf.buf[i] + cbuf.buf[i + 1] + cbuf.buf[i + 2]) / 3;
								fout << (mask & 0xf);
								mask = mask >> 4;
								fout << mask;
								bytes++;
								if (bytes == 250) {
									fout << endl;
									bytes = 0;
								}
							}
							if (bytes)
								fout << endl;
							cbuf.buf += cbuf.buf_rowstride;
						}
						/* use 1 bit depth, no more than 128 bytes per line (max width 1024) */
/*						lines = (w + 1023) / 1024 * h;
						fout << "%%BeginPreview: " << w << " " << h << " " << 1 << " " << lines << endl;
						fout << hex;
						for (j = 0; j < h; j++) {
							mask = 1; b = 0;
							bytes = 0;
							for (i = 0; i < 3 * w; i+= 3) {
								if (bytes == 0)
									fout << "%";
								if ((cbuf.buf[i] + cbuf.buf[i + 1] + cbuf.buf[i + 2]) / 3 < 0x80)
									b |= mask;
								mask <<= 1;
								if (mask == 16) {
									fout << (int) b;
									mask = 1;
									b = 0;
									bytes++;
									if (bytes == 128) {
										fout << endl;
										bytes = 0;
									}
								}
							}
							if (mask != 1) {
								fout << (int) b;
								bytes++;
							}
							if (bytes)
								fout << endl;
							cbuf.buf += cbuf.buf_rowstride;
						}*/
						fout << dec;
						fout << "%%EndPreview" << endl;
						g_object_unref (pixbuf);
					}
				} else if (!strncmp (buf + 2, "BeginProlog", strlen ("BeginProlog"))) {
					 fout << buf << endl;
					 fout << "save" << endl;
					 fout << "countdictstack" << endl;
					 fout << "mark" << endl;
					 fout << "newpath" << endl;
					 fout << "/showpage {} def" << endl;
					 fout << "/setpagedevice {pop} def" << endl;
					 fout << "%%EndProlog" << endl;
					 fout << "%%Page 1 1" << endl;
				} else if (!strncmp (buf + 2, "Trailer", strlen ("Trailer"))) {
					 fout << buf << endl;
					 fout << "cleartomark" << endl;
					 fout << "countdictstack" << endl;
					 fout << "exch sub { end } repeat" << endl;
					 fout << "restore" << endl;
				} else 
					 fout << buf << endl;
			} else
			 fout << buf << endl;
		}
		fin->close ();
		delete fin;
		GnomeVFSHandle *handle = NULL;
		GnomeVFSFileSize n;
		if (gnome_vfs_create (&handle, filename.c_str (), GNOME_VFS_OPEN_WRITE, true, 0644) == GNOME_VFS_OK)
			gnome_vfs_write (handle, fout.str ().c_str (), (GnomeVFSFileSize) fout.str ().size (), &n);
	} else if (!strcmp (type, "svg")) {
		xmlDocPtr doc = BuildSVG ();
		xmlIndentTreeOutput = true;
		xmlKeepBlanksDefault (0);
		xmlSaveFormatFile (filename.c_str (), doc, true);
		xmlFreeDoc (doc);
	} else {
		GdkPixbuf *pixbuf = BuildPixbuf (resolution);
		GnomeVFSHandle *handle = NULL;
		if (gnome_vfs_create (&handle, filename.c_str (), GNOME_VFS_OPEN_WRITE, true, 0644) == GNOME_VFS_OK) {
			GError *error = NULL;
			gdk_pixbuf_save_to_callbackv (pixbuf, do_save_image, handle, type, NULL, NULL, &error);
			if (error) {
				cerr << _("Unable to save image file: ") << error->message << endl;
				g_error_free (error);
			}
			gnome_vfs_close (handle); // hope there will be no error there
		}
		g_object_unref (pixbuf);
	}
	m_pData->ShowSelection (true);
}

xmlDocPtr gcpView::BuildSVG ()
{
	ArtDRect rect;
	m_pData->GetObjectBounds (m_pDoc, &rect);
	xmlDocPtr doc = xmlNewDoc ((const xmlChar*)"1.0");
	char *old_num_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
	char *buf;
	setlocale (LC_NUMERIC, "C");
	xmlNewDtd (doc,
		   (const xmlChar*)"svg", (const xmlChar*)"-//W3C//DTD SVG 1.1//EN",
		   (const xmlChar*)"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd");
	xmlDocSetRootElement (doc, xmlNewDocNode (doc, NULL, (const xmlChar*)"svg", NULL));
	xmlNsPtr ns = xmlNewNs (doc->children, (const xmlChar*)"http://www.w3.org/2000/svg", NULL);
	xmlSetNs (doc->children, ns);
	xmlNewProp (doc->children, (const xmlChar*)"version", (const xmlChar*)"1.1");
	rect.x0 = floor (rect.x0);
	rect.y0 = floor (rect.y0);
	rect.x1 = ceil (rect.x1);
	rect.y1 = ceil (rect.y1);
	buf = g_strdup_printf ("%g", rect.x1 - rect.x0);
	xmlNewProp (doc->children, (const xmlChar*)"width", (const xmlChar*)buf);
	g_free (buf);
	buf = g_strdup_printf ("%g", rect.y1 - rect.y0);
	xmlNewProp (doc->children, (const xmlChar*)"height", (const xmlChar*)buf);
	g_free (buf);
	xmlNodePtr node;
	node = xmlNewDocNode (doc, NULL, (const xmlChar*)"rect", NULL);
	xmlAddChild (doc->children, node);
	buf = g_strdup_printf ("%g", rect.x1 - rect.x0);
	xmlNewProp (node, (const xmlChar*)"width", (const xmlChar*)buf);
	g_free (buf);
	buf = g_strdup_printf ("%g", rect.y1 - rect.y0);
	xmlNewProp (node, (const xmlChar*)"height", (const xmlChar*)buf);
	g_free (buf);
	xmlNewProp (node, (const xmlChar*)"stroke", (const xmlChar*)"none");
	xmlNewProp (node, (const xmlChar*)"fill", (const xmlChar*)"white");
	if (rect.x0 != 0. || rect.y0 != 0) {
		node = xmlNewDocNode (doc, NULL, (const xmlChar*)"g", NULL);
		xmlAddChild (doc->children, node);
		buf = g_strdup_printf ("translate(%g,%g)", - rect.x0, - rect.y0);
		xmlNewProp (node, (const xmlChar*)"transform", (const xmlChar*)buf);
		g_free (buf);
	} else
		node = doc->children;
	g_printable_export_svg (G_PRINTABLE (m_pData->Group), doc, node);
	setlocale (LC_NUMERIC, old_num_locale);
	g_free (old_num_locale);
	return doc;
}

GdkPixbuf *gcpView::BuildPixbuf (int resolution)
{
	ArtDRect rect;
	m_pData->GetObjectBounds (m_pDoc, &rect);
	m_pData->ShowSelection (false);
	int w = (int) (ceil (rect.x1) - floor (rect.x0)), h = (int) (ceil (rect.y1) - floor (rect.y0));
	double zoom;
	if (resolution > 0) {
		int screenres = m_pDoc->GetApp ()->GetScreenResolution ();
		zoom = (double) resolution / screenres;
		w = (int) rint ((double) w * zoom);
		h = (int) rint ((double) h * zoom);
	} else
		zoom = 1.;
	gnome_canvas_set_pixels_per_unit (GNOME_CANVAS (m_pWidget), zoom);
	gnome_canvas_update_now (GNOME_CANVAS (m_pWidget));
	GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, w, h);
	gdk_pixbuf_fill (pixbuf, 0xffffffff);
	GnomeCanvasBuf buf;
	buf.buf = gdk_pixbuf_get_pixels (pixbuf);
	buf.rect.x0 = (int) floor (rect.x0 * zoom);
	buf.rect.x1 = (int) ceil (rect.x1 * zoom);
	buf.rect.y0 = (int) floor (rect.y0 * zoom);
	buf.rect.y1 = (int) ceil (rect.y1 * zoom);
	buf.buf_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
	buf.bg_color = 0xffffff;
	buf.is_buf = 1;
	(* GNOME_CANVAS_ITEM_GET_CLASS (m_pData->Group)->render) (GNOME_CANVAS_ITEM (m_pData->Group), &buf);
	// restore zoom level
	gnome_canvas_set_pixels_per_unit (GNOME_CANVAS (m_pWidget), m_pData->Zoom);
	return pixbuf;
}

void gcpView::EnsureSize ()
{
	GnomeCanvas *canvas = GNOME_CANVAS (m_pWidget);
	gnome_canvas_update_now (canvas);
	if (GTK_WIDGET_REALIZED (m_pWidget))
		g_signal_emit_by_name (m_pWidget, "update_bounds");
}

void gcpView::Zoom (double zoom)
{
	m_pData->Zoom = zoom;
	gnome_canvas_set_pixels_per_unit (GNOME_CANVAS (m_pWidget), zoom);
	EnsureSize ();
	// Call OnSize to be certain that the canvas scroll region will be correct
	OnSize (m_pWidget, m_width, m_height);
}

void gcpView::ShowCursor (bool show)
{
	if (m_ActiveRichText)
		g_object_set (G_OBJECT (m_ActiveRichText), "editing", show, NULL);
}

void gcpView::UpdateTheme ()
{
	if (m_sFontName)
		g_free (m_sFontName);
	if (m_sSmallFontName)
		g_free (m_sSmallFontName);
	pango_font_description_free (m_PangoFontDesc);
	pango_font_description_free (m_PangoSmallFontDesc);
	gcpTheme *pTheme = m_pDoc->GetTheme ();
	m_PangoFontDesc = pango_font_description_new ();
	pango_font_description_set_family (m_PangoFontDesc, pTheme->GetFontFamily ());
	pango_font_description_set_style (m_PangoFontDesc, pTheme->GetFontStyle ());
	pango_font_description_set_weight (m_PangoFontDesc, pTheme->GetFontWeight ());
	pango_font_description_set_variant (m_PangoFontDesc, pTheme->GetFontVariant ());
	pango_font_description_set_stretch (m_PangoFontDesc, pTheme->GetFontStretch ());
	pango_font_description_set_size (m_PangoFontDesc, pTheme->GetFontSize ());
	m_sFontName = pango_font_description_to_string (m_PangoFontDesc);
	m_PangoSmallFontDesc = pango_font_description_new ();
	pango_font_description_set_family (m_PangoSmallFontDesc, pTheme->GetFontFamily ());
	pango_font_description_set_style (m_PangoSmallFontDesc, pTheme->GetFontStyle ());
	pango_font_description_set_weight (m_PangoSmallFontDesc, pTheme->GetFontWeight ());
	pango_font_description_set_variant (m_PangoSmallFontDesc, pTheme->GetFontVariant ());
	pango_font_description_set_stretch (m_PangoSmallFontDesc, pTheme->GetFontStretch ());
	pango_font_description_set_size (m_PangoSmallFontDesc, pTheme->GetFontSize () * 2 / 3);
	m_sSmallFontName = pango_font_description_to_string (m_PangoSmallFontDesc);
	Update (m_pDoc);
}


syntax highlighted by Code2HTML, v. 0.9.1