// TipManager.cpp
// e.moon 12may99

#include "TipManager.h"
#include "TipManagerImpl.h"
#include "TipWindow.h"

#include <Autolock.h>
#include <Region.h>
#include <float.h>

#include "debug_tools.h"

__USE_CORTEX_NAMESPACE

// -------------------------------------------------------- //
// constants
// -------------------------------------------------------- //

// static instance (created on first call to TipManager::Instance().)
TipManager* TipManager::s_instance = 0;
BLocker TipManager::s_instanceLock("TipManager::s_instanceLock");

// special point value set to highly improbable position
const BPoint TipManager::s_useDefaultOffset(FLT_MIN, FLT_MIN);

// default tip position
const BPoint TipManager::s_defaultOffset(8.0, 8.0);

const bigtime_t		TipManager::s_defIdleTime		= 750000LL;
const bigtime_t		TipManager::s_sleepPeriod 	= 250000LL;

// -------------------------------------------------------- //
// *** dtor
// -------------------------------------------------------- //

TipManager::~TipManager() {}

// -------------------------------------------------------- //
// *** singleton access
// -------------------------------------------------------- //

/*static*/
TipManager* TipManager::Instance() {
	BAutolock _l(s_instanceLock);
	if(!s_instance)
		s_instance = new TipManager();
		
	return s_instance;
}

// kill current instance if any
/*static*/
void TipManager::QuitInstance() {
	BAutolock _l(s_instanceLock);
	if(s_instance) {
		s_instance->Lock();
		s_instance->Quit();
		s_instance = 0;
	}
}

// -------------------------------------------------------- //
// hidden constructor (use Instance() to access
// a single instance)
// -------------------------------------------------------- //

TipManager::TipManager() :
	
	BWindow(
		BRect(-100,-100,-100,-100),
		"TipManager",
		B_NO_BORDER_WINDOW_LOOK,
		B_FLOATING_ALL_WINDOW_FEEL,
		B_ASYNCHRONOUS_CONTROLS | B_AVOID_FOCUS),
	m_view(0) {
	
	m_view = new _TipManagerView(
		new TipWindow(),
		this,
		s_sleepPeriod,
		s_defIdleTime);
	AddChild(m_view);

	// start the window thread
	Show();
}


// -------------------------------------------------------- //
// add and remove tips
// -------------------------------------------------------- //

// add or modify a tip:

status_t TipManager::setTip(
	const BRect&			rect,
	const char*				text,
	BView*						view,
	offset_mode_t			offsetMode	/*=LEFT_OFFSET_FROM_RECT*/,
	BPoint						offset			/*=s_useDefaultOffset*/,
	uint32 						flags				/*=NONE*/) {
	
	ASSERT(text);
	ASSERT(m_view);
	
	BAutolock _l(this);
	return m_view->setTip(
		rect, text, view, offsetMode, offset, flags);
}
	
	
status_t TipManager::setTip(
	const char*				text,
	BView*						view,
	offset_mode_t			offsetMode	/*=LEFT_OFFSET_FROM_RECT*/,
	BPoint						offset			/*=s_useDefaultOffset*/,
	uint32 						flags				/*=NONE*/) {

	return setTip(
		BRect(), text, view, offsetMode, offset, flags);
}

// Remove all tips matching the given rectangle and/or child
// view.  Returns the number of tips removed.

status_t TipManager::removeTip(
	const BRect&		rect,
	BView*					view) {

	ASSERT(view);
	ASSERT(m_view);
	
	BAutolock _l(this);
	return m_view->removeTip(rect, view);
}

// If more than one tip is mapped to pChild, all are removed:
	
status_t TipManager::removeAll(
	BView*					view) {

	return removeTip(BRect(), view);
}

status_t TipManager::removeAll(
	BWindow*				window) {

//	PRINT((
//		"### TipManager::removeAll(): %p, %p\n", this, m_view->Looper()));
	
	ASSERT(window);
	ASSERT(m_view);
	ASSERT(m_view->Looper() == this); // +++++
	
	BAutolock _l(this);
	return m_view->removeAll(window);
}

// -------------------------------------------------------- //
// *** manual tip arming
// -------------------------------------------------------- //

// [e.moon 19oct99]
// Call when the mouse has entered a particular region of
// the screen for which you want a tip to be displayed.
// The tip will be displayed if the mouse stops moving
// for idleTime microseconds within the rectangle screenRect.

status_t TipManager::showTip(
	const char*						text,
	BRect									screenRect,
	offset_mode_t					offsetMode	/*=LEFT_OFFSET_FROM_RECT*/,
	BPoint								offset			/*=s_useDefaultOffset*/,
	uint32 								flags				/*=NONE*/) {
	
	ASSERT(text);
	ASSERT(m_view);
	
	BAutolock _l(this);
	return m_view->armTip(
		screenRect, text, offsetMode,	offset, flags);
}

// -------------------------------------------------------- //
// *** BWindow
// -------------------------------------------------------- //

// -------------------------------------------------------- //
// *** BLooper
// -------------------------------------------------------- //

bool TipManager::QuitRequested() {
	// ignored, since I receive key events bound for other apps
	return false;
}

// -------------------------------------------------------- //
// *** BHandler
// -------------------------------------------------------- //

