/* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */

/* AbiSource Application Framework
 * Copyright (C) 1998-2000 AbiSource, Inc.
 * Copyright (C) 2002 William Lachance 
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>

#include "ut_types.h"
#include "ut_assert.h"
#include "ut_files.h"
#include "ut_sleep.h"
#include "xap_ViewListener.h"
#include "xap_UnixApp.h"
#include "xap_UnixFrameImpl.h"
#include "ev_UnixKeyboard.h"
#include "ev_UnixMouse.h"
#include "ev_UnixMenuBar.h"
#include "ev_UnixMenuPopup.h"
#include "ev_UnixToolbar.h"
#include "ev_EditMethod.h"
#include "xav_View.h"
#include "fv_View.h"
#include "xad_Document.h"
#include "gr_Graphics.h"
#include "xap_UnixDialogHelper.h"
#include "xap_Strings.h"
#include "xap_Prefs.h"

#include "fv_View.h"

#ifdef HAVE_GNOME
// sorry about the XAP/AP separation breakage, but this is more important
#include "ie_types.h"
#include "ie_imp.h"
#include "ie_impGraphic.h"
#include "fg_Graphic.h"

#include <gnome.h>
#include <libgnomevfs/gnome-vfs.h>
#include <libgnomevfs/gnome-vfs-mime-utils.h>
#endif

#ifdef HAVE_GNOME

enum {
	TARGET_IMAGE,
	TARGET_URI_LIST,
	TARGET_URL,
	TARGET_UNKNOWN
} DragDropTypes;

// FIXME Rob: this should be split into a static and a dynamic part.
// At least the image formats could be added dynamically based on gdk_pixbuf_get_formats ()
// but we should be able to also dynamically determine document formats by looking at the
// loaded importers.
static const GtkTargetEntry drag_types[] =
	{
		{"application/rtf", 0, TARGET_URI_LIST},
		{"text/richtext", 0, TARGET_URI_LIST},
		{"text/rtf", 0, TARGET_URI_LIST},
		{"application/msword", 0, TARGET_URI_LIST},
		{"application/vnd.ms-word", 0, TARGET_URI_LIST},
		{"application/vnd.sun.xml.writer", 0, TARGET_URI_LIST},
		{"application/x-applix-word", 0, TARGET_URI_LIST},
		{"application/x-palm-database", 0, TARGET_URI_LIST},
		{"application/vnd.palm", 0, TARGET_URI_LIST},
		{"text/plain", 0, TARGET_URI_LIST},
		{"text/abiword", 0, TARGET_URI_LIST},
		{"application/x-abiword", 0, TARGET_URI_LIST},
		{"text/xml", 0, TARGET_URI_LIST},
		{"text/vnd.wap.wml", 0, TARGET_URI_LIST},
		{"image/png", 0, TARGET_IMAGE},
		{"image/jpeg", 0, TARGET_IMAGE},
		{"image/gif", 0, TARGET_IMAGE},
		{"image/x-xpixmap", 0, TARGET_IMAGE},
		{"image/bmp", 0, TARGET_IMAGE},
		{"image/x-bmp", 0, TARGET_IMAGE},
		{"image/x-cmu-raster", 0, TARGET_IMAGE},
		{"image/tiff", 0, TARGET_IMAGE},
		{"image/svg+xml", 0, TARGET_IMAGE},
		{"text/html", 0, TARGET_URL}, // hack
		{"text/html+xml", 0, TARGET_URL}, // hack
		{"_NETSCAPE_URL", 0, TARGET_URL},
		{"text/uri-list", 0, TARGET_URI_LIST}
	};

static int
s_mapMimeToUriType (const char * uri)
{
	if (!uri || !strlen(uri))
		return TARGET_UNKNOWN;

	char * mime = gnome_vfs_get_mime_type (uri);
	xxx_UT_DEBUGMSG(("DOM: mime %s dropped into AbiWord(%s)\n", mime, uri));

	int target = TARGET_UNKNOWN;

	for (size_t i = 0; i < NrElements (drag_types); i++)
		if (!UT_stricmp (mime, drag_types[i].target)) {
			target = drag_types[i].info;
			break;
		}
	
	g_free (mime);
	return target;
}

static bool
s_ensure_uri_on_disk (const gchar * uri, UT_UTF8String & outName)
{
	GnomeVFSResult    result = GNOME_VFS_OK;
	GnomeVFSHandle   *handle = NULL;
	gchar             buffer[1024];
	GnomeVFSFileSize  bytes_read;
	GnomeVFSURI 	 *hndl = NULL;	
	outName = "";
	hndl = gnome_vfs_uri_new (uri);
	if (hndl == NULL) 
		{
			xxx_UT_DEBUGMSG(("DOM: Invalid uri was %s \n", uri));
			return false;
		}

	if (gnome_vfs_uri_is_local (hndl)) {
		char * short_name = gnome_vfs_uri_to_string (hndl, static_cast<GnomeVFSURIHideOptions>(GNOME_VFS_URI_HIDE_USER_NAME | GNOME_VFS_URI_HIDE_PASSWORD |
													 GNOME_VFS_URI_HIDE_HOST_NAME | GNOME_VFS_URI_HIDE_HOST_PORT |
													 GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD | GNOME_VFS_URI_HIDE_FRAGMENT_IDENTIFIER));
		UT_uint32 i = 0;
		UT_uint32 len = strlen(short_name);
		UT_UTF8String left = "";
		while(i < len)
		{
			if(short_name[i] != '%')
			{
				left += short_name[i];
				i += 1;
			}
			else if(i+2 < len)
			{
				char c = 16*(short_name[i+1] - '0') +  short_name[i+2] - '0';
				left += c;
				i += 3;
			}
			else
			{
				left += short_name[i];
				i +=1;
			}
		}
		outName = left;
		UT_DEBUGMSG(("short_name %s szTmp %s \n",short_name,outName.utf8_str()));
		g_free (short_name);
		gnome_vfs_uri_unref (hndl);
		return true;
	}

	char * outCName;
	int fd = g_file_open_tmp ("XXXXXX", &outCName, NULL);
	if (fd == -1) {
		xxx_UT_DEBUGMSG(("DOM: no temp dir\n"));
		gnome_vfs_uri_unref (hndl);
		return false;
	}
	UT_uint32 i = 0;
	UT_uint32 len = strlen(outCName);
	UT_UTF8String left = "";
	while(i < len)
	{
		if(outCName[i] != '%')
		{
			left += outCName[i];
			i += 1;
		}
		else if(i+2 < len)
		{
			char c = 16*(outCName[i+1] - '0') +  outCName[i+2] - '0';
			left += c;
			i += 3;
		}
		else
		{
			left += outCName[i];
			i +=1;
		}
	}
	outName = left;
	UT_DEBUGMSG(("outCName %s szTmp %s \n",outCName,outName.utf8_str()));
	g_free (outCName);

	FILE * onDisk = fdopen (fd, "r");
	result = gnome_vfs_open_uri (&handle, hndl, GNOME_VFS_OPEN_READ);
	
	if(result != GNOME_VFS_OK)
		{
			xxx_UT_DEBUGMSG(("DOM: couldn't open handle\n"));
			gnome_vfs_uri_unref (hndl);
			fclose (onDisk);
			return false;
		}

	while (true) {
		result = gnome_vfs_read (handle, buffer, sizeof(buffer) - 1,
								 &bytes_read);
		if(!bytes_read || result != GNOME_VFS_OK) 
			break;
		fwrite (buffer, sizeof (char), bytes_read, onDisk);
	}

	fclose (onDisk);
	gnome_vfs_close (handle);
	gnome_vfs_uri_unref (hndl);

	return true;
}

static void
s_load_image (const UT_UTF8String & file, XAP_Frame * pFrame, FV_View * pView)
{
	IE_ImpGraphic * pIEG = 0;
	FG_Graphic    * pFG  = 0;
	UT_Error error = IE_ImpGraphic::constructImporter(file.utf8_str(), 0, &pIEG);
	if (error != UT_OK || !pIEG)
		{
			UT_DEBUGMSG(("Couldn't construct importer for %s\n", file.utf8_str()));
			return;
		}

	error = pIEG->importGraphic(file.utf8_str(), &pFG);

	DELETEP(pIEG);

	if (error != UT_OK || !pFG)
		{
			UT_DEBUGMSG(("Dom: could not import graphic (%s)\n", file.utf8_str()));
			return;
		}

	pView->cmdInsertGraphic(pFG);
	DELETEP(pFG);
}

static void
s_load_document (const UT_UTF8String & file, XAP_Frame * pFrame)
{
	XAP_Frame * pNewFrame = 0;
	if (pFrame->isDirty() || pFrame->getFilename() || 
		(pFrame->getViewNumber() > 0))
		pNewFrame = XAP_App::getApp()->newFrame ();
	else
		pNewFrame = pFrame;

	UT_Error error = pNewFrame->loadDocument(file.utf8_str(), 0 /* IEFT_Unknown */);
	if (error)
		{
			// TODO: warn user that we couldn't open that file		 
			// TODO: we crash if we just delete this without putting something
			// TODO: in it, so let's go ahead and open an untitled document
			// TODO: for now.
			xxx_UT_DEBUGMSG(("DOM: couldn't load document %s\n", file.utf8_str()));
			pNewFrame->loadDocument(NULL, 0 /* IEFT_Unknown */);
		}
}

