// Scintilla source code edit control
// ScintillaGTK.cxx - GTK+ specific subclass of ScintillaBase
// Copyright 1998-2004 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "Platform.h"

#if PLAT_GTK_WIN32
#include "Windows.h"
#endif

#include "Scintilla.h"
#include "ScintillaWidget.h"
#ifdef SCI_LEXER
#include "SciLexer.h"
#include "PropSet.h"
#include "Accessor.h"
#include "KeyWords.h"
#endif
#include "ContractionState.h"
#include "SVector.h"
#include "CellBuffer.h"
#include "CallTip.h"
#include "KeyMap.h"
#include "Indicator.h"
#include "XPM.h"
#include "LineMarker.h"
#include "Style.h"
#include "AutoComplete.h"
#include "ViewStyle.h"
#include "Document.h"
#include "Editor.h"
#include "SString.h"
#include "ScintillaBase.h"
#include "UniConversion.h"

#include "gtk/gtksignal.h"
#include "gtk/gtkmarshal.h"
#if GTK_MAJOR_VERSION >= 2
#include "scintilla-marshal.h"
#endif

#ifdef SCI_LEXER
#include <glib.h>
#include <gmodule.h>
#include "ExternalLexer.h"
#endif

#define INTERNATIONAL_INPUT

#if !PLAT_GTK_WIN32 || GTK_MAJOR_VERSION >= 2
#define USE_CONVERTER
#endif

#ifdef USE_CONVERTER
#include "Converter.h"
#endif

#ifdef _MSC_VER
// Constant conditional expressions are because of GTK+ headers
#pragma warning(disable: 4127)
// Ignore unreferenced local functions in GTK+ headers
#pragma warning(disable: 4505)
#endif

#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2
#define USE_GTK_CLIPBOARD
#endif

extern char *UTF8FromLatin1(const char *s, int &len);

class ScintillaGTK : public ScintillaBase {
	_ScintillaObject *sci;
	Window wText;
	Window scrollbarv;
	Window scrollbarh;
	GtkObject *adjustmentv;
	GtkObject *adjustmenth;
	int scrollBarWidth;
	int scrollBarHeight;

	// Because clipboard access is asynchronous, copyText is created by Copy
#ifndef USE_GTK_CLIPBOARD
	SelectionText copyText;
#endif

	SelectionText primary;

	GdkEventButton evbtn;
	bool capturedMouse;
	bool dragWasDropped;
	int lastKey;

	GtkWidgetClass *parentClass;

	static GdkAtom atomClipboard;
	static GdkAtom atomUTF8;
	static GdkAtom atomString;
	GdkAtom atomSought;

#if PLAT_GTK_WIN32
	CLIPFORMAT cfColumnSelect;
#endif

#ifdef INTERNATIONAL_INPUT
#if GTK_MAJOR_VERSION < 2
	// Input context used for supporting internationalized key entry
	GdkIC *ic;
	GdkICAttr *ic_attr;
#else
	Window wPreedit;
	Window wPreeditDraw;
	GtkIMContext *im_context;
#endif
#endif

	// Wheel mouse support
	unsigned int linesPerScroll;
	GTimeVal lastWheelMouseTime;
	gint lastWheelMouseDirection;
	gint wheelMouseIntensity;

	// Private so ScintillaGTK objects can not be copied
	ScintillaGTK(const ScintillaGTK &) : ScintillaBase() {}
	ScintillaGTK &operator=(const ScintillaGTK &) { return * this; }

public:
	ScintillaGTK(_ScintillaObject *sci_);
	virtual ~ScintillaGTK();
	static void ClassInit(GtkObjectClass* object_class, GtkWidgetClass *widget_class, GtkContainerClass *container_class);

private:
	virtual void Initialise();
	virtual void Finalise();
	virtual void DisplayCursor(Window::Cursor c);
	virtual void StartDrag();
	int TargetAsUTF8(char *text);
	int EncodedFromUTF8(char *utf8, char *encoded);
public: 	// Public for scintilla_send_message
	virtual sptr_t WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
private:
	virtual sptr_t DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
	virtual void SetTicking(bool on);
	virtual bool SetIdle(bool on);
	virtual void SetMouseCapture(bool on);
	virtual bool HaveMouseCapture();
	void FullPaint();
	virtual PRectangle GetClientRectangle();
	void SyncPaint(PRectangle rc);
	virtual void ScrollText(int linesToMove);
	virtual void SetVerticalScrollPos();
	virtual void SetHorizontalScrollPos();
	virtual bool ModifyScrollBars(int nMax, int nPage);
	void ReconfigureScrollBars();
	virtual void NotifyChange();
	virtual void NotifyFocus(bool focus);
	virtual void NotifyParent(SCNotification scn);
	void NotifyKey(int key, int modifiers);
	void NotifyURIDropped(const char *list);
	bool UseInputMethod() const;
	const char *CharacterSetID() const;
	virtual int KeyDefault(int key, int modifiers);
	virtual void CopyToClipboard(const SelectionText &selectedText);
	virtual void Copy();
	virtual void Paste();
	virtual void CreateCallTipWindow(PRectangle rc);
	virtual void AddToPopUp(const char *label, int cmd = 0, bool enabled = true);
	bool OwnPrimarySelection();
	virtual void ClaimSelection();
	void GetGtkSelectionText(GtkSelectionData *selectionData, SelectionText &selText);
	void ReceivedSelection(GtkSelectionData *selection_data);
	void ReceivedDrop(GtkSelectionData *selection_data);
	static void GetSelection(GtkSelectionData *selection_data, guint info, SelectionText *selected);
#ifdef USE_GTK_CLIPBOARD
	static void ClipboardGetSelection(GtkClipboard* clip, GtkSelectionData *selection_data, guint info, void *data);
	static void ClipboardClearSelection(GtkClipboard* clip, void *data);
#endif

	void UnclaimSelection(GdkEventSelection *selection_event);
	void Resize(int width, int height);

	// Callback functions
	void RealizeThis(GtkWidget *widget);
	static void Realize(GtkWidget *widget);
	void UnRealizeThis(GtkWidget *widget);
	static void UnRealize(GtkWidget *widget);
	void MapThis();
	static void Map(GtkWidget *widget);
	void UnMapThis();
	static void UnMap(GtkWidget *widget);
	static gint CursorMoved(GtkWidget *widget, int xoffset, int yoffset, ScintillaGTK *sciThis);
	static gint FocusIn(GtkWidget *widget, GdkEventFocus *event);
	static gint FocusOut(GtkWidget *widget, GdkEventFocus *event);
	static void SizeRequest(GtkWidget *widget, GtkRequisition *requisition);
	static void SizeAllocate(GtkWidget *widget, GtkAllocation *allocation);
	gint Expose(GtkWidget *widget, GdkEventExpose *ose);
	static gint ExposeMain(GtkWidget *widget, GdkEventExpose *ose);
	static void Draw(GtkWidget *widget, GdkRectangle *area);
	void ForAll(GtkCallback callback, gpointer callback_data);
	static void MainForAll(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data);

	static void ScrollSignal(GtkAdjustment *adj, ScintillaGTK *sciThis);
	static void ScrollHSignal(GtkAdjustment *adj, ScintillaGTK *sciThis);
	gint PressThis(GdkEventButton *event);
	static gint Press(GtkWidget *widget, GdkEventButton *event);
	static gint MouseRelease(GtkWidget *widget, GdkEventButton *event);
#if PLAT_GTK_WIN32 || (GTK_MAJOR_VERSION >= 2)
	static gint ScrollEvent(GtkWidget *widget, GdkEventScroll *event);
#endif
	static gint Motion(GtkWidget *widget, GdkEventMotion *event);
	gint KeyThis(GdkEventKey *event);
	static gint KeyPress(GtkWidget *widget, GdkEventKey *event);
	static gint KeyRelease(GtkWidget *widget, GdkEventKey *event);
#if GTK_MAJOR_VERSION >= 2
	static gint ExposePreedit(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis);
	gint ExposePreeditThis(GtkWidget *widget, GdkEventExpose *ose);
	static void Commit(GtkIMContext *context, char *str, ScintillaGTK *sciThis);
	void CommitThis(char *str);
	static void PreeditChanged(GtkIMContext *context, ScintillaGTK *sciThis);
	void PreeditChangedThis();
#endif
	static gint StyleSetText(GtkWidget *widget, GtkStyle *previous, void*);
	static gint RealizeText(GtkWidget *widget, void*);
	static void Destroy(GtkObject *object);
	static void SelectionReceived(GtkWidget *widget, GtkSelectionData *selection_data,
	                              guint time);
	static void SelectionGet(GtkWidget *widget, GtkSelectionData *selection_data,
	                         guint info, guint time);
	static gint SelectionClear(GtkWidget *widget, GdkEventSelection *selection_event);
#if GTK_MAJOR_VERSION < 2
	static gint SelectionNotify(GtkWidget *widget, GdkEventSelection *selection_event);
#endif
	static void DragBegin(GtkWidget *widget, GdkDragContext *context);
	static gboolean DragMotion(GtkWidget *widget, GdkDragContext *context,
	                           gint x, gint y, guint time);
	static void DragLeave(GtkWidget *widget, GdkDragContext *context,
	                      guint time);
	static void DragEnd(GtkWidget *widget, GdkDragContext *context);
	static gboolean Drop(GtkWidget *widget, GdkDragContext *context,
	                     gint x, gint y, guint time);
	static void DragDataReceived(GtkWidget *widget, GdkDragContext *context,
	                             gint x, gint y, GtkSelectionData *selection_data, guint info, guint time);
	static void DragDataGet(GtkWidget *widget, GdkDragContext *context,
	                        GtkSelectionData *selection_data, guint info, guint time);
	static gint TimeOut(ScintillaGTK *sciThis);
	static gint IdleCallback(ScintillaGTK *sciThis);
	static void PopUpCB(ScintillaGTK *sciThis, guint action, GtkWidget *widget);

	gint ExposeTextThis(GtkWidget *widget, GdkEventExpose *ose);
	static gint ExposeText(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis);

	static gint ExposeCT(GtkWidget *widget, GdkEventExpose *ose, CallTip *ct);
	static gint PressCT(GtkWidget *widget, GdkEventButton *event, ScintillaGTK *sciThis);

	static sptr_t DirectFunction(ScintillaGTK *sciThis,
	                             unsigned int iMessage, uptr_t wParam, sptr_t lParam);
};

enum {
    COMMAND_SIGNAL,
    NOTIFY_SIGNAL,
    LAST_SIGNAL
};

static gint scintilla_signals[LAST_SIGNAL] = { 0 };
static GtkWidgetClass* parent_class = NULL;

enum {
    TARGET_STRING,
    TARGET_TEXT,
    TARGET_COMPOUND_TEXT,
    TARGET_UTF8_STRING
};

GdkAtom ScintillaGTK::atomClipboard = 0;
GdkAtom ScintillaGTK::atomUTF8 = 0;
GdkAtom ScintillaGTK::atomString = 0;

static const GtkTargetEntry clipboardTargets[] = {
	{ "UTF8_STRING", 0, TARGET_UTF8_STRING },
	{ "STRING", 0, TARGET_STRING },
//	    { "TEXT", 0, TARGET_TEXT },
//	    { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
	{ "text/uri-list", 0, 0 },
};
static const gint nClipboardTargets = sizeof(clipboardTargets) / sizeof(clipboardTargets[0]);

static GtkWidget *PWidget(Window &w) {
	return reinterpret_cast<GtkWidget *>(w.GetID());
}

static ScintillaGTK *ScintillaFromWidget(GtkWidget *widget) {
	ScintillaObject *scio = reinterpret_cast<ScintillaObject *>(widget);
	return reinterpret_cast<ScintillaGTK *>(scio->pscin);
}

ScintillaGTK::ScintillaGTK(_ScintillaObject *sci_) :
		adjustmentv(0), adjustmenth(0),
		scrollBarWidth(30), scrollBarHeight(30),
		capturedMouse(false), dragWasDropped(false),
		lastKey(0), parentClass(0),
#ifdef INTERNATIONAL_INPUT
#if GTK_MAJOR_VERSION < 2
		ic(NULL),
		ic_attr(NULL),
#else
		im_context(NULL),
