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().) 54TipManager* TipManager::s_instance = 0; 55BLocker TipManager::s_instanceLock("TipManager::s_instanceLock"); 56 57// special point value set to highly improbable position 58const BPoint TipManager::s_useDefaultOffset(FLT_MIN, FLT_MIN); 59 60// default tip position 61const BPoint TipManager::s_defaultOffset(8.0, 8.0); 62 63const bigtime_t TipManager::s_defIdleTime = 750000LL; 64const bigtime_t TipManager::s_sleepPeriod = 250000LL; 65 66// -------------------------------------------------------- // 67// *** message filter 68// -------------------------------------------------------- // 69 70filter_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 97TipManager::~TipManager() {} 98 99// -------------------------------------------------------- // 100// *** singleton access 101// -------------------------------------------------------- // 102 103/*static*/ 104TipManager* 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*/ 114void 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 128TipManager::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 162status_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 179status_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 193status_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 206status_t TipManager::removeAll( 207 BView* view) { 208 209 return removeTip(BRect(), view); 210} 211 212status_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 236status_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 260status_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 278bool TipManager::QuitRequested() { 279 // ignored, since I receive key events bound for other apps 280 return false; 281} 282 283// -------------------------------------------------------- // 284// *** BHandler 285// -------------------------------------------------------- // 286 287void 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