// $Id: Clipboard.cpp 8019 2007-07-14 22:30:11Z trick $

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Deadly Rooms of Death.
 *
 * The Initial Developer of the Original Code is
 * Caravel Software.
 * Portions created by the Initial Developer are Copyright (C) 1995, 1996,
 * 1997, 2000, 2001, 2002 Caravel Software. All Rights Reserved.
 *
 * Contributor(s):
 * Matt Schikore (Schik), Gerry JJ
 *
 * ***** END LICENSE BLOCK ***** */

#ifdef WIN32
#  include <windows.h> //Should be first include.
#  pragma warning(disable:4786)
#endif

#include "Assert.h"
#include "Clipboard.h"
#include "Dyn.h"

#if defined __APPLE__
#include <vector>
#include <Carbon/Carbon.h>
#endif

//******************************************************************************
#if defined __linux__ || defined __FreeBSD__

Display *CClipboard::display = NULL;
Window   CClipboard::window;

void (*CClipboard::LockDisplay)(void);
void (*CClipboard::UnlockDisplay)(void);

#define TEXT ((int)(('T'<<24)|('E'<<16)|('X'<<8)|('T')))

int CClipboard::ClipboardFilter (const SDL_Event *event)
{
   if (event->type == SDL_SYSWMEVENT &&
         event->syswm.msg->event.xevent.type == SelectionRequest)
   {
      XSelectionRequestEvent *req = &event->syswm.msg->event.xevent.xselectionrequest;
      XEvent sevent;
      int seln_format;
      unsigned long nbytes, overflow;
      unsigned char *seln_data;
      sevent.xselection.type = SelectionNotify;
      sevent.xselection.display = req->display;
      sevent.xselection.selection = req->selection;
      sevent.xselection.target = None;
      sevent.xselection.property = None;
      sevent.xselection.requestor = req->requestor;
      sevent.xselection.time = req->time;

      if (Dyn::XGetWindowProperty(display, DefaultRootWindow(display),
            XA_CUT_BUFFER0, 0, INT_MAX/4, False, req->target,
            &sevent.xselection.target, &seln_format,
            &nbytes, &overflow, &seln_data) == Success)
      {
         if (sevent.xselection.target == req->target) {
            if (sevent.xselection.target == XA_STRING) {
               if (seln_data[nbytes - 1] == '\0') {
                  --nbytes;
               }
               Dyn::XChangeProperty(display, req->requestor, req->property,
                  sevent.xselection.target, seln_format, PropModeReplace,
                  seln_data, nbytes);
               sevent.xselection.property = req->property;
            }
            Dyn::XFree(seln_data);
         }
         Dyn::XSendEvent(display, req->requestor, False, 0, &sevent);
         Dyn::XSync(display, False);
      }
   }

   //Post event
   return 1;
}

bool CClipboard::LostScrap ()
{
   LockDisplay();
   bool bLost = (Dyn::XGetSelectionOwner(display, XA_PRIMARY) != window);
   UnlockDisplay();
   return bLost;
}

bool CClipboard::Init ()
{
   SDL_SysWMinfo info;
   SDL_VERSION(&info.version);
   if (SDL_GetWMInfo(&info) && info.subsystem == SDL_SYSWM_X11) {
      display = info.info.x11.display;
      window = info.info.x11.window;
      LockDisplay = info.info.x11.lock_func;
      UnlockDisplay = info.info.x11.unlock_func;

      //Enable SDL clipboard passing
      SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
      SDL_SetEventFilter(ClipboardFilter);
      return true;
   } else {
      display = NULL;   //No need to init the rest in this case
      return false;
   }
}

#endif //#if defined __linux__ || defined __FreeBSD__

//******************************************************************************
bool CClipboard::SetString(
//Copies the given string into the system clipboard
//Returns: true on success, false otherwise.
//
//Params:
	const string& sClip)  //(in)
{
#ifdef WIN32
	if (!OpenClipboard(NULL))
		return false;
	EmptyClipboard();

	HGLOBAL global = GlobalAlloc(GMEM_ZEROINIT, (sClip.size()+1)*sizeof(char));

	if (global == NULL) {
		CloseClipboard();
		return false;
	}

	LPSTR data = (LPSTR)GlobalLock(global);

	strcpy(data, sClip.c_str());

	GlobalUnlock(global);
	SetClipboardData(CF_TEXT, global);
	CloseClipboard();

	return true;

#elif defined(__linux__) || defined(__FreeBSD__)

   if (!display && !Init()) return false;

   LockDisplay();
   Dyn::XChangeProperty(display, DefaultRootWindow(display), XA_CUT_BUFFER0,
      XA_STRING, 8, PropModeReplace, (unsigned char *)sClip.c_str(), sClip.length());
   if (LostScrap())
      Dyn::XSetSelectionOwner(display, XA_PRIMARY, window, CurrentTime);
   UnlockDisplay();

   return true;

#elif defined(__APPLE__)
	ScrapRef scrap;
	OSStatus status = GetScrapByName(
		kScrapClipboardScrap,
		kScrapClearNamedScrap,
		&scrap
	);
	if (status < 0) return false;
	status = PutScrapFlavor(
		scrap,
		kScrapFlavorTypeText,
		0,
		sClip.length(),
		sClip.data()
	);
	if (status < 0) return false;
	return true;

#else
#warning How do you set system clipboard data on this system?
   return false;
#endif
}