static void
s_load_uri (XAP_Frame * pFrame, const char * uri)
{
	FV_View   * pView  = static_cast<FV_View*>(pFrame->getCurrentView ());

	int type = s_mapMimeToUriType (uri);
	if (type == TARGET_UNKNOWN)
		{
			xxx_UT_DEBUGMSG(("DOM: unknown uri type: %s\n", uri));
			return;
		}
	else if (type == TARGET_URL)
		{
			xxx_UT_DEBUGMSG(("DOM: hyperlink: %s\n", uri));
			pView->cmdInsertHyperlink(uri);
			return;
		}

	UT_UTF8String onDisk;
	if (!s_ensure_uri_on_disk (uri, onDisk))
		{
			xxx_UT_DEBUGMSG(("DOM: couldn't ensure %s on disk\n", uri));
			return;
		}

	xxx_UT_DEBUGMSG(("DOM: %s on disk\n", onDisk.utf8_str()));

	if (type == TARGET_IMAGE)
		{
			s_load_image (onDisk, pFrame, pView);
			return;
		}
	else
		{
			s_load_document (onDisk, pFrame);
			return;
		}
}

static void 
s_dnd_drop_event(GtkWidget        *widget,
				 GdkDragContext   * /*context*/,
				 gint              /*x*/,
				 gint              /*y*/,
				 GtkSelectionData *selection_data,
				 guint             info,
				 guint             /*time*/,
				 XAP_UnixFrameImpl * pFrameImpl)
{
	xxx_UT_DEBUGMSG(("DOM: dnd_drop_event being handled\n"));

	g_return_if_fail(widget != NULL);

	XAP_Frame * pFrame = pFrameImpl->getFrame ();

	const char * rawChar = reinterpret_cast<const char *>(selection_data->data);
	xxx_UT_DEBUGMSG(("DOM: text in selection = %s \n", rawChar));
	GList * names = gnome_vfs_uri_list_parse (rawChar);

	// single URI
	if (!names)
		s_load_uri (pFrame, rawChar);
	else {
		// multiple URIs
		for ( ; names != NULL; names = names->next) 
			{
				GnomeVFSURI * hndl = static_cast<GnomeVFSURI *>(names->data);
				char * uri = gnome_vfs_uri_to_string (hndl, GNOME_VFS_URI_HIDE_NONE);
				s_load_uri (pFrame, uri);
				g_free (uri);
			}
	}

	gnome_vfs_uri_list_free (names);
}

static void
s_dnd_real_drop_event (GtkWidget *widget, GdkDragContext * context, 
					   gint x, gint y, guint time, gpointer ppFrame)
{
	xxx_UT_DEBUGMSG(("DOM: dnd drop event\n"));
	GdkAtom selection = gdk_drag_get_selection(context);
	gtk_drag_get_data (widget,context,selection,time);
}

static void
s_dnd_drag_end (GtkWidget  *widget, GdkDragContext *context, gpointer ppFrame)
{
	xxx_UT_DEBUGMSG(("DOM: dnd end event\n"));
}

#endif // HAVE_GNOME

/*****************************************************************/

#define ENSUREP(p)		do { UT_ASSERT(p); if (!p) goto Cleanup; } while (0)

static void s_gtkMenuPositionFunc(GtkMenu * /* menu */, gint * x, gint * y, gboolean * push_in, gpointer user_data)
{
	struct UT_Point * p = static_cast<struct UT_Point *>(user_data);

	*x = p->x;
	*y = p->y;
	*push_in = TRUE ;
}

/****************************************************************/
XAP_UnixFrameImpl::XAP_UnixFrameImpl(XAP_Frame *pFrame, XAP_UnixApp * pApp) : 
	XAP_FrameImpl(pFrame),
	m_imContext(NULL),
	need_im_reset (false),
	m_bDoZoomUpdate(false),
	m_iZoomUpdateID(0),
	m_iAbiRepaintID(0),
	m_pUnixApp(pApp),
	m_pUnixMenu(NULL),
	m_pUnixPopup(NULL),
	m_dialogFactory(pFrame, static_cast<XAP_App *>(pApp))
{
}

XAP_UnixFrameImpl::~XAP_UnixFrameImpl() 
{ 	
	if(m_bDoZoomUpdate) {
		g_source_remove(m_iZoomUpdateID);
	}

	// only delete the things we created...
	if(m_iAbiRepaintID)
	{
		g_source_remove(m_iAbiRepaintID);
	}

	DELETEP(m_pUnixMenu);
	DELETEP(m_pUnixPopup);

	// unref the input method context
	g_object_unref (G_OBJECT (m_imContext));
}


void XAP_UnixFrameImpl::_fe::realize(GtkWidget * widget, GdkEvent * /*e*/,gpointer /*data*/)
{
}

void XAP_UnixFrameImpl::_fe::unrealize(GtkWidget * widget, GdkEvent * /*e*/,gpointer /*data*/)
{
}

void XAP_UnixFrameImpl::_fe::sizeAllocate(GtkWidget * widget, GdkEvent * /*e*/,gpointer /*data*/)
{
}

gint XAP_UnixFrameImpl::_fe::focusIn(GtkWidget * widget, GdkEvent * /*e*/,gpointer /*data*/)
{
	return FALSE; 
}

gint XAP_UnixFrameImpl::_fe::focusOut(GtkWidget * /* w*/, GdkEvent * /*e*/,gpointer /*data*/)
{
  return FALSE;
}

