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