/** 
 * @file lluictrl.cpp
 * @author James Cook, Richard Nelson, Tom Yedwab
 * @brief Abstract base class for UI controls
 *
 * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

//#include "llviewerprecompiledheaders.h"
#include "linden_common.h"

#include "lluictrl.h"

#include "llgl.h"
#include "llui.h"
#include "lluiconstants.h"
#include "llfocusmgr.h"
#include "v3color.h"

#include "llstring.h"
#include "llfontgl.h"
#include "llkeyboard.h"

const U32 MAX_STRING_LENGTH = 10;

LLUICtrl::LLUICtrl() :
	mCommitCallback(NULL),
	mFocusReceivedCallback(NULL),
	mFocusChangedCallback(NULL),
	mValidateCallback(NULL),
	mCallbackUserData(NULL),
	mTentative(FALSE),
	mTabStop(TRUE),
	mIsChrome(FALSE)
{
}

LLUICtrl::LLUICtrl(const LLString& name, const LLRect& rect, BOOL mouse_opaque,
	void (*on_commit_callback)(LLUICtrl*, void*),
	void* callback_userdata,
	U32 reshape)
:	// can't make this automatically follow top and left, breaks lots
	// of buttons in the UI. JC 7/20/2002
	LLView( name, rect, mouse_opaque, reshape ),
	mCommitCallback( on_commit_callback) ,
	mFocusReceivedCallback( NULL ),
	mFocusChangedCallback( NULL ),
	mValidateCallback( NULL ),
	mCallbackUserData( callback_userdata ),
	mTentative( FALSE ),
	mTabStop( TRUE ),
	mIsChrome(FALSE)
{
}

LLUICtrl::~LLUICtrl()
{
	gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit()
}

void LLUICtrl::onCommit()
{
	if( mCommitCallback )
	{
		mCommitCallback( this, mCallbackUserData );
	}
}

// virtual
BOOL LLUICtrl::setTextArg( const LLString& key, const LLString& text ) 
{ 
	return FALSE; 
}

// virtual
BOOL LLUICtrl::setLabelArg( const LLString& key, const LLString& text ) 
{ 
	return FALSE; 
}

// virtual
LLCtrlSelectionInterface* LLUICtrl::getSelectionInterface()	
{ 
	return NULL; 
}

// virtual
LLCtrlListInterface* LLUICtrl::getListInterface()				
{ 
	return NULL; 
}

// virtual
LLCtrlScrollInterface* LLUICtrl::getScrollInterface()			
{ 
	return NULL; 
}

// virtual
void LLUICtrl::setTabStop( BOOL b )	
{ 
	mTabStop = b;
}

// virtual
BOOL LLUICtrl::hasTabStop() const		
{ 
	return mTabStop;
}

// virtual
BOOL LLUICtrl::acceptsTextInput() const
{ 
	return FALSE; 
}

// virtual
void LLUICtrl::onTabInto()				
{
}

// virtual
void LLUICtrl::clear()					
{
}

// virtual
void LLUICtrl::setIsChrome(BOOL is_chrome)
{
	mIsChrome = is_chrome; 
}

// virtual
BOOL LLUICtrl::getIsChrome() const
{ 
	return mIsChrome; 
}

void LLUICtrl::onFocusReceived()
{
	if( mFocusReceivedCallback )
	{
		mFocusReceivedCallback( this, mCallbackUserData );
	}
	if( mFocusChangedCallback )
	{
		mFocusChangedCallback( this, mCallbackUserData );
	}
}

void LLUICtrl::onFocusLost()
{
	if( mFocusChangedCallback )
	{
		mFocusChangedCallback( this, mCallbackUserData );
	}
}

BOOL LLUICtrl::hasFocus() const
{
	return (gFocusMgr.childHasKeyboardFocus(this));
}

void LLUICtrl::setFocus(BOOL b)
{
	// focus NEVER goes to ui ctrls that are disabled!
	if (!mEnabled)
	{
		return;
	}
	if( b )
	{
		if (!hasFocus())
		{
			gFocusMgr.setKeyboardFocus( this, &LLUICtrl::onFocusLostCallback );
			onFocusReceived();
		}
	}
	else
	{
		if( gFocusMgr.childHasKeyboardFocus(this))
		{
			gFocusMgr.setKeyboardFocus( NULL, NULL );
			onFocusLost();
		}
	}
}

// static
void LLUICtrl::onFocusLostCallback( LLUICtrl* old_focus )
{
	old_focus->onFocusLost();
}

// this comparator uses the crazy disambiguating logic of LLCompareByTabOrder,
// but to switch up the order so that children that have the default tab group come first
// and those that are prior to the default tab group come last
class CompareByDefaultTabGroup: public LLCompareByTabOrder
{
public:
	CompareByDefaultTabGroup(LLView::child_tab_order_t order, S32 default_tab_group):
			LLCompareByTabOrder(order),
			mDefaultTabGroup(default_tab_group) {}
protected:
	/*virtual*/ bool compareTabOrders(const LLView::tab_order_t & a, const LLView::tab_order_t & b) const
	{
		S32 ag = a.first; // tab group for a
		S32 bg = b.first; // tab group for b
		// these two ifs have the effect of moving elements prior to the default tab group to the end of the list 
		// (still sorted relative to each other, though)
		if(ag < mDefaultTabGroup && bg >= mDefaultTabGroup) return false;
		if(bg < mDefaultTabGroup && ag >= mDefaultTabGroup) return true;
		return a < b;  // sort correctly if they're both on the same side of the default tab group
	}
	S32 mDefaultTabGroup;
};