void XAP_UnixFrameImpl::focusIMIn ()
{
	need_im_reset = true;
	gtk_im_context_focus_in(getIMContext());
	gtk_im_context_reset (getIMContext());
}

void XAP_UnixFrameImpl::focusIMOut ()
{
	need_im_reset = true;
	gtk_im_context_focus_out(getIMContext());
}

void XAP_UnixFrameImpl::resetIMContext()
{
  if (need_im_reset)
    {
      need_im_reset = false;
      gtk_im_context_reset (getIMContext());
    }
}

gboolean XAP_UnixFrameImpl::_fe::focus_in_event(GtkWidget *w,GdkEvent */*event*/,gpointer /*user_data*/)
{
	XAP_UnixFrameImpl * pFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	UT_ASSERT(pFrameImpl);
	XAP_Frame* pFrame = pFrameImpl->getFrame();
	g_object_set_data(G_OBJECT(w), "toplevelWindowFocus",
						GINT_TO_POINTER(TRUE));
	if (pFrame->getCurrentView())
	{
		pFrame->getCurrentView()->focusChange(gtk_grab_get_current() == NULL || gtk_grab_get_current() == w ? AV_FOCUS_HERE : AV_FOCUS_NEARBY);
	}
	pFrameImpl->focusIMIn ();
	//
	// FIXME: Return TRUE because of a bug in gtk2. If you return FALSE (like
	// you really should) you get a superfluous expose event which causes the
	// the screen to flicker. Thanks the Bernhard Herzoff (of Sketch fame) 
	// for giving the helpful clue here. 
    //
	// Try it again for gtk2.2 to see if this bug is still present.
	//
	return TRUE; 
}

gboolean XAP_UnixFrameImpl::_fe::focus_out_event(GtkWidget *w,GdkEvent */*event*/,gpointer /*user_data*/)
{
	XAP_UnixFrameImpl * pFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	UT_ASSERT(pFrameImpl);
	XAP_Frame* pFrame = pFrameImpl->getFrame();
	g_object_set_data(G_OBJECT(w), "toplevelWindowFocus",
						GINT_TO_POINTER(FALSE));
	if (pFrame->getCurrentView())
		pFrame->getCurrentView()->focusChange(AV_FOCUS_NONE);
	pFrameImpl->focusIMOut();
	return FALSE;
}

gint XAP_UnixFrameImpl::_fe::button_press_event(GtkWidget * w, GdkEventButton * e)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	pUnixFrameImpl->setTimeOfLastEvent(e->time);
	AV_View * pView = pFrame->getCurrentView();
	EV_UnixMouse * pUnixMouse = static_cast<EV_UnixMouse *>(pFrame->getMouse());
 
	gtk_grab_add(w);

	pUnixFrameImpl->resetIMContext ();

	if (pView)
		pUnixMouse->mouseClick(pView,e);
	return 1;
}

gint XAP_UnixFrameImpl::_fe::button_release_event(GtkWidget * w, GdkEventButton * e)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	pUnixFrameImpl->setTimeOfLastEvent(e->time);
	AV_View * pView = pFrame->getCurrentView();

	EV_UnixMouse * pUnixMouse = static_cast<EV_UnixMouse *>(pFrame->getMouse());

	gtk_grab_remove(w);

	if (pView)
		pUnixMouse->mouseUp(pView,e);

	return 1;
}

/*!
 * Background zoom updater. It updates the view zoom level after all configure
 * events have been processed. This is
 */
gint XAP_UnixFrameImpl::_fe::do_ZoomUpdate(gpointer /* XAP_UnixFrameImpl * */ p)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(p);
	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	AV_View * pView = pFrame->getCurrentView();
	if(!pView || pFrame->isFrameLocked() ||
	   (pUnixFrameImpl->m_bDoZoomUpdate && (pView->getGraphics()->tdu(pView->getWindowWidth()) == pUnixFrameImpl->m_iNewWidth) && (pView->getGraphics()->tdu(pView->getWindowHeight()) == pUnixFrameImpl->m_iNewHeight)))
	{
		pUnixFrameImpl->m_iZoomUpdateID = 0;
		pUnixFrameImpl->m_bDoZoomUpdate = false;
		return FALSE;
	}

	pUnixFrameImpl->m_bDoZoomUpdate = true;

	UT_sint32 iNewWidth = 0;
	UT_sint32 iNewHeight = 0;
	do
	{
		// currently, we blow away the old view.  This will change, rendering
		// the loop superfluous.
		pView = pFrame->getCurrentView();

		if(!pView)
		{
			pUnixFrameImpl->m_iZoomUpdateID = 0;
			pUnixFrameImpl->m_bDoZoomUpdate = false;
			return FALSE;
		}

		// oops, we're not ready yet.
		if (pView->isLayoutFilling())
			return TRUE;

		iNewWidth = pUnixFrameImpl->m_iNewWidth;
		iNewHeight = pUnixFrameImpl->m_iNewHeight;

		// don't need this for quickZoom
#if 0
		pUnixFrameImpl->_startViewAutoUpdater(); 
#endif
		pView->setWindowSize(iNewWidth, iNewHeight);
		pFrame->quickZoom(); // was update zoom

	}
	while((iNewWidth != pUnixFrameImpl->m_iNewWidth) || (iNewHeight != pUnixFrameImpl->m_iNewHeight));

	pUnixFrameImpl->m_iZoomUpdateID = 0;
	pUnixFrameImpl->m_bDoZoomUpdate = false;
	return FALSE;
}

gint XAP_UnixFrameImpl::_fe::configure_event(GtkWidget* w, GdkEventConfigure *e)
{
	// This is basically a resize event.

	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	AV_View * pView = pFrame->getCurrentView();
	XAP_Frame::tZoomType zType = pFrame->getZoomType();
	if (pView)
	{
		pUnixFrameImpl->m_iNewWidth = e->width;
		pUnixFrameImpl->m_iNewHeight = e->height;
		XAP_App * pApp = pFrame->getApp();
		UT_sint32 x,y;
		UT_uint32 width,height,flags;
		pApp->getGeometry(&x,&y,&width,&height,&flags);
//
// Who ever wants to change this code in the future. The height and widths you
// get from the event struct are the height and widths of the drawable area of
// the screen. We want the height and width of the entire widget which we get
// from the m_wTopLevelWindow widget.
// -- MES
//
        GtkWindow * pWin = NULL;
		if(pFrame->getFrameMode() == XAP_NormalFrame)
			pWin = GTK_WINDOW(pUnixFrameImpl->m_wTopLevelWindow);
        else
            pWin = GTK_WINDOW(pUnixFrameImpl->m_wTopLevelWindow->window);

        gint gwidth,gheight;
        gtk_window_get_size(pWin,&gwidth,&gheight);
        pApp->setGeometry(e->x,e->y,gwidth,gheight,flags);

		// Dynamic Zoom Implementation

		if(!pUnixFrameImpl->m_bDoZoomUpdate && (pUnixFrameImpl->m_iZoomUpdateID == 0))
		{
			pUnixFrameImpl->m_iZoomUpdateID = g_idle_add(static_cast<GSourceFunc>(do_ZoomUpdate), static_cast<gpointer>(pUnixFrameImpl));
		}
	}
	return 1;
}