#endif
#endif
		lastWheelMouseDirection(0),
		wheelMouseIntensity(0) {
	sci = sci_;
	wMain = GTK_WIDGET(sci);

#if PLAT_GTK_WIN32
 	// There does not seem to be a real standard for indicating that the clipboard
	// contains a rectangular selection, so copy Developer Studio.
	cfColumnSelect = static_cast<CLIPFORMAT>(
		::RegisterClipboardFormat("MSDEVColumnSelect"));

  	// Get intellimouse parameters when running on win32; otherwise use
	// reasonable default
#ifndef SPI_GETWHEELSCROLLLINES
#define SPI_GETWHEELSCROLLLINES   104
#endif
	::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerScroll, 0);
#else
	linesPerScroll = 4;
#endif
	lastWheelMouseTime.tv_sec = 0;
	lastWheelMouseTime.tv_usec = 0;

	Initialise();
}

ScintillaGTK::~ScintillaGTK() {
}

void ScintillaGTK::RealizeThis(GtkWidget *widget) {
	//Platform::DebugPrintf("ScintillaGTK::realize this\n");
	GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
	GdkWindowAttr attrs;
	attrs.window_type = GDK_WINDOW_CHILD;
	attrs.x = widget->allocation.x;
	attrs.y = widget->allocation.y;
	attrs.width = widget->allocation.width;
	attrs.height = widget->allocation.height;
	attrs.wclass = GDK_INPUT_OUTPUT;
	attrs.visual = gtk_widget_get_visual(widget);
	attrs.colormap = gtk_widget_get_colormap(widget);
	attrs.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
	GdkCursor *cursor = gdk_cursor_new(GDK_XTERM);
	attrs.cursor = cursor;
	widget->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attrs,
	                                GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR);
	gdk_window_set_user_data(widget->window, widget);
	gdk_window_set_background(widget->window, &widget->style->bg[GTK_STATE_NORMAL]);
	gdk_window_show(widget->window);
	gdk_cursor_destroy(cursor);
	widget->style = gtk_style_attach(widget->style, widget->window);
#ifdef INTERNATIONAL_INPUT
#if GTK_MAJOR_VERSION < 2
	if (gdk_im_ready() && (ic_attr = gdk_ic_attr_new()) != NULL) {
		gint width, height;
		GdkColormap *colormap;
		GdkEventMask mask;
		GdkICAttr *attr = ic_attr;
		GdkICAttributesType attrmask = GDK_IC_ALL_REQ;
		GdkIMStyle style;
		GdkIMStyle supported_style = (GdkIMStyle) (GDK_IM_PREEDIT_NONE |
		                             GDK_IM_PREEDIT_NOTHING |
		                             GDK_IM_PREEDIT_POSITION |
		                             GDK_IM_STATUS_NONE |
		                             GDK_IM_STATUS_NOTHING);

		if (widget->style && widget->style->font->type != GDK_FONT_FONTSET)
			supported_style = (GdkIMStyle) ((int) supported_style & ~GDK_IM_PREEDIT_POSITION);

		attr->style = style = gdk_im_decide_style(supported_style);
		attr->client_window = widget->window;

		if ((colormap = gtk_widget_get_colormap (widget)) != gtk_widget_get_default_colormap ()) {
			attrmask = (GdkICAttributesType) ((int) attrmask | GDK_IC_PREEDIT_COLORMAP);
			attr->preedit_colormap = colormap;
		}

		switch (style & GDK_IM_PREEDIT_MASK) {
		case GDK_IM_PREEDIT_POSITION:
			if (widget->style && widget->style->font->type != GDK_FONT_FONTSET)	{
				g_warning("over-the-spot style requires fontset");
				break;
			}

			attrmask = (GdkICAttributesType) ((int) attrmask | GDK_IC_PREEDIT_POSITION_REQ);
			gdk_window_get_size(widget->window, &width, &height);
			attr->spot_location.x = 0;
			attr->spot_location.y = height;
			attr->preedit_area.x = 0;
			attr->preedit_area.y = 0;
			attr->preedit_area.width = width;
			attr->preedit_area.height = height;
			attr->preedit_fontset = widget->style->font;

			break;
		}
		ic = gdk_ic_new(attr, attrmask);

		if (ic == NULL) {
			g_warning("Can't create input context.");
		} else {
			mask = gdk_window_get_events(widget->window);
			mask = (GdkEventMask) ((int) mask | gdk_ic_get_events(ic));
			gdk_window_set_events(widget->window, mask);

			if (GTK_WIDGET_HAS_FOCUS(widget))
				gdk_im_begin(ic, widget->window);
		}
	}
#else
	wPreedit = gtk_window_new(GTK_WINDOW_POPUP);
	wPreeditDraw = gtk_drawing_area_new();
	gtk_signal_connect(GTK_OBJECT(PWidget(wPreeditDraw)), "expose_event",
			   GtkSignalFunc(ExposePreedit), this);
	gtk_container_add(GTK_CONTAINER(PWidget(wPreedit)), PWidget(wPreeditDraw));
	gtk_widget_realize(PWidget(wPreedit));
	gtk_widget_realize(PWidget(wPreeditDraw));
	gtk_widget_show(PWidget(wPreeditDraw));

	im_context = gtk_im_multicontext_new();
	g_signal_connect(im_context, "commit",
			 G_CALLBACK(Commit), this);
	g_signal_connect(im_context, "preedit_changed",
			 G_CALLBACK(PreeditChanged), this);
	gtk_im_context_set_client_window(im_context, widget->window);
#endif
#endif
	gtk_signal_connect_after(GTK_OBJECT(PWidget(wText)), "style_set",
				 GtkSignalFunc(ScintillaGTK::StyleSetText), NULL);
	gtk_signal_connect_after(GTK_OBJECT(PWidget(wText)), "realize",
				 GtkSignalFunc(ScintillaGTK::RealizeText), NULL);
	gtk_widget_realize(PWidget(wText));
	gtk_widget_realize(PWidget(scrollbarv));
	gtk_widget_realize(PWidget(scrollbarh));
}

void ScintillaGTK::Realize(GtkWidget *widget) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	sciThis->RealizeThis(widget);
}

void ScintillaGTK::UnRealizeThis(GtkWidget *widget) {
	if (GTK_WIDGET_MAPPED(widget)) {
		gtk_widget_unmap(widget);
	}
	GTK_WIDGET_UNSET_FLAGS(widget, GTK_REALIZED);
	gtk_widget_unrealize(PWidget(wText));
	gtk_widget_unrealize(PWidget(scrollbarv));
	gtk_widget_unrealize(PWidget(scrollbarh));
#ifdef INTERNATIONAL_INPUT
#if GTK_MAJOR_VERSION < 2
	if (ic) {
		gdk_ic_destroy(ic);
		ic = NULL;
	}
	if (ic_attr) {
		gdk_ic_attr_destroy(ic_attr);
		ic_attr = NULL;
	}
#else
	gtk_widget_unrealize(PWidget(wPreedit));
	gtk_widget_unrealize(PWidget(wPreeditDraw));
	g_object_unref(im_context);
	im_context = NULL;
#endif
#endif
	if (GTK_WIDGET_CLASS(parentClass)->unrealize)
		GTK_WIDGET_CLASS(parentClass)->unrealize(widget);

	Finalise();
}

void ScintillaGTK::UnRealize(GtkWidget *widget) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	sciThis->UnRealizeThis(widget);
}

static void MapWidget(GtkWidget *widget) {
	if (widget &&
	        GTK_WIDGET_VISIBLE(widget) &&
	        !GTK_WIDGET_MAPPED(widget)) {
		gtk_widget_map(widget);
	}
}

void ScintillaGTK::MapThis() {
	//Platform::DebugPrintf("ScintillaGTK::map this\n");
	GTK_WIDGET_SET_FLAGS(PWidget(wMain), GTK_MAPPED);
	MapWidget(PWidget(wText));
	MapWidget(PWidget(scrollbarh));
	MapWidget(PWidget(scrollbarv));
	wMain.SetCursor(Window::cursorArrow);
	scrollbarv.SetCursor(Window::cursorArrow);
	scrollbarh.SetCursor(Window::cursorArrow);
	ChangeSize();
	gdk_window_show(PWidget(wMain)->window);
}

void ScintillaGTK::Map(GtkWidget *widget) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	sciThis->MapThis();
}

void ScintillaGTK::UnMapThis() {
	//Platform::DebugPrintf("ScintillaGTK::unmap this\n");
	GTK_WIDGET_UNSET_FLAGS(PWidget(wMain), GTK_MAPPED);
	gdk_window_hide(PWidget(wMain)->window);
	gtk_widget_unmap(PWidget(wText));
	gtk_widget_unmap(PWidget(scrollbarh));
	gtk_widget_unmap(PWidget(scrollbarv));
}

void ScintillaGTK::UnMap(GtkWidget *widget) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	sciThis->UnMapThis();
}

void ScintillaGTK::ForAll(GtkCallback callback, gpointer callback_data) {
	(*callback) (PWidget(wText), callback_data);
	(*callback) (PWidget(scrollbarv), callback_data);
	(*callback) (PWidget(scrollbarh), callback_data);
}

void ScintillaGTK::MainForAll(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) {
	ScintillaGTK *sciThis = ScintillaFromWidget((GtkWidget *)container);

	if (callback != NULL && include_internals) {
		sciThis->ForAll(callback, callback_data);
	}
}

#ifdef INTERNATIONAL_INPUT
#if GTK_MAJOR_VERSION < 2
gint ScintillaGTK::CursorMoved(GtkWidget *widget, int xoffset, int yoffset, ScintillaGTK *sciThis) {
	if (GTK_WIDGET_HAS_FOCUS(widget) && gdk_im_ready() && sciThis->ic &&
	        (gdk_ic_get_style(sciThis->ic) & GDK_IM_PREEDIT_POSITION)) {
		sciThis->ic_attr->spot_location.x = xoffset;
		sciThis->ic_attr->spot_location.y = yoffset;
		gdk_ic_set_attr(sciThis->ic, sciThis->ic_attr, GDK_IC_SPOT_LOCATION);
	}
	return FALSE;
}
#else
gint ScintillaGTK::CursorMoved(GtkWidget *, int xoffset, int yoffset, ScintillaGTK *sciThis) {
	GdkRectangle area;
	area.x = xoffset;
	area.y = yoffset;
	area.width = 1;
	area.height = 1;
	gtk_im_context_set_cursor_location(sciThis->im_context, &area);
	return FALSE;
}
#endif
#else
gint ScintillaGTK::CursorMoved(GtkWidget *, int, int, ScintillaGTK *) {
	return FALSE;
}
#endif

gint ScintillaGTK::FocusIn(GtkWidget *widget, GdkEventFocus * /*event*/) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("ScintillaGTK::focus in %x\n", sciThis);
	GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
	sciThis->SetFocusState(true);

#ifdef INTERNATIONAL_INPUT
#if GTK_MAJOR_VERSION < 2
	if (sciThis->ic)
		gdk_im_begin(sciThis->ic, widget->window);
#else
	if (sciThis->im_context != NULL) {
		gchar *str = NULL;
		gint cursor_pos;

		gtk_im_context_get_preedit_string(sciThis->im_context, &str, NULL, &cursor_pos);
		if (PWidget(sciThis->wPreedit) != NULL) {
			if (strlen(str) > 0) {
				gtk_widget_show(PWidget(sciThis->wPreedit));
			} else {
				gtk_widget_hide(PWidget(sciThis->wPreedit));
			}
		}
		g_free(str);
		gtk_im_context_focus_in(sciThis->im_context);
	}
#endif
#endif

	return FALSE;
}

gint ScintillaGTK::FocusOut(GtkWidget *widget, GdkEventFocus * /*event*/) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("ScintillaGTK::focus out %x\n", sciThis);
	GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
	sciThis->SetFocusState(false);

#ifdef INTERNATIONAL_INPUT
#if GTK_MAJOR_VERSION < 2
	gdk_im_end();
#else
	if (PWidget(sciThis->wPreedit) != NULL)
		gtk_widget_hide(PWidget(sciThis->wPreedit));
	if (sciThis->im_context != NULL)
		gtk_im_context_focus_out(sciThis->im_context);