//******************************************************************************
bool CClipboard::GetString(
//Copies the system clipboard string into sClip
//Returns: true on success, false otherwise.
//
//Params:
	string& sClip)  //(out)
{
#ifdef WIN32
	if (!OpenClipboard(NULL))
		return false;
	HGLOBAL global = GetClipboardData(CF_TEXT);
	if (global == NULL) {
		CloseClipboard();
		return false;
	}
	LPSTR data = (LPSTR)GlobalLock(global);
	sClip = data;
	GlobalUnlock(global);
	CloseClipboard();

	return true;

#elif defined(__linux__) || defined(__FreeBSD__)

   if (!display && !Init()) return false;

   LockDisplay();
   Window owner = Dyn::XGetSelectionOwner(display, XA_PRIMARY);
   UnlockDisplay();
   
   Atom selection;

   if (owner == None || owner == window) {
      owner = DefaultRootWindow(display);
      selection = XA_CUT_BUFFER0;
   } else {
      SDL_Event event;

      owner = window;
      LockDisplay();
      selection = Dyn::XInternAtom(display, "DeadlySelectionOfDeath", False);
      Dyn::XConvertSelection(display, XA_PRIMARY, XA_STRING, selection, owner, CurrentTime);
      UnlockDisplay();

      while (true) {
         SDL_WaitEvent(&event);
         if (event.type == SDL_SYSWMEVENT) {
            XEvent xevent = event.syswm.msg->event.xevent;
            if ((xevent.type == SelectionNotify) &&
                  (xevent.xselection.requestor == owner))
               break;
         }
      }
   }

   unsigned char *src;
   Atom seln_type;
   int seln_format;
   unsigned long nbytes, overflow;

   LockDisplay();
   if (Dyn::XGetWindowProperty(display, owner, selection, 0, INT_MAX/4, False,
         XA_STRING, &seln_type, &seln_format, &nbytes, &overflow,
         &src) == Success)
   {
      if (seln_type == XA_STRING)
         sClip = (char*)src;
      Dyn::XFree(src);
   }
   UnlockDisplay();

   return true;

#elif defined __APPLE__
	ScrapRef scrap;
	OSStatus status = GetScrapByName(
		kScrapClipboardScrap,
		kScrapGetNamedScrap,
		&scrap
	);
	if (status < 0) return false;
	Size size;
	status = GetScrapFlavorSize(
		scrap,
		kScrapFlavorTypeText,
		&size
	);
	if (status < 0) return false;
	std::vector<char> buffer(size);
	status = GetScrapFlavorData(
		scrap,
		kScrapFlavorTypeText,
		&size,
		&buffer.front()
	);
	if (status < 0) return false;
	sClip.resize(buffer.size());
	std::copy(buffer.begin(), buffer.end(), sClip.begin());

	return true;

#else
#warning How do you get system clipboard data on this system?
   return false;
#endif
}

//******************************************************************************
bool CClipboard::GetString(
//Copies the system clipboard string into sClip
//Returns: true on success, false otherwise.
//
//Params:
	WSTRING& sClip) //(out)
{
#ifdef WIN32
	if (!OpenClipboard(NULL))
		return false;
	HGLOBAL global = GetClipboardData(CF_UNICODETEXT);
	if (global == NULL) {
		CloseClipboard();
		return false;
	}
	LPWSTR data = (LPWSTR)GlobalLock(global);
	sClip = data;
	GlobalUnlock(global);
	CloseClipboard();

	return true;
#else
	string sStr;
	const bool bStatus = GetString(sStr);
	if (bStatus)
		AsciiToUnicode(sStr.c_str(), sClip);
	return bStatus;
#endif
}

//******************************************************************************
bool CClipboard::SetString(
//Copies the given string into the system clipboard
//Returns: true on success, false otherwise.
//
//Params:
	const WSTRING& sClip)  //(in)
{
#ifdef WIN32
	if (!OpenClipboard(NULL))
		return false;
	EmptyClipboard();

	HGLOBAL global = GlobalAlloc(GMEM_ZEROINIT, (sClip.size()+1)*sizeof(WCHAR));

	if (global == NULL) {
		CloseClipboard();
		return false;
	}

	LPWSTR data = (LPWSTR)GlobalLock(global);

	WCScpy(data, sClip.c_str());

	GlobalUnlock(global);
	SetClipboardData(CF_UNICODETEXT, global);
	CloseClipboard();

	return true;
#else
	char* pszBuffer = new char[sClip.size()+1];
	UnicodeToAscii(sClip, pszBuffer);
	const bool bRes = SetString(pszBuffer);
	delete[] pszBuffer;
	return bRes;
#endif
}