gint XAP_UnixFrameImpl::_fe::motion_notify_event(GtkWidget* w, GdkEventMotion* e)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	if(e->type == GDK_MOTION_NOTIFY)
	{
		//
		// swallow queued drag events and just get the last one.
		//
		GdkEvent  * eNext = gdk_event_peek();
		if(eNext && eNext->type == GDK_MOTION_NOTIFY)
		{
			g_object_unref(G_OBJECT(e));
			e = reinterpret_cast<GdkEventMotion *>(eNext);
			while(eNext && eNext->type == GDK_MOTION_NOTIFY)
			{
				xxx_UT_DEBUGMSG(("Swallowing drag event \n"));
				gdk_event_free(eNext);
				eNext = gdk_event_get();
				gdk_event_free(reinterpret_cast<GdkEvent *>(e));
				e = reinterpret_cast<GdkEventMotion *>(eNext);
				eNext = gdk_event_peek();
			}
			if(eNext != NULL)
			{
				gdk_event_free(eNext);
			}
		}
	}

	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	pUnixFrameImpl->setTimeOfLastEvent(e->time);
	AV_View * pView = pFrame->getCurrentView();
	EV_UnixMouse * pUnixMouse = static_cast<EV_UnixMouse *>(pFrame->getMouse());

	if (pView)
		pUnixMouse->mouseMotion(pView, e);

	return 1;
}

gint XAP_UnixFrameImpl::_fe::scroll_notify_event(GtkWidget* w, GdkEventScroll* e)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	pUnixFrameImpl->setTimeOfLastEvent(e->time);
	AV_View * pView = pFrame->getCurrentView();
	EV_UnixMouse * pUnixMouse = static_cast<EV_UnixMouse *>(pFrame->getMouse());

	if (pView)
		pUnixMouse->mouseScroll(pView, e);

	return 1;
}

gint XAP_UnixFrameImpl::_fe::key_release_event(GtkWidget* w, GdkEventKey* e)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));

	// Let IM handle the event first.
	if (gtk_im_context_filter_keypress(pUnixFrameImpl->getIMContext(), e)) {
	    xxx_UT_DEBUGMSG(("IMCONTEXT keyevent swallow: %lu\n", e->keyval));
		pUnixFrameImpl->queueIMReset ();
	    return 0;
	}
	return TRUE;
}

gint XAP_UnixFrameImpl::_fe::key_press_event(GtkWidget* w, GdkEventKey* e)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));

	// Let IM handle the event first.
	if (gtk_im_context_filter_keypress(pUnixFrameImpl->getIMContext(), e)) {
		pUnixFrameImpl->queueIMReset ();

		if ((e->state & GDK_MOD1_MASK) ||
			(e->state & GDK_MOD3_MASK) ||
			(e->state & GDK_MOD4_MASK))
			return 0;

		// ... else, stop this signal
		g_signal_stop_emission (G_OBJECT(w), 
								g_signal_lookup ("key_press_event", 
												 G_OBJECT_TYPE (w)), 0);
		return 1;
	}

	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	pUnixFrameImpl->setTimeOfLastEvent(e->time);
	AV_View * pView = pFrame->getCurrentView();
	ev_UnixKeyboard * pUnixKeyboard = static_cast<ev_UnixKeyboard *>(pFrame->getKeyboard());

	if (pView)
		pUnixKeyboard->keyPressEvent(pView, e);

	// HACK : This one's ugly.  If we continue through the callback chain,
	// HACK : GTK will pick up key presses and hand them off to widgets with
	// HACK : focus (like toolbar combos).  This is bad, since we have already
	// HACK : acted on the key press (like space, we would insert a space
	// HACK : in the document, but GTK will let space mean "open the menu"
	// HACK : to a combo).  The user is confused and things are annoying.
	// HACK :
	// HACK : We _could_ block all GTK key handling, and do everything
	// HACK : ourselves, but then we lose the automatic menu accelerator
	// HACK : bindings (Alt-F for File menu).
	// HACK :
	// HACK : What we do is let ONLY Alt-modified keys through to GTK.

	// If a modifier is down, return to let GTK catch

	// don't let GTK handle keys when mod2 (numlock) or mod5 (scroll lock) are down

	// What's "LOCK_MASK"?  I can't seem to trigger it with caps lock, scroll lock, or
	// num lock.
//		(e->state & GDK_LOCK_MASK))		// catch all keys with "num lock" down for now

	if ((e->state & GDK_MOD1_MASK) ||
		(e->state & GDK_MOD3_MASK) ||
		(e->state & GDK_MOD4_MASK))
		return 0;

	// ... else, stop this signal
	g_signal_stop_emission (G_OBJECT(w), 
							g_signal_lookup ("key_press_event", 
											 G_OBJECT_TYPE (w)), 0);
	return 1;
}

gint XAP_UnixFrameImpl::_fe::delete_event(GtkWidget * w, GdkEvent * /*event*/, gpointer /*data*/)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	XAP_App * pApp = pFrame->getApp();
	UT_ASSERT(pApp);
	if(pApp->isBonoboRunning())
		return FALSE;

	const EV_Menu_ActionSet * pMenuActionSet = pApp->getMenuActionSet();
	UT_ASSERT(pMenuActionSet);

	const EV_EditMethodContainer * pEMC = pApp->getEditMethodContainer();
	UT_ASSERT(pEMC);

	// was "closeWindow", TRUE, FALSE
	const EV_EditMethod * pEM = pEMC->findEditMethodByName("closeWindowX");
	UT_ASSERT(pEM);

	if (pEM)
	{
		if (pEM->Fn(pFrame->getCurrentView(),NULL))
		{
			// returning FALSE means destroy the window, continue along the
			// chain of Gtk destroy events

			return FALSE;
		}
	}

	// returning TRUE means do NOT destroy the window; halt the message
	// chain so it doesn't see destroy
	return TRUE;
}

gint XAP_UnixFrameImpl::_fe::expose(GtkWidget * w, GdkEventExpose* pExposeEvent)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	FV_View * pView = static_cast<FV_View *>(pUnixFrameImpl->getFrame()->getCurrentView());
	if((pUnixFrameImpl->m_bDoZoomUpdate) || (pUnixFrameImpl->m_iZoomUpdateID != 0))
	{
		return TRUE;
	}
	if(pView)
	{
		GR_Graphics * pGr = pView->getGraphics ();
		UT_Rect rClip;
		xxx_UT_DEBUGMSG(("Expose area: x %d y %d width %d  height %d \n",pExposeEvent->area.x,pExposeEvent->area.y,pExposeEvent->area.width,pExposeEvent->area.height));
		rClip.left = pGr->tlu(pExposeEvent->area.x);
		rClip.top = pGr->tlu(pExposeEvent->area.y);
		rClip.width = pGr->tlu(pExposeEvent->area.width)+1;
		rClip.height = pGr->tlu(pExposeEvent->area.height)+1;
		pGr->setExposePending(false);
		pView->draw(&rClip);
	}
	return FALSE;
}

/*!
 * Background abi repaint function.
\param XAP_UnixFrameImpl * p pointer to the FrameImpl that initiated this background
       repainter.
 */
