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