2 * Copyright 2009-2012, Axel Dörfler, axeld@pinc-software.de.
3 * Copyright 2009, Stephan Aßmus <superstippi@gmx.de>.
4 * All rights reserved. Distributed under the terms of the MIT License.
8 #include <ToolTipManager.h>
9 #include <ToolTipWindow.h>
14 #include <LayoutBuilder.h>
15 #include <MessageRunner.h>
18 #include <WindowPrivate.h>
22 static pthread_once_t sManagerInitOnce
= PTHREAD_ONCE_INIT
;
23 BToolTipManager
* BToolTipManager::sDefaultInstance
;
25 static const uint32 kMsgHideToolTip
= 'hide';
26 static const uint32 kMsgShowToolTip
= 'show';
27 static const uint32 kMsgCurrentToolTip
= 'curr';
28 static const uint32 kMsgCloseToolTip
= 'clos';
34 class ToolTipView
: public BView
{
36 ToolTipView(BToolTip
* tip
);
37 virtual ~ToolTipView();
39 virtual void AttachedToWindow();
40 virtual void DetachedFromWindow();
42 virtual void FrameResized(float width
, float height
);
43 virtual void MouseMoved(BPoint where
, uint32 transit
,
44 const BMessage
* dragMessage
);
45 virtual void KeyDown(const char* bytes
, int32 numBytes
);
50 void ResetWindowFrame();
51 void ResetWindowFrame(BPoint where
);
53 BToolTip
* Tip() const { return fToolTip
; }
54 bool IsTipHidden() const { return fHidden
; }
62 ToolTipView::ToolTipView(BToolTip
* tip
)
64 BView("tool tip", B_WILL_DRAW
| B_FRAME_EVENTS
),
68 fToolTip
->AcquireReference();
69 SetViewUIColor(B_TOOL_TIP_BACKGROUND_COLOR
);
70 SetHighUIColor(B_TOOL_TIP_TEXT_COLOR
);
72 BGroupLayout
* layout
= new BGroupLayout(B_VERTICAL
);
73 layout
->SetInsets(5, 5, 5, 5);
76 AddChild(fToolTip
->View());
80 ToolTipView::~ToolTipView()
82 fToolTip
->ReleaseReference();
87 ToolTipView::AttachedToWindow()
89 SetEventMask(B_POINTER_EVENTS
| B_KEYBOARD_EVENTS
, 0);
90 fToolTip
->AttachedToWindow();
95 ToolTipView::DetachedFromWindow()
97 BToolTipManager
* manager
= BToolTipManager::Manager();
100 RemoveChild(fToolTip
->View());
101 // don't delete this one!
102 fToolTip
->DetachedFromWindow();
109 ToolTipView::FrameResized(float width
, float height
)
116 ToolTipView::MouseMoved(BPoint where
, uint32 transit
,
117 const BMessage
* dragMessage
)
119 if (fToolTip
->IsSticky()) {
120 ResetWindowFrame(ConvertToScreen(where
));
121 } else if (transit
== B_ENTERED_VIEW
) {
122 // close instantly if the user managed to enter
125 // close with the preferred delay in case the mouse just moved
132 ToolTipView::KeyDown(const char* bytes
, int32 numBytes
)
134 if (!fToolTip
->IsSticky())
140 ToolTipView::HideTip()
145 BMessage
quit(kMsgCloseToolTip
);
146 BMessageRunner::StartSending(Window(), &quit
,
147 BToolTipManager::Manager()->HideDelay(), 1);
153 ToolTipView::ShowTip()
160 ToolTipView::ResetWindowFrame()
163 GetMouse(&where
, NULL
, false);
165 ResetWindowFrame(ConvertToScreen(where
));
169 /*! Tries to find the right frame to show the tool tip in, trying to use the
170 alignment that the tool tip specifies.
171 Makes sure the tool tip can be shown on screen in its entirety, ie. it will
172 resize the window if necessary.
175 ToolTipView::ResetWindowFrame(BPoint where
)
177 if (Window() == NULL
)
180 BSize size
= PreferredSize();
182 BScreen
screen(Window());
183 BRect screenFrame
= screen
.Frame().InsetBySelf(2, 2);
184 BPoint offset
= fToolTip
->MouseRelativeLocation();
186 // Ensure that the tip can be placed on screen completely
188 if (size
.width
> screenFrame
.Width())
189 size
.width
= screenFrame
.Width();
191 if (size
.width
> where
.x
- screenFrame
.left
192 && size
.width
> screenFrame
.right
- where
.x
) {
193 // There is no space to put the tip to the left or the right of the
194 // cursor, it can either be below or above it
195 if (size
.height
> where
.y
- screenFrame
.top
196 && where
.y
- screenFrame
.top
> screenFrame
.Height() / 2) {
197 size
.height
= where
.y
- offset
.y
- screenFrame
.top
;
198 } else if (size
.height
> screenFrame
.bottom
- where
.y
199 && screenFrame
.bottom
- where
.y
> screenFrame
.Height() / 2) {
200 size
.height
= screenFrame
.bottom
- where
.y
- offset
.y
;
204 // Find best alignment, starting with the requested one
206 BAlignment alignment
= fToolTip
->Alignment();
207 BPoint location
= where
;
208 bool doesNotFit
= false;
210 switch (alignment
.horizontal
) {
212 location
.x
-= size
.width
+ offset
.x
;
213 if (location
.x
< screenFrame
.left
) {
214 location
.x
= screenFrame
.left
;
219 location
.x
-= size
.width
/ 2 - offset
.x
;
220 if (location
.x
< screenFrame
.left
) {
221 location
.x
= screenFrame
.left
;
223 } else if (location
.x
+ size
.width
> screenFrame
.right
) {
224 location
.x
= screenFrame
.right
- size
.width
;
230 location
.x
+= offset
.x
;
231 if (location
.x
+ size
.width
> screenFrame
.right
) {
232 location
.x
= screenFrame
.right
- size
.width
;
238 if ((doesNotFit
&& alignment
.vertical
== B_ALIGN_MIDDLE
)
239 || (alignment
.vertical
== B_ALIGN_MIDDLE
240 && alignment
.horizontal
== B_ALIGN_CENTER
))
241 alignment
.vertical
= B_ALIGN_BOTTOM
;
243 // Adjust the tooltip position in cases where it would be partly out of the
244 // screen frame. Try to fit the tooltip on the requested side of the
245 // cursor, if that fails, try the opposite side, and if that fails again,
246 // give up and leave the tooltip under the mouse cursor.
247 bool firstTry
= true;
249 switch (alignment
.vertical
) {
251 location
.y
= where
.y
- size
.height
- offset
.y
;
252 if (location
.y
< screenFrame
.top
) {
253 alignment
.vertical
= firstTry
? B_ALIGN_BOTTOM
261 location
.y
-= size
.height
/ 2 - offset
.y
;
262 if (location
.y
< screenFrame
.top
)
263 location
.y
= screenFrame
.top
;
264 else if (location
.y
+ size
.height
> screenFrame
.bottom
)
265 location
.y
= screenFrame
.bottom
- size
.height
;
269 location
.y
= where
.y
+ offset
.y
;
270 if (location
.y
+ size
.height
> screenFrame
.bottom
) {
271 alignment
.vertical
= firstTry
? B_ALIGN_TOP
283 // Cut off any out-of-screen areas
285 if (screenFrame
.left
> where
.x
) {
286 size
.width
-= where
.x
- screenFrame
.left
;
287 where
.x
= screenFrame
.left
;
288 } else if (screenFrame
.right
< where
.x
+ size
.width
)
289 size
.width
= screenFrame
.right
- where
.x
;
291 if (screenFrame
.top
> where
.y
) {
292 size
.height
-= where
.y
- screenFrame
.top
;
293 where
.y
= screenFrame
.top
;
294 } else if (screenFrame
.bottom
< where
.y
+ size
.height
)
295 size
.height
-= screenFrame
.bottom
- where
.y
;
297 // Change window frame
299 Window()->ResizeTo(size
.width
, size
.height
);
300 Window()->MoveTo(where
);
307 ToolTipWindow::ToolTipWindow(BToolTip
* tip
, BPoint where
, void* owner
)
309 BWindow(BRect(0, 0, 250, 10).OffsetBySelf(where
), "tool tip",
310 B_BORDERED_WINDOW_LOOK
, kMenuWindowFeel
,
311 B_NOT_ZOOMABLE
| B_NOT_MINIMIZABLE
| B_AUTO_UPDATE_SIZE_LIMITS
312 | B_AVOID_FRONT
| B_AVOID_FOCUS
),
315 SetLayout(new BGroupLayout(B_VERTICAL
));
317 BToolTipManager
* manager
= BToolTipManager::Manager();
318 ToolTipView
* view
= new ToolTipView(tip
);
324 // figure out size and location
326 view
->ResetWindowFrame(where
);
331 ToolTipWindow::MessageReceived(BMessage
* message
)
333 ToolTipView
* view
= static_cast<ToolTipView
*>(ChildAt(0));
335 switch (message
->what
) {
336 case kMsgHideToolTip
:
340 case kMsgCurrentToolTip
:
342 BToolTip
* tip
= view
->Tip();
344 BMessage
reply(B_REPLY
);
345 reply
.AddPointer("current", tip
);
346 reply
.AddPointer("owner", fOwner
);
348 if (message
->SendReply(&reply
) == B_OK
)
349 tip
->AcquireReference();
353 case kMsgShowToolTip
:
357 case kMsgCloseToolTip
:
358 if (view
->IsTipHidden())
363 BWindow::MessageReceived(message
);
368 } // namespace BPrivate
374 /*static*/ BToolTipManager
*
375 BToolTipManager::Manager()
377 // Note: The check is not necessary; it's just faster than always calling
378 // pthread_once(). It requires reading/writing of pointers to be atomic
379 // on the architecture.
380 if (sDefaultInstance
== NULL
)
381 pthread_once(&sManagerInitOnce
, &_InitSingleton
);
383 return sDefaultInstance
;
388 BToolTipManager::ShowTip(BToolTip
* tip
, BPoint where
, void* owner
)
390 BToolTip
* current
= NULL
;
391 void* currentOwner
= NULL
;
393 if (fWindow
.SendMessage(kMsgCurrentToolTip
, &reply
) == B_OK
) {
394 reply
.FindPointer("current", (void**)¤t
);
395 reply
.FindPointer("owner", ¤tOwner
);
398 // Release reference from the message
400 current
->ReleaseReference();
402 if (current
== tip
|| currentOwner
== owner
) {
403 fWindow
.SendMessage(kMsgShowToolTip
);
407 fWindow
.SendMessage(kMsgHideToolTip
);
410 BWindow
* window
= new BPrivate::ToolTipWindow(tip
, where
, owner
);
413 fWindow
= BMessenger(window
);
419 BToolTipManager::HideTip()
421 fWindow
.SendMessage(kMsgHideToolTip
);
426 BToolTipManager::SetShowDelay(bigtime_t time
)
428 // between 10ms and 3s
431 else if (time
> 3000000)
439 BToolTipManager::ShowDelay() const
446 BToolTipManager::SetHideDelay(bigtime_t time
)
448 // between 0 and 0.5s
451 else if (time
> 500000)
459 BToolTipManager::HideDelay() const
465 BToolTipManager::BToolTipManager()
467 fLock("tool tip manager"),
474 BToolTipManager::~BToolTipManager()
480 BToolTipManager::_InitSingleton()
482 sDefaultInstance
= new BToolTipManager();