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// TipManagerImpl.cpp
33// e.moon 13may99
34
35#include <algorithm>
36
37#include "TipManager.h"
38#include "TipManagerImpl.h"
39#include "TipWindow.h"
40
41#include <Autolock.h>
42#include <Debug.h>
43#include <MessageRunner.h>
44#include <Region.h>
45#include <Screen.h>
46
47//#include "debug_tools.h"
48
49using namespace std;
50
51__USE_CORTEX_NAMESPACE
52
53// -------------------------------------------------------- //
54
55// [e.moon 13oct99] now matches entry by pointer
56class entry_target_matches_view { public:
57	const BView* pView;
58	entry_target_matches_view(const BView* p) : pView(p) {}
59	bool operator()(const _ViewEntry* view) const {
60		return view->target() == pView;
61	}
62};
63
64// -------------------------------------------------------- //
65// _ViewEntry impl.
66// -------------------------------------------------------- //
67
68// [e.moon 13oct99] delete tips & children
69_ViewEntry::~_ViewEntry() {
70	for(list<_ViewEntry*>::iterator it = m_childViews.begin();
71		it != m_childViews.end(); ++it) {
72		delete *it;
73	}
74	for(tip_entry_set::iterator it = m_tips.begin();
75		it != m_tips.end(); ++it) {
76		delete *it;
77	}
78}
79
80// add the given entry for the designated view
81// (which may be the target view or a child.)
82// returns B_OK on success, B_ERROR if the given view is
83// NOT a child .
84
85status_t _ViewEntry::add(BView* pView, const tip_entry& tipEntry) {
86
87	// walk up the view's parent tree, building a child-
88	// hierarchy list and looking for my target view.
89	// The list should be in descending order: the last
90	// entry is pView.
91	// +++++ move to separate method
92	list<BView*> parentOrder;
93	BView* pCurView = pView;
94	while(pCurView && pCurView != m_target) {
95		parentOrder.push_front(pCurView);
96		pCurView = pCurView->Parent();
97	}
98	if(pCurView != m_target)
99		return B_ERROR; // +++++ ever so descriptive
100
101	// walk down the child hierarchy, making ViewEntries as
102	// needed
103	_ViewEntry* viewEntry = this;
104
105	// [e.moon 13oct99] clone tipEntry
106	tip_entry* newTipEntry = new tip_entry(tipEntry);
107
108	for(list<BView*>::iterator itView = parentOrder.begin();
109		itView != parentOrder.end(); itView++) {
110
111		// look for this view in children of the current entry
112		list<_ViewEntry*>::iterator itEntry =
113			find_if(
114				viewEntry->m_childViews.begin(),
115				viewEntry->m_childViews.end(),
116				entry_target_matches_view(*itView));
117
118		// add new _ViewEntry if necessary
119		if(itEntry == viewEntry->m_childViews.end()) {
120			viewEntry->m_childViews.push_back(new _ViewEntry(*itView, viewEntry));
121			viewEntry = viewEntry->m_childViews.back();
122		} else
123			viewEntry = *itEntry;
124	}
125
126	// found a home; can it hold the tip?
127	if(viewEntry->m_tips.size() &&
128		!(*viewEntry->m_tips.begin())->rect.IsValid()) {
129		// [e.moon 13oct99] clean up
130		delete newTipEntry;
131		return B_ERROR; // +++++ error: full-view tip leaves no room
132	}
133
134	// remove matching tip if any, then add the new one:
135	// [e.moon 13oct99] ref'd by pointer
136	tip_entry_set::iterator itFound = viewEntry->m_tips.find(newTipEntry);
137	if(itFound != viewEntry->m_tips.end()) {
138		delete *itFound;
139		viewEntry->m_tips.erase(itFound);
140	}
141
142	pair<tip_entry_set::iterator, bool> ret;
143	ret = viewEntry->m_tips.insert(newTipEntry);
144
145	// something's terribly wrong if insert() failed
146	ASSERT(ret.second);
147
148	return B_OK;
149}
150
151// remove tip matching the given rect's upper-left corner or
152// all tips if rect is invalid.
153// returns B_OK on success, B_ERROR on failure
154status_t _ViewEntry::remove(
155	BView* pView, const BRect& rect) {
156
157	// walk up the view's parent tree, building a child-
158	// hierarchy list and looking for my target view.
159	// The list should be in descending order: the last
160	// entry is pView.
161	// +++++ move to separate method
162	list<BView*> parentOrder;
163	BView* pCurView = pView;
164	while(pCurView && pCurView != m_target) {
165		parentOrder.push_front(pCurView);
166		pCurView = pCurView->Parent();
167	}
168	if(pCurView != m_target)
169		return B_ERROR; // +++++ ever so descriptive
170
171	// walk down the child tree to the entry for the
172	// target view
173	_ViewEntry* viewEntry = this;
174	for(list<BView*>::iterator itView = parentOrder.begin();
175		itView != parentOrder.end(); itView++) {
176
177		// look for this view in children of the current entry
178		list<_ViewEntry*>::iterator itEntry =
179			find_if(
180				viewEntry->m_childViews.begin(),
181				viewEntry->m_childViews.end(),
182				entry_target_matches_view(*itView));
183
184		// it'd better be there!
185		if(itEntry == viewEntry->m_childViews.end())
186			return B_ERROR;
187
188		viewEntry = *itEntry;
189	}
190
191	// remove matching entries:
192	// [13oct99 e.moon] now ref'd by pointer; find and erase all matching tips
193	if(rect.IsValid()) {
194		tip_entry matchEntry(rect);
195		tip_entry_set::iterator it = viewEntry->m_tips.lower_bound(&matchEntry);
196		tip_entry_set::iterator itEnd = viewEntry->m_tips.upper_bound(&matchEntry);
197		while(it != itEnd) {
198			// found one; erase it
199			delete *it;
200			viewEntry->m_tips.erase(it++);
201		}
202	}
203	else {
204		// invalid rect == wildcard
205
206//		PRINT((
207//			"### _ViewEntry::remove(): WILDCARD MODE\n"));
208
209		// [13oct99 e.moon] free all tip entries
210		for(
211			tip_entry_set::iterator it = viewEntry->m_tips.begin();
212			it != viewEntry->m_tips.end(); ++it) {
213			delete *it;
214		}
215		viewEntry->m_tips.clear();
216
217//		PRINT((
218//			"### - freed all tips\n"));
219
220		// [27oct99 e.moon] remove all child views
221		for(
222			list<_ViewEntry*>::iterator itChild = viewEntry->m_childViews.begin();
223			itChild != viewEntry->m_childViews.end(); ++itChild) {
224
225			delete *itChild;
226		}
227		viewEntry->m_childViews.clear();
228
229//		PRINT((
230//			"### - freed all child views\n"));
231
232		// remove the view entry if possible
233		if(viewEntry->m_parent) {
234			PRINT((
235				"### - removing view entry from %p\n",
236					viewEntry->m_parent));
237
238			list<_ViewEntry*>::iterator it =
239				find_if(
240					viewEntry->m_parent->m_childViews.begin(),
241					viewEntry->m_parent->m_childViews.end(),
242					entry_target_matches_view(pView));
243			ASSERT(it != viewEntry->m_parent->m_childViews.end());
244
245			_ViewEntry* parent = viewEntry->m_parent;
246			delete viewEntry;
247			parent->m_childViews.erase(it);
248		}
249	}
250
251	return B_OK;
252}
253
254// match the given point (in target's view coordinates)
255// against tips in this view and child views.  recurse.
256
257pair<BView*, const tip_entry*> _ViewEntry::match(
258	BPoint point, BPoint screenPoint) {
259
260	// fetch this view's current frame rect
261	BRect f = Frame();
262
263	// check for a full-frame tip:
264
265	const tip_entry* pFront = fullFrameTip();
266	if(pFront) {
267		// match, and stop recursing here; children can't have tips.
268		m_target->ConvertFromParent(&f);
269		return make_pair(m_target, f.Contains(point) ? pFront : 0);
270	}
271
272	// match against tips for my target view
273	if(m_tips.size()) {
274
275		tip_entry matchEntry(BRect(point, point));
276		tip_entry_set::iterator itCur = m_tips.lower_bound(&matchEntry);
277//		tip_entry_set::iterator itCur = m_tips.begin();
278		tip_entry_set::iterator itEnd = m_tips.end();
279
280		while(itCur != itEnd) {
281			// match:
282			const tip_entry* entry = *itCur;
283			if(entry->rect.Contains(point))
284				return pair<BView*, const tip_entry*>(m_target, entry);
285
286			++itCur;
287		}
288	}
289
290	// recurse through children
291	for(list<_ViewEntry*>::iterator it = m_childViews.begin();
292		it != m_childViews.end(); it++) {
293
294		_ViewEntry* entry = *it;
295		BPoint childPoint =
296			entry->target()->ConvertFromParent(point);
297
298		pair<BView*, const tip_entry*> ret = entry->match(
299			childPoint,
300			screenPoint);
301
302		if(ret.second)
303			return ret;
304	}
305
306	// nothing found
307	return pair<BView*, const tip_entry*>(0, 0);
308}
309
310// get frame rect (in parent view's coordinates)
311BRect _ViewEntry::Frame() {
312	ASSERT(m_target);
313//	ASSERT(m_target->Parent());
314
315	// +++++ if caching or some weird proxy mechanism
316	//       works out, return a cached BRect here
317	//       rather than asking every view every time!
318
319	BRect f = m_target->Frame();
320	return f;
321}
322
323// returns pointer to sole entry in the set if it's
324// a full-frame tip, or 0 if there's no full-frame tip
325const tip_entry* _ViewEntry::fullFrameTip() const {
326	if(m_tips.size()) {
327		const tip_entry* front = *m_tips.begin();
328		if(!front->rect.IsValid()) {
329			return front;
330		}
331	}
332	return 0;
333}
334
335size_t _ViewEntry::countTips() const {
336
337	size_t tips = m_tips.size();
338	for(list<_ViewEntry*>::const_iterator it = m_childViews.begin();
339		it != m_childViews.end(); it++) {
340		tips += (*it)->countTips();
341	}
342
343	return tips;
344}
345
346
347void _ViewEntry::dump(int indent) {
348	BString s;
349	s.SetTo('\t', indent);
350	PRINT((
351		"%s_ViewEntry '%s'\n",
352		s.String(),
353		m_target->Name()));
354
355	for(tip_entry_set::iterator it = m_tips.begin();
356		it != m_tips.end(); ++it) {
357		(*it)->dump(indent + 1);
358	}
359	for(list<_ViewEntry*>::iterator it = m_childViews.begin();
360		it != m_childViews.end(); it++) {
361		(*it)->dump(indent + 1);
362	}
363}
364
365// -------------------------------------------------------- //
366// _WindowEntry impl
367// -------------------------------------------------------- //
368
369_WindowEntry::~_WindowEntry() {
370	for(list<_ViewEntry*>::iterator it = m_views.begin();
371		it != m_views.end(); ++it) {
372		delete *it;
373	}
374}
375
376// add the given entry for the designated view (which must
377// be attached to the target window)
378// returns B_OK on success, B_ERROR if:
379// - the given view is NOT attached to the target window, or
380// - tips can't be added to this view due to it, or one of its
381//   parents, having a full-frame tip.
382
383status_t _WindowEntry::add(
384	BView*											view,
385	const tip_entry&						entry) {
386
387	ASSERT(view);
388	if(view->Window() != target())
389		return B_ERROR;
390
391	// find top-level view
392	BView* parent = view;
393	while(parent && parent->Parent())
394		parent = parent->Parent();
395
396	// look for a _ViewEntry matching the parent & hand off
397	for(list<_ViewEntry*>::iterator it = m_views.begin();
398		it != m_views.end(); ++it)
399		if((*it)->target() == parent)
400			return (*it)->add(view, entry);
401
402	// create _ViewEntry for the parent & hand off
403	_ViewEntry* v = new _ViewEntry(parent, 0);
404	m_views.push_back(v);
405
406	return v->add(view, entry);
407}
408
409// remove tip matching the given rect's upper-left corner or
410// all tips if rect is invalid.
411// returns B_ERROR on failure -- if there are no entries for
412// the given view -- or B_OK otherwise.
413
414status_t _WindowEntry::remove(
415	BView*											view,
416	const BRect&								rect) {
417
418	ASSERT(view);
419	if(view->Window() != target())
420		return B_ERROR;
421
422	// find top-level view
423	BView* parent = view;
424	while(parent && parent->Parent())
425		parent = parent->Parent();
426
427	// look for a matching _ViewEntry	& hand off
428	for(list<_ViewEntry*>::iterator it = m_views.begin();
429		it != m_views.end(); ++it)
430		if((*it)->target() == parent) {
431
432			// do it
433			status_t ret = (*it)->remove(view, rect);
434
435			if(!(*it)->countTips()) {
436				// remove empty entry
437				delete *it;
438				m_views.erase(it);
439			}
440			return ret;
441		}
442
443	// not found
444	PRINT((
445		"!!! _WindowEntry::remove(): no matching view\n"));
446	return B_ERROR;
447}
448
449// match the given point (in screen coordinates)
450// against tips in this window's views.
451
452pair<BView*, const tip_entry*> _WindowEntry::match(
453	BPoint											screenPoint) {
454
455	for(list<_ViewEntry*>::iterator it = m_views.begin();
456		it != m_views.end(); ++it) {
457
458		// +++++ failing on invalid views? [e.moon 27oct99]
459
460		BView* target = (*it)->target();
461		if(target->Window() != m_target) {
462			PRINT((
463				"!!! _WindowEntry::match(): unexpected window for target view (%p)\n",
464				target));
465
466			// skip it
467			return pair<BView*,const tip_entry*>(0,0);
468		}
469		pair<BView*,const tip_entry*> ret = (*it)->match(
470			(*it)->target()->ConvertFromScreen(screenPoint),
471			screenPoint);
472		if(ret.second)
473			return ret;
474	}
475
476	return pair<BView*,const tip_entry*>(0,0);
477}
478
479void _WindowEntry::dump(int indent) {
480	BString s;
481	s.SetTo('\t', indent);
482	PRINT((
483		"%s_WindowEntry '%s'\n",
484		s.String(),
485		m_target->Name()));
486
487	for(list<_ViewEntry*>::iterator it = m_views.begin();
488		it != m_views.end(); it++) {
489		(*it)->dump(indent + 1);
490	}
491}
492
493
494// -------------------------------------------------------- //
495// _TipManagerView
496// -------------------------------------------------------- //
497
498// -------------------------------------------------------- //
499// *** ctor/dtor
500// -------------------------------------------------------- //
501
502_TipManagerView::~_TipManagerView() {
503	for(list<_WindowEntry*>::iterator it = m_windows.begin();
504		it != m_windows.end(); ++it) {
505		delete *it;
506	}
507	if(m_messageRunner)
508		delete m_messageRunner;
509
510	// clean up the tip-display window
511	m_tipWindow->Lock();
512	m_tipWindow->Quit();
513}
514
515_TipManagerView::_TipManagerView(
516	TipWindow*									tipWindow,
517	TipManager*									manager,
518	bigtime_t										updatePeriod,
519	bigtime_t										idlePeriod) :
520	BView(
521		BRect(0,0,0,0),
522		"_TipManagerView",
523		B_FOLLOW_NONE,
524		B_PULSE_NEEDED),
525	m_tipWindow(tipWindow),
526	m_manager(manager),
527	m_messageRunner(0),
528	m_tipWindowState(TIP_WINDOW_HIDDEN),
529	m_updatePeriod(updatePeriod),
530	m_idlePeriod(idlePeriod),
531	m_lastEventTime(0LL),
532	m_triggered(false),
533	m_armedTip(0) {
534
535	ASSERT(m_tipWindow);
536	ASSERT(m_manager);
537
538	// +++++ is this cheating?
539	m_tipWindow->Run();
540
541	// request to be sent all mouse & keyboard events
542	SetEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS);
543
544	// don't draw
545	SetViewColor(B_TRANSPARENT_COLOR);
546}
547
548
549// -------------------------------------------------------- //
550// *** operations
551// -------------------------------------------------------- //
552
553// Prepare a 'one-off' tip: this interrupts normal mouse-watching
554// behavior while the mouse remains in the given screen rectangle.
555// If it idles long enough, the tip window is displayed.
556
557status_t _TipManagerView::armTip(
558	const BRect&								screenRect,
559	const char*									text,
560	TipManager::offset_mode_t		offsetMode,
561	BPoint											offset,
562	uint32 											flags) {
563
564	ASSERT(Looper()->IsLocked());
565
566	ASSERT(text);
567	if(!screenRect.IsValid())
568		return B_BAD_VALUE;
569
570	// cancel previous manual tip operation
571	if(m_armedTip) {
572		ASSERT(m_tipWindowState == TIP_WINDOW_ARMED);
573		delete m_armedTip;
574		m_armedTip = 0;
575	}
576
577	// is a tip with the same screen rect visible? update it:
578	if(m_tipWindowState == TIP_WINDOW_VISIBLE &&
579		m_visibleTipRect == screenRect) {
580		BAutolock _l(m_tipWindow);
581		m_tipWindow->setText(text);
582		return B_OK;
583	}
584
585	// create new entry; enter 'armed' state
586	m_armedTip = new tip_entry(
587		screenRect,
588		text,
589		offsetMode,
590		offset,
591		flags);
592	m_tipWindowState = TIP_WINDOW_ARMED;
593
594	return B_OK;
595}
596
597// Hide tip corresponding to the given screenRect, if any.
598// [e.moon 29nov99] Cancel 'one-off' tip for the given rect if any.
599
600status_t _TipManagerView::hideTip(
601	const BRect&								screenRect) {
602
603	ASSERT(Looper()->IsLocked());
604
605	// check for armed tip
606	if(m_armedTip) {
607		ASSERT(m_tipWindowState == TIP_WINDOW_ARMED);
608		if(m_armedTip->rect == screenRect) {
609			// cancel it
610			delete m_armedTip;
611			m_armedTip = 0;
612			m_tipWindowState = TIP_WINDOW_HIDDEN;
613			return B_OK;
614		}
615	}
616
617	// check for visible tip
618	if(m_tipWindowState != TIP_WINDOW_VISIBLE)
619		return B_BAD_VALUE;
620
621	if(m_visibleTipRect != screenRect)
622		return B_BAD_VALUE;
623
624	_hideTip();
625	m_tipWindowState = TIP_WINDOW_HIDDEN;
626
627	return B_OK;
628}
629
630status_t _TipManagerView::setTip(
631	const BRect&								rect,
632	const char*									text,
633	BView*											view,
634	TipManager::offset_mode_t		offsetMode,
635	BPoint											offset,
636	uint32 											flags) {
637
638	ASSERT(text);
639	ASSERT(view);
640	ASSERT(Looper()->IsLocked());
641
642	BWindow* window = view->Window();
643	if(!window)
644		return B_ERROR; // can't add non-attached views
645
646	// construct & add an entry
647	tip_entry e(rect, text, offsetMode, offset, flags);
648
649	for(
650		list<_WindowEntry*>::iterator it = m_windows.begin();
651		it != m_windows.end(); ++it) {
652		if((*it)->target() == window)
653			return (*it)->add(view, e);
654	}
655
656	// create new window entry
657	_WindowEntry* windowEntry = new _WindowEntry(window);
658	m_windows.push_back(windowEntry);
659
660	return windowEntry->add(view, e);
661}
662
663// [e.moon 27oct99]
664// +++++ broken for 'remove all' mode (triggered by invalid rect):
665// doesn't remove entries.
666status_t _TipManagerView::removeTip(
667	const BRect&								rect,
668	BView*											view) {
669
670	ASSERT(view);
671	ASSERT(Looper()->IsLocked());
672
673	BWindow* window = view->Window();
674	if(!window) {
675		PRINT((
676			"!!! _TipManagerView::removeTip(): not attached !!!\n"));
677		return B_ERROR; // can't add non-attached views
678	}
679
680	// hand off to the entry for the containing window
681	for(
682		list<_WindowEntry*>::iterator it = m_windows.begin();
683		it != m_windows.end(); ++it) {
684		if((*it)->target() == window) {
685
686//			PRINT((
687//				"### _TipManagerView::removeTip(%.0f,%.0f - %.0f,%.0f)\n"
688//				"    * BEFORE\n\n",
689//				rect.left, rect.top, rect.right, rect.bottom));
690//			(*it)->dump(1);
691
692			// remove
693			status_t ret = (*it)->remove(view, rect);
694
695			if(!(*it)->countViews()) {
696
697				// emptied window entry; remove it
698				delete *it;
699				m_windows.erase(it);
700//				PRINT((
701//					"    (removed window entry)\n"));
702			}
703//			else {
704//				PRINT((
705//					"    * AFTER\n\n"));
706//				(*it)->dump(1);
707//			}
708			return ret;
709		}
710	}
711
712	PRINT((
713		"!!! _TipManagerView::removeTip(): window entry not found!\n\n"));
714	return B_ERROR;
715}
716
717status_t _TipManagerView::removeAll(
718	BWindow*										window) {
719
720	ASSERT(window);
721	ASSERT(Looper()->IsLocked());
722
723//	PRINT((
724//		"### _TipManagerView::removeAll()\n"));
725
726	for(
727		list<_WindowEntry*>::iterator it = m_windows.begin();
728		it != m_windows.end(); ++it) {
729		if((*it)->target() == window) {
730			delete *it;
731			m_windows.erase(it);
732			return B_OK;
733		}
734	}
735
736	PRINT((
737		"!!! _TipManagerView::removeAll(): window entry not found!\n"));
738	return B_ERROR;
739}
740
741// -------------------------------------------------------- //
742// *** BView
743// -------------------------------------------------------- //
744
745void _TipManagerView::AttachedToWindow() {
746
747//	PRINT((
748//		"### _TipManagerView::AttachedToWindow()\n"));
749
750	// start the updates flowing
751	m_messageRunner = new BMessageRunner(
752		BMessenger(this),
753		new BMessage(M_TIME_PASSED),
754		m_updatePeriod);
755}
756
757void _TipManagerView::KeyDown(
758	const char*									bytes,
759	int32												count) {
760
761	// no longer attached?
762	if(!Window())
763		return;
764
765	// hide the tip
766	if(m_tipWindowState == TIP_WINDOW_VISIBLE) {
767		_hideTip();
768		m_tipWindowState = TIP_WINDOW_HIDDEN;
769	}
770
771	m_lastEventTime = system_time();
772}
773
774void _TipManagerView::MouseDown(
775	BPoint											point) {
776
777	// no longer attached?
778	if(!Window())
779		return;
780
781	// hide the tip
782	if(m_tipWindowState == TIP_WINDOW_VISIBLE) {
783		_hideTip();
784		m_tipWindowState = TIP_WINDOW_HIDDEN;
785	}
786
787	m_lastEventTime = system_time();
788	ConvertToScreen(&point);
789	m_lastMousePoint = point;
790}
791
792void _TipManagerView::MouseMoved(
793	BPoint											point,
794	uint32											orientation,
795	const BMessage*							dragMessage) {
796
797//	PRINT((
798//		"### _TipManagerView::MouseMoved()\n"));
799
800	// no longer attached?
801	if(!Window())
802		return;
803
804	ConvertToScreen(&point);
805
806	bool moved = (point != m_lastMousePoint);
807
808	if(m_tipWindowState == TIP_WINDOW_ARMED) {
809		ASSERT(m_armedTip);
810		if(moved && !m_armedTip->rect.Contains(point)) {
811			// mouse has moved outside the tip region,
812			// disarming this manually-armed tip.
813			m_tipWindowState = TIP_WINDOW_HIDDEN;
814			delete m_armedTip;
815			m_armedTip = 0;
816		}
817	}
818	else if(m_tipWindowState == TIP_WINDOW_VISIBLE) {
819		ASSERT(m_visibleTipRect.IsValid());
820
821		if(moved && !m_visibleTipRect.Contains(point)) {
822			// hide the tip
823			_hideTip();
824			m_tipWindowState = TIP_WINDOW_HIDDEN;
825		}
826
827		// don't reset timing state until the tip is closed
828		return;
829	}
830
831	// if the mouse has moved, reset timing state:
832	if(moved) {
833		m_lastMousePoint = point;
834		m_lastEventTime = system_time();
835		m_triggered = false;
836	}
837}
838
839// -------------------------------------------------------- //
840// *** BHandler
841// -------------------------------------------------------- //
842
843void _TipManagerView::MessageReceived(
844	BMessage*										message) {
845	switch(message->what) {
846		case M_TIME_PASSED:
847			_timePassed();
848			break;
849
850		default:
851			_inherited::MessageReceived(message);
852	}
853}
854
855// -------------------------------------------------------- //
856// implementation
857// -------------------------------------------------------- //
858
859inline void _TipManagerView::_timePassed() {
860
861//	PRINT((
862//		"### _TipManagerView::_timePassed()\n"));
863
864	// no longer attached?
865	if(!Window())
866		return;
867
868	// has the mouse already triggered at this point?
869	if(m_triggered)
870		// yup; nothing more to do
871		return;
872
873	// see if the mouse has idled for long enough to trigger
874	bigtime_t now = system_time();
875	if(now - m_lastEventTime < m_idlePeriod)
876		// nope
877		return;
878
879	// trigger!
880	m_triggered = true;
881
882	if(m_tipWindowState == TIP_WINDOW_ARMED) {
883		// a tip has been manually set
884		ASSERT(m_armedTip);
885		m_visibleTipRect = m_armedTip->rect;
886		_showTip(m_armedTip);
887		m_tipWindowState = TIP_WINDOW_VISIBLE;
888
889		// clean up
890		delete m_armedTip;
891		m_armedTip = 0;
892		return;
893	}
894
895	// look for a tip under the current mouse point
896	for(
897		list<_WindowEntry*>::iterator it = m_windows.begin();
898		it != m_windows.end(); ++it) {
899
900		// lock the window
901		BWindow* window = (*it)->target();
902		ASSERT(window);
903
904		// [e.moon 21oct99] does autolock work in this context?
905		//BAutolock _l(window);
906		window->Lock();
907
908		// match
909		pair<BView*, const tip_entry*> found =
910			(*it)->match(m_lastMousePoint);
911
912		// if no tip found, or the view's no longer attached, bail:
913		if(!found.second || found.first->Window() != window) {
914			window->Unlock();
915			continue;
916		}
917
918		// found a tip under the mouse; see if it's obscured
919		// by another window
920		BRegion clipRegion;
921		found.first->GetClippingRegion(&clipRegion);
922		if(!clipRegion.Contains(
923			found.first->ConvertFromScreen(m_lastMousePoint))) {
924			// view hidden; don't show tip
925			window->Unlock();
926			continue;
927		}
928
929		// show the tip
930		if(found.second->rect.IsValid())
931			m_visibleTipRect = found.first->ConvertToScreen(
932				found.second->rect);
933		else
934			m_visibleTipRect = found.first->ConvertToScreen(
935				found.first->Bounds());
936
937		_showTip(found.second);
938		m_tipWindowState = TIP_WINDOW_VISIBLE;
939
940		window->Unlock();
941		break;
942	}
943}
944
945inline void _TipManagerView::_showTip(
946	const tip_entry*						entry) {
947
948//	PRINT((
949//		"### _TipManagerView::_showTip()\n"));
950
951	ASSERT(m_tipWindow);
952	ASSERT(m_tipWindowState != TIP_WINDOW_VISIBLE);
953	ASSERT(entry);
954
955	BAutolock _l(m_tipWindow);
956
957	// set text
958	m_tipWindow->SetWorkspaces(B_ALL_WORKSPACES);
959	m_tipWindow->setText(entry->text.String());
960
961	// figure position
962	BPoint offset = (entry->offset == TipManager::s_useDefaultOffset) ?
963		TipManager::s_defaultOffset :
964		entry->offset;
965
966	BPoint p;
967	switch(entry->offsetMode) {
968		case TipManager::LEFT_OFFSET_FROM_RECT:
969			p = m_visibleTipRect.RightTop() + offset;
970			break;
971		case TipManager::LEFT_OFFSET_FROM_POINTER:
972			p = m_lastMousePoint + offset;
973			break;
974		case TipManager::RIGHT_OFFSET_FROM_RECT:
975			p = m_visibleTipRect.LeftTop();
976			p.x -= offset.x;
977			p.y += offset.y;
978			p.x -= m_tipWindow->Frame().Width();
979			break;
980		case TipManager::RIGHT_OFFSET_FROM_POINTER:
981			p = m_lastMousePoint;
982			p.x -= offset.x;
983			p.y += offset.y;
984			p.x -= m_tipWindow->Frame().Width();
985			break;
986		default:
987			ASSERT(!"bad offset mode");
988	}
989
990	// constrain window to be on-screen:
991	m_tipWindow->MoveTo(p);
992
993	BRect screenR = BScreen(m_tipWindow).Frame();
994	BRect tipR = m_tipWindow->Frame();
995
996	if(tipR.left < screenR.left)
997		tipR.left = screenR.left;
998	else if(tipR.right > screenR.right)
999		tipR.left = screenR.right - tipR.Width();
1000
1001	if(tipR.top < screenR.top)
1002		tipR.top = screenR.top;
1003	else if(tipR.bottom > screenR.bottom)
1004		tipR.top = screenR.bottom - tipR.Height();
1005
1006	if(tipR.LeftTop() != p)
1007		m_tipWindow->MoveTo(tipR.LeftTop());
1008
1009	if(m_tipWindow->IsHidden())
1010		m_tipWindow->Show();
1011}
1012
1013inline void _TipManagerView::_hideTip() {
1014//	PRINT((
1015//		"### _TipManagerView::_hideTip()\n"));
1016
1017	ASSERT(m_tipWindow);
1018	ASSERT(m_tipWindowState == TIP_WINDOW_VISIBLE);
1019	BAutolock _l(m_tipWindow);
1020
1021	if(m_tipWindow->IsHidden())
1022		return;
1023
1024	m_tipWindow->Hide();
1025}
1026
1027// END -- TipManagerImpl.cpp --
1028