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