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;
entry_target_matches_view(const BView * p)58 entry_target_matches_view(const BView* p) : pView(p) {}
operator ()(const _ViewEntry * view) const59 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
~_ViewEntry()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
add(BView * pView,const tip_entry & tipEntry)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
remove(BView * pView,const BRect & rect)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
match(BPoint point,BPoint screenPoint)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)
Frame()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
fullFrameTip() const325 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
countTips() const335 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
dump(int indent)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
~_WindowEntry()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
add(BView * view,const tip_entry & entry)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
remove(BView * view,const BRect & rect)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
match(BPoint screenPoint)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
dump(int indent)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
~_TipManagerView()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
_TipManagerView(TipWindow * tipWindow,TipManager * manager,bigtime_t updatePeriod,bigtime_t idlePeriod)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
armTip(const BRect & screenRect,const char * text,TipManager::offset_mode_t offsetMode,BPoint offset,uint32 flags)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
hideTip(const BRect & screenRect)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
setTip(const BRect & rect,const char * text,BView * view,TipManager::offset_mode_t offsetMode,BPoint offset,uint32 flags)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.
removeTip(const BRect & rect,BView * view)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
removeAll(BWindow * window)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
AttachedToWindow()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
KeyDown(const char * bytes,int32 count)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
MouseDown(BPoint point)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
MouseMoved(BPoint point,uint32 orientation,const BMessage * dragMessage)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
MessageReceived(BMessage * message)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
_timePassed()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
_showTip(const tip_entry * entry)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
_hideTip()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