#endif
#endif

	return FALSE;
}

void ScintillaGTK::SizeRequest(GtkWidget *widget, GtkRequisition *requisition) {
	requisition->width = 600;
	requisition->height = gdk_screen_height();
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	GtkRequisition child_requisition;
	gtk_widget_size_request(PWidget(sciThis->scrollbarh), &child_requisition);
	gtk_widget_size_request(PWidget(sciThis->scrollbarv), &child_requisition);
}

void ScintillaGTK::SizeAllocate(GtkWidget *widget, GtkAllocation *allocation) {
	widget->allocation = *allocation;
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	if (GTK_WIDGET_REALIZED(widget))
		gdk_window_move_resize(widget->window,
		                       widget->allocation.x,
		                       widget->allocation.y,
		                       widget->allocation.width,
		                       widget->allocation.height);

	sciThis->Resize(allocation->width, allocation->height);

#ifdef INTERNATIONAL_INPUT
#if GTK_MAJOR_VERSION < 2
	if (sciThis->ic && (gdk_ic_get_style(sciThis->ic) & GDK_IM_PREEDIT_POSITION)) {
		gint width, height;

		gdk_window_get_size(widget->window, &width, &height);
		sciThis->ic_attr->preedit_area.width = width;
		sciThis->ic_attr->preedit_area.height = height;

		gdk_ic_set_attr(sciThis->ic, sciThis->ic_attr, GDK_IC_PREEDIT_AREA);
	}
#endif
#endif
}

void ScintillaGTK::Initialise() {
	//Platform::DebugPrintf("ScintillaGTK::Initialise\n");
	parentClass = reinterpret_cast<GtkWidgetClass *>(
	                  gtk_type_class(gtk_container_get_type()));

	GTK_WIDGET_SET_FLAGS(PWidget(wMain), GTK_CAN_FOCUS);
	GTK_WIDGET_SET_FLAGS(GTK_WIDGET(PWidget(wMain)), GTK_SENSITIVE);
	gtk_widget_set_events(PWidget(wMain),
	                      GDK_EXPOSURE_MASK
	                      | GDK_STRUCTURE_MASK
	                      | GDK_KEY_PRESS_MASK
	                      | GDK_KEY_RELEASE_MASK
	                      | GDK_FOCUS_CHANGE_MASK
	                      | GDK_LEAVE_NOTIFY_MASK
	                      | GDK_BUTTON_PRESS_MASK
	                      | GDK_BUTTON_RELEASE_MASK
	                      | GDK_POINTER_MOTION_MASK
	                      | GDK_POINTER_MOTION_HINT_MASK);

	wText = gtk_drawing_area_new();
	gtk_widget_set_parent(PWidget(wText), PWidget(wMain));
	gtk_widget_show(PWidget(wText));
	gtk_signal_connect(GTK_OBJECT(PWidget(wText)), "expose_event",
			   GtkSignalFunc(ScintillaGTK::ExposeText), this);
	gtk_widget_set_events(PWidget(wText), GDK_EXPOSURE_MASK);
#if GTK_MAJOR_VERSION >= 2
	// Avoid background drawing flash
	gtk_widget_set_double_buffered(PWidget(wText), FALSE);
#endif
	gtk_drawing_area_size(GTK_DRAWING_AREA(PWidget(wText)),
	                      100,100);

	adjustmentv = gtk_adjustment_new(0.0, 0.0, 201.0, 1.0, 20.0, 20.0);
	scrollbarv = gtk_vscrollbar_new(GTK_ADJUSTMENT(adjustmentv));
	GTK_WIDGET_UNSET_FLAGS(PWidget(scrollbarv), GTK_CAN_FOCUS);
	gtk_signal_connect(GTK_OBJECT(adjustmentv), "value_changed",
	                   GTK_SIGNAL_FUNC(ScrollSignal), this);
	gtk_widget_set_parent(PWidget(scrollbarv), PWidget(wMain));
	gtk_widget_show(PWidget(scrollbarv));

	adjustmenth = gtk_adjustment_new(0.0, 0.0, 101.0, 1.0, 20.0, 20.0);
	scrollbarh = gtk_hscrollbar_new(GTK_ADJUSTMENT(adjustmenth));
	GTK_WIDGET_UNSET_FLAGS(PWidget(scrollbarh), GTK_CAN_FOCUS);
	gtk_signal_connect(GTK_OBJECT(adjustmenth), "value_changed",
	                   GTK_SIGNAL_FUNC(ScrollHSignal), this);
	gtk_widget_set_parent(PWidget(scrollbarh), PWidget(wMain));
	gtk_widget_show(PWidget(scrollbarh));

	gtk_widget_grab_focus(PWidget(wMain));

	gtk_selection_add_targets(GTK_WIDGET(PWidget(wMain)), GDK_SELECTION_PRIMARY,
	                          clipboardTargets, nClipboardTargets);

#ifndef USE_GTK_CLIPBOARD
	gtk_selection_add_targets(GTK_WIDGET(PWidget(wMain)), atomClipboard,
	                          clipboardTargets, nClipboardTargets);
#endif

	gtk_drag_dest_set(GTK_WIDGET(PWidget(wMain)),
	                  GTK_DEST_DEFAULT_ALL, clipboardTargets, nClipboardTargets,
	                  static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE));

	SetTicking(true);
}

void ScintillaGTK::Finalise() {
	SetTicking(false);
	ScintillaBase::Finalise();
}

void ScintillaGTK::DisplayCursor(Window::Cursor c) {
	if (cursorMode == SC_CURSORNORMAL)
		wText.SetCursor(c);
	else
		wText.SetCursor(static_cast<Window::Cursor>(cursorMode));
}

void ScintillaGTK::StartDrag() {
	dragWasDropped = false;
	static const GtkTargetEntry targets[] = {
	    { "UTF8_STRING", 0, TARGET_UTF8_STRING },
	    { "STRING", 0, TARGET_STRING },
	    // { "TEXT", 0, TARGET_TEXT },
	    // { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
	};
	static const gint n_targets = sizeof(targets) / sizeof(targets[0]);
	GtkTargetList *tl = gtk_target_list_new(targets, n_targets);
	gtk_drag_begin(GTK_WIDGET(PWidget(wMain)),
	               tl,
	               static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE),
	               evbtn.button,
	               reinterpret_cast<GdkEvent *>(&evbtn));
}

#ifdef USE_CONVERTER
static char *ConvertText(int *lenResult, char *s, size_t len, const char *charSetDest, const char *charSetSource) {
	*lenResult = 0;
	char *destForm = 0;
	Converter conv(charSetDest, charSetSource);
	if (conv) {
		destForm = new char[len*3+1];
		char *pin = s;
		size_t inLeft = len;
		char *pout = destForm;
		size_t outLeft = len*3+1;
		size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
		if (conversions == ((size_t)(-1))) {
fprintf(stderr, "iconv %s->%s failed for %s\n", charSetSource, charSetDest, static_cast<char *>(s));
			delete []destForm;
			destForm = 0;
		} else {
//fprintf(stderr, "iconv OK %s %d\n", destForm, pout - destForm);
			*pout = '\0';
			*lenResult = pout - destForm;
		}
	} else {
fprintf(stderr, "Can not iconv %s %s\n", charSetDest, charSetSource);
	}
	if (!destForm) {
		destForm = new char[1];
		destForm[0] = '\0';
		*lenResult = 0;
	}
	return destForm;
}
#endif

// Returns the target converted to UTF8.
// Return the length in bytes.
int ScintillaGTK::TargetAsUTF8(char *text) {
	int targetLength = targetEnd - targetStart;
	if (IsUnicodeMode()) {
		if (text) {
			pdoc->GetCharRange(text, targetStart, targetLength);
		}
	} else {
		// Need to convert
#ifdef USE_CONVERTER
		const char *charSetBuffer = CharacterSetID();
		if (*charSetBuffer) {
//~ fprintf(stderr, "AsUTF8 %s %d  %0d-%0d\n", charSetBuffer, targetLength, targetStart, targetEnd);
			char *s = new char[targetLength];
			if (s) {
				pdoc->GetCharRange(s, targetStart, targetLength);
//~ fprintf(stderr, "    \"%s\"\n", s);
				if (text) {
					char *tmputf = ConvertText(&targetLength, s, targetLength, "UTF-8", charSetBuffer);
					memcpy(text, tmputf, targetLength);
					delete []tmputf;
//~ fprintf(stderr, "    \"%s\"\n", text);
				}
				delete []s;
			}
		} else {
			if (text) {
				pdoc->GetCharRange(text, targetStart, targetLength);
			}
		}
#else
		// Fail
		return 0;
#endif
	}
//~ fprintf(stderr, "Length = %d bytes\n", targetLength);
	return targetLength;
}

// Translates a nul terminated UTF8 string into the document encoding.
// Return the length of the result in bytes.
int ScintillaGTK::EncodedFromUTF8(char *utf8, char *encoded) {
	int inputLength = (lengthForEncode >= 0) ? lengthForEncode : strlen(utf8);
	if (IsUnicodeMode()) {
		if (encoded) {
			memcpy(encoded, utf8, inputLength);
		}
		return inputLength;
	} else {
		// Need to convert
#ifdef USE_CONVERTER
		const char *charSetBuffer = CharacterSetID();
		if (*charSetBuffer) {
//~ fprintf(stderr, "Encode %s %d\n", charSetBuffer, inputLength);
			int outLength = 0;
			char *tmpEncoded = ConvertText(&outLength, utf8, inputLength, charSetBuffer, "UTF-8");
			if (tmpEncoded) {
//~ fprintf(stderr, "    \"%s\"\n", tmpEncoded);
				if (encoded) {
					memcpy(encoded, tmpEncoded, outLength);
				}
				delete []tmpEncoded;
			}
			return outLength;
		} else {
			if (encoded) {
				memcpy(encoded, utf8, inputLength);
			}
			return inputLength;
		}
#endif
	}
	// Fail
	return 0;
}

sptr_t ScintillaGTK::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
	switch (iMessage) {

	case SCI_GRABFOCUS:
		gtk_widget_grab_focus(PWidget(wMain));
		break;

	case SCI_GETDIRECTFUNCTION:
		return reinterpret_cast<sptr_t>(DirectFunction);

	case SCI_GETDIRECTPOINTER:
		return reinterpret_cast<sptr_t>(this);

#ifdef SCI_LEXER
	case SCI_LOADLEXERLIBRARY:
		LexerManager::GetInstance()->Load(reinterpret_cast<const char*>(wParam));
		break;
#endif
	case SCI_TARGETASUTF8:
		return TargetAsUTF8(reinterpret_cast<char*>(lParam));

	case SCI_ENCODEDFROMUTF8:
		return EncodedFromUTF8(reinterpret_cast<char*>(wParam),
			reinterpret_cast<char*>(lParam));

	default:
		return ScintillaBase::WndProc(iMessage, wParam, lParam);
	}
	return 0l;
}

sptr_t ScintillaGTK::DefWndProc(unsigned int, uptr_t, sptr_t) {
	return 0;
}

void ScintillaGTK::SetTicking(bool on) {
	if (timer.ticking != on) {
		timer.ticking = on;
		if (timer.ticking) {
			timer.tickerID = reinterpret_cast<TickerID>(gtk_timeout_add(timer.tickSize, (GtkFunction)TimeOut, this));
		} else {
			gtk_timeout_remove(GPOINTER_TO_UINT(timer.tickerID));
		}
	}
	timer.ticksToWait = caret.period;
}

bool ScintillaGTK::SetIdle(bool on) {
	if (on) {
		// Start idler, if it's not running.
		if (idler.state == false) {
			idler.state = true;
			idler.idlerID = reinterpret_cast<IdlerID>
				(gtk_idle_add((GtkFunction)IdleCallback, this));
		}
	} else {
		// Stop idler, if it's running
		if (idler.state == true) {
			idler.state = false;
			gtk_idle_remove(GPOINTER_TO_UINT(idler.idlerID));
		}
	}
	return true;
}

void ScintillaGTK::SetMouseCapture(bool on) {
	if (mouseDownCaptures) {
		if (on) {
			gtk_grab_add(GTK_WIDGET(PWidget(wMain)));
		} else {
			gtk_grab_remove(GTK_WIDGET(PWidget(wMain)));
		}
	}
	capturedMouse = on;
}

