xref: /haiku/src/kits/interface/ToolTipManager.cpp (revision 9760dcae2038d47442f4658c2575844c6cf92c40)
1 /*
2  * Copyright 2009, 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.
5  */
6 
7 
8 #include <ToolTipManager.h>
9 #include <ToolTipWindow.h>
10 
11 #include <pthread.h>
12 
13 #include <Autolock.h>
14 #include <LayoutBuilder.h>
15 #include <MessageRunner.h>
16 #include <Screen.h>
17 
18 #include <WindowPrivate.h>
19 #include <ToolTip.h>
20 
21 
22 static pthread_once_t sManagerInitOnce = PTHREAD_ONCE_INIT;
23 BToolTipManager* BToolTipManager::sDefaultInstance;
24 
25 static const uint32 kMsgHideToolTip = 'hide';
26 static const uint32 kMsgShowToolTip = 'show';
27 static const uint32 kMsgCurrentToolTip = 'curr';
28 static const uint32 kMsgCloseToolTip = 'clos';
29 
30 
31 namespace BPrivate {
32 
33 
34 class ToolTipView : public BView {
35 public:
36 								ToolTipView(BToolTip* tip);
37 	virtual						~ToolTipView();
38 
39 	virtual	void				AttachedToWindow();
40 	virtual	void				DetachedFromWindow();
41 
42 	virtual	void				FrameResized(float width, float height);
43 	virtual	void				MouseMoved(BPoint where, uint32 transit,
44 									const BMessage* dragMessage);
45 
46 			void				HideTip();
47 			void				ShowTip();
48 			void				ResetWindowFrame(BPoint where);
49 
50 			BToolTip*			Tip() const { return fToolTip; }
51 			bool				IsTipHidden() const { return fHidden; }
52 
53 private:
54 			BToolTip*			fToolTip;
55 			bool				fHidden;
56 };
57 
58 
59 ToolTipView::ToolTipView(BToolTip* tip)
60 	:
61 	BView("tool tip", B_WILL_DRAW | B_FRAME_EVENTS),
62 	fToolTip(tip),
63 	fHidden(false)
64 {
65 	fToolTip->AcquireReference();
66 	SetViewColor(ui_color(B_TOOL_TIP_BACKGROUND_COLOR));
67 
68 	BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
69 	layout->SetInsets(5, 5, 5, 5);
70 	SetLayout(layout);
71 
72 	AddChild(fToolTip->View());
73 }
74 
75 
76 ToolTipView::~ToolTipView()
77 {
78 	fToolTip->ReleaseReference();
79 }
80 
81 
82 void
83 ToolTipView::AttachedToWindow()
84 {
85 	SetEventMask(B_POINTER_EVENTS, 0);
86 	fToolTip->AttachedToWindow();
87 }
88 
89 
90 void
91 ToolTipView::DetachedFromWindow()
92 {
93 	BToolTipManager* manager = BToolTipManager::Manager();
94 	manager->Lock();
95 
96 	RemoveChild(fToolTip->View());
97 		// don't delete this one!
98 	fToolTip->DetachedFromWindow();
99 
100 	manager->Unlock();
101 }
102 
103 
104 void
105 ToolTipView::FrameResized(float width, float height)
106 {
107 	BPoint where;
108 	GetMouse(&where, NULL, false);
109 
110 	ResetWindowFrame(ConvertToScreen(where));
111 }
112 
113 
114 void
115 ToolTipView::MouseMoved(BPoint where, uint32 transit,
116 	const BMessage* dragMessage)
117 {
118 	if (fToolTip->IsSticky()) {
119 		ResetWindowFrame(ConvertToScreen(where));
120 	} else if (transit == B_ENTERED_VIEW) {
121 		// close instantly if the user managed to enter
122 		Window()->Quit();
123 	} else {
124 		// close with the preferred delay in case the mouse just moved
125 		HideTip();
126 	}
127 }
128 
129 
130 void
131 ToolTipView::HideTip()
132 {
133 	if (fHidden)
134 		return;
135 
136 	BMessage quit(kMsgCloseToolTip);
137 	BMessageRunner::StartSending(Window(), &quit,
138 		BToolTipManager::Manager()->HideDelay(), 1);
139 	fHidden = true;
140 }
141 
142 
143 void
144 ToolTipView::ShowTip()
145 {
146 	fHidden = false;
147 }
148 
149 
150 /*!	Tries to find the right frame to show the tool tip in, trying to use the
151 	alignment that the tool tip specifies.
152 	Makes sure the tool tip can be shown on screen in its entirety, ie. it will
153 	resize the window if necessary.
154 */
155 void
156 ToolTipView::ResetWindowFrame(BPoint where)
157 {
158 	if (Window() == NULL)
159 		return;
160 
161 	BSize size = PreferredSize();
162 
163 	BScreen screen(Window());
164 	BRect screenFrame = screen.Frame().InsetBySelf(2, 2);
165 	BPoint offset = fToolTip->MouseRelativeLocation();
166 
167 	// Ensure that the tip can be placed on screen completely
168 
169 	if (size.width > screenFrame.Width())
170 		size.width = screenFrame.Width();
171 
172 	if (size.width > where.x - screenFrame.left
173 		&& size.width > screenFrame.right - where.x) {
174 		// There is no space to put the tip to the left or the right of the
175 		// cursor, it can either be below or above it
176 		if (size.height > where.y - screenFrame.top
177 			&& where.y - screenFrame.top > screenFrame.Height() / 2) {
178 			size.height = where.y - offset.y - screenFrame.top;
179 		} else if (size.height > screenFrame.bottom - where.y
180 			&& screenFrame.bottom - where.y > screenFrame.Height() / 2) {
181 			size.height = screenFrame.bottom - where.y - offset.y;
182 		}
183 	}
184 
185 	// Find best alignment, starting with the requested one
186 
187 	BAlignment alignment = fToolTip->Alignment();
188 	BPoint location = where;
189 	bool doesNotFit = false;
190 
191 	switch (alignment.horizontal) {
192 		case B_ALIGN_LEFT:
193 			location.x -= size.width + offset.x;
194 			if (location.x < screenFrame.left) {
195 				location.x = screenFrame.left;
196 				doesNotFit = true;
197 			}
198 			break;
199 		case B_ALIGN_CENTER:
200 			location.x -= size.width / 2 - offset.x;
201 			if (location.x < screenFrame.left) {
202 				location.x = screenFrame.left;
203 				doesNotFit = true;
204 			} else if (location.x + size.width > screenFrame.right) {
205 				location.x = screenFrame.right - size.width;
206 				doesNotFit = true;
207 			}
208 			break;
209 
210 		default:
211 			location.x += offset.x;
212 			if (location.x + size.width > screenFrame.right) {
213 				location.x = screenFrame.right - size.width;
214 				doesNotFit = true;
215 			}
216 			break;
217 	}
218 
219 	if ((doesNotFit && alignment.vertical == B_ALIGN_MIDDLE)
220 		|| (alignment.vertical == B_ALIGN_MIDDLE
221 			&& alignment.horizontal == B_ALIGN_CENTER))
222 		alignment.vertical = B_ALIGN_BOTTOM;
223 
224 	while (true) {
225 		switch (alignment.vertical) {
226 			case B_ALIGN_TOP:
227 				location.y = where.y - size.height - offset.y;
228 				if (location.y < screenFrame.top) {
229 					alignment.vertical = B_ALIGN_BOTTOM;
230 					continue;
231 				}
232 				break;
233 
234 			case B_ALIGN_MIDDLE:
235 				location.y -= size.height / 2 - offset.y;
236 				if (location.y < screenFrame.top)
237 					location.y = screenFrame.top;
238 				else if (location.y + size.height > screenFrame.bottom)
239 					location.y = screenFrame.bottom - size.height;
240 				break;
241 
242 			default:
243 				location.y = where.y + offset.y;
244 				if (location.y + size.height > screenFrame.bottom) {
245 					alignment.vertical = B_ALIGN_TOP;
246 					continue;
247 				}
248 				break;
249 		}
250 		break;
251 	}
252 
253 	where = location;
254 
255 	// Cut off any out-of-screen areas
256 
257 	if (screenFrame.left > where.x) {
258 		size.width -= where.x - screenFrame.left;
259 		where.x = screenFrame.left;
260 	} else if (screenFrame.right < where.x + size.width)
261 		size.width = screenFrame.right - where.x;
262 
263 	if (screenFrame.top > where.y) {
264 		size.height -= where.y - screenFrame.top;
265 		where.y = screenFrame.top;
266 	} else if (screenFrame.bottom < where.y + size.height)
267 		size.height -= screenFrame.bottom - where.y;
268 
269 	// Change window frame
270 
271 	Window()->ResizeTo(size.width, size.height);
272 	Window()->MoveTo(where);
273 }
274 
275 
276 // #pragma mark -
277 
278 
279 ToolTipWindow::ToolTipWindow(BToolTip* tip, BPoint where)
280 	:
281 	BWindow(BRect(0, 0, 250, 10).OffsetBySelf(where), "tool tip",
282 		B_BORDERED_WINDOW_LOOK, kMenuWindowFeel,
283 		B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_AUTO_UPDATE_SIZE_LIMITS
284 			| B_AVOID_FRONT | B_AVOID_FOCUS)
285 {
286 	SetLayout(new BGroupLayout(B_VERTICAL));
287 
288 	BToolTipManager* manager = BToolTipManager::Manager();
289 	ToolTipView* view = new ToolTipView(tip);
290 
291 	manager->Lock();
292 	AddChild(view);
293 	manager->Unlock();
294 
295 	// figure out size and location
296 
297 	view->ResetWindowFrame(where);
298 }
299 
300 
301 void
302 ToolTipWindow::MessageReceived(BMessage* message)
303 {
304 	ToolTipView* view = static_cast<ToolTipView*>(ChildAt(0));
305 
306 	switch (message->what) {
307 		case kMsgHideToolTip:
308 			view->HideTip();
309 			break;
310 
311 		case kMsgCurrentToolTip:
312 		{
313 			BToolTip* tip = view->Tip();
314 
315 			BMessage reply(B_REPLY);
316 			reply.AddPointer("current", tip);
317 
318 			if (message->SendReply(&reply) == B_OK)
319 				tip->AcquireReference();
320 			break;
321 		}
322 
323 		case kMsgShowToolTip:
324 			view->ShowTip();
325 			break;
326 
327 		case kMsgCloseToolTip:
328 			if (view->IsTipHidden())
329 				Quit();
330 			break;
331 
332 		default:
333 			BWindow::MessageReceived(message);
334 	}
335 }
336 
337 
338 }	// namespace BPrivate
339 
340 
341 // #pragma mark -
342 
343 
344 /*static*/ BToolTipManager*
345 BToolTipManager::Manager()
346 {
347 	// Note: The check is not necessary; it's just faster than always calling
348 	// pthread_once(). It requires reading/writing of pointers to be atomic
349 	// on the architecture.
350 	if (sDefaultInstance == NULL)
351 		pthread_once(&sManagerInitOnce, &_InitSingleton);
352 
353 	return sDefaultInstance;
354 }
355 
356 
357 /*static*/ void
358 BToolTipManager::_InitSingleton()
359 {
360 	sDefaultInstance = new BToolTipManager();
361 }
362 
363 
364 void
365 BToolTipManager::ShowTip(BToolTip* tip, BPoint point)
366 {
367 	BToolTip* current = NULL;
368 	BMessage reply;
369 	if (fWindow.SendMessage(kMsgCurrentToolTip, &reply) == B_OK)
370 		reply.FindPointer("current", (void**)&current);
371 
372 	if (current != NULL)
373 		current->ReleaseReference();
374 
375 	if (current == tip) {
376 		fWindow.SendMessage(kMsgShowToolTip);
377 		return;
378 	}
379 
380 	fWindow.SendMessage(kMsgHideToolTip);
381 
382 	if (current != NULL)
383 		current->ReleaseReference();
384 
385 	if (tip != NULL) {
386 		BWindow* window = new BPrivate::ToolTipWindow(tip, point);
387 		window->Show();
388 
389 		fWindow = BMessenger(window);
390 	}
391 }
392 
393 
394 void
395 BToolTipManager::HideTip()
396 {
397 	fWindow.SendMessage(kMsgHideToolTip);
398 }
399 
400 
401 void
402 BToolTipManager::SetShowDelay(bigtime_t time)
403 {
404 	// between 10ms and 3s
405 	if (time < 10000)
406 		time = 10000;
407 	else if (time > 3000000)
408 		time = 3000000;
409 
410 	fShowDelay = time;
411 }
412 
413 
414 bigtime_t
415 BToolTipManager::ShowDelay() const
416 {
417 	return fShowDelay;
418 }
419 
420 
421 void
422 BToolTipManager::SetHideDelay(bigtime_t time)
423 {
424 	// between 0 and 0.5s
425 	if (time < 0)
426 		time = 0;
427 	else if (time > 500000)
428 		time = 500000;
429 
430 	fHideDelay = time;
431 }
432 
433 
434 bigtime_t
435 BToolTipManager::HideDelay() const
436 {
437 	return fHideDelay;
438 }
439 
440 
441 BToolTipManager::BToolTipManager()
442 	:
443 	fLock("tool tip manager"),
444 	fShowDelay(750000),
445 	fHideDelay(50000)
446 {
447 }
448 
449 
450 BToolTipManager::~BToolTipManager()
451 {
452 }
453