// sorter for plugging into the query
class DefaultTabGroupFirstSorter : public LLQuerySorter, public LLSingleton<DefaultTabGroupFirstSorter>
{
public:
	/*virtual*/ void operator() (LLView * parent, viewList_t &children) const
	{
		children.sort(CompareByDefaultTabGroup(parent->getCtrlOrder(), parent->getDefaultTabGroup()));
	}		
};

BOOL LLUICtrl::focusFirstItem(BOOL prefer_text_fields)
{
	// try to select default tab group child
	LLCtrlQuery query = LLView::getTabOrderQuery();
	// sort things such that the default tab group is at the front
	query.setSorter(DefaultTabGroupFirstSorter::getInstance());
	LLView::child_list_t result = query(this);
	if(result.size() > 0)
	{
		LLUICtrl * ctrl = static_cast<LLUICtrl*>(result.front());
		if(!ctrl->hasFocus())
		{
			ctrl->setFocus(TRUE);
			ctrl->onTabInto();  
			gFocusMgr.triggerFocusFlash();
		}
		return TRUE;
	}	
	// fall back on default behavior if we didn't find anything
	return LLView::focusFirstItem(prefer_text_fields);
}

/*
// Don't let the children handle the tool tip.  Handle it here instead.
BOOL LLUICtrl::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen)
{
	BOOL handled = FALSE;
	if (getVisible() && pointInView( x, y ) ) 
	{
		if( !mToolTipMsg.empty() )
		{
			msg = mToolTipMsg;

			// Convert rect local to screen coordinates
			localPointToScreen( 
				0, 0, 
				&(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) );
			localPointToScreen(
				mRect.getWidth(), mRect.getHeight(),
				&(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) );

			handled = TRUE;
		}
	}

	if (!handled)
	{
		return LLView::handleToolTip(x, y, msg, sticky_rect_screen);
	}

	return handled;
}*/

void LLUICtrl::initFromXML(LLXMLNodePtr node, LLView* parent)
{
	BOOL has_tab_stop = hasTabStop();
	node->getAttributeBOOL("tab_stop", has_tab_stop);

	setTabStop(has_tab_stop);

	LLView::initFromXML(node, parent);
}

LLXMLNodePtr LLUICtrl::getXML(bool save_children) const
{
	LLXMLNodePtr node = LLView::getXML(save_children);
	node->createChild("tab_stop", TRUE)->setBoolValue(hasTabStop());

	return node;
}

// *NOTE: If other classes derive from LLPanel, they will need to be
// added to this function.
LLPanel* LLUICtrl::getParentPanel() const
{
	LLView* parent = getParent();
	while (parent 
		   && parent->getWidgetType() != WIDGET_TYPE_PANEL
		   && parent->getWidgetType() != WIDGET_TYPE_FLOATER)
	{
		parent = parent->getParent();
	}
	return reinterpret_cast<LLPanel*>(parent);
}

// virtual
void LLUICtrl::setTentative(BOOL b)									
{ 
	mTentative = b; 
}

// virtual
BOOL LLUICtrl::getTentative() const									
{ 
	return mTentative; 
}

// virtual
void LLUICtrl::setDoubleClickCallback( void (*cb)(void*) )				
{ 
}

// virtual
void LLUICtrl::setColor(const LLColor4& color)							
{ }

// virtual
void LLUICtrl::setMinValue(LLSD min_value)								
{ }

// virtual
void LLUICtrl::setMaxValue(LLSD max_value)								
{ }
