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 SetViewColor(ui_color(B_TOOL_TIP_BACKGROUND_COLOR
));
71 BGroupLayout
* layout
= new BGroupLayout(B_VERTICAL
);
72 layout
->SetInsets(5, 5, 5, 5);
75 AddChild(fToolTip
->View());
79 ToolTipView::~ToolTipView()
81 fToolTip
->ReleaseReference();
86 ToolTipView::AttachedToWindow()
88 SetEventMask(B_POINTER_EVENTS
| B_KEYBOARD_EVENTS
, 0);
89 fToolTip
->AttachedToWindow();
94 ToolTipView::DetachedFromWindow()
96 BToolTipManager
* manager
= BToolTipManager::Manager();
99 RemoveChild(fToolTip
->View());
100 // don't delete this one!
101 fToolTip
->DetachedFromWindow();
108 ToolTipView::FrameResized(float width
, float height
)
115 ToolTipView::MouseMoved(BPoint where
, uint32 transit
,
116 const BMessage
* dragMessage
)
118 if (fToolTip
->IsSticky()) {
119 ResetWindowFrame(ConvertToScreen(where
));
120 } else if (transit
== B_ENTERED_VIEW
) {
121 // close instantly if the user managed to enter
124 // close with the preferred delay in case the mouse just moved
131 ToolTipView::KeyDown(const char* bytes
, int32 numBytes
)
133 if (!fToolTip
->IsSticky())
139 ToolTipView::HideTip()
144 BMessage
quit(kMsgCloseToolTip
);
145 BMessageRunner::StartSending(Window(), &quit
,
146 BToolTipManager::Manager()->HideDelay(), 1);
152 ToolTipView::ShowTip()
159 ToolTipView::ResetWindowFrame()
162 GetMouse(&where
, NULL
, false);
164 ResetWindowFrame(ConvertToScreen(where
));
168 /*! Tries to find the right frame to show the tool tip in, trying to use the
169 alignment that the tool tip specifies.
170 Makes sure the tool tip can be shown on screen in its entirety, ie. it will
171 resize the window if necessary.
174 ToolTipView::ResetWindowFrame(BPoint where
)
176 if (Window() == NULL
)
179 BSize size
= PreferredSize();
181 BScreen
screen(Window());
182 BRect screenFrame
= screen
.Frame().InsetBySelf(2, 2);
183 BPoint offset
= fToolTip
->MouseRelativeLocation();
185 // Ensure that the tip can be placed on screen completely
187 if (size
.width
> screenFrame
.Width())
188 size
.width
= screenFrame
.Width();
190 if (size
.width
> where
.x
- screenFrame
.left
191 && size
.width
> screenFrame
.right
- where
.x
) {
192 // There is no space to put the tip to the left or the right of the
193 // cursor, it can either be below or above it
194 if (size
.height
> where
.y
- screenFrame
.top
195 && where
.y
- screenFrame
.top
> screenFrame
.Height() / 2) {
196 size
.height
= where
.y
- offset
.y
- screenFrame
.top
;
197 } else if (size
.height
> screenFrame
.bottom
- where
.y
198 && screenFrame
.bottom
- where
.y
> screenFrame
.Height() / 2) {
199 size
.height
= screenFrame
.bottom
- where
.y
- offset
.y
;
203 // Find best alignment, starting with the requested one
205 BAlignment alignment
= fToolTip
->Alignment();
206 BPoint location
= where
;
207 bool doesNotFit
= false;
209 switch (alignment
.horizontal
) {
211 location
.x
-= size
.width
+ offset
.x
;
212 if (location
.x
< screenFrame
.left
) {
213 location
.x
= screenFrame
.left
;
218 location
.x
-= size
.width
/ 2 - offset
.x
;
219 if (location
.x
< screenFrame
.left
) {
220 location
.x
= screenFrame
.left
;
222 } else if (location
.x
+ size
.width
> screenFrame
.right
) {
223 location
.x
= screenFrame
.right
- size
.width
;
229 location
.x
+= offset
.x
;
230 if (location
.x
+ size
.width
> screenFrame
.right
) {
231 location
.x
= screenFrame
.right
- size
.width
;
237 if ((doesNotFit
&& alignment
.vertical
== B_ALIGN_MIDDLE
)
238 || (alignment
.vertical
== B_ALIGN_MIDDLE
239 && alignment
.horizontal
== B_ALIGN_CENTER
))
240 alignment
.vertical
= B_ALIGN_BOTTOM
;
242 // Adjust the tooltip position in cases where it would be partly out of the
243 // screen frame. Try to fit the tooltip on the requested side of the
244 // cursor, if that fails, try the opposite side, and if that fails again,
245 // give up and leave the tooltip under the mouse cursor.
246 bool firstTry
= true;
248 switch (alignment
.vertical
) {
250 location
.y
= where
.y
- size
.height
- offset
.y
;
251 if (location
.y
< screenFrame
.top
) {
252 alignment
.vertical
= firstTry
? B_ALIGN_BOTTOM
260 location
.y
-= size
.height
/ 2 - offset
.y
;
261 if (location
.y
< screenFrame
.top
)
262 location
.y
= screenFrame
.top
;
263 else if (location
.y
+ size
.height
> screenFrame
.bottom
)
264 location
.y
= screenFrame
.bottom
- size
.height
;
268 location
.y
= where
.y
+ offset
.y
;
269 if (location
.y
+ size
.height
> screenFrame
.bottom
) {
270 alignment
.vertical
= firstTry
? B_ALIGN_TOP
282 // Cut off any out-of-screen areas
284 if (screenFrame
.left
> where
.x
) {
285 size
.width
-= where
.x
- screenFrame
.left
;
286 where
.x
= screenFrame
.left
;
287 } else if (screenFrame
.right
< where
.x
+ size
.width
)
288 size
.width
= screenFrame
.right
- where
.x
;
290 if (screenFrame
.top
> where
.y
) {
291 size
.height
-= where
.y
- screenFrame
.top
;
292 where
.y
= screenFrame
.top
;
293 } else if (screenFrame
.bottom
< where
.y
+ size
.height
)
294 size
.height
-= screenFrame
.bottom
- where
.y
;
296 // Change window frame
298 Window()->ResizeTo(size
.width
, size
.height
);
299 Window()->MoveTo(where
);
306 ToolTipWindow::ToolTipWindow(BToolTip
* tip
, BPoint where
, void* owner
)
308 BWindow(BRect(0, 0, 250, 10).OffsetBySelf(where
), "tool tip",
309 B_BORDERED_WINDOW_LOOK
, kMenuWindowFeel
,
310 B_NOT_ZOOMABLE
| B_NOT_MINIMIZABLE
| B_AUTO_UPDATE_SIZE_LIMITS
311 | B_AVOID_FRONT
| B_AVOID_FOCUS
),
314 SetLayout(new BGroupLayout(B_VERTICAL
));
316 BToolTipManager
* manager
= BToolTipManager::Manager();
317 ToolTipView
* view
= new ToolTipView(tip
);
323 // figure out size and location
325 view
->ResetWindowFrame(where
);
330 ToolTipWindow::MessageReceived(BMessage
* message
)
332 ToolTipView
* view
= static_cast<ToolTipView
*>(ChildAt(0));
334 switch (message
->what
) {
335 case kMsgHideToolTip
:
339 case kMsgCurrentToolTip
:
341 BToolTip
* tip
= view
->Tip();
343 BMessage
reply(B_REPLY
);
344 reply
.AddPointer("current", tip
);
345 reply
.AddPointer("owner", fOwner
);
347 if (message
->SendReply(&reply
) == B_OK
)
348 tip
->AcquireReference();
352 case kMsgShowToolTip
:
356 case kMsgCloseToolTip
:
357 if (view
->IsTipHidden())
362 BWindow::MessageReceived(message
);
367 } // namespace BPrivate
373 /*static*/ BToolTipManager
*
374 BToolTipManager::Manager()
376 // Note: The check is not necessary; it's just faster than always calling
377 // pthread_once(). It requires reading/writing of pointers to be atomic
378 // on the architecture.
379 if (sDefaultInstance
== NULL
)
380 pthread_once(&sManagerInitOnce
, &_InitSingleton
);
382 return sDefaultInstance
;
387 BToolTipManager::ShowTip(BToolTip
* tip
, BPoint where
, void* owner
)
389 BToolTip
* current
= NULL
;
390 void* currentOwner
= NULL
;
392 if (fWindow
.SendMessage(kMsgCurrentToolTip
, &reply
) == B_OK
) {
393 reply
.FindPointer("current", (void**)¤t
);
394 reply
.FindPointer("owner", ¤tOwner
);
397 // Release reference from the message
399 current
->ReleaseReference();
401 if (current
== tip
|| currentOwner
== owner
) {
402 fWindow
.SendMessage(kMsgShowToolTip
);
406 fWindow
.SendMessage(kMsgHideToolTip
);
409 BWindow
* window
= new BPrivate::ToolTipWindow(tip
, where
, owner
);
412 fWindow
= BMessenger(window
);
418 BToolTipManager::HideTip()
420 fWindow
.SendMessage(kMsgHideToolTip
);
425 BToolTipManager::SetShowDelay(bigtime_t time
)
427 // between 10ms and 3s
430 else if (time
> 3000000)
438 BToolTipManager::ShowDelay() const
445 BToolTipManager::SetHideDelay(bigtime_t time
)
447 // between 0 and 0.5s
450 else if (time
> 500000)
458 BToolTipManager::HideDelay() const
464 BToolTipManager::BToolTipManager()
466 fLock("tool tip manager"),
473 BToolTipManager::~BToolTipManager()
479 BToolTipManager::_InitSingleton()
481 sDefaultInstance
= new BToolTipManager();