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// TipManager.cpp 33// e.moon 12may99 34 35#include "TipManager.h" 36#include "TipManagerImpl.h" 37#include "TipWindow.h" 38 39#include <Autolock.h> 40#include <Message.h> 41#include <MessageFilter.h> 42#include <Region.h> 43#include <float.h> 44 45__USE_CORTEX_NAMESPACE 46 47// -------------------------------------------------------- // 48// constants 49// -------------------------------------------------------- // 50 51// static instance (created on first call to TipManager::Instance().) 52TipManager* TipManager::s_instance = 0; 53BLocker TipManager::s_instanceLock("TipManager::s_instanceLock"); 54 55// special point value set to highly improbable position 56const BPoint TipManager::s_useDefaultOffset(FLT_MIN, FLT_MIN); 57 58// default tip position 59const BPoint TipManager::s_defaultOffset(8.0, 8.0); 60 61const bigtime_t TipManager::s_defIdleTime = 750000LL; 62const bigtime_t TipManager::s_sleepPeriod = 250000LL; 63 64// -------------------------------------------------------- // 65// *** message filter 66// -------------------------------------------------------- // 67 68filter_result ignore_quit_key( 69 BMessage* message, 70 BHandler** target, 71 BMessageFilter* filter) 72{ 73 switch(message->what) 74 { 75 // filter command-Q 76 case B_KEY_DOWN: 77 { 78 if((modifiers() & B_COMMAND_KEY)) 79 { 80 int8 key; 81 message->FindInt8("byte", &key); 82 if(key == 'q') 83 return B_SKIP_MESSAGE; 84 } 85 break; 86 } 87 } 88 return B_DISPATCH_MESSAGE; 89} 90 91// -------------------------------------------------------- // 92// *** dtor 93// -------------------------------------------------------- // 94 95TipManager::~TipManager() {} 96 97// -------------------------------------------------------- // 98// *** singleton access 99// -------------------------------------------------------- // 100 101/*static*/ 102TipManager* TipManager::Instance() { 103 BAutolock _l(s_instanceLock); 104 if(!s_instance) 105 s_instance = new TipManager(); 106 107 return s_instance; 108} 109 110// kill current instance if any 111/*static*/ 112void TipManager::QuitInstance() { 113 BAutolock _l(s_instanceLock); 114 if(s_instance) { 115 s_instance->Lock(); 116 s_instance->Quit(); 117 s_instance = 0; 118 } 119} 120 121// -------------------------------------------------------- // 122// hidden constructor (use Instance() to access 123// a single instance) 124// -------------------------------------------------------- // 125 126TipManager::TipManager() : 127 128 BWindow( 129 BRect(-100,-100,-100,-100), 130 "TipManager", 131 B_NO_BORDER_WINDOW_LOOK, 132 B_FLOATING_ALL_WINDOW_FEEL, 133 B_ASYNCHRONOUS_CONTROLS | B_AVOID_FOCUS), 134 m_view(0) { 135 136 AddCommonFilter( 137 new BMessageFilter( 138 B_PROGRAMMED_DELIVERY, 139 B_ANY_SOURCE, 140 &ignore_quit_key)); 141 142 m_view = new _TipManagerView( 143 new TipWindow(), 144 this, 145 s_sleepPeriod, 146 s_defIdleTime); 147 AddChild(m_view); 148 149 // start the window thread 150 Show(); 151} 152 153 154// -------------------------------------------------------- // 155// add and remove tips 156// -------------------------------------------------------- // 157 158// add or modify a tip: 159 160status_t TipManager::setTip( 161 const BRect& rect, 162 const char* text, 163 BView* view, 164 offset_mode_t offsetMode /*=LEFT_OFFSET_FROM_RECT*/, 165 BPoint offset /*=s_useDefaultOffset*/, 166 uint32 flags /*=NONE*/) { 167 168 ASSERT(text); 169 ASSERT(m_view); 170 171 BAutolock _l(this); 172 return m_view->setTip( 173 rect, text, view, offsetMode, offset, flags); 174} 175 176 177status_t TipManager::setTip( 178 const char* text, 179 BView* view, 180 offset_mode_t offsetMode /*=LEFT_OFFSET_FROM_RECT*/, 181 BPoint offset /*=s_useDefaultOffset*/, 182 uint32 flags /*=NONE*/) { 183 184 return setTip( 185 BRect(), text, view, offsetMode, offset, flags); 186} 187 188// Remove all tips matching the given rectangle and/or child 189// view. Returns the number of tips removed. 190 191status_t TipManager::removeTip( 192 const BRect& rect, 193 BView* view) { 194 195 ASSERT(view); 196 ASSERT(m_view); 197 198 BAutolock _l(this); 199 return m_view->removeTip(rect, view); 200} 201 202// If more than one tip is mapped to pChild, all are removed: 203 204status_t TipManager::removeAll( 205 BView* view) { 206 207 return removeTip(BRect(), view); 208} 209 210status_t TipManager::removeAll( 211 BWindow* window) { 212 213// PRINT(( 214// "### TipManager::removeAll(): %p, %p\n", this, m_view->Looper())); 215 216 ASSERT(window); 217 ASSERT(m_view); 218 ASSERT(m_view->Looper() == this); // +++++ 219 220 BAutolock _l(this); 221 return m_view->removeAll(window); 222} 223 224// -------------------------------------------------------- // 225// *** manual tip arming 226// -------------------------------------------------------- // 227 228// [e.moon 19oct99] 229// Call when the mouse has entered a particular region of 230// the screen for which you want a tip to be displayed. 231// The tip will be displayed if the mouse stops moving 232// for idleTime microseconds within the rectangle screenRect. 233 234status_t TipManager::showTip( 235 const char* text, 236 BRect screenRect, 237 offset_mode_t offsetMode /*=LEFT_OFFSET_FROM_RECT*/, 238 BPoint offset /*=s_useDefaultOffset*/, 239 uint32 flags /*=NONE*/) { 240 241 ASSERT(text); 242 ASSERT(m_view); 243 244 BAutolock _l(this); 245 return m_view->armTip( 246 screenRect, text, offsetMode, offset, flags); 247} 248 249// [e.moon 22oct99] 250// Call to immediately hide a visible tip. You need to know 251// the screen rectangle for which the tip was shown (which is easy 252// if was displayed due to a showTip() call -- pass the same 253// screenRect argument.) 254// If the tip was found & hidden, returns B_OK; if there's 255// no visible tip or it was triggered by a different rectangle, 256// returns B_BAD_VALUE. 257 258status_t TipManager::hideTip( 259 BRect screenRect) { 260 261 ASSERT(m_view); 262 263 BAutolock _l(this); 264 return m_view->hideTip(screenRect); 265} 266 267 268// -------------------------------------------------------- // 269// *** BWindow 270// -------------------------------------------------------- // 271 272// -------------------------------------------------------- // 273// *** BLooper 274// -------------------------------------------------------- // 275 276bool TipManager::QuitRequested() { 277 // ignored, since I receive key events bound for other apps 278 return false; 279} 280 281// -------------------------------------------------------- // 282// *** BHandler 283// -------------------------------------------------------- // 284 285void TipManager::MessageReceived( 286 BMessage* message) { 287 288 switch(message->what) { 289 default: 290 _inherited::MessageReceived(message); 291 } 292} 293 294//// -------------------------------------------------------- // 295//// BasicThread impl. 296//// -------------------------------------------------------- // 297// 298//// +++++ 299//// 12aug99: a locking bug seems to cause occasional 300//// crashes on shutdown after the looper's been deleted. 301//// +++++ 302//// 23sep99: probably fixed; the TipManager needs to be manually 303//// killed before the window is deleted. 304// 305//void TipManager::run() { 306// 307// BPoint point, lastPoint, screenPoint; 308// bigtime_t curTime, lastTime; 309// uint32 buttons; 310// 311// bool bTipVisible = false; 312// BRect tipScreenRect; 313// 314// lastTime = 0; 315// curTime = 0; 316// 317// // [e.moon 27sep99] 318// // store whether the tip has fired at the current point 319// bool fired = false; 320// 321// ASSERT(m_tree); 322// BView* pOwningView = m_tree->target(); 323// 324// while(!stopping()) { 325// snooze(s_sleepPeriod); 326// if(stopping()) 327// break; 328// 329// // wait for the view to show up 330// if(!pOwningView->Parent() || !pOwningView->Window()) 331// continue; 332// 333// // get current mouse position 334// pOwningView->LockLooper(); 335// 336// pOwningView->GetMouse(&point, &buttons, false); 337// screenPoint = pOwningView->ConvertToScreen(point); 338// 339// pOwningView->UnlockLooper(); 340// 341// // has it been sitting in one place long enough? 342// bool bMoved = (point != lastPoint); 343// 344// if(bMoved) { 345// lastTime = curTime; 346// fired = false; 347// } 348// else if(fired) { 349// // [27sep99 e.moon] the tip has already fired, and 350// // the mouse hasn't moved; bail out now 351// continue; 352// } 353// 354// curTime = system_time(); 355// bool bIdle = !bMoved && lastTime && (curTime - lastTime) > m_idleTime; 356// lastPoint = point; 357// 358// if(bTipVisible) { 359// // hide tip once mouse moves outside its rectangle 360// if(!tipScreenRect.Contains(screenPoint)) { 361// m_tipWindow->Lock(); 362// if(!m_tipWindow->IsHidden()) // tip may hide itself [7sep99] 363// m_tipWindow->Hide(); 364// bTipVisible = false; 365// m_tipWindow->Unlock(); 366// } 367// } else if(bIdle) { 368// 369// // mouse has idled at a given point long enough; 370// // look for a tip at that position and display one if found: 371// 372// fired = true; 373// 374// pOwningView->LockLooper(); 375// 376// // make sure this part of the view is actually visible 377// if(!pOwningView->Window()->Frame().Contains(screenPoint)) { 378// pOwningView->UnlockLooper(); 379// continue; 380// } 381// 382// // look for a tip under the mouse 383// m_tipWindow->Lock(); 384// pair<BView*, const tip_entry*> found = 385// m_tree->match(point, screenPoint); 386// 387// if(!found.second) { 388// // none found; move on 389// pOwningView->UnlockLooper(); 390// m_tipWindow->Unlock(); 391// continue; 392// } 393// 394// BView* pTipTarget = found.first; 395// const tip_entry& entry = *found.second; 396// 397// // test the screen point against the view's clipping region; 398// // if no match, the given point is likely covered by another 399// // window (so stop recursing) 400// 401// BRegion clipRegion; 402// pTipTarget->GetClippingRegion(&clipRegion); 403// if(!clipRegion.Contains( 404// pTipTarget->ConvertFromScreen(screenPoint))) { 405// // move on 406// pOwningView->UnlockLooper(); 407// m_tipWindow->Unlock(); 408// continue; 409// } 410// 411// // found one; set up the tip window: 412// BRect entryFrame = pTipTarget->ConvertToScreen(entry.rect); 413// 414// // set text (this has the side effect of resizing the 415// // window) 416// 417// ASSERT(m_tipWindow); 418// m_tipWindow->setText(entry.text.String()); 419// 420// // figure out where to display it: 421// 422// BPoint offset = (entry.offset == s_useDefaultOffset) ? 423// s_defaultOffset : 424// entry.offset; 425// 426// BPoint p; 427// switch(entry.offsetMode) { 428// case LEFT_OFFSET_FROM_RECT: 429// p = entryFrame.RightTop() + offset; 430// break; 431// case LEFT_OFFSET_FROM_POINTER: 432// p = screenPoint + offset; 433// break; 434// case RIGHT_OFFSET_FROM_RECT: 435// p = entryFrame.LeftTop(); 436// p.x -= offset.x; 437// p.y += offset.y; 438// p.x -= m_tipWindow->Frame().Width(); 439// break; 440// case RIGHT_OFFSET_FROM_POINTER: 441// p = screenPoint; 442// p.x -= offset.x; 443// p.y += offset.y; 444// p.x -= m_tipWindow->Frame().Width(); 445// break; 446// default: 447// ASSERT(!"bad offset mode"); 448// } 449// 450// // do it: 451// 452// m_tipWindow->MoveTo(p); 453// m_tipWindow->Show(); 454// 455// bTipVisible = true; 456// tipScreenRect = entryFrame; 457// 458// m_tipWindow->Unlock(); 459// pOwningView->UnlockLooper(); 460// 461// } // if(bIdle ... 462// } // while(!stopping ... 463//} 464 465// END -- TipManager.cpp -- 466 467