bool ScintillaGTK::HaveMouseCapture() {
	return capturedMouse;
}

// Redraw all of text area. This paint will not be abandoned.
void ScintillaGTK::FullPaint() {
#if GTK_MAJOR_VERSION < 2
	paintState = painting;
	rcPaint = GetClientRectangle();
	//Platform::DebugPrintf("ScintillaGTK::FullPaint %0d,%0d %0d,%0d\n",
	//	rcPaint.left, rcPaint.top, rcPaint.right, rcPaint.bottom);
	paintingAllText = true;
	if ((PWidget(wText))->window) {
		Surface *sw = Surface::Allocate();
		if (sw) {
			sw->Init(PWidget(wText)->window, PWidget(wText));
			Paint(sw, rcPaint);
			sw->Release();
			delete sw;
		}
	}
	paintState = notPainting;
#else
	wText.InvalidateAll();
#endif
}

PRectangle ScintillaGTK::GetClientRectangle() {
	PRectangle rc = wMain.GetClientPosition();
	if (verticalScrollBarVisible)
		rc.right -= scrollBarWidth;
	if (horizontalScrollBarVisible && (wrapState == eWrapNone))
		rc.bottom -= scrollBarHeight;
	// Move to origin
	rc.right -= rc.left;
	rc.bottom -= rc.top;
	rc.left = 0;
	rc.top = 0;
	return rc;
}

// Synchronously paint a rectangle of the window.
void ScintillaGTK::SyncPaint(PRectangle rc) {
	paintState = painting;
	rcPaint = rc;
	PRectangle rcClient = GetClientRectangle();
	paintingAllText = rcPaint.Contains(rcClient);
	if ((PWidget(wText))->window) {
		Surface *sw = Surface::Allocate();
		if (sw) {
			sw->Init(PWidget(wText)->window, PWidget(wText));
			Paint(sw, rc);
			sw->Release();
			delete sw;
		}
	}
	if (paintState == paintAbandoned) {
		// Painting area was insufficient to cover new styling or brace highlight positions
		FullPaint();
	}
	paintState = notPainting;
}

void ScintillaGTK::ScrollText(int linesToMove) {
	int diff = vs.lineHeight * -linesToMove;
	//Platform::DebugPrintf("ScintillaGTK::ScrollText %d %d %0d,%0d %0d,%0d\n", linesToMove, diff,
	//	rc.left, rc.top, rc.right, rc.bottom);
	GtkWidget *wi = PWidget(wText);

#if GTK_MAJOR_VERSION < 2
	PRectangle rc = GetClientRectangle();
	GdkGC *gc = gdk_gc_new(wi->window);

	// Set up gc so we get GraphicsExposures from gdk_draw_pixmap
	//  which calls XCopyArea
	gdk_gc_set_exposures(gc, TRUE);

	// Redraw exposed bit : scrolling upwards
	if (diff > 0) {
		gdk_draw_pixmap(wi->window,
		                gc, wi->window,
		                0, diff,
		                0, 0,
		                rc.Width()-1, rc.Height() - diff);
		SyncPaint(PRectangle(0, rc.Height() - diff,
		                     rc.Width(), rc.Height()+1));

	// Redraw exposed bit : scrolling downwards
	} else {
		gdk_draw_pixmap(wi->window,
		                gc, wi->window,
		                0, 0,
		                0, -diff,
		                rc.Width()-1, rc.Height() + diff);
		SyncPaint(PRectangle(0, 0, rc.Width(), -diff));
	}

	// Look for any graphics expose
	GdkEvent* event;
	while ((event = gdk_event_get_graphics_expose(wi->window)) != NULL) {
		gtk_widget_event(wi, event);
		if (event->expose.count == 0) {
			gdk_event_free(event);
			break;
		}
		gdk_event_free(event);
	}

	gdk_gc_unref(gc);
#else
	gdk_window_scroll(wi->window, 0, -diff);
	gdk_window_process_updates(wi->window, FALSE);
#endif
}

void ScintillaGTK::SetVerticalScrollPos() {
	DwellEnd(true);
	gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmentv), topLine);
}

void ScintillaGTK::SetHorizontalScrollPos() {
	DwellEnd(true);
	gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmenth), xOffset / 2);
}

bool ScintillaGTK::ModifyScrollBars(int nMax, int nPage) {
	bool modified = false;
	int pageScroll = LinesToScroll();

	if (GTK_ADJUSTMENT(adjustmentv)->upper != (nMax + 1) ||
	        GTK_ADJUSTMENT(adjustmentv)->page_size != nPage ||
	        GTK_ADJUSTMENT(adjustmentv)->page_increment != pageScroll) {
		GTK_ADJUSTMENT(adjustmentv)->upper = nMax + 1;
		GTK_ADJUSTMENT(adjustmentv)->page_size = nPage;
		GTK_ADJUSTMENT(adjustmentv)->page_increment = pageScroll;
		gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmentv));
		modified = true;
	}

	PRectangle rcText = GetTextRectangle();
	int horizEndPreferred = scrollWidth;
	if (horizEndPreferred < 0)
		horizEndPreferred = 0;
	unsigned int pageWidth = rcText.Width();
	if (GTK_ADJUSTMENT(adjustmenth)->upper != horizEndPreferred ||
	        GTK_ADJUSTMENT(adjustmenth)->page_size != pageWidth) {
		GTK_ADJUSTMENT(adjustmenth)->upper = horizEndPreferred;
		GTK_ADJUSTMENT(adjustmenth)->page_size = pageWidth;
		gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmenth));
		modified = true;
	}
	return modified;
}

void ScintillaGTK::ReconfigureScrollBars() {
	PRectangle rc = wMain.GetClientPosition();
	Resize(rc.Width(), rc.Height());
}

void ScintillaGTK::NotifyChange() {
	gtk_signal_emit(GTK_OBJECT(sci), scintilla_signals[COMMAND_SIGNAL],
	                Platform::LongFromTwoShorts(GetCtrlID(), SCEN_CHANGE), PWidget(wMain));
}

void ScintillaGTK::NotifyFocus(bool focus) {
	gtk_signal_emit(GTK_OBJECT(sci), scintilla_signals[COMMAND_SIGNAL],
	                Platform::LongFromTwoShorts(GetCtrlID(), focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS), PWidget(wMain));
}

void ScintillaGTK::NotifyParent(SCNotification scn) {
	scn.nmhdr.hwndFrom = PWidget(wMain);
	scn.nmhdr.idFrom = GetCtrlID();
	gtk_signal_emit(GTK_OBJECT(sci), scintilla_signals[NOTIFY_SIGNAL],
	                GetCtrlID(), &scn);
}

void ScintillaGTK::NotifyKey(int key, int modifiers) {
	SCNotification scn = {0};
	scn.nmhdr.code = SCN_KEY;
	scn.ch = key;
	scn.modifiers = modifiers;

	NotifyParent(scn);
}

void ScintillaGTK::NotifyURIDropped(const char *list) {
	SCNotification scn = {0};
	scn.nmhdr.code = SCN_URIDROPPED;
	scn.text = list;

	NotifyParent(scn);
}

bool ScintillaGTK::UseInputMethod() const {
	switch (vs.styles[STYLE_DEFAULT].characterSet) {
	case SC_CHARSET_CHINESEBIG5:
	case SC_CHARSET_GB2312:
	case SC_CHARSET_HANGUL:
	case SC_CHARSET_SHIFTJIS:
	case SC_CHARSET_JOHAB:
	case SC_CHARSET_HEBREW:
	case SC_CHARSET_ARABIC:
	case SC_CHARSET_VIETNAMESE:
	case SC_CHARSET_THAI:
		return true;
	default:
		return false;
	}
}

const char *CharacterSetID(int characterSet);

const char *ScintillaGTK::CharacterSetID() const {
	return ::CharacterSetID(vs.styles[STYLE_DEFAULT].characterSet);
}

#if GTK_MAJOR_VERSION >= 2
#define IS_ACC(x) \
	((x) >= 65103 && (x) <= 65111)
#define IS_CHAR(x) \
	((x) >= 0 && (x) <= 128)

#define IS_ACC_OR_CHAR(x) \
	(IS_CHAR(x)) || (IS_ACC(x))

#define IS_ACC(x) \
	((x) >= 65103 && (x) <= 65111)
#define IS_CHAR(x) \
	((x) >= 0 && (x) <= 128)

#define IS_ACC_OR_CHAR(x) \
	(IS_CHAR(x)) || (IS_ACC(x))

static int MakeAccent(int key, int acc) {
	const char *conv[] = {
		"aeiounc AEIOUNC",
		"ãeiõuñc~ÃEIÕUÑC",
		"áéíóúnç'ÁÉÍÓÚNÇ",
		"àèìòùnc`ÀÈÌÒÙNC",
		"âêîôûnc^ÂÊÎÔÛNC",
		"äëïöünc¨ÄËÏÖÜNC"
	};
	int idx;
	for (idx = 0; idx < 15; ++idx) {
		if (char(key) == conv[0][idx]) {
			break;
		}
	}
	if (idx == 15) {
		return key;
	}
	if (acc == GDK_dead_tilde) { // ~
		return int((unsigned char)(conv[1][idx]));
	} else if (acc == GDK_dead_acute) { // '
		return int((unsigned char)(conv[2][idx]));
	} else if (acc == GDK_dead_grave) { // `
		return int((unsigned char)(conv[3][idx]));
	} else if (acc == GDK_dead_circumflex) { // ^
		return int((unsigned char)(conv[4][idx]));
	} else if (acc == GDK_dead_diaeresis) { // "
		return int((unsigned char)(conv[5][idx]));
	}
	return key;
}
#endif

int ScintillaGTK::KeyDefault(int key, int modifiers) {
	if (!(modifiers & SCI_CTRL) && !(modifiers & SCI_ALT)) {
#if GTK_MAJOR_VERSION >= 2
		if (!UseInputMethod()) {
			char utfVal[4]="\0\0\0";
			wchar_t wcs[2];
			if (IS_CHAR(key) && IS_ACC(lastKey)) {
				lastKey = key = MakeAccent(key, lastKey);
			}
			if (IS_ACC_OR_CHAR(key)) {
				lastKey = key;
			}
			wcs[0] = gdk_keyval_to_unicode(key);
			wcs[1] = 0;
			UTF8FromUCS2(wcs, 1, utfVal, 3);
			if (key <= 0xFE00) {
				if (IsUnicodeMode()) {
					AddCharUTF(utfVal,strlen(utfVal));
					return 1;
				} else {
					const char *source = CharacterSetID();
					if (*source) {
						Converter conv(source, "UTF-8");
						if (conv) {
							char localeVal[4]="\0\0\0";
							char *pin = utfVal;
							size_t inLeft = strlen(utfVal);
							char *pout = localeVal;
							size_t outLeft = sizeof(localeVal);
							size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
							if (conversions != ((size_t)(-1))) {
								*pout = '\0';
								for (int i=0; localeVal[i]; i++) {
									AddChar(localeVal[i]);
								}
								return 1;
							}
						}
					}
				}
			}
		}
#endif
		if (key < 256) {
			AddChar(key);
			return 1;
		} else {
			// Pass up to container in case it is an accelerator
			NotifyKey(key, modifiers);
			return 0;
		}
	} else {
		// Pass up to container in case it is an accelerator
		NotifyKey(key, modifiers);
		return 0;
	}
	//Platform::DebugPrintf("SK-key: %d %x %x\n",key, modifiers);
}

void ScintillaGTK::CopyToClipboard(const SelectionText &selectedText) {
#ifndef USE_GTK_CLIPBOARD
	copyText.Copy(selectedText);
	gtk_selection_owner_set(GTK_WIDGET(PWidget(wMain)),
				atomClipboard,
				GDK_CURRENT_TIME);
#else
	GtkClipboard *clipBoard;
	clipBoard = gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), atomClipboard);
	if (clipBoard == NULL) // Occurs if widget isn't in a toplevel
		return;

	SelectionText *clipText = new SelectionText();
	clipText->Copy(selectedText);

	gtk_clipboard_set_with_data(clipBoard, clipboardTargets, nClipboardTargets,
				    ClipboardGetSelection, ClipboardClearSelection, clipText);