gint XAP_UnixFrameImpl::_fe::abi_expose_repaint(gpointer p)
{
//
// Grab our pointer so we can do useful stuff.
//
	UT_DEBUGMSG(("-----------------Doing Repaint----------------\n"));
	UT_Rect localCopy;
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(p);
	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	FV_View * pV = static_cast<FV_View *>(pFrame->getCurrentView());
	if(!pV || (pV->getPoint() == 0))
		return TRUE;

	GR_Graphics * pG = pV->getGraphics();
	if(pG->isDontRedraw())
	{
//
// Come back later
//
		return TRUE;
	}
	pG->setSpawnedRedraw(true);
	if(pG->isExposePending())
	{
		while(pG->isExposedAreaAccessed())
		{
			pFrame->nullUpdate();
			UT_usleep(10); // 10 microseconds
		}
		pG->setExposedAreaAccessed(true);
		localCopy.set(pG->getPendingRect()->left,pG->getPendingRect()->top,
					  pG->getPendingRect()->width,pG->getPendingRect()->height);
//
// Clear out this set of expose info
//
		pG->setExposePending(false);
		pG->setExposedAreaAccessed(false);
		pV->draw(&localCopy);
	}
//
// OK we've finshed. Wait for the next signal
//
	pG->setSpawnedRedraw(false);
	return TRUE;
}

void XAP_UnixFrameImpl::_fe::vScrollChanged(GtkAdjustment * w, gpointer /*data*/)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	AV_View * pView = pFrame->getCurrentView();

	if (pView)
		pView->sendVerticalScrollEvent(static_cast<UT_sint32>(w->value));
}

void XAP_UnixFrameImpl::_fe::hScrollChanged(GtkAdjustment * w, gpointer /*data*/)
{
	XAP_UnixFrameImpl * pUnixFrameImpl = static_cast<XAP_UnixFrameImpl *>(g_object_get_data(G_OBJECT(w), "user_data"));
	XAP_Frame* pFrame = pUnixFrameImpl->getFrame();
	AV_View * pView = pFrame->getCurrentView();

	if (pView)
		pView->sendHorizontalScrollEvent(static_cast<UT_sint32>(w->value));
}

void XAP_UnixFrameImpl::_fe::destroy(GtkWidget * /*widget*/, gpointer /*data*/)
{
}

/*****************************************************************/

void XAP_UnixFrameImpl::_nullUpdate() const
{
   	for (UT_uint32 i = 0; (i < 5) && gtk_events_pending(); i++)
		gtk_main_iteration ();
}

void XAP_UnixFrameImpl::_initialize()
{
    	// get a handle to our keyboard binding mechanism
 	// and to our mouse binding mechanism.
 	EV_EditEventMapper * pEEM = XAP_App::getApp()->getEditEventMapper();
 	UT_ASSERT(pEEM);

	m_pKeyboard = new ev_UnixKeyboard(pEEM);
	UT_ASSERT(m_pKeyboard);

	m_pMouse = new EV_UnixMouse(pEEM);
	UT_ASSERT(m_pMouse);

	//
	// Start background repaint
	//
#if 0
	if(m_iAbiRepaintID == 0)
		m_iAbiRepaintID = g_timeout_add_full(0,100,static_cast<GtkFunction>(XAP_UnixFrameImpl::_fe::abi_expose_repaint), static_cast<gpointer>(this),NULL);
	else
	{
		g_source_remove(m_iAbiRepaintID);
		m_iAbiRepaintID = g_timeout_add_full(0, 100,static_cast<GtkFunction>(XAP_UnixFrameImpl::_fe::abi_expose_repaint), static_cast<gpointer>(this), NULL);
	}
#endif
}

void XAP_UnixFrameImpl::_setCursor(GR_Graphics::Cursor c)
{
//	if (m_cursor == c)
//		return;
//	m_cursor = c;
	FV_View * pView = static_cast<FV_View *>(getFrame()->getCurrentView());
	if(pView)
	{
		GR_Graphics * pG = pView->getGraphics();
		if(pG && pG->queryProperties( GR_Graphics::DGP_PAPER))
			return;
	}
	if(getTopLevelWindow() == NULL || (m_iFrameMode != XAP_NormalFrame))
		return;

	GdkCursorType cursor_number;

	switch (c)
	{
	default:
		UT_ASSERT(UT_NOT_IMPLEMENTED);
		/*FALLTHRU*/
	case GR_Graphics::GR_CURSOR_DEFAULT:
		cursor_number = GDK_LEFT_PTR;
		break;

	case GR_Graphics::GR_CURSOR_IBEAM:
		cursor_number = GDK_XTERM;
		break;

	//I have changed the shape of the arrow so get a consistent
	//behaviour in the bidi build; I think the new arrow is better
	//for the purpose anyway

	case GR_Graphics::GR_CURSOR_RIGHTARROW:
		cursor_number = GDK_SB_RIGHT_ARROW; //GDK_ARROW;
		break;

	case GR_Graphics::GR_CURSOR_LEFTARROW:
		cursor_number = GDK_SB_LEFT_ARROW; //GDK_LEFT_PTR;
		break;

	case GR_Graphics::GR_CURSOR_IMAGE:
		cursor_number = GDK_FLEUR;
		break;

	case GR_Graphics::GR_CURSOR_IMAGESIZE_NW:
		cursor_number = GDK_TOP_LEFT_CORNER;
		break;

	case GR_Graphics::GR_CURSOR_IMAGESIZE_N:
		cursor_number = GDK_TOP_SIDE;
		break;

	case GR_Graphics::GR_CURSOR_IMAGESIZE_NE:
		cursor_number = GDK_TOP_RIGHT_CORNER;
		break;

	case GR_Graphics::GR_CURSOR_IMAGESIZE_E:
		cursor_number = GDK_RIGHT_SIDE;
		break;

	case GR_Graphics::GR_CURSOR_IMAGESIZE_SE:
		cursor_number = GDK_BOTTOM_RIGHT_CORNER;
		break;

	case GR_Graphics::GR_CURSOR_IMAGESIZE_S:
		cursor_number = GDK_BOTTOM_SIDE;
		break;

	case GR_Graphics::GR_CURSOR_IMAGESIZE_SW:
		cursor_number = GDK_BOTTOM_LEFT_CORNER;
		break;

	case GR_Graphics::GR_CURSOR_IMAGESIZE_W:
		cursor_number = GDK_LEFT_SIDE;
		break;

	case GR_Graphics::GR_CURSOR_LEFTRIGHT:
		cursor_number = GDK_SB_H_DOUBLE_ARROW;
		break;

	case GR_Graphics::GR_CURSOR_UPDOWN:
		cursor_number = GDK_SB_V_DOUBLE_ARROW;
		break;

	case GR_Graphics::GR_CURSOR_EXCHANGE:
		cursor_number = GDK_EXCHANGE;
		break;

	case GR_Graphics::GR_CURSOR_GRAB:
		cursor_number = GDK_HAND1;
		break;

	case GR_Graphics::GR_CURSOR_LINK:
		cursor_number = GDK_HAND2;
		break;

	case GR_Graphics::GR_CURSOR_WAIT:
		cursor_number = GDK_WATCH;
		break;
	}

	GdkCursor * cursor = gdk_cursor_new(cursor_number);
	gdk_window_set_cursor(getTopLevelWindow()->window, cursor);
	gdk_window_set_cursor(getVBoxWidget()->window, cursor);
	gdk_window_set_cursor(m_wSunkenBox->window, cursor);
	gdk_window_set_cursor(m_wStatusBar->window, cursor);
	gdk_cursor_unref(cursor);
}

UT_sint32 XAP_UnixFrameImpl::_setInputMode(const char * szName)
{
	UT_sint32 result = XAP_App::getApp()->setInputMode(szName);
	if (result == 1)
	{
		// if it actually changed we need to update keyboard and mouse

		EV_EditEventMapper * pEEM = XAP_App::getApp()->getEditEventMapper();
		UT_ASSERT(pEEM);

		m_pKeyboard->setEditEventMap(pEEM);
		m_pMouse->setEditEventMap(pEEM);
	}

	return result;
}