void TipManager::MessageReceived(
	BMessage*							message) {

	switch(message->what) {
		default:
			_inherited::MessageReceived(message);
	}
}

//// -------------------------------------------------------- //
//// BasicThread impl.
//// -------------------------------------------------------- //
//
//// +++++
//// 12aug99: a locking bug seems to cause occasional
////          crashes on shutdown after the looper's been deleted.
//// +++++
//// 23sep99: probably fixed; the TipManager needs to be manually
////          killed before the window is deleted.
//
//void TipManager::run() {
//
//	BPoint point, lastPoint, screenPoint;
//	bigtime_t curTime, lastTime;
//	uint32 buttons;
//
//	bool bTipVisible = false;
//	BRect tipScreenRect;
//
//	lastTime = 0;
//	curTime = 0;
//	
//	// [e.moon 27sep99]
//	// store whether the tip has fired at the current point
//	bool fired = false;
//	
//	ASSERT(m_tree);
//	BView* pOwningView = m_tree->target();
//		
//	while(!stopping()) {
//		snooze(s_sleepPeriod);
//		if(stopping())
//			break;
//
//		// wait for the view to show up		
//		if(!pOwningView->Parent() || !pOwningView->Window())
//			continue;
//
//		// get current mouse position
//		pOwningView->LockLooper();
//
//		pOwningView->GetMouse(&point, &buttons, false);
//		screenPoint = pOwningView->ConvertToScreen(point);
//
//		pOwningView->UnlockLooper();
//
//		// has it been sitting in one place long enough?
//		bool bMoved = (point != lastPoint);
//
//		if(bMoved) {
//			lastTime = curTime;
//			fired = false;
//		}
//		else if(fired) {
//			// [27sep99 e.moon] the tip has already fired, and
//			// the mouse hasn't moved; bail out now
//			continue;
//		}
//	
//		curTime = system_time();			
//		bool bIdle = !bMoved && lastTime && (curTime - lastTime) > m_idleTime;
//		lastPoint = point;
//
//		if(bTipVisible) {
//			// hide tip once mouse moves outside its rectangle
//			if(!tipScreenRect.Contains(screenPoint)) {
//				m_tipWindow->Lock();
//				if(!m_tipWindow->IsHidden()) // tip may hide itself [7sep99]
//					m_tipWindow->Hide();
//				bTipVisible = false;
//				m_tipWindow->Unlock();
//			}
//		} else if(bIdle) {
//
//			// mouse has idled at a given point long enough;
//			// look for a tip at that position and display one if found:
//			
//			fired = true;
//			
//			pOwningView->LockLooper();
//			
//			// make sure this part of the view is actually visible
//			if(!pOwningView->Window()->Frame().Contains(screenPoint)) {
//				pOwningView->UnlockLooper();
//				continue;
//			}
//
//			// look for a tip under the mouse
//			m_tipWindow->Lock();
//			pair<BView*, const tip_entry*> found =
//				m_tree->match(point, screenPoint);
//			
//			if(!found.second) {
//				// none found; move on
//				pOwningView->UnlockLooper();
//				m_tipWindow->Unlock();
//				continue;
//			}
//			
//			BView* pTipTarget = found.first;
//			const tip_entry& entry = *found.second;
//
//			// test the screen point against the view's clipping region;
//			// if no match, the given point is likely covered by another
//			// window (so stop recursing)
//	
//			BRegion clipRegion;
//			pTipTarget->GetClippingRegion(&clipRegion);
//			if(!clipRegion.Contains(
//				pTipTarget->ConvertFromScreen(screenPoint))) {
//				// move on
//				pOwningView->UnlockLooper();
//				m_tipWindow->Unlock();
//				continue;
//			}
//
//			// found one; set up the tip window:
//			BRect entryFrame = pTipTarget->ConvertToScreen(entry.rect);
//						
//			// set text (this has the side effect of resizing the
//			// window)
//					
//			ASSERT(m_tipWindow);
//			m_tipWindow->setText(entry.text.String());
//
//			// figure out where to display it:
//			
//			BPoint offset = (entry.offset == s_useDefaultOffset) ?
//				s_defaultOffset :
//				entry.offset;
//
//			BPoint p;
//			switch(entry.offsetMode) {
//				case LEFT_OFFSET_FROM_RECT:
//					p = entryFrame.RightTop() + offset;
//					break;
//				case LEFT_OFFSET_FROM_POINTER:
//					p = screenPoint + offset;
//					break;
//				case RIGHT_OFFSET_FROM_RECT:
//					p = entryFrame.LeftTop();
//					p.x -= offset.x;
//					p.y += offset.y;
//					p.x -= m_tipWindow->Frame().Width();
//					break;
//				case RIGHT_OFFSET_FROM_POINTER:
//					p = screenPoint;
//					p.x -= offset.x;
//					p.y += offset.y;
//					p.x -= m_tipWindow->Frame().Width();
//					break;
//				default:
//					ASSERT(!"bad offset mode");
//			}
//										
//			// do it:
//					
//			m_tipWindow->MoveTo(p);
//			m_tipWindow->Show();
//					
//			bTipVisible = true;
//			tipScreenRect = entryFrame;
//			
//			m_tipWindow->Unlock();
//			pOwningView->UnlockLooper();
//
//		} // if(bIdle ...
//	} // while(!stopping ...
//}

// END -- TipManager.cpp --