#endif
}

void ScintillaGTK::Copy() {
	if (currentPos != anchor) {
#ifndef USE_GTK_CLIPBOARD
		CopySelectionRange(&copyText);
		gtk_selection_owner_set(GTK_WIDGET(PWidget(wMain)),
		                        atomClipboard,
		                        GDK_CURRENT_TIME);
#else
		GtkClipboard *clipBoard;
		clipBoard = gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), atomClipboard);
		if (clipBoard == NULL) // Occurs if widget isn't in a toplevel
			return;

		SelectionText *clipText = new SelectionText();
		CopySelectionRange(clipText);

		gtk_clipboard_set_with_data(clipBoard, clipboardTargets, nClipboardTargets,
					    ClipboardGetSelection, ClipboardClearSelection, clipText);

#endif
#if PLAT_GTK_WIN32
		if (selType == selRectangle) {
			::OpenClipboard(NULL);
			::SetClipboardData(cfColumnSelect, 0);
			::CloseClipboard();
		}
#endif
	}
}

void ScintillaGTK::Paste() {
	atomSought = atomUTF8;
	gtk_selection_convert(GTK_WIDGET(PWidget(wMain)),
	                      atomClipboard, atomSought, GDK_CURRENT_TIME);
}

void ScintillaGTK::CreateCallTipWindow(PRectangle rc) {
	if (!ct.wCallTip.Created()) {
		ct.wCallTip = gtk_window_new(GTK_WINDOW_POPUP);
		ct.wDraw = gtk_drawing_area_new();
		gtk_container_add(GTK_CONTAINER(PWidget(ct.wCallTip)), PWidget(ct.wDraw));
		gtk_signal_connect(GTK_OBJECT(PWidget(ct.wDraw)), "expose_event",
				   GtkSignalFunc(ScintillaGTK::ExposeCT), &ct);
		gtk_signal_connect(GTK_OBJECT(PWidget(ct.wDraw)), "button_press_event",
				   GtkSignalFunc(ScintillaGTK::PressCT), static_cast<void *>(this));
		gtk_widget_set_events(PWidget(ct.wDraw),
			GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK);
	}
	gtk_drawing_area_size(GTK_DRAWING_AREA(PWidget(ct.wDraw)),
	                      rc.Width(), rc.Height());
	ct.wDraw.Show();
	//ct.wCallTip.Show();
	//gtk_widget_set_usize(PWidget(ct.wCallTip), rc.Width(), rc.Height());
	gdk_window_resize(PWidget(ct.wCallTip)->window, rc.Width(), rc.Height());
}

void ScintillaGTK::AddToPopUp(const char *label, int cmd, bool enabled) {
	char fulllabel[200];
	strcpy(fulllabel, "/");
	strcat(fulllabel, label);
	GtkItemFactoryCallback menuSig = GtkItemFactoryCallback(PopUpCB);
	GtkItemFactoryEntry itemEntry = {
	    fulllabel, NULL,
	    menuSig,
	    cmd,
	    const_cast<gchar *>(label[0] ? "<Item>" : "<Separator>"),
#if GTK_MAJOR_VERSION >= 2
	    NULL
#endif
	};
	gtk_item_factory_create_item(GTK_ITEM_FACTORY(popup.GetID()),
	                             &itemEntry, this, 1);
	if (cmd) {
		GtkWidget *item = gtk_item_factory_get_widget_by_action(
		                      reinterpret_cast<GtkItemFactory *>(popup.GetID()), cmd);
		if (item)
			gtk_widget_set_sensitive(item, enabled);
	}
}

bool ScintillaGTK::OwnPrimarySelection() {
	return ((gdk_selection_owner_get(GDK_SELECTION_PRIMARY)
		== GTK_WIDGET(PWidget(wMain))->window) &&
			(GTK_WIDGET(PWidget(wMain))->window != NULL));
}

void ScintillaGTK::ClaimSelection() {
	// X Windows has a 'primary selection' as well as the clipboard.
	// Whenever the user selects some text, we become the primary selection
	if (currentPos != anchor) {
		primarySelection = true;
		gtk_selection_owner_set(GTK_WIDGET(PWidget(wMain)),
		                        GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME);
		primary.Free();
	} else if (OwnPrimarySelection()) {
		primarySelection = true;
		if (primary.s == NULL)
			gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME);
	} else {
		primarySelection = false;
		primary.Free();
	}
}

// Detect rectangular text, convert line ends to current mode, convert from or to UTF-8
void ScintillaGTK::GetGtkSelectionText(GtkSelectionData *selectionData, SelectionText &selText) {
	char *data = reinterpret_cast<char *>(selectionData->data);
	int len = selectionData->length;
	GdkAtom selectionType = selectionData->type;

	// Return empty string if selection is not a string
	if ((selectionType != GDK_TARGET_STRING) && (selectionType != atomUTF8)) {
		char *empty = new char[1];
		empty[0] = '\0';
		selText.Set(empty, 0, SC_CP_UTF8, 0, false);
		return;
	}

	// Check for "\n\0" ending to string indicating that selection is rectangular
	bool isRectangular;
#if PLAT_GTK_WIN32
	isRectangular = ::IsClipboardFormatAvailable(cfColumnSelect) != 0;
#else
	isRectangular = ((len > 2) && (data[len - 1] == 0 && data[len - 2] == '\n'));
#endif

	char *dest;
	if (selectionType == GDK_TARGET_STRING) {
		dest = Document::TransformLineEnds(&len, data, len, pdoc->eolMode);
		if (IsUnicodeMode()) {
			// Unknown encoding so assume in Latin1
			char *destPrevious = dest;
			dest = UTF8FromLatin1(dest, len);
			selText.Set(dest, len, SC_CP_UTF8, 0, selText.rectangular);
			delete []destPrevious;
		} else {
			// Assume buffer is in same encoding as selection
			selText.Set(dest, len, pdoc->dbcsCodePage,
				vs.styles[STYLE_DEFAULT].characterSet, isRectangular);
		}
	} else {	// UTF-8
		dest = Document::TransformLineEnds(&len, data, len, pdoc->eolMode);
		selText.Set(dest, len, SC_CP_UTF8, 0, isRectangular);
#ifdef USE_CONVERTER
		const char *charSetBuffer = CharacterSetID();
		if (!IsUnicodeMode() && *charSetBuffer) {
//fprintf(stderr, "Convert to locale %s\n", CharacterSetID());
				// Convert to locale
				dest = ConvertText(&len, selText.s, selText.len, charSetBuffer, "UTF-8");
				selText.Set(dest, len, pdoc->dbcsCodePage,
					vs.styles[STYLE_DEFAULT].characterSet, selText.rectangular);
		}
#endif
	}
}

void ScintillaGTK::ReceivedSelection(GtkSelectionData *selection_data) {
	if ((selection_data->selection == atomClipboard) ||
		(selection_data->selection == GDK_SELECTION_PRIMARY)) {
		if ((atomSought == atomUTF8) && (selection_data->length <= 0)) {
			atomSought = atomString;
			gtk_selection_convert(GTK_WIDGET(PWidget(wMain)),
					      selection_data->selection, atomSought, GDK_CURRENT_TIME);
		} else if ((selection_data->length > 0) &&
			((selection_data->type == GDK_TARGET_STRING) || (selection_data->type == atomUTF8))) {
			SelectionText selText;
			GetGtkSelectionText(selection_data, selText);

			pdoc->BeginUndoAction();
			if (selection_data->selection != GDK_SELECTION_PRIMARY) {
				ClearSelection();
			}
			int selStart = SelectionStart();

			if (selText.rectangular) {
				PasteRectangular(selStart, selText.s, selText.len);
			} else {
				pdoc->InsertString(currentPos, selText.s, selText.len);
				SetEmptySelection(currentPos + selText.len);
			}
			pdoc->EndUndoAction();
		}
	}
//	else fprintf(stderr, "Target non string %d %d\n", (int)(selection_data->type),
//		(int)(atomUTF8));
	Redraw();
}

void ScintillaGTK::ReceivedDrop(GtkSelectionData *selection_data) {
	dragWasDropped = true;
	if ((selection_data->type == GDK_TARGET_STRING) || (selection_data->type == atomUTF8)) {
		if (selection_data->length > 0) {
			SelectionText selText;
			GetGtkSelectionText(selection_data, selText);
			DropAt(posDrop, selText.s, false, selText.rectangular);
		}
	} else {
		char *ptr = reinterpret_cast<char *>(selection_data->data);
		NotifyURIDropped(ptr);
	}
	Redraw();
}



void ScintillaGTK::GetSelection(GtkSelectionData *selection_data, guint info, SelectionText *text) {
#if GTK_MAJOR_VERSION >= 2
	// Convert text to utf8 if it isn't already
	SelectionText *converted = 0;
	if ((text->codePage != SC_CP_UTF8) && (info == TARGET_UTF8_STRING)) {
		const char *charSet = ::CharacterSetID(text->characterSet);
		if (*charSet) {
			int new_len;
			char* tmputf = ConvertText(&new_len, text->s, text->len, "UTF-8", charSet);
			converted = new SelectionText();
			converted->Set(tmputf, new_len, SC_CP_UTF8, 0, text->rectangular);
			text = converted;
		}
	}

	// Here is a somewhat evil kludge.
	// As I can not work out how to store data on the clipboard in multiple formats
	// and need some way to mark the clipping as being stream or rectangular,
	// the terminating \0 is included in the length for rectangular clippings.
	// All other tested aplications behave benignly by ignoring the \0.
	// The #if is here because on Windows cfColumnSelect clip entry is used
	// instead as standard indicator of rectangularness (so no need to kludge)
	int len = strlen(text->s);
#if PLAT_GTK_WIN32 == 0
	if (text->rectangular)
		len++;
#endif

	if (info == TARGET_UTF8_STRING) {
		gtk_selection_data_set_text(selection_data, text->s, len);
	} else {
		gtk_selection_data_set(selection_data,
			static_cast<GdkAtom>(GDK_SELECTION_TYPE_STRING),
			8, reinterpret_cast<unsigned char *>(text->s), len);
	}
	delete converted;

#else /* Gtk 1 */
	char *selBuffer = text->s;

#if PLAT_GTK_WIN32

	// Many native win32 programs require \n line endings,
	 // so make a copy of the clip text now with newlines converted

	int new_len;
	char *tmpstr = Document::TransformLineEnds(&new_len, selBuffer, text->len, SC_EOL_LF);
	selBuffer = tmpstr;
#endif
	char *tmputf = 0;
	if ((info == TARGET_UTF8_STRING) || (info == TARGET_STRING)) {
		int len = strlen(selBuffer);
#ifdef USE_CONVERTER
		// Possible character set conversion
		const char *charSetBuffer = ::CharacterSetID(text->characterSet);
		if (info == TARGET_UTF8_STRING) {
			//fprintf(stderr, "Copy to clipboard as UTF-8\n");
			if (text->codePage != SC_CP_UTF8) {
				// Convert to UTF-8
	//fprintf(stderr, "Convert to UTF-8 from %s\n", charSetBuffer);
				tmputf = ConvertText(&len, selBuffer, len, "UTF-8", charSetBuffer);
				selBuffer = tmputf;
			}
		} else if (info == TARGET_STRING) {
			if (text->codePage == SC_CP_UTF8) {
	//fprintf(stderr, "Convert to locale %s\n", charSetBuffer);
				// Convert to locale
				tmputf = ConvertText(&len, selBuffer, len, charSetBuffer, "UTF-8");
				selBuffer = tmputf;
			}
		}
#endif

		// Here is a somewhat evil kludge.
		// As I can not work out how to store data on the clipboard in multiple formats
		// and need some way to mark the clipping as being stream or rectangular,
		// the terminating \0 is included in the length for rectangular clippings.
		// All other tested aplications behave benignly by ignoring the \0.
		// The #if is here because on Windows cfColumnSelect clip entry is used
                // instead as standard indicator of rectangularness (so no need to kludge)
#if PLAT_GTK_WIN32 == 0
		if (text->rectangular)
			len++;
#endif
		gtk_selection_data_set(selection_data,
					(info == TARGET_STRING) ?
					static_cast<GdkAtom>(GDK_SELECTION_TYPE_STRING) : atomUTF8,
		                       8, reinterpret_cast<unsigned char *>(selBuffer),
		                       len);
	} else if ((info == TARGET_TEXT) || (info == TARGET_COMPOUND_TEXT)) {
		guchar *text;
		GdkAtom encoding;
		gint format;
		gint new_length;

		gdk_string_to_compound_text(reinterpret_cast<char *>(selBuffer),
		                            &encoding, &format, &text, &new_length);
		gtk_selection_data_set(selection_data, encoding, format, text, new_length);
		gdk_free_compound_text(text);
	}

	delete []tmputf;
#if PLAT_GTK_WIN32
	delete []tmpstr;
#endif
#endif /* Gtk >= 2 */
}