GtkWidget * XAP_UnixFrameImpl::getTopLevelWindow(void) const
{
	return m_wTopLevelWindow;
}

GtkWidget * XAP_UnixFrameImpl::getVBoxWidget(void) const
{
	return m_wVBox;
}

XAP_DialogFactory * XAP_UnixFrameImpl::_getDialogFactory(void)
{
	return &m_dialogFactory;
}

// TODO: split me up into smaller pieces/subfunctions
void XAP_UnixFrameImpl::createTopLevelWindow(void)
{
	// create a top-level window for us.

	static GdkPixbuf * wmIcon = NULL ;

	// load the icon only once
	if (!wmIcon)
	{
		GError *err = NULL ;
		UT_String icon_location = XAP_App::getApp()->getAbiSuiteLibDir();
		icon_location += "/icons/abiword_16.xpm" ;
		wmIcon = gdk_pixbuf_new_from_file(icon_location.c_str(),&err);
	}
	
	bool bResult;
	if(m_iFrameMode == XAP_NormalFrame)
	{
		m_wTopLevelWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
		g_object_set_data(G_OBJECT(m_wTopLevelWindow), "ic_attr", NULL);
		g_object_set_data(G_OBJECT(m_wTopLevelWindow), "ic", NULL);
		gtk_window_set_title(GTK_WINDOW(m_wTopLevelWindow),
				     m_pUnixApp->getApplicationTitleForTitleBar());
		gtk_window_set_resizable(GTK_WINDOW(m_wTopLevelWindow), TRUE);
		gtk_window_set_role(GTK_WINDOW(m_wTopLevelWindow), "topLevelWindow");
		if ( wmIcon )
			gtk_window_set_icon(GTK_WINDOW(m_wTopLevelWindow), wmIcon);
	}
	g_object_set_data(G_OBJECT(m_wTopLevelWindow), "toplevelWindow",
						m_wTopLevelWindow);
	g_object_set_data(G_OBJECT(m_wTopLevelWindow), "toplevelWindowFocus",
						GINT_TO_POINTER(FALSE));
	g_object_set_data(G_OBJECT(m_wTopLevelWindow), "user_data", this); 

	_setGeometry ();

	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "realize",
					   G_CALLBACK(_fe::realize), NULL);
	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "unrealize",
					   G_CALLBACK(_fe::unrealize), NULL);
	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "size_allocate",
					   G_CALLBACK(_fe::sizeAllocate), NULL);
	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "focus_in_event",
					   G_CALLBACK(_fe::focusIn), NULL);
	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "focus_out_event",
					   G_CALLBACK(_fe::focusOut), NULL);

#ifdef HAVE_GNOME
	gtk_drag_dest_set (m_wTopLevelWindow,
					   GTK_DEST_DEFAULT_ALL,
					   drag_types, 
					   NrElements(drag_types), 
					   GDK_ACTION_COPY);

	g_signal_connect (G_OBJECT (m_wTopLevelWindow),
					  "drag_data_get",
					  G_CALLBACK (s_dnd_drop_event), 
					  static_cast<gpointer>(this));
	g_signal_connect (G_OBJECT (m_wTopLevelWindow), 
					  "drag_data_received",
					  G_CALLBACK (s_dnd_drop_event), 
					  static_cast<gpointer>(this));	
  	g_signal_connect (G_OBJECT (m_wTopLevelWindow), 
					  "drag_drop",
					  G_CALLBACK (s_dnd_real_drop_event), 
					  static_cast<gpointer>(this));
	
  	g_signal_connect (G_OBJECT (m_wTopLevelWindow), 
					  "drag_end",
					  G_CALLBACK (s_dnd_drag_end), 
					  static_cast<gpointer>(this));
#endif

	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "delete_event",
					   G_CALLBACK(_fe::delete_event), NULL);
	// here we connect the "destroy" event to a signal handler.
	// This event occurs when we call gtk_widget_destroy() on the window,
	// or if we return 'FALSE' in the "delete_event" callback.
	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "destroy",
					   G_CALLBACK(_fe::destroy), NULL);

	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "focus_in_event",
					   G_CALLBACK(_fe::focus_in_event), NULL);
	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "focus_out_event",
					   G_CALLBACK(_fe::focus_out_event), NULL);

	// create a VBox inside it.

	m_wVBox = gtk_vbox_new(FALSE,0);
	g_object_set_data(G_OBJECT(m_wTopLevelWindow), "vbox", m_wVBox);
	g_object_set_data(G_OBJECT(m_wVBox),"user_data", this);
	gtk_container_add(GTK_CONTAINER(m_wTopLevelWindow), m_wVBox);

	if (m_iFrameMode != XAP_NoMenusWindowLess) {
		// synthesize a menu from the info in our base class.
		m_pUnixMenu = new EV_UnixMenuBar(m_pUnixApp, getFrame(), m_szMenuLayoutName,
										 m_szMenuLabelSetName);
		UT_ASSERT(m_pUnixMenu);
		bResult = m_pUnixMenu->synthesizeMenuBar();
		UT_ASSERT(bResult);
	}

	// create a toolbar instance for each toolbar listed in our base class.
	// TODO for some reason, the toolbar functions require the TLW to be
	// TODO realized (they reference m_wTopLevelWindow->window) before we call them.

	if(m_iFrameMode == XAP_NormalFrame)
		gtk_widget_realize(m_wTopLevelWindow);

	_createIMContext(m_wTopLevelWindow->window);

	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "key_press_event",
					   G_CALLBACK(_fe::key_press_event), NULL);
	g_signal_connect(G_OBJECT(m_wTopLevelWindow), "key_release_event",
					   G_CALLBACK(_fe::key_release_event), NULL);

	if(m_iFrameMode == XAP_NormalFrame)
		_createToolbars();

	// Let the app-specific frame code create the contents of
	// the child area of the window (between the toolbars and
	// the status bar).
	m_wSunkenBox = _createDocumentWindow();
	gtk_container_add(GTK_CONTAINER(m_wVBox), m_wSunkenBox);
	gtk_widget_show(m_wSunkenBox);

	// Create statusLet the app-specific frame code create the status bar
	// if it wants to.  we will put it below the document
	// window (a peer with toolbars and the overall sunkenbox)
	// so that it will appear outside of the scrollbars.
	m_wStatusBar = NULL;
	if(m_iFrameMode == XAP_NormalFrame)
		m_wStatusBar = _createStatusBarWindow();

	if (m_wStatusBar) 
	{
		gtk_widget_show(m_wStatusBar);
		gtk_box_pack_end(GTK_BOX(m_wVBox), m_wStatusBar, FALSE, FALSE, 0);
	}

	gtk_widget_show(m_wVBox);

	// set the icon
	if(m_iFrameMode == XAP_NormalFrame)
		_setWindowIcon();
}

void XAP_UnixFrameImpl::_createIMContext(GdkWindow *w)
{
	m_imContext = gtk_im_multicontext_new();
	gtk_im_context_set_use_preedit (m_imContext, FALSE); // show alternate preedit stuff in a separate window or somesuch
	gtk_im_context_set_client_window(m_imContext, w);
	g_signal_connect(G_OBJECT(m_imContext), "commit", 
					 G_CALLBACK(_imCommit_cb), this);
	g_signal_connect (m_imContext, "preedit_changed",
					  G_CALLBACK (_imPreeditChanged_cb), this);
	g_signal_connect (m_imContext, "retrieve_surrounding",
					  G_CALLBACK (_imRetrieveSurrounding_cb), this);
	g_signal_connect (m_imContext, "delete_surrounding",
					  G_CALLBACK (_imDeleteSurrounding_cb), this);
}

