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
ignore_quit_key(BMessage * message,BHandler ** target,BMessageFilter * filter)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
~TipManager()95 TipManager::~TipManager() {}
96
97 // -------------------------------------------------------- //
98 // *** singleton access
99 // -------------------------------------------------------- //
100
101 /*static*/
Instance()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*/
QuitInstance()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
TipManager()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
setTip(const BRect & rect,const char * text,BView * view,offset_mode_t offsetMode,BPoint offset,uint32 flags)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
setTip(const char * text,BView * view,offset_mode_t offsetMode,BPoint offset,uint32 flags)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
removeTip(const BRect & rect,BView * view)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
removeAll(BView * view)204 status_t TipManager::removeAll(
205 BView* view) {
206
207 return removeTip(BRect(), view);
208 }
209
removeAll(BWindow * window)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
showTip(const char * text,BRect screenRect,offset_mode_t offsetMode,BPoint offset,uint32 flags)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
hideTip(BRect screenRect)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
QuitRequested()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
MessageReceived(BMessage * message)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