#ifdef USE_GTK_CLIPBOARD
void ScintillaGTK::ClipboardGetSelection(GtkClipboard *, GtkSelectionData *selection_data, guint info, void *data) {
	GetSelection(selection_data, info, static_cast<SelectionText*>(data));
}

void ScintillaGTK::ClipboardClearSelection(GtkClipboard *, void *data) {
	SelectionText *obj = static_cast<SelectionText*>(data);
	delete obj;
}
#endif

void ScintillaGTK::UnclaimSelection(GdkEventSelection *selection_event) {
	//Platform::DebugPrintf("UnclaimSelection\n");
	if (selection_event->selection == GDK_SELECTION_PRIMARY) {
		//Platform::DebugPrintf("UnclaimPrimarySelection\n");
		if (!OwnPrimarySelection()) {
			primary.Free();
			primarySelection = false;
			FullPaint();
		}
	}
}

void ScintillaGTK::Resize(int width, int height) {
	//Platform::DebugPrintf("Resize %d %d\n", width, height);
	//printf("Resize %d %d\n", width, height);

	// Not always needed, but some themes can have different sizes of scrollbars
	scrollBarWidth = GTK_WIDGET(PWidget(scrollbarv))->requisition.width;
	scrollBarHeight = GTK_WIDGET(PWidget(scrollbarh))->requisition.height;

	// These allocations should never produce negative sizes as they would wrap around to huge
	// unsigned numbers inside GTK+ causing warnings.
	bool showSBHorizontal = horizontalScrollBarVisible && (wrapState == eWrapNone);
	int horizontalScrollBarHeight = scrollBarHeight;
	if (!showSBHorizontal)
		horizontalScrollBarHeight = 0;
	int verticalScrollBarHeight = scrollBarWidth;
	if (!verticalScrollBarVisible)
		verticalScrollBarHeight = 0;

	GtkAllocation alloc;
	alloc.x = 0;
	if (showSBHorizontal) {
		alloc.y = height - scrollBarHeight;
		alloc.width = Platform::Maximum(1, width - scrollBarWidth) + 1;
		alloc.height = horizontalScrollBarHeight;
	} else {
		alloc.y = -scrollBarHeight;
		alloc.width = 0;
		alloc.height = 0;
	}
	gtk_widget_size_allocate(GTK_WIDGET(PWidget(scrollbarh)), &alloc);

	alloc.y = 0;
	if (verticalScrollBarVisible) {
		alloc.x = width - scrollBarWidth;
		alloc.width = scrollBarWidth;
		alloc.height = Platform::Maximum(1, height - scrollBarHeight) + 1;
		if (!showSBHorizontal)
			alloc.height += scrollBarWidth-1;
	} else {
		alloc.x = -scrollBarWidth;
		alloc.width = 0;
		alloc.height = 0;
	}
	gtk_widget_size_allocate(GTK_WIDGET(PWidget(scrollbarv)), &alloc);
	if (GTK_WIDGET_MAPPED(PWidget(wMain))) {
		ChangeSize();
	}

	alloc.x = 0;
	alloc.y = 0;
	alloc.width = Platform::Maximum(1, width - scrollBarWidth);
	alloc.height = Platform::Maximum(1, height - scrollBarHeight);
	if (!showSBHorizontal)
		alloc.height += scrollBarWidth;
	gtk_widget_size_allocate(GTK_WIDGET(PWidget(wText)), &alloc);
}

static void SetAdjustmentValue(GtkObject *object, int value) {
	GtkAdjustment *adjustment = GTK_ADJUSTMENT(object);
	int maxValue = static_cast<int>(
		adjustment->upper - adjustment->page_size);
	if (value > maxValue)
		value = maxValue;
	if (value < 0)
		value = 0;
	gtk_adjustment_set_value(adjustment, value);
}

gint ScintillaGTK::PressThis(GdkEventButton *event) {
	//Platform::DebugPrintf("Press %x time=%d state = %x button = %x\n",this,event->time, event->state, event->button);
	// Do not use GTK+ double click events as Scintilla has its own double click detection
	if (event->type != GDK_BUTTON_PRESS)
		return FALSE;

	evbtn = *event;
	Point pt;
	pt.x = int(event->x);
	pt.y = int(event->y);
	PRectangle rcClient = GetClientRectangle();
	//Platform::DebugPrintf("Press %0d,%0d in %0d,%0d %0d,%0d\n",
	//	pt.x, pt.y, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
	if ((pt.x > rcClient.right) || (pt.y > rcClient.bottom)) {
		Platform::DebugPrintf("Bad location\n");
		return FALSE;
	}

	bool ctrl = (event->state & GDK_CONTROL_MASK) != 0;

	gtk_widget_grab_focus(PWidget(wMain));
	if (event->button == 1) {
		// On X, instead of sending literal modifiers use control instead of alt
		// This is because most X window managers grab alt + click for moving
#if !PLAT_GTK_WIN32
		ButtonDown(pt, event->time,
				    (event->state & GDK_SHIFT_MASK) != 0,
				    (event->state & GDK_CONTROL_MASK) != 0,
				    (event->state & GDK_CONTROL_MASK) != 0);
#else
		ButtonDown(pt, event->time,
				    (event->state & GDK_SHIFT_MASK) != 0,
				    (event->state & GDK_CONTROL_MASK) != 0,
				    (event->state & GDK_MOD1_MASK) != 0);
#endif
	} else if (event->button == 2) {
		// Grab the primary selection if it exists
		Position pos = PositionFromLocation(pt);
		if (OwnPrimarySelection() && primary.s == NULL)
			CopySelectionRange(&primary);

		SetSelection(pos, pos);
		atomSought = atomUTF8;
		gtk_selection_convert(GTK_WIDGET(PWidget(wMain)), GDK_SELECTION_PRIMARY,
		                      atomSought, event->time);
	} else if (event->button == 3) {
		if (displayPopupMenu) {
			// PopUp menu
			// Convert to screen
			int ox = 0;
			int oy = 0;
			gdk_window_get_origin(PWidget(wMain)->window, &ox, &oy);
			ContextMenu(Point(pt.x + ox, pt.y + oy));
		} else {
			return FALSE;
		}
	} else if (event->button == 4) {
		// Wheel scrolling up (only xwin gtk does it this way)
		if (ctrl)
			SetAdjustmentValue(adjustmenth, (xOffset / 2) - 6);
		else
			SetAdjustmentValue(adjustmentv, topLine - 3);
	} else if (event->button == 5) {
		// Wheel scrolling down (only xwin gtk does it this way)
		if (ctrl)
			SetAdjustmentValue(adjustmenth, (xOffset / 2) + 6);
		else
			SetAdjustmentValue(adjustmentv, topLine + 3);
	}
#if GTK_MAJOR_VERSION >= 2
	return TRUE;
#else
	return FALSE;
#endif
}

gint ScintillaGTK::Press(GtkWidget *widget, GdkEventButton *event) {
	if (event->window != widget->window)
		return FALSE;
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	return sciThis->PressThis(event);
}

gint ScintillaGTK::MouseRelease(GtkWidget *widget, GdkEventButton *event) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("Release %x %d %d\n",sciThis,event->time,event->state);
	if (!sciThis->HaveMouseCapture())
		return FALSE;
	if (event->button == 1) {
		Point pt;
		pt.x = int(event->x);
		pt.y = int(event->y);
		//Platform::DebugPrintf("Up %x %x %d %d %d\n",
		//	sciThis,event->window,event->time, pt.x, pt.y);
		if (event->window != PWidget(sciThis->wMain)->window)
			// If mouse released on scroll bar then the position is relative to the
			// scrollbar, not the drawing window so just repeat the most recent point.
			pt = sciThis->ptMouseLast;
		sciThis->ButtonUp(pt, event->time, (event->state & 4) != 0);
	}
	return FALSE;
}

// win32gtk has a special wheel mouse event for whatever reason and doesn't
// use the button4/5 trick used under X windows.
#if PLAT_GTK_WIN32 || (GTK_MAJOR_VERSION >= 2)
gint ScintillaGTK::ScrollEvent(GtkWidget *widget,
                               GdkEventScroll *event) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);

	if (widget == NULL || event == NULL)
		return FALSE;

	// Compute amount and direction to scroll (even tho on win32 there is
	// intensity of scrolling info in the native message, gtk doesn't
	// support this so we simulate similarly adaptive scrolling)
	int cLineScroll;
	int timeDelta = 1000000;
	GTimeVal curTime;
	g_get_current_time(&curTime);
	if (curTime.tv_sec == sciThis->lastWheelMouseTime.tv_sec)
		timeDelta = curTime.tv_usec - sciThis->lastWheelMouseTime.tv_usec;
	else if (curTime.tv_sec == sciThis->lastWheelMouseTime.tv_sec + 1)
		timeDelta = 1000000 + (curTime.tv_usec - sciThis->lastWheelMouseTime.tv_usec);
	if ((event->direction == sciThis->lastWheelMouseDirection) && (timeDelta < 250000)) {
		if (sciThis->wheelMouseIntensity < 12)
			sciThis->wheelMouseIntensity++;
		cLineScroll = sciThis->wheelMouseIntensity;
	} else {
		cLineScroll = sciThis->linesPerScroll;
		if (cLineScroll == 0)
			cLineScroll = 4;
		sciThis->wheelMouseIntensity = cLineScroll;
	}
	if (event->direction == GDK_SCROLL_UP) {
		cLineScroll *= -1;
	}
	g_get_current_time(&sciThis->lastWheelMouseTime);
	sciThis->lastWheelMouseDirection = event->direction;

	// Note:  Unpatched versions of win32gtk don't set the 'state' value so
	// only regular scrolling is supported there.  Also, unpatched win32gtk
	// issues spurious button 2 mouse events during wheeling, which can cause
	// problems (a patch for both was submitted by archaeopteryx.com on 13Jun2001)

	// Data zoom not supported
	if (event->state & GDK_SHIFT_MASK) {
		return FALSE;
	}

	// Text font size zoom
	if (event->state & GDK_CONTROL_MASK) {
		if (cLineScroll < 0) {
			sciThis->KeyCommand(SCI_ZOOMIN);
			return TRUE;
		} else {
			sciThis->KeyCommand(SCI_ZOOMOUT);
			return TRUE;
		}

	// Regular scrolling
	} else {
		sciThis->ScrollTo(sciThis->topLine + cLineScroll);
		return TRUE;
	}
}
#endif

gint ScintillaGTK::Motion(GtkWidget *widget, GdkEventMotion *event) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("Motion %x %d\n",sciThis,event->time);
	if (event->window != widget->window)
		return FALSE;
	int x = 0;
	int y = 0;
	GdkModifierType state;
	if (event->is_hint) {
		gdk_window_get_pointer(event->window, &x, &y, &state);
	} else {
		x = static_cast<int>(event->x);
		y = static_cast<int>(event->y);
		state = static_cast<GdkModifierType>(event->state);
	}
	//Platform::DebugPrintf("Move %x %x %d %c %d %d\n",
	//	sciThis,event->window,event->time,event->is_hint? 'h' :'.', x, y);
	Point pt(x, y);
	sciThis->ButtonMove(pt);
	return FALSE;
}