void XAP_UnixFrameImpl::_imPreeditChanged_cb (GtkIMContext *context, gpointer data)
{
	UT_DEBUGMSG(("Preedit Changed\n"));

#if 0
	gchar *preedit_string;
	gint cursor_pos;
	
	gtk_im_context_get_preedit_string (context,
									   &preedit_string, NULL,
									   &cursor_pos);
	entry->preedit_length = strlen (preedit_string);
	cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1));
	entry->preedit_cursor = cursor_pos;
	g_free (preedit_string);
	
	gtk_entry_recompute (entry);
#endif
}

gint XAP_UnixFrameImpl::_imRetrieveSurrounding_cb (GtkIMContext *context, gpointer data)
{
	UT_DEBUGMSG(("Retrieve Surrounding\n"));

	XAP_UnixFrameImpl * pImpl = static_cast<XAP_UnixFrameImpl*>(data);
	FV_View * pView = static_cast<FV_View*>(pImpl->getFrame()->getCurrentView ());

	PT_DocPosition begin_p, end_p, here;

	begin_p = pView->mapDocPosSimple (FV_DOCPOS_BOB);
	end_p = pView->mapDocPosSimple (FV_DOCPOS_EOB);
	here = pView->getInsPoint ();

	UT_UCSChar * text = pView->getTextBetweenPos (begin_p, end_p);

	if (!text)
		return TRUE;

	UT_UTF8String utf (text);
	DELETEPV(text);

	gtk_im_context_set_surrounding (context,
									utf.utf8_str(),
									utf.byteLength (),
									g_utf8_offset_to_pointer(utf.utf8_str(), here - begin_p) - utf.utf8_str());

	return TRUE;
}

gint XAP_UnixFrameImpl::_imDeleteSurrounding_cb (GtkIMContext *slave, gint offset, gint n_chars, gpointer data)
{
	XAP_UnixFrameImpl * pImpl = static_cast<XAP_UnixFrameImpl*>(data);
	FV_View * pView = static_cast<FV_View*>(pImpl->getFrame()->getCurrentView ());

	PT_DocPosition insPt = pView->getInsPoint ();
	if ((gint) insPt + offset < 0)
		return TRUE;

	pView->moveInsPtTo (insPt + offset);
	pView->cmdCharDelete (true, n_chars);

	return TRUE;
}

// Actual keyboard commit should be done here.
void XAP_UnixFrameImpl::_imCommit_cb(GtkIMContext *imc, const gchar *text, gpointer data)
{
	XAP_UnixFrameImpl * impl = static_cast<XAP_UnixFrameImpl*>(data);
	impl->_imCommit (imc, text);
}

// Actual keyboard commit should be done here.
void XAP_UnixFrameImpl::_imCommit(GtkIMContext *imc, const gchar * text)
{
	XAP_Frame* pFrame = getFrame();
	AV_View * pView   = pFrame->getCurrentView();
	ev_UnixKeyboard * pUnixKeyboard = static_cast<ev_UnixKeyboard *>(pFrame->getKeyboard());

	pUnixKeyboard->charDataEvent(pView, static_cast<EV_EditBits>(0), text, strlen(text));
}

GtkIMContext * XAP_UnixFrameImpl::getIMContext()
{
	return m_imContext;
}

void XAP_UnixFrameImpl::_setGeometry ()
{
	UT_sint32 app_x = 0;
	UT_sint32 app_y = 0;
	UT_uint32 app_w = 0;
	UT_uint32 app_h = 0;
	UT_uint32 app_f = 0;

	XAP_App * pApp = m_pUnixApp->XAP_App::getApp ();
	pApp->getGeometry (&app_x, &app_y, &app_w, &app_h, &app_f);
	// (ignore app_x, app_y & app_f since the WM will set them for us just fine)
	  
	// This is now done with --geometry parsing.
	if (app_w == 0 || app_w > USHRT_MAX) app_w = 760;
        if (app_h == 0 || app_h > USHRT_MAX) app_h = 520;

	UT_DEBUGMSG(("xap_UnixFrameImpl: app-width=%lu, app-height=%lu\n",
				 static_cast<unsigned long>(app_w),static_cast<unsigned long>(app_h)));

	// set geometry hints as the user requested
	gint user_x = 0;
	gint user_y = 0;
	guint user_w = static_cast<guint>(app_w);
	guint user_h = static_cast<guint>(app_h);
	UT_uint32 user_f = 0;

	m_pUnixApp->getWinGeometry (&user_x, &user_y, &user_w, &user_h, &user_f);

	UT_DEBUGMSG(("xap_UnixFrameImpl: user-width=%u, user-height=%u\n",
				 static_cast<unsigned>(user_w),static_cast<unsigned>(user_h)));

	// Get fall-back defaults from preferences
	UT_sint32 pref_x = 0;
	UT_sint32 pref_y = 0;
	UT_uint32 pref_w = static_cast<UT_uint32>(app_w);
	UT_uint32 pref_h = static_cast<UT_uint32>(app_h);
	UT_uint32 pref_f = 0;

	m_pUnixApp->getPrefs()->getGeometry (&pref_x, &pref_y, &pref_w, &pref_h, &pref_f);

	UT_DEBUGMSG(("xap_UnixFrameImpl: pref-width=%lu, pref-height=%lu\n",
				 static_cast<unsigned long>(pref_w),static_cast<unsigned long>(pref_h)));

	if (!(user_f & XAP_UnixApp::GEOMETRY_FLAG_SIZE))
		if (pref_f & PREF_FLAG_GEOMETRY_SIZE)
			{
				user_w = static_cast<guint>(pref_w);
				user_h = static_cast<guint>(pref_h);
				user_f |= XAP_UnixApp::GEOMETRY_FLAG_SIZE;
			}
	if (!(user_f & XAP_UnixApp::GEOMETRY_FLAG_POS))
		if (pref_f & PREF_FLAG_GEOMETRY_POS)
			{
				user_x = static_cast<gint>(pref_x);
				user_y = static_cast<gint>(pref_y);
				user_f |= XAP_UnixApp::GEOMETRY_FLAG_POS;
			}

	UT_DEBUGMSG(("xap_UnixFrameImpl: user-x=%d, user-y=%d\n",
				 static_cast<int>(user_x),static_cast<int>(user_y)));

	if (!(user_f & XAP_UnixApp::GEOMETRY_FLAG_SIZE))
		{
			user_w = static_cast<guint>(app_w);
			user_h = static_cast<guint>(app_h);
		}

	if (user_w > USHRT_MAX) user_w = app_w;
        if (user_h > USHRT_MAX) user_h = app_h;

	GdkGeometry geom;
	geom.min_width   = 100;
	geom.min_height  = 100;
	if(getFrame()->getFrameMode() == XAP_NormalFrame)
	{
		gtk_window_set_geometry_hints (GTK_WINDOW(m_wTopLevelWindow), m_wTopLevelWindow, &geom,
									   static_cast<GdkWindowHints>(GDK_HINT_MIN_SIZE));

		gtk_window_set_default_size (GTK_WINDOW(m_wTopLevelWindow), user_w, user_h);
	}

	// Because we're clever, we only honor this flag when we
	// are the first (well, only) top level frame available.
	// This is so the user's window manager can find better
	// places for new windows, instead of having our windows
	// pile upon each other.

	if (m_pUnixApp->getFrameCount () <= 1)
		if (user_f & XAP_UnixApp::GEOMETRY_FLAG_POS)
			{
				gtk_window_move (GTK_WINDOW(m_wTopLevelWindow), user_x, user_y);
			}

	// Remember geometry settings for next time
	m_pUnixApp->getPrefs()->setGeometry (user_x, user_y, user_w, user_h, user_f);
}

