#include "CtrlCore.h"

#ifdef GUI_X11

#include <X11/Xlocale.h>

namespace Upp {

#ifdef _DEBUG

bool Ctrl::LogMessages

// = true _DBG_
;
bool __X11_Grabbing = false;
#endif

#define LLOG(x)      // DLOG(x)
#define LTIMING(x)   // RTIMING(x)
#define LDUMP(x)     // DDUMP(x)
#define LDUMPC(x)    // DDUMPC(x)

// #define SYNCHRONIZE

#define x_Event(x) { x, #x },

static
struct XEventMap {
	int         ID;
	const char *name;
}
sXevent[] = {
#include "X11Event.i"
	{ 0, NULL }
};

ArrayMap<Window, Ctrl::XWindow>& Ctrl::Xwindow()
{
	return Single< ArrayMap<Window, XWindow> >();
}

int       Ctrl::WndCaretTime;
bool      Ctrl::WndCaretVisible;
int       Ctrl::Xbuttons;
Window    Ctrl::grabWindow, Ctrl::focusWindow;
int       Ctrl::Xeventtime;

int       Ctrl::PopupGrab;
Ptr<Ctrl> Ctrl::popupWnd;

Point     Ctrl::mousePos;

static int s_starttime;

void Ctrl::DoPaint(const Vector<Rect>& invalid)
{
	GuiLock __;
	if(IsVisible()) {
		LTIMING("DoPaint");
		fullrefresh = false;
//		if(GLX) return;
		GC gc = XCreateGC(Xdisplay, (Drawable)top->window, 0, 0);
		XftDraw *xftdraw = XftDrawCreate(Xdisplay, (Drawable) top->window,
		                                 DefaultVisual(Xdisplay, Xscreenno), Xcolormap);
		SystemDraw draw(top->window, gc, xftdraw, invalid);
		painting = true;
		UpdateArea(draw, draw.GetClip());
		painting = false;
		XftDrawDestroy(xftdraw);
		XFreeGC(Xdisplay, gc);
	}
}

void  Ctrl::WndScrollView(const Rect& r, int dx, int dy)
{
	GuiLock __;
	if(r.IsEmpty() || !GetWindow()) return;
	int cx = r.Width() - abs(dx);
	int cy = r.Height() - abs(dy);
	GC gc = XCreateGC(Xdisplay, GetWindow(), 0, 0);
	XCopyArea(Xdisplay, GetWindow(), GetWindow(), gc,
	          r.left + max(-dx, 0), r.top + max(-dy, 0), cx, cy,
	          r.left + max(dx, 0), r.top + max(dy, 0));
	XFreeGC(Xdisplay, gc);
	XWindow *xw = GetXWindow();
	Vector<Rect> ur;
	if(xw)
		for(int i = 0; i < xw->invalid.GetCount(); i++)
			if(xw->invalid[i].Intersects(r))
				ur.Add(xw->invalid[i]);
	for(int i = 0; i < ur.GetCount(); i++)
		Invalidate(*xw, ur[i].Offseted(dx, dy));
}

bool Ctrl::IsWaitingEvent()
{
	ASSERT_(IsMainThread(), "IsWaitingEvent can only run in the main thread");
	GuiLock __;
	return XPending(Xdisplay);
}

Ctrl *Ctrl::CtrlFromWindow(Window w)
{
	GuiLock __;
	int q = Xwindow().Find(w);
	return q >= 0 ? Xwindow()[q].ctrl : NULL;
}

Ctrl::XWindow *Ctrl::GetXWindow()
{
	GuiLock __;
	if(!top) return NULL;
	int q = Xwindow().Find(top->window);
	return q >= 0 ? &Xwindow()[q] : NULL;
}
// 01/12/2007 - mdelfede
// added support for windowed controls

// Gets handle of window containing control
Window Ctrl::GetParentWindow(void) const
{
	GuiLock __;
	Ctrl const *q = GetParentWindowCtrl();
	if(q)
		return q->top->window;
	else
		return 0;

} // END Ctrl::GetParentWindow()

// Get control with parentwindow as handle
Ctrl *Ctrl::GetParentWindowCtrl(void) const
{
	GuiLock __;
	Ctrl *q = parent;
	while(q && !q->top)
		q = q->parent;
	return q;

} // END Ctrl::GetParentWindowCtrl()

// Gets the rect inside the parent window
Rect Ctrl::GetRectInParentWindow(void) const
{
	GuiLock __;
	Rect r = GetScreenRect();
	Ctrl *q = parent;
	while(q && !q->top)
		q = q->parent;
	if(q)
	{
		Rect pr = q->GetScreenRect();
		r -= pr.TopLeft();
	}
	return r;
}
// 01/12/2007 - END

bool Ctrl::HookProc(XEvent *event) { return false; }

void DnDRequest(XSelectionRequestEvent *se);
void DnDClear();

dword X11mods(dword key)
{
	dword mod = 0;
	if(key & K_ALT)
		mod |= Mod1Mask;
	if(key & K_SHIFT)
		mod |= ShiftMask;
	if(key & K_CTRL)
		mod |= ControlMask;
	return mod;
}

Vector<Event<> > Ctrl::hotkey;
Vector<dword> Ctrl::keyhot;
Vector<dword> Ctrl::modhot;

int Ctrl::RegisterSystemHotKey(dword key, Function<void ()> cb)
{
	GuiLock __;
	ASSERT(key >= K_DELTA);
	bool b = TrapX11Errors();
	KeyCode k = XKeysymToKeycode(Xdisplay, key & 0xffff);
	dword mod = X11mods(key);
	bool r = false;
	for(dword nlock = 0; nlock < 2; nlock++)
		for(dword clock = 0; clock < 2; clock++)
			for(dword slock = 0; slock < 2; slock++)
				r = XGrabKey(Xdisplay, k,
				             mod | (nlock * Mod2Mask) | (clock * LockMask) | (slock * Mod3Mask),
				             Xroot, true, GrabModeAsync, GrabModeAsync) || r;
	UntrapX11Errors(b);
	if(!r) return -1;
	int q = hotkey.GetCount();
	for(int i = 0; i < hotkey.GetCount(); i++)
		if(!hotkey[i]) {
			q = i;
			break;
		}
	hotkey.At(q) << cb;
	keyhot.At(q) = k;
	modhot.At(q) = mod;
	return q;
}

void Ctrl::UnregisterSystemHotKey(int id)
{
	GuiLock __;
	if(id >= 0 && id < hotkey.GetCount() && hotkey[id]) {
		bool b = TrapX11Errors();
		for(dword nlock = 0; nlock < 2; nlock++)
			for(dword clock = 0; clock < 2; clock++)
				for(dword slock = 0; slock < 2; slock++)
					XUngrabKey(Xdisplay, keyhot[id],
					           modhot[id] | (nlock * Mod2Mask) | (clock * LockMask) | (slock * Mod3Mask),
					           Xroot);
		UntrapX11Errors(b);
		hotkey[id].Clear();
	}
}

void Ctrl::ProcessEvent(XEvent *event)
{
	GuiLock __;
	if(xim && XFilterEvent(event, None))
		return;
	if(event->type == KeyPress)
		for(int i = 0; i < hotkey.GetCount(); i++)
			if(hotkey[i] && keyhot[i] == event->xkey.keycode
			   && modhot[i] == (event->xkey.state & (Mod1Mask|ShiftMask|ControlMask))) {
				hotkey[i]();
				return;
			}
	ArrayMap<Window, Ctrl::XWindow>& xmap = Xwindow();
	for(int i = 0; i < xmap.GetCount(); i++)
		if(xmap[i].ctrl && xmap[i].ctrl->HookProc(event))
			return;
	FocusSync();
	if(event->type == PropertyNotify &&
	   (event->xproperty.atom == XAtom("_QT_SETTINGS_TIMESTAMP_") ||
	    event->xproperty.atom == XAtom("_KDE_NET_USER_TIME"))) {
		LLOG("Property notify " << XAtomName(event->xproperty.atom)
		     << " " << (void *)event->xany.window);
		for(int i = 0; i < Xwindow().GetCount(); i++) {
			if(Xwindow().GetKey(i)) {
				Ctrl *q = Xwindow()[i].ctrl;
				if(q) q->Refresh();
			}
		}
	}
	if(event->type == SelectionRequest &&
	   event->xselectionrequest.owner == xclipboard().win) {
		if(event->xselectionrequest.selection == XAtom("XdndSelection"))
			DnDRequest(&event->xselectionrequest);
		else
			xclipboard().Request(&event->xselectionrequest);
		return;
	}
	if(event->type == SelectionClear &&
	   event->xselectionclear.window == xclipboard().win) {
		if(event->xselectionclear.selection == XAtom("PRIMARY")) {
			sel_formats.Clear();
			sel_ctrl = NULL;
		}
		if(event->xselectionclear.selection == XAtom("CLIPBOARD"))
			xclipboard().Clear();
		if(event->xselectionrequest.selection == XAtom("XdndSelection"))
			DnDClear();
	   	return;
	}
	int q = xmap.Find(event->xany.window);
#ifdef _DEBUG
	bool eo = false;
	if(LogMessages && event->type != NoExpose && event->type != PropertyNotify
	               && event->type != MotionNotify)
		for(XEventMap *m = sXevent; m->ID; m++)
			if(m->ID == event->type) {
				if(!s_starttime)
					s_starttime = GetTickCount();
				int t = GetTickCount() - s_starttime;
				VppLog() << Format("%d.%01d", t / 1000, t % 1000);
				VppLog() << " EVENT " << Format("%-20.20s", m->name);
				VppLog() << "[window: " << event->xany.window << "] ";
				if(q >= 0)
					VppLog() << '<' << UPP::Name(Xwindow()[q].ctrl) << '>';
				else
					VppLog() << "<unknown ctrl> ";
				if(event->type == ClientMessage)
					VppLog() << ": " << XAtomName(event->xclient.message_type);
				VppLog() << '\n';
				eo = true;
				break;
			}
#endif
	DropStatusEvent(event);
	if(q < 0) {
		if(event->type == ButtonRelease && popupWnd)
			popupWnd->SetFocus();
		return;
	}
	XWindow& w = xmap[q];
	if(w.ctrl) {
		w.ctrl->EventProc(w, event);
		if(w.ctrl)
			w.ctrl->SyncMoves();
	}
	else
		xmap.SetKey(q, None);
	DefferedFocusSync();
#ifdef _DEBUG
#ifdef UPP_HEAP
	if(MemoryCheck)
		UPP::MemoryCheck();
#endif
	if(eo)
		LLOG("------ end of event processing ----------");
#endif
}

void Ctrl::TimerAndPaint() {
	GuiLock __;
	LTIMING("TimerAndPaint");
	TimerProc(GetTickCount());
	for(int i = 0; i < Xwindow().GetCount(); i++) {
		XWindow& xw = Xwindow()[i];
		if(Xwindow().GetKey(i) && xw.exposed && xw.invalid.GetCount()) {
			if(xw.ctrl) {
				LLOG("..and paint " << UPP::Name(xw.ctrl));
				xw.ctrl->SyncScroll();
				Vector<Rect> x = pick(xw.invalid);
				xw.invalid.Clear();
				xw.ctrl->DoPaint(x);
			}
			else
				Xwindow().SetKey(i, None);
		}
	}
	SyncCaret();
	AnimateCaret();
	XSync(Xdisplay, false);
	SyncIMPosition();
}

bool Ctrl::ProcessEvent(bool *)
{
	GuiLock __;
	if(!IsWaitingEvent()) return false;
	XEvent event;
	XNextEvent(Xdisplay, &event);
	ProcessEvent(&event);
	return true;
}

void SweepMkImageCache();

bool Ctrl::ProcessEvents(bool *)
{
	ASSERT_(IsMainThread(), "ProcessEvents can only run in the main thread");
	GuiLock __;
	if(ProcessEvent()) {
		while(ProcessEvent() && (!LoopCtrl || LoopCtrl->InLoop())); // LoopCtrl-MF 071008
		TimerAndPaint();
		SweepMkImageCache();
		return true;
	}
	TimerAndPaint();
	SweepMkImageCache();
	return false;
}

static bool WakePipeOK;
static int  WakePipe[2];

INITBLOCK {
	WakePipeOK = pipe(WakePipe) == 0;
	fcntl(WakePipe[0], F_SETFL, O_NONBLOCK);
	fcntl(WakePipe[1], F_SETFL, O_NONBLOCK);
}

void WakeUpGuiThread()
{
	if(WakePipeOK)
		IGNORE_RESULT(write(WakePipe[1], "\1", 1));
}

void Ctrl::GuiSleep(int ms)
{
	GuiLock __;
	LLOG(GetTickCount() << " GUISLEEP " << ms);
	ASSERT_(IsMainThread(), "Only main thread can perform GuiSleep");
	timeval timeout;
	timeout.tv_sec = ms / 1000;
	timeout.tv_usec = ms % 1000 * 1000;
	XFlush(Xdisplay);
	fd_set fdset;
	FD_ZERO(&fdset);
	FD_SET(Xconnection, &fdset);
	if(WakePipeOK)
		FD_SET(WakePipe[0], &fdset);
	int level = LeaveGuiMutexAll(); // Give  a chance to nonmain threads to touch GUI things
	select((WakePipeOK ? max(WakePipe[0], Xconnection) : Xconnection) + 1, &fdset, NULL, NULL, &timeout);
	char h;
	while(WakePipeOK && read(WakePipe[0], &h, 1) > 0) // There might be relatively harmless race condition here causing delay in ICall
		LLOG(GetTickCount() << " WakeUpGuiThread detected!"); // or "void" passes through timer & paint
	EnterGuiMutex(level);
}

static int granularity = 10;

void Ctrl::SetTimerGranularity(int ms)
{
	GuiLock __;
	granularity = ms;
}

void Ctrl::SysEndLoop()
{
}

void Ctrl::EventLoop(Ctrl *ctrl)
{
	GuiLock __;
	ASSERT_(IsMainThread(), "EventLoop can only run in the main thread");
	ASSERT(LoopLevel == 0 || ctrl);
	LoopLevel++;
	int64 loopno = ++EventLoopNo;
	LLOG("Entering event loop at level " << LoopLevel << LOG_BEGIN);
	Ctrl *ploop = NULL;
	if(ctrl) {
		ploop = LoopCtrl;
		LoopCtrl = ctrl;
		ctrl->inloop = true;
	}

	while(loopno > EndSessionLoopNo && (ctrl ? ctrl->InLoop() && ctrl->IsOpen() : GetTopCtrls().GetCount())) {
		XEvent event;
		GuiSleep(granularity);
		SyncMousePos();
		while(IsWaitingEvent()) {
			LTIMING("XNextEvent");
			XNextEvent(Xdisplay, &event);
			ProcessEvent(&event);
		}
		TimerAndPaint();
		SweepMkImageCache();
	}

	if(ctrl)
		LoopCtrl = ploop;

	LoopLevel--;
	LLOG(LOG_END << "Leaving event loop ");
}

void Ctrl::SyncExpose()
{
	GuiLock __;
	if(!top) return;
	XEvent event;
	while(top && XCheckTypedWindowEvent(Xdisplay, top->window, Expose, &event))
		ProcessEvent(&event);
	while(top && XCheckTypedWindowEvent(Xdisplay, top->window, GraphicsExpose, &event))
		ProcessEvent(&event);
}

void Ctrl::Create(Ctrl *owner, bool redirect, bool savebits)
{
	GuiLock __;
	ASSERT_(IsMainThread(), "Only main thread can create windows");
	LLOG("Create " << Name() << " " << GetRect());
	ASSERT(!IsChild() && !IsOpen());
	LLOG("Ungrab1");
	ReleaseGrab();
	XSetWindowAttributes swa;
	swa.bit_gravity = ForgetGravity;
	swa.background_pixmap = None;
	swa.override_redirect = redirect;
	swa.save_under = savebits;
	Color c = SColorPaper();
	swa.background_pixel = GetXPixel(c.GetR(), c.GetG(), c.GetB());
	Rect r = GetRect();
	isopen = true;
	Window w = XCreateWindow(Xdisplay, RootWindow(Xdisplay, Xscreenno),
	                         r.left, r.top, r.Width(), r.Height(),
	                         0, CopyFromParent, InputOutput, CopyFromParent,
	                         CWBitGravity|CWSaveUnder|CWOverrideRedirect|CWBackPixmap,
	                         &swa);
	if(!w) XError("XCreateWindow failed !");
	int i = Xwindow().Find(None);
	if(i >= 0) Xwindow().SetKey(i, w);
	XWindow& cw = i >= 0 ? Xwindow()[i] : Xwindow().Add(w);
	cw.ctrl = this;
	cw.exposed = false;
	cw.owner = owner;
	
	cw.xic = NULL;
	
	if(xim) {
		cw.xic = XCreateIC(xim,
		                   XNInputStyle, XIMPreeditNothing|XIMStatusNothing,
		                   XNClientWindow, w,
		                   NULL);
	}
	top = new Top;
	top->window = w;
	long im_event_mask = 0;
	if(cw.xic)
		XGetICValues(cw.xic, XNFilterEvents, &im_event_mask, NULL);
	XSelectInput(Xdisplay, w, ExposureMask|StructureNotifyMask|KeyPressMask|
	             FocusChangeMask|KeyPressMask|KeyReleaseMask|PointerMotionMask|
	             ButtonPressMask|ButtonReleaseMask|PropertyChangeMask|
	             VisibilityChangeMask|im_event_mask);
	int version = 5;
	XChangeProperty(Xdisplay, w, XAtom("XdndAware"), XA_ATOM, 32,
					0, (byte *)&version, 1);
	CancelMode();
	if(r.Contains(GetMousePos()))
		DispatchMouse(MOUSEMOVE, GetMousePos() - r.TopLeft());

	if(redirect) {
		int windowType = XInternAtom(Xdisplay, "_NET_WM_WINDOW_TYPE_POPUP_MENU", false);
		XChangeProperty(Xdisplay, w, XInternAtom(Xdisplay, "_NET_WM_WINDOW_TYPE", false), XA_ATOM, 32, 
		                PropModeReplace, (byte *)&windowType, 1);
	}
	
	RefreshLayoutDeep();
	
	SyncIMPosition();
}

void Ctrl::WndDestroy()
{
	GuiLock __;
	LLOG("WndDestroy " << Name());
	if(!top || !isopen) return;
	AddGlobalRepaint();
	bool revertfocus = HasWndFocus();
	for(int i = 0; i < Xwindow().GetCount(); i++) {
		LOGBEGIN();
		XWindow& w = Xwindow()[i];
		if(Xwindow().GetKey(i) != None && w.owner == this && w.ctrl->IsOpen())
			w.ctrl->WndDestroy();
		LOGEND();
	}
	Ptr<Ctrl> owner;
	int i = Xwindow().Find(top->window);
	if(i >= 0) {
		XWindow& w = Xwindow()[i];
		owner = w.owner;
		w.invalid.Clear();
		if(w.xic)
			XDestroyIC(w.xic);
	}
	isopen = false;
	if(focusWindow == top->window)
		focusWindow = None;
	if(grabWindow == top->window)
		grabWindow = None;
	XDestroyWindow(Xdisplay, top->window);
	if(i >= 0) {
		Xwindow().SetKey(i, None);
		top->window = None;
		Xwindow()[i].ctrl = NULL;
	}

	if(revertfocus && owner)
		owner->TakeFocus();

	if(focusWindow) {
		int q = Xwindow().Find(focusWindow);
		if(q >= 0) {
			XIC xic = Xwindow()[q].xic;
			if(xic) {
				XSetICFocus(xic);
				SyncIMPosition();
			}
		}
	}

	delete top;
	top = NULL;
	FocusSync();
}

Vector<Ctrl *> Ctrl::GetTopCtrls()
{
	GuiLock __;
	Vector<Ctrl *> v;
	const ArrayMap<Window, Ctrl::XWindow>& w = Xwindow();
	for(int i = 0; i < w.GetCount(); i++)
		if(w.GetKey(i) && w[i].ctrl && !w[i].ctrl->parent)
			v.Add(w[i].ctrl);
	return v;
}

void Ctrl::StartPopupGrab()
{
	GuiLock __;
	if(PopupGrab == 0) {
		if(!top) return;
		if(XGrabPointer(
		   Xdisplay, top->window, true,
		   ButtonPressMask|ButtonReleaseMask|PointerMotionMask|EnterWindowMask|LeaveWindowMask,
		   GrabModeAsync, GrabModeAsync, None, None, CurrentTime) == GrabSuccess) {
				PopupGrab++;
				popupWnd = GetTopWindow();
#ifdef _DEBUG
				__X11_Grabbing = true;
#endif
			}
	}
}

void Ctrl::EndPopupGrab()
{
	GuiLock __;
	if(PopupGrab == 0) return;
	if(--PopupGrab == 0) {
		XUngrabPointer(Xdisplay, CurrentTime);
		XFlush(Xdisplay);
#ifdef _DEBUG
		__X11_Grabbing = false;
#endif
	}
}

void Ctrl::PopUp(Ctrl *owner, bool savebits, bool activate, bool, bool)
{
	GuiLock __;
	LLOG("POPUP: " << UPP::Name(this));
	Ctrl *q = owner ? owner->GetTopCtrl() : GetActiveCtrl();
	ignoretakefocus = true;
	Create(q, true, savebits);
	if(activate) {
		q->StartPopupGrab();
		popupgrab = true;
	}
	if(top) popup = true;
	WndShow(visible);
	if(activate && IsEnabled())
		SetFocus();
	if(top) top->owner = owner;
	StateH(OPEN);
}

Ctrl *Ctrl::GetActiveCtrl()
{
	GuiLock __;
	return focusCtrl ? focusCtrl->GetTopCtrl() : NULL;
}

bool  Ctrl::IsAlphaSupported()
{
	return IsCompositedGui();
}

void  Ctrl::SetAlpha(byte alpha)
{
	GuiLock __;
	Window hwnd = GetWindow();
	if (!IsAlphaSupported() || parent || !top || !hwnd)
		return;
	unsigned int opacity = (unsigned int) 16843009 * alpha;
	Atom aw_opacity = XInternAtom(Xdisplay, "_NET_WM_WINDOW_OPACITY", XFalse);
	XChangeProperty(Xdisplay, hwnd, aw_opacity, XA_CARDINAL, 32, PropModeReplace,
					(unsigned char *) &opacity, 1);
}

bool Ctrl::IsCompositedGui()
{
	GuiLock __;
	static bool b = XGetSelectionOwner(Xdisplay, XAtom(String().Cat() << "_NET_WM_CM_S" << Xscreenno)) != None;
	return b;
}

Ctrl *Ctrl::GetOwner()
{
	GuiLock __;
	if(!IsOpen()) return NULL;
	int q = Xwindow().Find(GetWindow());
	return q >= 0 ? Xwindow()[q].owner : NULL;
}

void Ctrl::WndShow(bool b)
{
	GuiLock __;
	LLOG("WndShow " << b);
	if(top) {
		XWindowAttributes xwa;
		XGetWindowAttributes(Xdisplay, top->window, &xwa);
		bool v = xwa.map_state == IsViewable;
		if(b == v) return;
		if(b)
			XMapWindow(Xdisplay, top->window);
		else
			XUnmapWindow(Xdisplay, top->window);
		visible = b;
		StateH(SHOW);
	}
}

void Ctrl::WndUpdate()
{
	GuiLock __;
	LTIMING("WndUpdate");
	LLOG("WNDUPDATE");
	if(!top) return;
	SyncExpose();
	if(!top) return;
	XWindow& xw = Xwindow().Get(top->window);
	if(xw.exposed && xw.invalid.GetCount()) {
		SyncScroll();
		DoPaint(xw.invalid);
		xw.invalid.Clear();
		LTIMING("WndUpdate XSync");
		XSync(Xdisplay, false);
	}
}

void Ctrl::WndUpdate(const Rect& r)
{
	GuiLock __;
	LTIMING("WndUpdate Rect");
	LLOG("WNDUPDATE " << r);
	if(!top) return;
	SyncExpose();
	XWindow& xw = Xwindow().Get(top->window);
	bool dummy;
	SyncScroll();
	DoPaint(Intersect(xw.invalid, r, dummy));
	LTIMING("WndUpdate XSync");
	XSync(Xdisplay, false);
	xw.invalid = Subtract(xw.invalid, r, dummy);
}

void Ctrl::WndSetPos(const Rect& r)
{
	GuiLock __;
	if(!top) return;
	LLOG("WndSetPos0 " << Name() << r);
	AddGlobalRepaint();
	XMoveResizeWindow(Xdisplay, top->window, r.left, r.top, r.Width(), r.Height());
	rect = r;
	SetWndRect(r);
}

bool Ctrl::IsWndOpen() const
{
	return top;
}

bool Ctrl::SetWndCapture()
{
	GuiLock __;
	if(!IsEnabled() || !top || !IsVisible()) return false;
	if(top->window == grabWindow) return true;
	int status = XGrabPointer(
		Xdisplay, top->window, false,
		ButtonPressMask|ButtonReleaseMask|PointerMotionMask|EnterWindowMask|LeaveWindowMask,
		GrabModeAsync, GrabModeAsync, None, None, CurrentTime
	);
	if(status) return false;
#ifdef _DEBUG
		__X11_Grabbing = true;
#endif
	LLOG("Capture set ok");
	grabWindow = top->window;
	return true;
}

bool Ctrl::HasWndCapture() const
{
	GuiLock __;
	return top && top->window == grabWindow;
}

void Ctrl::ReleaseGrab()
{
	GuiLock __;
	if(grabWindow) {
		LLOG("RELEASE GRAB");
		XUngrabPointer(Xdisplay, CurrentTime);
		XFlush(Xdisplay);
		grabWindow = None;
#ifdef _DEBUG
		__X11_Grabbing = false;
#endif
	}
}

bool Ctrl::ReleaseWndCapture()
{
	GuiLock __;
	LLOG("Releasing capture");
	if(top && top->window == grabWindow) {
		LLOG("Ungrab3");
		ReleaseGrab();
		return true;
	}
	return false;
}

void Ctrl::SetMouseCursor(const Image& image)
{
	GuiLock __;
	static Image img;
	static Cursor shc;
	if(img.GetSerialId() != image.GetSerialId()) {
		img = image;
		Cursor hc = (Cursor)CursorX11(IsNull(img) ? Image::Arrow() : img);
		for(int i = 0; i < Xwindow().GetCount(); i++) {
			Window wnd = Xwindow().GetKey(i);
			if(wnd)
				XDefineCursor(Xdisplay, wnd, hc);
		}
		if(shc)
			XFreeCursor(Xdisplay, shc);
		shc = hc;
		XFlush(Xdisplay);
	}
}

void ClearKbdState_();

void Ctrl::TakeFocus()
{
	LLOG("TAKE_FOCUS0 " << Name());
	GuiLock __;
	if(IsChild()) {
		Ctrl *w = GetTopCtrl();
		if(w) w->TakeFocus();
		return;
	}
	XWindow *w = GetXWindow();
	if(!w)
		return;
	if(ignoretakefocus) {
		LLOG("IGNORED TAKE_FOCUS (caused by CreateWindow)");
		if(focusWindow != top->window && focusWindow != None)
			XSetInputFocus(Xdisplay, focusWindow, RevertToParent, CurrentTime);
		return;
	}
	if(w->owner) {
		LLOG("IGNORED TAKE_FOCUS (window is owned)");
		return;
	}
	LLOG("TAKE_FOCUS " << Name());
	if(IsEnabled() && IsVisible() && top->window != GetXServerFocusWindow()) {
		ClearKbdState_();
		SetWndFocus();
	}
	if(this != focusCtrlWnd) {
		if(IsEnabled()) {
			SetLastActive(w, this);
			LLOG("Activate " << Name());
			SetFocusWnd();
		}
		else {
			LLOG("Activate of disabled window" << Name());
			if(w->last_active && w->last_active->IsOpen() && w->last_active->IsEnabled() &&
			   w->last_active != this) {
				LLOG("    activating last active: " << UPP::Name(w->last_active));
				w->last_active->TakeFocus();
			}
		}
	}

	return;
}

void Ctrl::KillFocus(Window window)
{
	GuiLock __;
	if(!window)
		return;
	int q = Xwindow().Find(window);
	if(q < 0)
		return;
	XWindow& w = Xwindow()[q];
	if(w.ctrl) 
		w.ctrl->KillFocusWnd();
}

bool Ctrl::SetWndFocus()
{
	GuiLock __;
	LLOG("SetWndFocus " << Name());
	if(top && top->window != focusWindow && IsEnabled() && IsVisible()) {
		LLOG("Setting focus... ");
		LTIMING("XSetInfputFocus");
		Ptr<Ctrl> _this = this;
		KillFocus(focusWindow);
		if(_this && top) {
			XSetInputFocus(Xdisplay, top->window, RevertToParent, CurrentTime);
			focusWindow = top->window;
			SetFocusWnd();
		}
		return true;
	}
	return false;
}

bool Ctrl::HasWndFocus() const
{
	GuiLock __;
	return top && top->window == focusWindow;
}

Window Ctrl::GetXServerFocusWindow()
{
	GuiLock __;
	Window w;
	int    dummy;
	XGetInputFocus(Xdisplay, &w, &dummy);
	return w;
}

void Ctrl::FocusSync()
{
	GuiLock __;
	Window fw = GetXServerFocusWindow();
	if(fw != focusWindow) {
		LLOG("FocusSync to " << FormatIntHex(fw));
		if(fw) {
			int q = Xwindow().Find(fw);
			if(q >= 0) {
				LLOG("Focus to app window");
				Window fwo = focusWindow;
				focusWindow = fw;
				XWindow& w = Xwindow()[q];
				if(w.ctrl) {
					if(w.ctrl->IsPopUp()) {
						AnimateCaret();
						return;
					}
					KillFocus(fwo);
//					focusWindow = None; // 1010-10-13 cxl (SetWndFocus does not set this)
					w.ctrl->SetFocusWnd();
					AnimateCaret();
					return;
				}
			}
		}
		KillFocus(focusWindow);
		focusWindow = None;
		AnimateCaret();
	}
}

void  Ctrl::SyncIMPosition()
{
/*
	if(!focusWindow)
		return;
	int q = Xwindow().Find(focusWindow);
	if(q < 0)
		return;
	XWindow& xw = Xwindow()[q];
	XIC xic = xw.xic;
	if(xw.xic && xw.ctrl) {
		XVaNestedList   preedit_attr;
		XPoint          spot;
		spot.x = xw.ctrl->caretx + xw.ctrl->caretcx;
		spot.y = xw.ctrl->carety + xw.ctrl->caretcy;
		preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
		XSetICValues(xw.xic, XNPreeditAttributes, preedit_attr, NULL);
		XFree(preedit_attr);
	}
*/
}

void  Ctrl::AnimateCaret()
{
	GuiLock __;
	int v = !(((GetTickCount() - WndCaretTime) / 500) & 1);
	if(v != WndCaretVisible) {
		RefreshCaret();
		WndCaretVisible = v;
	}
}

void Ctrl::Invalidate(XWindow& xw, const Rect& _r)
{
	GuiLock __;
	LTIMING("Invalidate");
	LLOG("Invalidate " << UPP::Name(xw.ctrl) << " " << _r);
	AddRefreshRect(xw.invalid, _r);
}

void Ctrl::AddGlobalRepaint()
{
	GuiLock __;
	Rect rect = GetRect();
	rect.Inflate(32, 32);//TODO !!! Not correct !!! should read frame extent instead... - or shift GraphicsExposes /  NoExposes...
	ArrayMap<Window, Ctrl::XWindow>& w = Xwindow();
	for(int i = 0; i < w.GetCount(); i++)
		if(w.GetKey(i) && w[i].ctrl) {
			const Rect& r = w[i].ctrl->rect;
			if(rect.Intersects(r))
				Invalidate(w[i], Rect(rect.Offseted(-r.TopLeft())));
		}
}

void Ctrl::WndInvalidateRect(const Rect& r)
{
	GuiLock __;
	if(!top) return;
	LLOG("WndInvalidateRect0 " << r);
	Invalidate(Xwindow().Get(top->window), r);
}

void Ctrl::SetWndForeground()
{
	GuiLock __;
	LLOG("SetWndForeground " << Name());
	if(!top || !IsVisible()) return;
	if(!IsEnabled()) {
		LLOG("Not enabled");
		XWindow *w = GetXWindow();
		if(w && w->last_active && w->last_active != this &&
		   w->last_active->IsOpen() && w->last_active->IsEnabled())
			w->last_active->SetWndForeground();
	}
	else {
		Ptr<Ctrl> _this = this;
		SetWndFocus();
		if(_this && top)
			XRaiseWindow(Xdisplay, top->window);
	}
}

bool Ctrl::IsWndForeground() const
{
	GuiLock __;
	const Ctrl *q = GetTopWindow();
	return ~focusCtrlWnd == (q ? q : GetTopCtrl());
}

void Ctrl::WndEnable(bool b)
{
	GuiLock __;
	LLOG("WndEnable");
	if(!top) return;
	if(!b) {
		ReleaseCapture();
		if(HasWndFocus())
			XSetInputFocus(Xdisplay, None, RevertToPointerRoot, CurrentTime);
	}
}

// 01/12/2007 - mdelfede
// added support for windowed controls
Ctrl::XWindow *Ctrl::AddXWindow(Window &w)
{
	GuiLock __;
	int i = Xwindow().Find(None);
	if(i >= 0)
		Xwindow().SetKey(i, w);
	XWindow& cw = i >= 0 ? Xwindow()[i] : Xwindow().Add(w);
	cw.ctrl    = this;
	cw.exposed = true;
	cw.owner   = GetParent();
	cw.xic     = NULL;
	return &cw;
}

void Ctrl::RemoveXWindow(Window &w)
{
	GuiLock __;
	int i = Xwindow().Find(w);
	if(i >= 0) {
		Xwindow().SetKey(i, None);
		Xwindow()[i].ctrl = NULL;
	}

}
Ctrl::XWindow *Ctrl::XWindowFromWindow(Window &w)
{
	GuiLock __;
	int i = Xwindow().Find(w);
	if(i >= 0)
		return &Xwindow()[i];
	else
		return NULL;
}

// Synchronizes the native windows inside ctrls
void Ctrl::SyncNativeWindows(void)
{
	GuiLock __;
	ArrayMap<Window, Ctrl::XWindow>& xwindows = Xwindow();
	for(int i = 0; i < xwindows.GetCount(); i++)
	{
		XWindow &xw = xwindows[i];
		Window w = xwindows.GetKey(i);
		if(xw.ctrl && xw.ctrl->parent && w)
		{
			Window dummy;
			int x, y;
			unsigned int width, height, border, depth;
			XGetGeometry(Xdisplay, w, &dummy, &x, &y, &width, &height, &border, &depth);
			Rect r = xw.ctrl->GetRectInParentWindow();
			if( (x != r.left || y != r.top) && ((int)width == r.Width() && (int)height == r.Height()))
				XMoveWindow(Xdisplay, w, r.left, r.top);
			else if( (x == r.left && y == r.top) && ((int)width != r.Width() || (int)height != r.Height()))
				XResizeWindow(Xdisplay, w, r.Width(), r.Height());
			else if( x != r.left || y != r.top || (int)width != r.Width() || (int)height != r.Height())
				XMoveResizeWindow(Xdisplay, w, r.left, r.top, r.Width(), r.Height());
		}
	}

} // END Ctrl::SyncNativeWindows()

// 01/12/2007 - END

ViewDraw::ViewDraw(Ctrl *ctrl)
{
	EnterGuiMutex();
	Ctrl *top = ctrl->GetTopCtrl();
	Rect r = ctrl->GetScreenView() - top->GetScreenRect().TopLeft();
	Vector<Rect> clip;
	clip.Add(r);
	dw = top->GetWindow();
	gc = XCreateGC(Xdisplay, dw, 0, 0);
	xftdraw = XftDrawCreate(Xdisplay, (Drawable) dw,
	                        DefaultVisual(Xdisplay, Xscreenno), Xcolormap);
	Init(clip, r.TopLeft());
}

ViewDraw::~ViewDraw()
{
	XFreeGC(Xdisplay, gc);
	LeaveGuiMutex();
}

}

#endif