// Map the keypad keys to their equivalent functions
static int KeyTranslate(int keyIn) {
	switch (keyIn) {
	case GDK_ISO_Left_Tab:
		return SCK_TAB;
	case GDK_KP_Down:
		return SCK_DOWN;
	case GDK_KP_Up:
		return SCK_UP;
	case GDK_KP_Left:
		return SCK_LEFT;
	case GDK_KP_Right:
		return SCK_RIGHT;
	case GDK_KP_Home:
		return SCK_HOME;
	case GDK_KP_End:
		return SCK_END;
	case GDK_KP_Page_Up:
		return SCK_PRIOR;
	case GDK_KP_Page_Down:
		return SCK_NEXT;
	case GDK_KP_Delete:
		return SCK_DELETE;
	case GDK_KP_Insert:
		return SCK_INSERT;
	case GDK_KP_Enter:
		return SCK_RETURN;

	case GDK_Down:
		return SCK_DOWN;
	case GDK_Up:
		return SCK_UP;
	case GDK_Left:
		return SCK_LEFT;
	case GDK_Right:
		return SCK_RIGHT;
	case GDK_Home:
		return SCK_HOME;
	case GDK_End:
		return SCK_END;
	case GDK_Page_Up:
		return SCK_PRIOR;
	case GDK_Page_Down:
		return SCK_NEXT;
	case GDK_Delete:
		return SCK_DELETE;
	case GDK_Insert:
		return SCK_INSERT;
	case GDK_Escape:
		return SCK_ESCAPE;
	case GDK_BackSpace:
		return SCK_BACK;
	case GDK_Tab:
		return SCK_TAB;
	case GDK_Return:
		return SCK_RETURN;
	case GDK_KP_Add:
		return SCK_ADD;
	case GDK_KP_Subtract:
		return SCK_SUBTRACT;
	case GDK_KP_Divide:
		return SCK_DIVIDE;
	default:
		return keyIn;
	}
}

gint ScintillaGTK::KeyThis(GdkEventKey *event) {
	//Platform::DebugPrintf("SC-key: %d %x [%s]\n",
	//	event->keyval, event->state, (event->length > 0) ? event->string : "empty");
#if GTK_MAJOR_VERSION >= 2
	if (UseInputMethod()) {
		if (gtk_im_context_filter_keypress(im_context, event)) {
			return 1;
		}
	}
#endif
	if (!event->keyval) {
		return true;
	}

	bool shift = (event->state & GDK_SHIFT_MASK) != 0;
	bool ctrl = (event->state & GDK_CONTROL_MASK) != 0;
	bool alt = (event->state & GDK_MOD1_MASK) != 0;
	int key = event->keyval;
	if (ctrl && (key < 128))
		key = toupper(key);
	else if (!ctrl && (key >= GDK_KP_Multiply && key <= GDK_KP_9))
		key &= 0x7F;
	// Hack for keys over 256 and below command keys but makes Hungarian work.
	// This will have to change for Unicode
	else if (key >= 0xFE00)
		key = KeyTranslate(key);
	else if (IsUnicodeMode())
		;	// No operation
#if GTK_MAJOR_VERSION < 2
	else if ((key >= 0x100) && (key < 0x1000))
		key &= 0xff;
#endif

	bool consumed = false;
	bool added = KeyDown(key, shift, ctrl, alt, &consumed) != 0;
	if (!consumed)
		consumed = added;
	//Platform::DebugPrintf("SK-key: %d %x %x\n",event->keyval, event->state, consumed);
	if (event->keyval == 0xffffff && event->length > 0) {
		ClearSelection();
		if (pdoc->InsertString(CurrentPosition(), event->string)) {
			MovePositionTo(CurrentPosition() + event->length);
		}
	}
	return consumed;
}

gint ScintillaGTK::KeyPress(GtkWidget *widget, GdkEventKey *event) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	return sciThis->KeyThis(event);
}

gint ScintillaGTK::KeyRelease(GtkWidget *, GdkEventKey * /*event*/) {
	//Platform::DebugPrintf("SC-keyrel: %d %x %3s\n",event->keyval, event->state, event->string);
	return FALSE;
}

#if GTK_MAJOR_VERSION >= 2
gint ScintillaGTK::ExposePreedit(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis) {
	return sciThis->ExposePreeditThis(widget, ose);
}

gint ScintillaGTK::ExposePreeditThis(GtkWidget *widget, GdkEventExpose *ose) {
	gchar *str;
	gint cursor_pos;
	PangoAttrList *attrs;

	gtk_im_context_get_preedit_string(im_context, &str, &attrs, &cursor_pos);
	PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), str);
	pango_layout_set_attributes(layout, attrs);

	GdkGC *gc = gdk_gc_new(widget->window);
	GdkColor color[2] = {   {0, 0x0000, 0x0000, 0x0000},
                            {0, 0xffff, 0xffff, 0xffff}};
	gdk_color_alloc(gdk_colormap_get_system(), color);
	gdk_color_alloc(gdk_colormap_get_system(), color + 1);

	gdk_gc_set_foreground(gc, color + 1);
	gdk_draw_rectangle(widget->window, gc, TRUE, ose->area.x, ose->area.y,
	                   ose->area.width, ose->area.height);

	gdk_gc_set_foreground(gc, color);
	gdk_gc_set_background(gc, color + 1);
	gdk_draw_layout(widget->window, gc, 0, 0, layout);

	gdk_gc_unref(gc);
	g_free(str);
	pango_attr_list_unref(attrs);
	g_object_unref(layout);
	return TRUE;
}

void ScintillaGTK::Commit(GtkIMContext *, char  *str, ScintillaGTK *sciThis) {
	sciThis->CommitThis(str);
}

void ScintillaGTK::CommitThis(char *str) {
	AddCharUTF(str, strlen(str));
}

void ScintillaGTK::PreeditChanged(GtkIMContext *, ScintillaGTK *sciThis) {
	sciThis->PreeditChangedThis();
}

void ScintillaGTK::PreeditChangedThis() {
	gchar *str;
	PangoAttrList *attrs;
	gint cursor_pos;
	gtk_im_context_get_preedit_string(im_context, &str, &attrs, &cursor_pos);
	if (strlen(str) > 0){
		PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), str);
		pango_layout_set_attributes(layout, attrs);

		gint w, h;
		pango_layout_get_pixel_size(layout, &w, &h);
		g_object_unref(layout);

		gint x, y;
		gdk_window_get_origin((PWidget(wText))->window, &x, &y);

		Point pt = LocationFromPosition(currentPos);
		if (pt.x < 0)
			pt.x = 0;
		if (pt.y < 0)
			pt.y = 0;

		gtk_window_move(GTK_WINDOW(PWidget(wPreedit)), x+pt.x, y+pt.y);
		gtk_window_resize(GTK_WINDOW(PWidget(wPreedit)), w, h);
		gtk_widget_show(PWidget(wPreedit));
		gtk_widget_queue_draw_area(PWidget(wPreeditDraw), 0, 0, w, h);
	} else {
		gtk_widget_hide(PWidget(wPreedit));
	}
	g_free(str);
	pango_attr_list_unref(attrs);
}
#endif

gint ScintillaGTK::StyleSetText(GtkWidget *widget, GtkStyle *, void*) {
	if (widget->window != NULL)
		gdk_window_set_back_pixmap(widget->window, NULL, FALSE);
	return FALSE;
}

gint ScintillaGTK::RealizeText(GtkWidget *widget, void*) {
	if (widget->window != NULL)
		gdk_window_set_back_pixmap(widget->window, NULL, FALSE);
	return FALSE;
}

void ScintillaGTK::Destroy(GtkObject* object) {
	ScintillaObject *scio = reinterpret_cast<ScintillaObject *>(object);
	// This avoids a double destruction
	if (!scio->pscin)
		return;
	ScintillaGTK *sciThis = reinterpret_cast<ScintillaGTK *>(scio->pscin);
	//Platform::DebugPrintf("Destroying %x %x\n", sciThis, object);
	sciThis->Finalise();

	if (GTK_OBJECT_CLASS(parent_class)->destroy)
		(* GTK_OBJECT_CLASS(parent_class)->destroy)(object);

	delete sciThis;
	scio->pscin = 0;
}

static void DrawChild(GtkWidget *widget, GdkRectangle *area) {
	GdkRectangle areaIntersect;
	if (widget &&
	        GTK_WIDGET_DRAWABLE(widget) &&
	        gtk_widget_intersect(widget, area, &areaIntersect)) {
		gtk_widget_draw(widget, &areaIntersect);
	}
}

void ScintillaGTK::Draw(GtkWidget *widget, GdkRectangle *area) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("Draw %p %0d,%0d %0d,%0d\n", widget, area->x, area->y, area->width, area->height);
	PRectangle rcPaint(area->x, area->y, area->x + area->width, area->y + area->height);
	sciThis->SyncPaint(rcPaint);
	if (GTK_WIDGET_DRAWABLE(PWidget(sciThis->wMain))) {
		DrawChild(PWidget(sciThis->scrollbarh), area);
		DrawChild(PWidget(sciThis->scrollbarv), area);
	}

#ifdef INTERNATIONAL_INPUT
	Point pt = sciThis->LocationFromPosition(sciThis->currentPos);
	pt.y += sciThis->vs.lineHeight - 2;
	if (pt.x < 0) pt.x = 0;
	if (pt.y < 0) pt.y = 0;
	CursorMoved(widget, pt.x, pt.y, sciThis);
#endif
}

gint ScintillaGTK::ExposeTextThis(GtkWidget * /*widget*/, GdkEventExpose *ose) {
	paintState = painting;

	rcPaint.left = ose->area.x;
	rcPaint.top = ose->area.y;
	rcPaint.right = ose->area.x + ose->area.width;
	rcPaint.bottom = ose->area.y + ose->area.height;

	PRectangle rcClient = GetClientRectangle();
	paintingAllText = rcPaint.Contains(rcClient);
	Surface *surfaceWindow = Surface::Allocate();
	if (surfaceWindow) {
		surfaceWindow->Init(PWidget(wText)->window, PWidget(wText));
		Paint(surfaceWindow, rcPaint);
		surfaceWindow->Release();
		delete surfaceWindow;
	}
	if (paintState == paintAbandoned) {
		// Painting area was insufficient to cover new styling or brace highlight positions
		FullPaint();
	}
	paintState = notPainting;
	return FALSE;
}

gint ScintillaGTK::ExposeText(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis) {
	return sciThis->ExposeTextThis(widget, ose);
}

gint ScintillaGTK::ExposeMain(GtkWidget *widget, GdkEventExpose *ose) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("Expose Main %0d,%0d %0d,%0d\n",
	//ose->area.x, ose->area.y, ose->area.width, ose->area.height);
	return sciThis->Expose(widget, ose);
}

gint ScintillaGTK::Expose(GtkWidget *, GdkEventExpose *ose) {
	//fprintf(stderr, "Expose %0d,%0d %0d,%0d\n",
	//ose->area.x, ose->area.y, ose->area.width, ose->area.height);

#if GTK_MAJOR_VERSION < 2

	paintState = painting;

	rcPaint.left = ose->area.x;
	rcPaint.top = ose->area.y;
	rcPaint.right = ose->area.x + ose->area.width;
	rcPaint.bottom = ose->area.y + ose->area.height;

	PRectangle rcClient = GetClientRectangle();
	paintingAllText = rcPaint.Contains(rcClient);
	Surface *surfaceWindow = Surface::Allocate();
	if (surfaceWindow) {
		surfaceWindow->Init(PWidget(wMain)->window, PWidget(wMain));

		// Fill the corner between the scrollbars
		if (verticalScrollBarVisible) {
			if (horizontalScrollBarVisible && (wrapState == eWrapNone)) {
				PRectangle rcCorner = wMain.GetClientPosition();
				rcCorner.left = rcCorner.right - scrollBarWidth + 1;
				rcCorner.top = rcCorner.bottom - scrollBarHeight + 1;
				//fprintf(stderr, "Corner %0d,%0d %0d,%0d\n",
				//rcCorner.left, rcCorner.top, rcCorner.right, rcCorner.bottom);
				surfaceWindow->FillRectangle(rcCorner,
					vs.styles[STYLE_LINENUMBER].back.allocated);
			}
		}

		//Paint(surfaceWindow, rcPaint);
		surfaceWindow->Release();
		delete surfaceWindow;
	}
	if (paintState == paintAbandoned) {
		// Painting area was insufficient to cover new styling or brace highlight positions
		FullPaint();
	}
	paintState = notPainting;

#else
	// For GTK+ 2, the text is painted in ExposeText
	gtk_container_propagate_expose(
		GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarh), ose);
	gtk_container_propagate_expose(
		GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarv), ose);
