/******************************************************************************
 ClipboardWidget.cc

	This widget shows how to support copy and paste, using the X Selection
	mechanism encapsulated in JXSelectionManager.

	ConvertSelection() and Paste() do the real work.  In this simple case,
	we simply send and receive the buffer maintained by the JString.  In
	general, the safest way to send formatted data is in ASCII format.
	strstreams make it very easy to pack and unpack the data.

	BASE CLASS = JXWidget

	Written by Glenn Bach - 1998.

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

#include "ClipboardWidget.h"
#include <JXWidget.h>
#include <JXWindowPainter.h>
#include <JXTextMenu.h>
#include <JXMenuBar.h>
#include <JXColormap.h>
#include <JXSelectionManager.h>
#include <jXPainterUtil.h>
#include <jXConstants.h>
#include <jXGlobals.h>
#include <jAssert.h>

// This defines the menu title and menu items
static const JCharacter* kEditMenuTitleStr = "Edit";
static const JCharacter* kEditMenuStr =
	"Copy %k Meta-C | Paste %k Meta-V";

enum
{
	kCopyCmd = 1,
	kPasteCmd
};

/******************************************************************************
 Constructor

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

ClipboardWidget::ClipboardWidget
	(
	const JCharacter* 	text,
	JXMenuBar* 			menuBar,
	JXContainer* 		enclosure,
	const HSizingOption hSizing,
	const VSizingOption vSizing,
	const JCoordinate 	x,
	const JCoordinate 	y,
	const JCoordinate 	w,
	const JCoordinate 	h
	)
	:
	JXWidget(enclosure, hSizing, vSizing, x, y, w, h),
	itsText(text)
{
	// Set the background color.
	JXColormap* cmap = GetColormap();
	SetBackColor(cmap->GetWhiteColor());

    // Create the menu and attach it to the menu bar.
    itsEditMenu = menuBar->AppendTextMenu(kEditMenuTitleStr);

    // Set the menu items.
    itsEditMenu->SetMenuItems(kEditMenuStr);

    // The menu items don't need to be disabled
    itsEditMenu->SetUpdateAction(JXMenu::kDisableNone);

    // Listen for messages from the menu.
	ListenTo(itsEditMenu);

	// Register the data types that we support.
	JXSelectionManager* selMgr = GetSelectionManager();

	AddSelectionTarget(kJXClipboardName, XA_STRING);
	AddSelectionTarget(kJXClipboardName, selMgr->GetTextXAtom());
}

/******************************************************************************
 Destructor

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

ClipboardWidget::~ClipboardWidget()
{
	// There is nothing to delete.
	// itsEditMenu is deleted by the menu bar
}

/******************************************************************************
 Receive (virtual protected)

	Listen for menu selections and deactivated dialog windows.

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

void
ClipboardWidget::Receive
	(
	JBroadcaster*	sender,
	const Message&	message
	)
{
	// Check to see if the a menu item was selected.
	if (sender == itsEditMenu && message.Is(JXMenu::kItemSelected))
		{
		// Cast the sender so we can access its functions.
		 const JXMenu::ItemSelected* selection =
			dynamic_cast(const JXMenu::ItemSelected*, &message);
		assert( selection != NULL );

		// Handle the menu selection
		HandleEditMenu(selection->GetIndex());
		}
}

/******************************************************************************
 Draw

	This gets called by the event loop every time the ClipboardWidget
	needs to be redrawn.

	p is the Painter that you use to draw to the screen.  The main reason
	for Painter is to hide the system dependent details of drawing,
	and to provide a uniform interface for drawing to the screen, to an
	offscreen image, and to a printer.

	rect is the area that needs to be redrawn.  In simple cases, you
	can just draw everything.  For complex widgets, you often want to
	optimize and only draw the part inside rect.

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

void
ClipboardWidget::Draw
	(
	JXWindowPainter& p,
	const JRect&     rect
	)
{
	// We need the colormap in order to specify colors.
	JXColormap* cmap = GetColormap();

	// This is where everything happens
	// See JPainter.h for available functions

	// This sets the color of the pen.
	p.SetPenColor(cmap->GetCyanColor());

	// This draws our rectangle.
	p.Rect(10, 10, 150, 50);

	// This draws itsText.
	p.String(20,20,itsText,
				130, JPainter::kHAlignCenter,
				30, JPainter::kVAlignCenter);
}

/******************************************************************************
 DrawBorder

	This gets called by the event loop every time the ClipboardWidget's
	border needs to be redrawn. Since we don't want a border, we leave it
	blank.

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

void
ClipboardWidget::DrawBorder
	(
	JXWindowPainter& p,
	const JRect&     frame
	)
{
}

/******************************************************************************
 SetText

	This sets our text string.

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

void
ClipboardWidget::SetText
	(
	const JCharacter* text
	)
{
	// Set our JString to the specified text.
	itsText = text;
}

/******************************************************************************
 GetText

	This retreives our text string.

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

const JString&
ClipboardWidget::GetText()
	const
{
	return itsText;
}

/******************************************************************************
 HandleEditMenu

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

void
ClipboardWidget::HandleEditMenu
	(
	const JIndex index
	)
{
	if (index == kCopyCmd)
		{
		// We now own the selection, so we need to let X know about it.
		BecomeSelectionOwner(kJXClipboardName);
		}
	else if (index == kPasteCmd)
		{
		// Paste if the clipboard has the type we need.
		Paste();
		}
}

/******************************************************************************
 ConvertSelection

	Convert selection to the specified type and return kTrue,
	or return kFalse if the conversion cannot be accomplished.

	*returnType must be actual data type.  For example, when "TEXT" is
	requested, one often returns XA_STRING.

	*data must be allocated with "new unsigned char[]" and will be deleted
	by the caller.  *dataLength must be set to the length of *data.

	*bitsPerBlock must be set to the number of bits per element of data.
	e.g.	If data is text, *bitsPerBlock=8.
			If data is an int, *bitsPerBlock=sizeof(int)*8

	Since X performs byte swapping when *bitsPerBlock > 8, mixed data is
	packed one byte at a time to insure that it can be correctly decoded.

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

JBoolean
ClipboardWidget::ConvertSelection
	(
	const Atom 		name,
	const Atom 		requestType,
	Atom* 			returnType,
	unsigned char**	data,
	JSize* 			dataLength,
	JSize* 			bitsPerBlock
	)
{
	*bitsPerBlock = 8;

	// Get the selection manager.
	JXSelectionManager* selMgr = GetSelectionManager();

	// We check to make sure that the request is for data from the
	// clipboard, and it is a data type that we support.  Since we're
	// storing text, we can support the standard text forms in addition to
	// our own.

	if (name == kJXClipboardName &&
		(requestType == XA_STRING ||
		 requestType == selMgr->GetTextXAtom()))
		{
		// Set the return type to a standard form.
		*returnType = XA_STRING;

		// Set the length of the data.
		*dataLength = itsText.GetLength();

		// Allocate the return buffer.
		*data = new unsigned char[ *dataLength ];

		// If it fails, we don't have to die.
		if (*data == NULL)
			{
			return kFalse;
			}
		else
			{
			// Copy our text to the return buffer.
			memcpy(*data, itsText, *dataLength);
			return kTrue;
			}
		}

	// If we got here, then we weren't able to deal with the request.
	return kFalse;
}

/******************************************************************************
 Paste

	We try to get either XA_STRING or TEXT.  TEXT is polymorphic, so we
	check the returned type and discard it if it is not XA_STRING.

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

void
ClipboardWidget::Paste()
{
	// Get the window and selection manager for use below.
	JXWindow* window           = GetWindow();
	JXSelectionManager* selMgr = GetSelectionManager();

	// If the clipboard is not empty, retrieve the available types.
	JArray<Atom> typeList;
	if (selMgr->GetAvailableTypes(kJXClipboardName, CurrentTime,
								  window, &typeList))
		{

		// Loop through the available types to see if the clipboard has
		// one that we want.
		const JSize typeCount = typeList.GetElementCount();
		for (JIndex i=1; i<=typeCount; i++)
			{
			const Atom atom = typeList.GetElement(i);

			// Check if the i-th type is one we can use.
			if (atom == XA_STRING || atom == selMgr->GetTextXAtom())
				{
				// Get the data of the appropriate type.
				unsigned char* data = NULL;
				JSize dataLength;
				Atom returnType;
				JXSelectionManager::DeleteMethod dMethod;
				if (selMgr->GetSelectionData(
						kJXClipboardName, CurrentTime, window,
						atom, &returnType, &data, &dataLength,
						&dMethod))
					{
					// We can only handle the simplest format.
					if (returnType == XA_STRING)
						{
						// Copy the data into our text.
						itsText.Set(reinterpret_cast<JCharacter*>(data), dataLength);

						// Our text changed, so we need to refresh.
						Refresh();
						}

					// This is required to delete the allocated data.
					// Forgetting to do this will cause a memory leak!
					selMgr->DeleteSelectionData(&data, dMethod);

					if (returnType == XA_STRING)
						{
						// We succeeded, so we return.
						return;
						}
					}
				else
					{
					(JGetUserNotification())->ReportError(
						"Unable to retrieve text from the clipboard.");
					}
				}
			}

		// If we got this far, the data type that we want wasn't on the
		// clipboard.
		(JGetUserNotification())->ReportError("Unable to paste from clipboard.");
		}
	else
		{
		// There isn't anything on the clipboard.
		(JGetUserNotification())->ReportError("Clipboard is empty.");
		}
}
