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().) 52 TipManager* TipManager::s_instance = 0; 53 BLocker TipManager::s_instanceLock("TipManager::s_instanceLock"); 54 55 // special point value set to highly improbable position 56 const BPoint TipManager::s_useDefaultOffset(FLT_MIN, FLT_MIN); 57 58 // default tip position 59 const BPoint TipManager::s_defaultOffset(8.0, 8.0); 60 61 const bigtime_t TipManager::s_defIdleTime = 750000LL; 62 const bigtime_t TipManager::s_sleepPeriod = 250000LL; 63 64 // -------------------------------------------------------- // 65 // *** message filter 66 // -------------------------------------------------------- // 67 68 filter_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 95 TipManager::~TipManager() {} 96 97 // -------------------------------------------------------- // 98 // *** singleton access 99 // -------------------------------------------------------- // 100 101 /*static*/ 102 TipManager* 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*/ 112 void 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 126 TipManager::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 160 status_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 177 status_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 191 status_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 204 status_t TipManager::removeAll( 205 BView* view) { 206 207 return removeTip(BRect(), view); 208 } 209 210 status_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 234 status_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 258 status_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 276 bool TipManager::QuitRequested() { 277 // ignored, since I receive key events bound for other apps 278 return false; 279 } 280 281 // -------------------------------------------------------- // 282 // *** BHandler 283 // -------------------------------------------------------- // 284 285 void 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