#endif

	return FALSE;
}

void ScintillaGTK::ScrollSignal(GtkAdjustment *adj, ScintillaGTK *sciThis) {
	sciThis->ScrollTo(static_cast<int>(adj->value), false);
}

void ScintillaGTK::ScrollHSignal(GtkAdjustment *adj, ScintillaGTK *sciThis) {
	sciThis->HorizontalScrollTo(static_cast<int>(adj->value * 2));
}

void ScintillaGTK::SelectionReceived(GtkWidget *widget,
                                     GtkSelectionData *selection_data, guint) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("Selection received\n");
	sciThis->ReceivedSelection(selection_data);
}

void ScintillaGTK::SelectionGet(GtkWidget *widget,
                                GtkSelectionData *selection_data, guint info, guint) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("Selection get\n");
	if (selection_data->selection == GDK_SELECTION_PRIMARY) {
		if (sciThis->primary.s == NULL) {
			sciThis->CopySelectionRange(&sciThis->primary);
		}
		sciThis->GetSelection(selection_data, info, &sciThis->primary);
	}
#ifndef USE_GTK_CLIPBOARD
	else {
		sciThis->GetSelection(selection_data, info, &sciThis->copyText);
	}
#endif
}

gint ScintillaGTK::SelectionClear(GtkWidget *widget, GdkEventSelection *selection_event) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("Selection clear\n");
	sciThis->UnclaimSelection(selection_event);
	return gtk_selection_clear(widget, selection_event);
}

#if GTK_MAJOR_VERSION < 2
gint ScintillaGTK::SelectionNotify(GtkWidget *widget, GdkEventSelection *selection_event) {
	//Platform::DebugPrintf("Selection notify\n");
	return gtk_selection_notify(widget, selection_event);
}
#endif

void ScintillaGTK::DragBegin(GtkWidget *, GdkDragContext *) {
	//Platform::DebugPrintf("DragBegin\n");
}

gboolean ScintillaGTK::DragMotion(GtkWidget *widget, GdkDragContext *context,
                                  gint x, gint y, guint dragtime) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("DragMotion %d %d %x %x %x\n", x, y,
	//	context->actions, context->suggested_action, sciThis);
	Point npt(x, y);
	sciThis->inDragDrop = true;
	sciThis->SetDragPosition(sciThis->PositionFromLocation(npt));
	gdk_drag_status(context, context->suggested_action, dragtime);
	return FALSE;
}

void ScintillaGTK::DragLeave(GtkWidget *widget, GdkDragContext * /*context*/, guint) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	sciThis->SetDragPosition(invalidPosition);
	//Platform::DebugPrintf("DragLeave %x\n", sciThis);
}

void ScintillaGTK::DragEnd(GtkWidget *widget, GdkDragContext * /*context*/) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	// If drag did not result in drop here or elsewhere
	if (!sciThis->dragWasDropped)
		sciThis->SetEmptySelection(sciThis->posDrag);
	sciThis->SetDragPosition(invalidPosition);
	//Platform::DebugPrintf("DragEnd %x %d\n", sciThis, sciThis->dragWasDropped);
}

gboolean ScintillaGTK::Drop(GtkWidget *widget, GdkDragContext * /*context*/,
                            gint, gint, guint) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	//Platform::DebugPrintf("Drop %x\n", sciThis);
	sciThis->SetDragPosition(invalidPosition);
	return FALSE;
}

void ScintillaGTK::DragDataReceived(GtkWidget *widget, GdkDragContext * /*context*/,
                                    gint, gint, GtkSelectionData *selection_data, guint /*info*/, guint) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	sciThis->ReceivedDrop(selection_data);
	sciThis->SetDragPosition(invalidPosition);
}

void ScintillaGTK::DragDataGet(GtkWidget *widget, GdkDragContext *context,
                               GtkSelectionData *selection_data, guint info, guint) {
	ScintillaGTK *sciThis = ScintillaFromWidget(widget);
	sciThis->dragWasDropped = true;
	if (sciThis->currentPos != sciThis->anchor) {
		sciThis->GetSelection(selection_data, info, &sciThis->drag);
	}
	if (context->action == GDK_ACTION_MOVE) {
		int selStart = sciThis->SelectionStart();
		int selEnd = sciThis->SelectionEnd();
		if (sciThis->posDrop > selStart) {
			if (sciThis->posDrop > selEnd)
				sciThis->posDrop = sciThis->posDrop - (selEnd - selStart);
			else
				sciThis->posDrop = selStart;
			sciThis->posDrop = sciThis->pdoc->ClampPositionIntoDocument(sciThis->posDrop);
		}
		sciThis->ClearSelection();
	}
	sciThis->SetDragPosition(invalidPosition);
}

int ScintillaGTK::TimeOut(ScintillaGTK *sciThis) {
	sciThis->Tick();
	return 1;
}

int ScintillaGTK::IdleCallback(ScintillaGTK *sciThis) {
	// Idler will be automatically stoped, if there is nothing
	// to do while idle.
	bool ret = sciThis->Idle();
	if (ret == false) {
		// FIXME: This will remove the idler from GTK, we don't want to
		// remove it as it is removed automatically when this function
		// returns false (although, it should be harmless).
		sciThis->SetIdle(false);
	}
	return ret;
}

void ScintillaGTK::PopUpCB(ScintillaGTK *sciThis, guint action, GtkWidget *) {
	if (action) {
		sciThis->Command(action);
	}
}

gint ScintillaGTK::PressCT(GtkWidget *widget, GdkEventButton *event, ScintillaGTK *sciThis) {
	if (event->window != widget->window)
		return FALSE;
	if (event->type != GDK_BUTTON_PRESS)
		return FALSE;
	Point pt;
	pt.x = int(event->x);
	pt.y = int(event->y);
	sciThis->ct.MouseClick(pt);
	sciThis->CallTipClick();
#if GTK_MAJOR_VERSION >= 2
	return TRUE;
#else
	return FALSE;
#endif
}

gint ScintillaGTK::ExposeCT(GtkWidget *widget, GdkEventExpose * /*ose*/, CallTip *ctip) {
	Surface *surfaceWindow = Surface::Allocate();
	if (surfaceWindow) {
		surfaceWindow->Init(widget->window, widget);
		ctip->PaintCT(surfaceWindow);
		surfaceWindow->Release();
		delete surfaceWindow;
	}
	return TRUE;
}

sptr_t ScintillaGTK::DirectFunction(
    ScintillaGTK *sciThis, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
	return sciThis->WndProc(iMessage, wParam, lParam);
}

sptr_t scintilla_send_message(ScintillaObject *sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
	ScintillaGTK *psci = reinterpret_cast<ScintillaGTK *>(sci->pscin);
	return psci->WndProc(iMessage, wParam, lParam);
}

static void scintilla_class_init(ScintillaClass *klass);
static void scintilla_init(ScintillaObject *sci);

extern void Platform_Initialise();
extern void Platform_Finalise();

GtkType scintilla_get_type() {
	static GtkType scintilla_type = 0;

	if (!scintilla_type) {
		Platform_Initialise();
		static GtkTypeInfo scintilla_info = {
		    "Scintilla",
		    sizeof (ScintillaObject),
		    sizeof (ScintillaClass),
		    (GtkClassInitFunc) scintilla_class_init,
		    (GtkObjectInitFunc) scintilla_init,
		    (gpointer) NULL,
		    (gpointer) NULL,
		    0
		};

		scintilla_type = gtk_type_unique(gtk_container_get_type(), &scintilla_info);
	}

	return scintilla_type;
}

void ScintillaGTK::ClassInit(GtkObjectClass* object_class, GtkWidgetClass *widget_class, GtkContainerClass *container_class) {
    atomClipboard = gdk_atom_intern("CLIPBOARD", FALSE);
    atomUTF8 = gdk_atom_intern("UTF8_STRING", FALSE);
    atomString = GDK_SELECTION_TYPE_STRING;

	// Define default signal handlers for the class:  Could move more
	// of the signal handlers here (those that currently attached to wDraw
	// in Initialise() may require coordinate translation?)

	object_class->destroy = Destroy;

	widget_class->size_request = SizeRequest;
	widget_class->size_allocate = SizeAllocate;
	widget_class->expose_event = ExposeMain;
#if GTK_MAJOR_VERSION < 2
	widget_class->draw = Draw;
#endif

	widget_class->motion_notify_event = Motion;
	widget_class->button_press_event = Press;
	widget_class->button_release_event = MouseRelease;
#if PLAT_GTK_WIN32 || (GTK_MAJOR_VERSION >= 2)
	widget_class->scroll_event = ScrollEvent;
#endif
	widget_class->key_press_event = KeyPress;
	widget_class->key_release_event = KeyRelease;
	widget_class->focus_in_event = FocusIn;
	widget_class->focus_out_event = FocusOut;
	widget_class->selection_received = SelectionReceived;
	widget_class->selection_get = SelectionGet;
	widget_class->selection_clear_event = SelectionClear;
#if GTK_MAJOR_VERSION < 2
	widget_class->selection_notify_event = SelectionNotify;
#endif

	widget_class->drag_data_received = DragDataReceived;
	widget_class->drag_motion = DragMotion;
	widget_class->drag_leave = DragLeave;
	widget_class->drag_end = DragEnd;
	widget_class->drag_drop = Drop;
	widget_class->drag_data_get = DragDataGet;

	widget_class->realize = Realize;
	widget_class->unrealize = UnRealize;
	widget_class->map = Map;
	widget_class->unmap = UnMap;

	container_class->forall = MainForAll;
}

#if GTK_MAJOR_VERSION < 2
#define GTK_CLASS_TYPE(c) (c->type)
#define SIG_MARSHAL gtk_marshal_NONE__INT_POINTER
#else
#define SIG_MARSHAL scintilla_marshal_NONE__INT_POINTER
#endif
#define MARSHAL_ARGUMENTS GTK_TYPE_INT, GTK_TYPE_POINTER

static void scintilla_class_init(ScintillaClass *klass) {
	GtkObjectClass *object_class = (GtkObjectClass*) klass;
	GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
	GtkContainerClass *container_class = (GtkContainerClass*) klass;

	parent_class = (GtkWidgetClass*) gtk_type_class(gtk_container_get_type());

	scintilla_signals[COMMAND_SIGNAL] = gtk_signal_new(
	                                        "command",
	                                        GTK_RUN_LAST,
	                                        GTK_CLASS_TYPE(object_class),
	                                        GTK_SIGNAL_OFFSET(ScintillaClass, command),
	                                        SIG_MARSHAL,
	                                        GTK_TYPE_NONE,
	                                        2, MARSHAL_ARGUMENTS);

	scintilla_signals[NOTIFY_SIGNAL] = gtk_signal_new(
	                                       SCINTILLA_NOTIFY,
	                                       GTK_RUN_LAST,
	                                       GTK_CLASS_TYPE(object_class),
	                                       GTK_SIGNAL_OFFSET(ScintillaClass, notify),
	                                       SIG_MARSHAL,
	                                       GTK_TYPE_NONE,
	                                       2, MARSHAL_ARGUMENTS);
#if GTK_MAJOR_VERSION < 2
	gtk_object_class_add_signals(object_class,
	                             reinterpret_cast<unsigned int *>(scintilla_signals), LAST_SIGNAL);
#endif
	klass->command = NULL;
	klass->notify = NULL;

	ScintillaGTK::ClassInit(object_class, widget_class, container_class);
}

static void scintilla_init(ScintillaObject *sci) {
	GTK_WIDGET_SET_FLAGS(sci, GTK_CAN_FOCUS);
	sci->pscin = new ScintillaGTK(sci);
}

GtkWidget* scintilla_new() {
	return GTK_WIDGET(gtk_type_new(scintilla_get_type()));
}

void scintilla_set_id(ScintillaObject *sci, int id) {
	ScintillaGTK *psci = reinterpret_cast<ScintillaGTK *>(sci->pscin);
	psci->ctrlID = id;
}

void scintilla_release_resources(void) {
	Platform_Finalise();
}
