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