xref: /haiku/src/apps/cortex/TipManager/TipManagerImpl.cpp (revision 362efe0c9f36d3dd38b22d2c24ac02e54b189d7c)
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 
49 using namespace std;
50 
51 __USE_CORTEX_NAMESPACE
52 
53 // -------------------------------------------------------- //
54 
55 // [e.moon 13oct99] now matches entry by pointer
56 class 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 
85 status_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
154 status_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 
257 pair<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)
311 BRect _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
325 const 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 
335 size_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 
347 void _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 
383 status_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 
414 status_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 
452 pair<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 
479 void _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 
557 status_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 
600 status_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 
630 status_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.
666 status_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 
717 status_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 
745 void _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 
757 void _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 
774 void _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 
792 void _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 
843 void _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 
859 inline 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 
945 inline 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 
1013 inline 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