/*!
 * This code is used by the dynamic menu API to rebuild the menus after a
 * a change in the menu structure.
 */
void XAP_UnixFrameImpl::_rebuildMenus(void)
{
	// destroy old menu
	m_pUnixMenu->destroy();
	DELETEP(m_pUnixMenu);
	
	// build new one.
	m_pUnixMenu = new EV_UnixMenuBar(m_pUnixApp, getFrame(),
					 m_szMenuLayoutName,
					 m_szMenuLabelSetName);
	UT_ASSERT(m_pUnixMenu);
	bool bResult = m_pUnixMenu->rebuildMenuBar();
	UT_ASSERT(bResult);
}

/*!
 * This code is used by the dynamic toolbar API to rebuild a toolbar after a
 * a change in the toolbar structure.
 */
void XAP_UnixFrameImpl::_rebuildToolbar(UT_uint32 ibar)
{
	XAP_Frame*	pFrame = getFrame();
	// Destroy the old toolbar
	EV_Toolbar * pToolbar = static_cast<EV_Toolbar *>(m_vecToolbars.getNthItem(ibar));
	const char * szTBName = reinterpret_cast<const char *>(m_vecToolbarLayoutNames.getNthItem(ibar));
	EV_UnixToolbar * pUTB = static_cast<EV_UnixToolbar *>(pToolbar);
	UT_sint32 oldpos = pUTB->destroy();

	// Delete the old class
	delete pToolbar;
	if(oldpos < 0) {
		return;
	}

	// Build a new one.
	pToolbar = _newToolbar(m_pUnixApp, pFrame, szTBName,
			       static_cast<const char *>(m_szToolbarLabelSetName));
	static_cast<EV_UnixToolbar *>(pToolbar)->rebuildToolbar(oldpos);
	m_vecToolbars.setNthItem(ibar, pToolbar, NULL);
	// Refill the framedata pointers

	pFrame->refillToolbarsInFrameData();
	pFrame->repopulateCombos();
}

bool XAP_UnixFrameImpl::_close()
{
	gtk_widget_destroy(m_wTopLevelWindow);

	return true;
}

bool XAP_UnixFrameImpl::_raise()
{
	UT_ASSERT(m_wTopLevelWindow);

	gtk_window_present(GTK_WINDOW (m_wTopLevelWindow));

	return true;
}

bool XAP_UnixFrameImpl::_show()
{
	if(m_wTopLevelWindow) {
		gtk_widget_show(m_wTopLevelWindow);
	}

	return true;
}

bool XAP_UnixFrameImpl::_updateTitle()
{
	if (!XAP_FrameImpl::_updateTitle() || (m_wTopLevelWindow== NULL) || (m_iFrameMode != XAP_NormalFrame))
	{
		// no relevant change, so skip it
		return false;
	}

	if(getFrame()->getFrameMode() == XAP_NormalFrame)
	{
		const char * szTitle = getFrame()->getTitle(MAX_TITLE_LENGTH);
		gtk_window_set_title(GTK_WINDOW(m_wTopLevelWindow), szTitle);
	}
	return true;
}

bool XAP_UnixFrameImpl::_runModalContextMenu(AV_View * /* pView */, const char * szMenuName,
					       UT_sint32 x, UT_sint32 y)
{
	XAP_Frame*	pFrame = getFrame();
	bool bResult = true;

	UT_ASSERT(!m_pUnixPopup);

	// WL_REFACTOR: we DON'T want to do this
	m_pUnixPopup = new EV_UnixMenuPopup(m_pUnixApp, pFrame, szMenuName, m_szMenuLabelSetName);
	if (m_pUnixPopup && m_pUnixPopup->synthesizeMenuPopup())
	{
		// Add our InputMethod selection item to the popup menu. Borrowed
		// from gtkentry.c
		GtkWidget * menuitem;
		GtkWidget * submenu;
		GtkWidget * menu = m_pUnixPopup->getMenuHandle();

		menuitem = gtk_separator_menu_item_new ();
		gtk_widget_show (menuitem);
		gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);

		const XAP_StringSet * pSS = m_pUnixApp->getStringSet();

		menuitem = gtk_menu_item_new_with_label (pSS->getValue(XAP_STRING_ID_XIM_Methods));

		gtk_widget_show (menuitem);
		submenu = gtk_menu_new ();
		gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
		
		gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);

		gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(m_imContext), 
											 GTK_MENU_SHELL(submenu));

		// the popup will steal the mouse and so we won't get the
		// button_release_event and we won't know to release our
		// grab.  so let's do it here.  (when raised from a keyboard
		// context menu, we may not have a grab, but that should be ok.
		GtkWidget * w = gtk_grab_get_current();
		if (w)
			gtk_grab_remove(w);

		//
		// OK lets not immediately drop the menu if the user releases the mouse button.
		// From the gtk FAQ.
		//
		GdkEvent * event = gtk_get_current_event();
		GdkEventButton *bevent = reinterpret_cast<GdkEventButton *>(event);

		GtkRequisition req ;
		gtk_widget_size_request (m_pUnixPopup->getMenuHandle(), &req);
		if(w)
		{
			gdk_window_get_origin(w->window, &x,&y);
		}
		x += static_cast<UT_sint32>(bevent->x);
		y += static_cast<UT_sint32>(bevent->y);

		UT_Point pt;
		pt.x = x;
		pt.y = y;

		gtk_menu_popup(GTK_MENU(m_pUnixPopup->getMenuHandle()), NULL, NULL,
			       s_gtkMenuPositionFunc, &pt, bevent->button, bevent->time);


		// We run this menu synchronously, since GTK doesn't.
		// Popup menus have a special "unmap" function to call
		// gtk_main_quit() when they're done.
		gtk_main();
	}

	if (pFrame->getCurrentView())
		pFrame->getCurrentView()->focusChange( AV_FOCUS_HERE);

	DELETEP(m_pUnixPopup);
	return bResult;
}

void XAP_UnixFrameImpl::setTimeOfLastEvent(guint32 eventTime)
{
	m_pUnixApp->setTimeOfLastEvent(eventTime);
}

void XAP_UnixFrameImpl::_queue_resize()
{
	gtk_widget_queue_resize(m_wTopLevelWindow);
}

EV_Menu* XAP_UnixFrameImpl::_getMainMenu()
{
	return m_pUnixMenu;
}


void XAP_UnixFrameImpl::_setFullScreen(bool changeToFullScreen)
{
	if (changeToFullScreen)
		gtk_window_fullscreen (GTK_WINDOW(m_wTopLevelWindow));
	else
		gtk_window_unfullscreen (GTK_WINDOW(m_wTopLevelWindow));
}

EV_Toolbar * XAP_UnixFrameImpl::_newToolbar(XAP_App *pApp, XAP_Frame *pFrame,
					      const char *szLayout,
					      const char *szLanguage)
{
	return (new EV_UnixToolbar(static_cast<XAP_UnixApp *>(pApp), pFrame, szLayout, szLanguage));
}