]> git.saurik.com Git - wxWidgets.git/blob - src/generic/richtooltipg.cpp
new file added
[wxWidgets.git] / src / generic / richtooltipg.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/richtooltipg.cpp
3 // Purpose: Implementation of wxRichToolTip.
4 // Author: Vadim Zeitlin
5 // Created: 2011-10-07
6 // RCS-ID: $Id: wxhead.cpp,v 1.11 2010-04-22 12:44:51 zeitlin Exp $
7 // Copyright: (c) 2011 Vadim Zeitlin <vadim@wxwidgets.org>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // for compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_RICHTOOLTIP
27
28 #ifndef WX_PRECOMP
29 #include "wx/dcmemory.h"
30 #include "wx/icon.h"
31 #include "wx/region.h"
32 #include "wx/settings.h"
33 #include "wx/sizer.h"
34 #include "wx/statbmp.h"
35 #include "wx/stattext.h"
36 #include "wx/timer.h"
37 #include "wx/utils.h"
38 #endif // WX_PRECOMP
39
40 #include "wx/private/richtooltip.h"
41 #include "wx/generic/private/richtooltip.h"
42
43 #include "wx/artprov.h"
44 #include "wx/custombgwin.h"
45 #include "wx/display.h"
46 #include "wx/graphics.h"
47 #include "wx/popupwin.h"
48 #include "wx/textwrapper.h"
49
50 #ifdef __WXMSW__
51 #include "wx/msw/uxtheme.h"
52
53 static const int TTP_BALLOONTITLE = 4;
54
55 static const int TMT_TEXTCOLOR = 3803;
56 static const int TMT_GRADIENTCOLOR1 = 3810;
57 static const int TMT_GRADIENTCOLOR2 = 3811;
58 #endif
59
60 // ----------------------------------------------------------------------------
61 // wxRichToolTipPopup: the popup window used by wxRichToolTip.
62 // ----------------------------------------------------------------------------
63
64 class wxRichToolTipPopup :
65 public wxCustomBackgroundWindow<wxPopupTransientWindow>
66 {
67 public:
68 wxRichToolTipPopup(wxWindow* parent,
69 const wxString& title,
70 const wxString& message,
71 const wxIcon& icon,
72 wxTipKind tipKind,
73 const wxFont& titleFont_) :
74 m_timer(this)
75 {
76 Create(parent, wxFRAME_SHAPED);
77
78
79 wxBoxSizer* const sizerTitle = new wxBoxSizer(wxHORIZONTAL);
80 if ( icon.IsOk() )
81 {
82 sizerTitle->Add(new wxStaticBitmap(this, wxID_ANY, icon),
83 wxSizerFlags().Centre().Border(wxRIGHT));
84 }
85 //else: Simply don't show any icon.
86
87 wxStaticText* const labelTitle = new wxStaticText(this, wxID_ANY, "");
88 labelTitle->SetLabelText(title);
89
90 wxFont titleFont(titleFont_);
91 if ( !titleFont.IsOk() )
92 {
93 // Determine the appropriate title font for the current platform.
94 titleFont = labelTitle->GetFont();
95
96 #ifdef __WXMSW__
97 // When using themes MSW tooltips use larger bluish version of the
98 // normal font.
99 wxUxThemeEngine* const theme = GetTooltipTheme();
100 if ( theme )
101 {
102 titleFont.MakeLarger();
103
104 COLORREF c;
105 if ( FAILED(theme->GetThemeColor
106 (
107 wxUxThemeHandle(parent, L"TOOLTIP"),
108 TTP_BALLOONTITLE,
109 0,
110 TMT_TEXTCOLOR,
111 &c
112 )) )
113 {
114 // Use the standard value of this colour as fallback.
115 c = 0x993300;
116 }
117
118 labelTitle->SetForegroundColour(wxRGBToColour(c));
119 }
120 else
121 #endif // __WXMSW__
122 {
123 // Everything else, including "classic" MSW look uses just the
124 // bold version of the base font.
125 titleFont.MakeBold();
126 }
127 }
128
129 labelTitle->SetFont(titleFont);
130 sizerTitle->Add(labelTitle, wxSizerFlags().Centre());
131
132 wxBoxSizer* const sizerTop = new wxBoxSizer(wxVERTICAL);
133 sizerTop->Add(sizerTitle,
134 wxSizerFlags().DoubleBorder(wxLEFT|wxRIGHT|wxTOP));
135
136 // Use a spacer as we don't want to have a double border between the
137 // elements, just a simple one will do.
138 sizerTop->AddSpacer(wxSizerFlags::GetDefaultBorder());
139
140 wxTextSizerWrapper wrapper(this);
141 wxSizer* sizerText = wrapper.CreateSizer(message, -1 /* No wrapping */);
142
143 #ifdef __WXMSW__
144 if ( icon.IsOk() && GetTooltipTheme() )
145 {
146 // Themed tooltips under MSW align the text with the title, not
147 // with the icon, so use a helper horizontal sizer in this case.
148 wxBoxSizer* const sizerTextIndent = new wxBoxSizer(wxHORIZONTAL);
149 sizerTextIndent->AddSpacer(icon.GetWidth());
150 sizerTextIndent->Add(sizerText,
151 wxSizerFlags().Border(wxLEFT).Centre());
152
153 sizerText = sizerTextIndent;
154 }
155 #endif // !__WXMSW__
156 sizerTop->Add(sizerText,
157 wxSizerFlags().DoubleBorder(wxLEFT|wxRIGHT|wxBOTTOM)
158 .Centre());
159
160 SetSizer(sizerTop);
161
162 const int offsetY = SetTipShapeAndSize(tipKind, GetBestSize());
163 if ( offsetY > 0 )
164 {
165 // Offset our contents by the tip height to make it appear in the
166 // main rectangle.
167 sizerTop->PrependSpacer(offsetY);
168 }
169
170 Layout();
171 }
172
173 void SetBackgroundColours(wxColour colStart, wxColour colEnd)
174 {
175 if ( !colStart.IsOk() )
176 {
177 // Determine the best colour(s) to use on our own.
178 #ifdef __WXMSW__
179 wxUxThemeEngine* const theme = GetTooltipTheme();
180 if ( theme )
181 {
182 wxUxThemeHandle hTheme(GetParent(), L"TOOLTIP");
183
184 COLORREF c1, c2;
185 if ( FAILED(theme->GetThemeColor
186 (
187 hTheme,
188 TTP_BALLOONTITLE,
189 0,
190 TMT_GRADIENTCOLOR1,
191 &c1
192 )) ||
193 FAILED(theme->GetThemeColor
194 (
195 hTheme,
196 TTP_BALLOONTITLE,
197 0,
198 TMT_GRADIENTCOLOR2,
199 &c2
200 )) )
201 {
202 c1 = 0xffffff;
203 c2 = 0xf0e5e4;
204 }
205
206 colStart = wxRGBToColour(c1);
207 colEnd = wxRGBToColour(c2);
208 }
209 else
210 #endif // __WXMSW__
211 {
212 colStart = wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK);
213 }
214 }
215
216 if ( colEnd.IsOk() )
217 {
218 // Use gradient-filled background bitmap.
219 const wxSize size = GetClientSize();
220 wxBitmap bmp(size);
221 {
222 wxMemoryDC dc(bmp);
223 dc.Clear();
224 dc.GradientFillLinear(size, colStart, colEnd, wxDOWN);
225 }
226
227 SetBackgroundBitmap(bmp);
228 }
229 else // Use solid colour.
230 {
231 SetBackgroundColour(colStart);
232 }
233 }
234
235 void DoShow()
236 {
237 wxPoint pos = GetTipPoint();
238
239 // We want our anchor point to coincide with this position so offset
240 // the position of the top left corner passed to Move() accordingly.
241 pos -= m_anchorPos;
242
243 Move(pos, wxSIZE_NO_ADJUSTMENTS);
244
245 Popup();
246 }
247
248 void SetTimeout(unsigned timeout)
249 {
250 if ( !timeout )
251 return;
252
253 Connect(wxEVT_TIMER, wxTimerEventHandler(wxRichToolTipPopup::OnTimer));
254
255 m_timer.Start(timeout, true /* one shot */);
256 }
257
258 protected:
259 virtual void OnDismiss()
260 {
261 Destroy();
262 }
263
264 private:
265 #ifdef __WXMSW__
266 // Returns non-NULL theme only if we're using Win7-style tooltips.
267 static wxUxThemeEngine* GetTooltipTheme()
268 {
269 // Even themed applications under XP still use "classic" tooltips.
270 if ( wxGetWinVersion() <= wxWinVersion_XP )
271 return NULL;
272
273 return wxUxThemeEngine::GetIfActive();
274 }
275 #endif // __WXMSW__
276
277 // For now we just hard code the tip height, would be nice to do something
278 // smarter in the future.
279 static int GetTipHeight()
280 {
281 #ifdef __WXMSW__
282 if ( GetTooltipTheme() )
283 return 20;
284 #endif // __WXMSW__
285
286 return 15;
287 }
288
289 // Get the point to which our tip should point.
290 wxPoint GetTipPoint() const
291 {
292 // Currently we always use the middle of the window. It seems that MSW
293 // native tooltips use a different point but it's not really clear how
294 // do they determine it nor whether it's worth the trouble to emulate
295 // their behaviour.
296 const wxRect r = GetParent()->GetScreenRect();
297 return wxPoint(r.x + r.width/2, r.y + r.height/2);
298 }
299
300 // Choose the correct orientation depending on the window position.
301 //
302 // Also use the tip kind appropriate for the current environment. For MSW
303 // the right triangles are used and for Mac the equilateral ones as this is
304 // the prevailing kind under these systems. For everything else we go with
305 // right triangles as well but without any real rationale so this could be
306 // tweaked in the future.
307 wxTipKind GetBestTipKind() const
308 {
309 const wxPoint pos = GetTipPoint();
310
311 // Use GetFromWindow() and not GetFromPoint() here to try to get the
312 // correct display even if the tip point itself is not visible.
313 int dpy = wxDisplay::GetFromWindow(GetParent());
314 if ( dpy == wxNOT_FOUND )
315 dpy = 0; // What else can we do?
316
317 const wxRect rectDpy = wxDisplay(dpy).GetClientArea();
318
319 #ifdef __WXMAC__
320 return pos.y > rectDpy.height/2 ? wxTipKind_Bottom : wxTipKind_Top;
321 #else // !__WXMAC__
322 return pos.y > rectDpy.height/2
323 ? pos.x > rectDpy.width/2
324 ? wxTipKind_BottomRight
325 : wxTipKind_BottomLeft
326 : pos.x > rectDpy.width/2
327 ? wxTipKind_TopRight
328 : wxTipKind_TopLeft;
329 #endif // __WXMAC__/!__WXMAC__
330 }
331
332 // Set the size and shape of the tip window and returns the offset of its
333 // content area from the top (horizontal offset is always 0 currently).
334 int SetTipShapeAndSize(wxTipKind tipKind, const wxSize& contentSize)
335 {
336 #if wxUSE_GRAPHICS_CONTEXT
337 wxSize size = contentSize;
338
339 // The size is the vertical size and the offset is the distance from
340 // edge for asymmetric tips, currently hard-coded to be the same as the
341 // size.
342 const int tipSize = GetTipHeight();
343 const int tipOffset = tipSize;
344
345 // The horizontal position of the tip.
346 int x = -1;
347
348 // The vertical coordinates of the tip base and apex.
349 int yBase = -1,
350 yApex = -1;
351
352 // The offset of the content part of the window.
353 int dy = -1;
354
355 // Define symbolic names for the rectangle corners and mid-way points
356 // that we use below in an attempt to make the code more clear. Notice
357 // that these values must be consecutive as we iterate over them.
358 enum RectPoint
359 {
360 RectPoint_TopLeft,
361 RectPoint_Top,
362 RectPoint_TopRight,
363 RectPoint_Right,
364 RectPoint_BotRight,
365 RectPoint_Bot,
366 RectPoint_BotLeft,
367 RectPoint_Left,
368 RectPoint_Max
369 };
370
371 // The starting point for AddArcToPoint() calls below, we iterate over
372 // all RectPoints from it.
373 RectPoint pointStart = RectPoint_Max;
374
375
376 // Hard-coded radius of the round main rectangle corners.
377 const double RADIUS = 5;
378
379 // Create a path defining the shape of the tooltip window.
380 wxGraphicsPath
381 path = wxGraphicsRenderer::GetDefaultRenderer()->CreatePath();
382
383 if ( tipKind == wxTipKind_Auto )
384 tipKind = GetBestTipKind();
385
386 // Points defining the tip shape (in clockwise order as we must end at
387 // tipPoints[0] after drawing the rectangle outline in this order).
388 wxPoint2DDouble tipPoints[3];
389
390 switch ( tipKind )
391 {
392 case wxTipKind_Auto:
393 wxFAIL_MSG( "Impossible kind value" );
394 break;
395
396 case wxTipKind_TopLeft:
397 x = tipOffset;
398 yApex = 0;
399 yBase = tipSize;
400 dy = tipSize;
401
402 tipPoints[0] = wxPoint2DDouble(x, yBase);
403 tipPoints[1] = wxPoint2DDouble(x, yApex);
404 tipPoints[2] = wxPoint2DDouble(x + tipSize, yBase);
405
406 pointStart = RectPoint_TopRight;
407 break;
408
409 case wxTipKind_TopRight:
410 x = size.x - tipOffset;
411 yApex = 0;
412 yBase = tipSize;
413 dy = tipSize;
414
415 tipPoints[0] = wxPoint2DDouble(x - tipSize, yBase);
416 tipPoints[1] = wxPoint2DDouble(x, yApex);
417 tipPoints[2] = wxPoint2DDouble(x, yBase);
418
419 pointStart = RectPoint_TopRight;
420 break;
421
422 case wxTipKind_BottomLeft:
423 x = tipOffset;
424 yApex = size.y + tipSize;
425 yBase = size.y;
426 dy = 0;
427
428 tipPoints[0] = wxPoint2DDouble(x + tipSize, yBase);
429 tipPoints[1] = wxPoint2DDouble(x, yApex);
430 tipPoints[2] = wxPoint2DDouble(x, yBase);
431
432 pointStart = RectPoint_BotLeft;
433 break;
434
435 case wxTipKind_BottomRight:
436 x = size.x - tipOffset;
437 yApex = size.y + tipSize;
438 yBase = size.y;
439 dy = 0;
440
441 tipPoints[0] = wxPoint2DDouble(x, yBase);
442 tipPoints[1] = wxPoint2DDouble(x, yApex);
443 tipPoints[2] = wxPoint2DDouble(x - tipSize, yBase);
444
445 pointStart = RectPoint_BotLeft;
446 break;
447
448 case wxTipKind_Top:
449 x = size.x/2;
450 yApex = 0;
451 yBase = tipSize;
452 dy = tipSize;
453
454 {
455 // A half-side of an equilateral triangle is its altitude
456 // divided by sqrt(3) ~= 1.73.
457 const double halfside = tipSize/1.73;
458
459 tipPoints[0] = wxPoint2DDouble(x - halfside, yBase);
460 tipPoints[1] = wxPoint2DDouble(x, yApex);
461 tipPoints[2] = wxPoint2DDouble(x + halfside, yBase);
462 }
463
464 pointStart = RectPoint_TopRight;
465 break;
466
467 case wxTipKind_Bottom:
468 x = size.x/2;
469 yApex = size.y + tipSize;
470 yBase = size.y;
471 dy = 0;
472
473 {
474 const double halfside = tipSize/1.73;
475
476 tipPoints[0] = wxPoint2DDouble(x + halfside, yBase);
477 tipPoints[1] = wxPoint2DDouble(x, yApex);
478 tipPoints[2] = wxPoint2DDouble(x - halfside, yBase);
479 }
480
481 pointStart = RectPoint_BotLeft;
482 break;
483
484 case wxTipKind_None:
485 x = size.x/2;
486 dy = 0;
487
488 path.AddRoundedRectangle(0, 0, size.x, size.y, RADIUS);
489 break;
490 }
491
492 wxASSERT_MSG( dy != -1, wxS("Unknown tip kind?") );
493
494 size.y += tipSize;
495 SetSize(size);
496
497 if ( tipKind != wxTipKind_None )
498 {
499 path.MoveToPoint(tipPoints[0]);
500 path.AddLineToPoint(tipPoints[1]);
501 path.AddLineToPoint(tipPoints[2]);
502
503 const double xLeft = 0.;
504 const double xMid = size.x/2.;
505 const double xRight = size.x;
506
507 const double yTop = dy;
508 const double yMid = (dy + size.y)/2.;
509 const double yBot = dy + contentSize.y;
510
511 wxPoint2DDouble rectPoints[RectPoint_Max];
512 rectPoints[RectPoint_TopLeft] = wxPoint2DDouble(xLeft, yTop);
513 rectPoints[RectPoint_Top] = wxPoint2DDouble(xMid, yTop);
514 rectPoints[RectPoint_TopRight] = wxPoint2DDouble(xRight, yTop);
515 rectPoints[RectPoint_Right] = wxPoint2DDouble(xRight, yMid);
516 rectPoints[RectPoint_BotRight] = wxPoint2DDouble(xRight, yBot);
517 rectPoints[RectPoint_Bot] = wxPoint2DDouble(xMid, yBot);
518 rectPoints[RectPoint_BotLeft] = wxPoint2DDouble(xLeft, yBot);
519 rectPoints[RectPoint_Left] = wxPoint2DDouble(xLeft, yMid);
520
521 // Iterate over all rectangle rectPoints for the first 3 corners.
522 unsigned n = pointStart;
523 for ( unsigned corner = 0; corner < 3; corner++ )
524 {
525 const wxPoint2DDouble& pt1 = rectPoints[n];
526
527 n = (n + 1) % RectPoint_Max;
528
529 const wxPoint2DDouble& pt2 = rectPoints[n];
530
531 path.AddArcToPoint(pt1.m_x, pt1.m_y, pt2.m_x, pt2.m_y, RADIUS);
532
533 n = (n + 1) % RectPoint_Max;
534 }
535
536 // Last one wraps to the first point of the tip.
537 const wxPoint2DDouble& pt1 = rectPoints[n];
538 const wxPoint2DDouble& pt2 = tipPoints[0];
539
540 path.AddArcToPoint(pt1.m_x, pt1.m_y, pt2.m_x, pt2.m_y, RADIUS);
541
542 path.CloseSubpath();
543 }
544
545 SetShape(path);
546 #else // !wxUSE_GRAPHICS_CONTEXT
547 int x = contentSize.x/2,
548 yApex = 0,
549 dy = 0;
550
551 SetSize(contentSize);
552 #endif // wxUSE_GRAPHICS_CONTEXT/!wxUSE_GRAPHICS_CONTEXT
553
554 m_anchorPos.x = x;
555 m_anchorPos.y = yApex;
556
557 return dy;
558 }
559
560 // Timer event handler hides the tooltip when the timeout expires.
561 void OnTimer(wxTimerEvent& WXUNUSED(event))
562 {
563 // Doing "Notify" here ensures that our OnDismiss() is called and so we
564 // also Destroy() ourselves. We could use Dismiss() and call Destroy()
565 // explicitly from here as well.
566 DismissAndNotify();
567 }
568
569
570 // The anchor point offset if we show a tip or the middle of the top side
571 // otherwise.
572 wxPoint m_anchorPos;
573
574 // The timer counting down the time until we're hidden.
575 wxTimer m_timer;
576
577 wxDECLARE_NO_COPY_CLASS(wxRichToolTipPopup);
578 };
579
580 // ----------------------------------------------------------------------------
581 // wxRichToolTipGenericImpl: generic implementation of wxRichToolTip.
582 // ----------------------------------------------------------------------------
583
584 void
585 wxRichToolTipGenericImpl::SetBackgroundColour(const wxColour& col,
586 const wxColour& colEnd)
587 {
588 m_colStart = col;
589 m_colEnd = colEnd;
590 }
591
592 void wxRichToolTipGenericImpl::SetCustomIcon(const wxIcon& icon)
593 {
594 m_icon = icon;
595 }
596
597 void wxRichToolTipGenericImpl::SetStandardIcon(int icon)
598 {
599 switch ( icon & wxICON_MASK )
600 {
601 case wxICON_WARNING:
602 case wxICON_ERROR:
603 case wxICON_INFORMATION:
604 // Although we don't use this icon in a list, we need a smallish
605 // icon here and not an icon of a typical message box size so use
606 // wxART_LIST to get it.
607 m_icon = wxArtProvider::GetIcon
608 (
609 wxArtProvider::GetMessageBoxIconId(icon),
610 wxART_LIST
611 );
612 break;
613
614 case wxICON_QUESTION:
615 wxFAIL_MSG("Question icon doesn't make sense for a tooltip");
616 break;
617
618 case wxICON_NONE:
619 m_icon = wxNullIcon;
620 break;
621 }
622 }
623
624 void wxRichToolTipGenericImpl::SetTimeout(unsigned milliseconds)
625 {
626 m_timeout = milliseconds;
627 }
628
629 void wxRichToolTipGenericImpl::SetTipKind(wxTipKind tipKind)
630 {
631 m_tipKind = tipKind;
632 }
633
634 void wxRichToolTipGenericImpl::SetTitleFont(const wxFont& font)
635 {
636 m_titleFont = font;
637 }
638
639 void wxRichToolTipGenericImpl::ShowFor(wxWindow* win)
640 {
641 // Set the focus to the window the tooltip refers to to make it look active.
642 win->SetFocus();
643
644 wxRichToolTipPopup* const popup = new wxRichToolTipPopup
645 (
646 win,
647 m_title,
648 m_message,
649 m_icon,
650 m_tipKind,
651 m_titleFont
652 );
653
654 popup->SetBackgroundColours(m_colStart, m_colEnd);
655
656 popup->DoShow();
657
658 popup->SetTimeout(m_timeout);
659 }
660
661 // Currently only wxMSW provides a native implementation.
662 #ifndef __WXMSW__
663
664 /* static */
665 wxRichToolTipImpl*
666 wxRichToolTipImpl::Create(const wxString& title, const wxString& message)
667 {
668 return new wxRichToolTipGenericImpl(title, message);
669 }
670
671 #endif // !__WXMSW__
672
673 #endif // wxUSE_RICHTOOLTIP