1/*
2 * Copyright (c) 1999-2000, Eric Moon.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions, and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions, and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * 3. The name of the author may not be used to endorse or promote products
17 *    derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32// TipManager.cpp
33// e.moon 12may99
34
35#include "TipManager.h"
36#include "TipManagerImpl.h"
37#include "TipWindow.h"
38
39#include <Autolock.h>
40#include <Message.h>
41#include <MessageFilter.h>
42#include <Region.h>
43#include <float.h>
44
45#include "debug_tools.h"
46
47__USE_CORTEX_NAMESPACE
48
49// -------------------------------------------------------- //
50// constants
51// -------------------------------------------------------- //
52
53// static instance (created on first call to TipManager::Instance().)
54TipManager* TipManager::s_instance = 0;
55BLocker TipManager::s_instanceLock("TipManager::s_instanceLock");
56
57// special point value set to highly improbable position
58const BPoint TipManager::s_useDefaultOffset(FLT_MIN, FLT_MIN);
59
60// default tip position
61const BPoint TipManager::s_defaultOffset(8.0, 8.0);
62
63const bigtime_t		TipManager::s_defIdleTime		= 750000LL;
64const bigtime_t		TipManager::s_sleepPeriod 	= 250000LL;
65
66// -------------------------------------------------------- //
67// *** message filter
68// -------------------------------------------------------- //
69
70filter_result ignore_quit_key(
71	BMessage* message,
72	BHandler** target,
73	BMessageFilter* filter)
74{
75	switch(message->what)
76	{
77		// filter command-Q
78		case B_KEY_DOWN:
79		{
80			if((modifiers() & B_COMMAND_KEY))
81			{
82				int8 key;
83				message->FindInt8("byte", &key);
84				if(key == 'q')
85					return B_SKIP_MESSAGE;
86			}
87			break;
88		}
89	}
90	return B_DISPATCH_MESSAGE;
91}
92
93// -------------------------------------------------------- //
94// *** dtor
95// -------------------------------------------------------- //
96
97TipManager::~TipManager() {}
98
99// -------------------------------------------------------- //
100// *** singleton access
101// -------------------------------------------------------- //
102
103/*static*/
104TipManager* TipManager::Instance() {
105	BAutolock _l(s_instanceLock);
106	if(!s_instance)
107		s_instance = new TipManager();
108
109	return s_instance;
110}
111
112// kill current instance if any
113/*static*/
114void TipManager::QuitInstance() {
115	BAutolock _l(s_instanceLock);
116	if(s_instance) {
117		s_instance->Lock();
118		s_instance->Quit();
119		s_instance = 0;
120	}
121}
122
123// -------------------------------------------------------- //
124// hidden constructor (use Instance() to access
125// a single instance)
126// -------------------------------------------------------- //
127
128TipManager::TipManager() :
129
130	BWindow(
131		BRect(-100,-100,-100,-100),
132		"TipManager",
133		B_NO_BORDER_WINDOW_LOOK,
134		B_FLOATING_ALL_WINDOW_FEEL,
135		B_ASYNCHRONOUS_CONTROLS | B_AVOID_FOCUS),
136	m_view(0) {
137
138	AddCommonFilter(
139		new BMessageFilter(
140			B_PROGRAMMED_DELIVERY,
141			B_ANY_SOURCE,
142			&ignore_quit_key));
143
144	m_view = new _TipManagerView(
145		new TipWindow(),
146		this,
147		s_sleepPeriod,
148		s_defIdleTime);
149	AddChild(m_view);
150
151	// start the window thread
152	Show();
153}
154
155
156// -------------------------------------------------------- //
157// add and remove tips
158// -------------------------------------------------------- //
159
160// add or modify a tip:
161
162status_t TipManager::setTip(
163	const BRect&			rect,
164	const char*				text,
165	BView*						view,
166	offset_mode_t			offsetMode	/*=LEFT_OFFSET_FROM_RECT*/,
167	BPoint						offset			/*=s_useDefaultOffset*/,
168	uint32 						flags				/*=NONE*/) {
169
170	ASSERT(text);
171	ASSERT(m_view);
172
173	BAutolock _l(this);
174	return m_view->setTip(
175		rect, text, view, offsetMode, offset, flags);
176}
177
178
179status_t TipManager::setTip(
180	const char*				text,
181	BView*						view,
182	offset_mode_t			offsetMode	/*=LEFT_OFFSET_FROM_RECT*/,
183	BPoint						offset			/*=s_useDefaultOffset*/,
184	uint32 						flags				/*=NONE*/) {
185
186	return setTip(
187		BRect(), text, view, offsetMode, offset, flags);
188}
189
190// Remove all tips matching the given rectangle and/or child
191// view.  Returns the number of tips removed.
192
193status_t TipManager::removeTip(
194	const BRect&		rect,
195	BView*					view) {
196
197	ASSERT(view);
198	ASSERT(m_view);
199
200	BAutolock _l(this);
201	return m_view->removeTip(rect, view);
202}
203
204// If more than one tip is mapped to pChild, all are removed:
205
206status_t TipManager::removeAll(
207	BView*					view) {
208
209	return removeTip(BRect(), view);
210}
211
212status_t TipManager::removeAll(
213	BWindow*				window) {
214
215//	PRINT((
216//		"### TipManager::removeAll(): %p, %p\n", this, m_view->Looper()));
217
218	ASSERT(window);
219	ASSERT(m_view);
220	ASSERT(m_view->Looper() == this); // +++++
221
222	BAutolock _l(this);
223	return m_view->removeAll(window);
224}
225
226// -------------------------------------------------------- //
227// *** manual tip arming
228// -------------------------------------------------------- //
229
230// [e.moon 19oct99]
231// Call when the mouse has entered a particular region of
232// the screen for which you want a tip to be displayed.
233// The tip will be displayed if the mouse stops moving
234// for idleTime microseconds within the rectangle screenRect.
235
236status_t TipManager::showTip(
237	const char*						text,
238	BRect									screenRect,
239	offset_mode_t					offsetMode	/*=LEFT_OFFSET_FROM_RECT*/,
240	BPoint								offset			/*=s_useDefaultOffset*/,
241	uint32 								flags				/*=NONE*/) {
242
243	ASSERT(text);
244	ASSERT(m_view);
245
246	BAutolock _l(this);
247	return m_view->armTip(
248	  screenRect, text, offsetMode, offset, flags);
249}
250
251// [e.moon 22oct99]
252// Call to immediately hide a visible tip.  You need to know
253// the screen rectangle for which the tip was shown (which is easy
254// if was displayed due to a showTip() call -- pass the same
255// screenRect argument.)
256// If the tip was found & hidden, returns B_OK; if there's
257// no visible tip or it was triggered by a different rectangle,
258// returns B_BAD_VALUE.
259
260status_t TipManager::hideTip(
261	BRect									screenRect) {
262
263	ASSERT(m_view);
264
265	BAutolock _l(this);
266	return m_view->hideTip(screenRect);
267}
268
269
270// -------------------------------------------------------- //
271// *** BWindow
272// -------------------------------------------------------- //
273
274// -------------------------------------------------------- //
275// *** BLooper
276// -------------------------------------------------------- //
277
278bool TipManager::QuitRequested() {
279	// ignored, since I receive key events bound for other apps
280	return false;
281}
282
283// -------------------------------------------------------- //
284// *** BHandler
285// -------------------------------------------------------- //
286
287void TipManager::MessageReceived(
288	BMessage*							message) {
289
290	switch(message->what) {
291		default:
292			_inherited::MessageReceived(message);
293	}
294}
295
296//// -------------------------------------------------------- //
297//// BasicThread impl.
298//// -------------------------------------------------------- //
299//
300//// +++++
301//// 12aug99: a locking bug seems to cause occasional
302////          crashes on shutdown after the looper's been deleted.
303//// +++++
304//// 23sep99: probably fixed; the TipManager needs to be manually
305////          killed before the window is deleted.
306//
307//void TipManager::run() {
308//
309//	BPoint point, lastPoint, screenPoint;
310//	bigtime_t curTime, lastTime;
311//	uint32 buttons;
312//
313//	bool bTipVisible = false;
314//	BRect tipScreenRect;
315//
316//	lastTime = 0;
317//	curTime = 0;
318//
319//	// [e.moon 27sep99]
320//	// store whether the tip has fired at the current point
321//	bool fired = false;
322//
323//	ASSERT(m_tree);
324//	BView* pOwningView = m_tree->target();
325//
326//	while(!stopping()) {
327//		snooze(s_sleepPeriod);
328//		if(stopping())
329//			break;
330//
331//		// wait for the view to show up
332//		if(!pOwningView->Parent() || !pOwningView->Window())
333//			continue;
334//
335//		// get current mouse position
336//		pOwningView->LockLooper();
337//
338//		pOwningView->GetMouse(&point, &buttons, false);
339//		screenPoint = pOwningView->ConvertToScreen(point);
340//
341//		pOwningView->UnlockLooper();
342//
343//		// has it been sitting in one place long enough?
344//		bool bMoved = (point != lastPoint);
345//
346//		if(bMoved) {
347//			lastTime = curTime;
348//			fired = false;
349//		}
350//		else if(fired) {
351//			// [27sep99 e.moon] the tip has already fired, and
352//			// the mouse hasn't moved; bail out now
353//			continue;
354//		}
355//
356//		curTime = system_time();
357//		bool bIdle = !bMoved && lastTime && (curTime - lastTime) > m_idleTime;
358//		lastPoint = point;
359//
360//		if(bTipVisible) {
361//			// hide tip once mouse moves outside its rectangle
362//			if(!tipScreenRect.Contains(screenPoint)) {
363//				m_tipWindow->Lock();
364//				if(!m_tipWindow->IsHidden()) // tip may hide itself [7sep99]
365//					m_tipWindow->Hide();
366//				bTipVisible = false;
367//				m_tipWindow->Unlock();
368//			}
369//		} else if(bIdle) {
370//
371//			// mouse has idled at a given point long enough;
372//			// look for a tip at that position and display one if found:
373//
374//			fired = true;
375//
376//			pOwningView->LockLooper();
377//
378//			// make sure this part of the view is actually visible
379//			if(!pOwningView->Window()->Frame().Contains(screenPoint)) {
380//				pOwningView->UnlockLooper();
381//				continue;
382//			}
383//
384//			// look for a tip under the mouse
385//			m_tipWindow->Lock();
386//			pair<BView*, const tip_entry*> found =
387//				m_tree->match(point, screenPoint);
388//
389//			if(!found.second) {
390//				// none found; move on
391//				pOwningView->UnlockLooper();
392//				m_tipWindow->Unlock();
393//				continue;
394//			}
395//
396//			BView* pTipTarget = found.first;
397//			const tip_entry& entry = *found.second;
398//
399//			// test the screen point against the view's clipping region;
400//			// if no match, the given point is likely covered by another
401//			// window (so stop recursing)
402//
403//			BRegion clipRegion;
404//			pTipTarget->GetClippingRegion(&clipRegion);
405//			if(!clipRegion.Contains(
406//				pTipTarget->ConvertFromScreen(screenPoint))) {
407//				// move on
408//				pOwningView->UnlockLooper();
409//				m_tipWindow->Unlock();
410//				continue;
411//			}
412//
413//			// found one; set up the tip window:
414//			BRect entryFrame = pTipTarget->ConvertToScreen(entry.rect);
415//
416//			// set text (this has the side effect of resizing the
417//			// window)
418//
419//			ASSERT(m_tipWindow);
420//			m_tipWindow->setText(entry.text.String());
421//
422//			// figure out where to display it:
423//
424//			BPoint offset = (entry.offset == s_useDefaultOffset) ?
425//				s_defaultOffset :
426//				entry.offset;
427//
428//			BPoint p;
429//			switch(entry.offsetMode) {
430//				case LEFT_OFFSET_FROM_RECT:
431//					p = entryFrame.RightTop() + offset;
432//					break;
433//				case LEFT_OFFSET_FROM_POINTER:
434//					p = screenPoint + offset;
435//					break;
436//				case RIGHT_OFFSET_FROM_RECT:
437//					p = entryFrame.LeftTop();
438//					p.x -= offset.x;
439//					p.y += offset.y;
440//					p.x -= m_tipWindow->Frame().Width();
441//					break;
442//				case RIGHT_OFFSET_FROM_POINTER:
443//					p = screenPoint;
444//					p.x -= offset.x;
445//					p.y += offset.y;
446//					p.x -= m_tipWindow->Frame().Width();
447//					break;
448//				default:
449//					ASSERT(!"bad offset mode");
450//			}
451//
452//			// do it:
453//
454//			m_tipWindow->MoveTo(p);
455//			m_tipWindow->Show();
456//
457//			bTipVisible = true;
458//			tipScreenRect = entryFrame;
459//
460//			m_tipWindow->Unlock();
461//			pOwningView->UnlockLooper();
462//
463//		} // if(bIdle ...
464//	} // while(!stopping ...
465//}
466
467// END -- TipManager.cpp --
468
469