1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/richtooltipg.cpp
3 // Purpose: Implementation of wxRichToolTip.
4 // Author: Vadim Zeitlin
7 // Copyright: (c) 2011 Vadim Zeitlin <vadim@wxwidgets.org>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 // for compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
29 #include "wx/dcmemory.h"
31 #include "wx/region.h"
32 #include "wx/settings.h"
34 #include "wx/statbmp.h"
35 #include "wx/stattext.h"
40 #include "wx/private/richtooltip.h"
41 #include "wx/generic/private/richtooltip.h"
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"
51 #include "wx/msw/uxtheme.h"
53 static const int TTP_BALLOONTITLE
= 4;
55 static const int TMT_TEXTCOLOR
= 3803;
56 static const int TMT_GRADIENTCOLOR1
= 3810;
57 static const int TMT_GRADIENTCOLOR2
= 3811;
60 // ----------------------------------------------------------------------------
61 // wxRichToolTipPopup: the popup window used by wxRichToolTip.
62 // ----------------------------------------------------------------------------
64 class wxRichToolTipPopup
:
65 public wxCustomBackgroundWindow
<wxPopupTransientWindow
>
68 wxRichToolTipPopup(wxWindow
* parent
,
69 const wxString
& title
,
70 const wxString
& message
,
73 const wxFont
& titleFont_
) :
76 Create(parent
, wxFRAME_SHAPED
);
79 wxBoxSizer
* const sizerTitle
= new wxBoxSizer(wxHORIZONTAL
);
82 sizerTitle
->Add(new wxStaticBitmap(this, wxID_ANY
, icon
),
83 wxSizerFlags().Centre().Border(wxRIGHT
));
85 //else: Simply don't show any icon.
87 wxStaticText
* const labelTitle
= new wxStaticText(this, wxID_ANY
, "");
88 labelTitle
->SetLabelText(title
);
90 wxFont
titleFont(titleFont_
);
91 if ( !titleFont
.IsOk() )
93 // Determine the appropriate title font for the current platform.
94 titleFont
= labelTitle
->GetFont();
97 // When using themes MSW tooltips use larger bluish version of the
99 wxUxThemeEngine
* const theme
= GetTooltipTheme();
102 titleFont
.MakeLarger();
105 if ( FAILED(theme
->GetThemeColor
107 wxUxThemeHandle(parent
, L
"TOOLTIP"),
114 // Use the standard value of this colour as fallback.
118 labelTitle
->SetForegroundColour(wxRGBToColour(c
));
123 // Everything else, including "classic" MSW look uses just the
124 // bold version of the base font.
125 titleFont
.MakeBold();
129 labelTitle
->SetFont(titleFont
);
130 sizerTitle
->Add(labelTitle
, wxSizerFlags().Centre());
132 wxBoxSizer
* const sizerTop
= new wxBoxSizer(wxVERTICAL
);
133 sizerTop
->Add(sizerTitle
,
134 wxSizerFlags().DoubleBorder(wxLEFT
|wxRIGHT
|wxTOP
));
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());
140 wxTextSizerWrapper
wrapper(this);
141 wxSizer
* sizerText
= wrapper
.CreateSizer(message
, -1 /* No wrapping */);
144 if ( icon
.IsOk() && GetTooltipTheme() )
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());
153 sizerText
= sizerTextIndent
;
156 sizerTop
->Add(sizerText
,
157 wxSizerFlags().DoubleBorder(wxLEFT
|wxRIGHT
|wxBOTTOM
)
162 const int offsetY
= SetTipShapeAndSize(tipKind
, GetBestSize());
165 // Offset our contents by the tip height to make it appear in the
167 sizerTop
->PrependSpacer(offsetY
);
173 void SetBackgroundColours(wxColour colStart
, wxColour colEnd
)
175 if ( !colStart
.IsOk() )
177 // Determine the best colour(s) to use on our own.
179 wxUxThemeEngine
* const theme
= GetTooltipTheme();
182 wxUxThemeHandle
hTheme(GetParent(), L
"TOOLTIP");
185 if ( FAILED(theme
->GetThemeColor
193 FAILED(theme
->GetThemeColor
206 colStart
= wxRGBToColour(c1
);
207 colEnd
= wxRGBToColour(c2
);
212 colStart
= wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK
);
218 // Use gradient-filled background bitmap.
219 const wxSize size
= GetClientSize();
224 dc
.GradientFillLinear(size
, colStart
, colEnd
, wxDOWN
);
227 SetBackgroundBitmap(bmp
);
229 else // Use solid colour.
231 SetBackgroundColour(colStart
);
237 wxPoint pos
= GetTipPoint();
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.
243 Move(pos
, wxSIZE_NO_ADJUSTMENTS
);
251 void SetTimeoutAndShow(unsigned timeout
, unsigned delay
)
253 if ( !timeout
&& !delay
)
259 Connect(wxEVT_TIMER
, wxTimerEventHandler(wxRichToolTipPopup::OnTimer
));
261 m_timeout
= timeout
; // set for use in OnTimer if we have a delay
262 m_delayShow
= delay
!= 0;
267 m_timer
.Start((delay
? delay
: timeout
), true /* one shot */);
271 virtual void OnDismiss()
278 // Returns non-NULL theme only if we're using Win7-style tooltips.
279 static wxUxThemeEngine
* GetTooltipTheme()
281 // Even themed applications under XP still use "classic" tooltips.
282 if ( wxGetWinVersion() <= wxWinVersion_XP
)
285 return wxUxThemeEngine::GetIfActive();
289 // For now we just hard code the tip height, would be nice to do something
290 // smarter in the future.
291 static int GetTipHeight()
294 if ( GetTooltipTheme() )
301 // Get the point to which our tip should point.
302 wxPoint
GetTipPoint() const
304 // Currently we always use the middle of the window. It seems that MSW
305 // native tooltips use a different point but it's not really clear how
306 // do they determine it nor whether it's worth the trouble to emulate
308 const wxRect r
= GetParent()->GetScreenRect();
309 return wxPoint(r
.x
+ r
.width
/2, r
.y
+ r
.height
/2);
312 // Choose the correct orientation depending on the window position.
314 // Also use the tip kind appropriate for the current environment. For MSW
315 // the right triangles are used and for Mac the equilateral ones as this is
316 // the prevailing kind under these systems. For everything else we go with
317 // right triangles as well but without any real rationale so this could be
318 // tweaked in the future.
319 wxTipKind
GetBestTipKind() const
321 const wxPoint pos
= GetTipPoint();
323 // Use GetFromWindow() and not GetFromPoint() here to try to get the
324 // correct display even if the tip point itself is not visible.
325 int dpy
= wxDisplay::GetFromWindow(GetParent());
326 if ( dpy
== wxNOT_FOUND
)
327 dpy
= 0; // What else can we do?
329 const wxRect rectDpy
= wxDisplay(dpy
).GetClientArea();
332 return pos
.y
> rectDpy
.height
/2 ? wxTipKind_Bottom
: wxTipKind_Top
;
334 return pos
.y
> rectDpy
.height
/2
335 ? pos
.x
> rectDpy
.width
/2
336 ? wxTipKind_BottomRight
337 : wxTipKind_BottomLeft
338 : pos
.x
> rectDpy
.width
/2
341 #endif // __WXMAC__/!__WXMAC__
344 // Set the size and shape of the tip window and returns the offset of its
345 // content area from the top (horizontal offset is always 0 currently).
346 int SetTipShapeAndSize(wxTipKind tipKind
, const wxSize
& contentSize
)
348 #if wxUSE_GRAPHICS_CONTEXT
349 wxSize size
= contentSize
;
351 // The size is the vertical size and the offset is the distance from
352 // edge for asymmetric tips, currently hard-coded to be the same as the
354 const int tipSize
= GetTipHeight();
355 const int tipOffset
= tipSize
;
357 // The horizontal position of the tip.
360 // The vertical coordinates of the tip base and apex.
364 // The offset of the content part of the window.
367 // Define symbolic names for the rectangle corners and mid-way points
368 // that we use below in an attempt to make the code more clear. Notice
369 // that these values must be consecutive as we iterate over them.
383 // The starting point for AddArcToPoint() calls below, we iterate over
384 // all RectPoints from it.
385 RectPoint pointStart
= RectPoint_Max
;
388 // Hard-coded radius of the round main rectangle corners.
389 const double RADIUS
= 5;
391 // Create a path defining the shape of the tooltip window.
393 path
= wxGraphicsRenderer::GetDefaultRenderer()->CreatePath();
395 if ( tipKind
== wxTipKind_Auto
)
396 tipKind
= GetBestTipKind();
398 // Points defining the tip shape (in clockwise order as we must end at
399 // tipPoints[0] after drawing the rectangle outline in this order).
400 wxPoint2DDouble tipPoints
[3];
405 wxFAIL_MSG( "Impossible kind value" );
408 case wxTipKind_TopLeft
:
414 tipPoints
[0] = wxPoint2DDouble(x
, yBase
);
415 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
416 tipPoints
[2] = wxPoint2DDouble(x
+ tipSize
, yBase
);
418 pointStart
= RectPoint_TopRight
;
421 case wxTipKind_TopRight
:
422 x
= size
.x
- tipOffset
;
427 tipPoints
[0] = wxPoint2DDouble(x
- tipSize
, yBase
);
428 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
429 tipPoints
[2] = wxPoint2DDouble(x
, yBase
);
431 pointStart
= RectPoint_TopRight
;
434 case wxTipKind_BottomLeft
:
436 yApex
= size
.y
+ tipSize
;
440 tipPoints
[0] = wxPoint2DDouble(x
+ tipSize
, yBase
);
441 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
442 tipPoints
[2] = wxPoint2DDouble(x
, yBase
);
444 pointStart
= RectPoint_BotLeft
;
447 case wxTipKind_BottomRight
:
448 x
= size
.x
- tipOffset
;
449 yApex
= size
.y
+ tipSize
;
453 tipPoints
[0] = wxPoint2DDouble(x
, yBase
);
454 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
455 tipPoints
[2] = wxPoint2DDouble(x
- tipSize
, yBase
);
457 pointStart
= RectPoint_BotLeft
;
467 // A half-side of an equilateral triangle is its altitude
468 // divided by sqrt(3) ~= 1.73.
469 const double halfside
= tipSize
/1.73;
471 tipPoints
[0] = wxPoint2DDouble(x
- halfside
, yBase
);
472 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
473 tipPoints
[2] = wxPoint2DDouble(x
+ halfside
, yBase
);
476 pointStart
= RectPoint_TopRight
;
479 case wxTipKind_Bottom
:
481 yApex
= size
.y
+ tipSize
;
486 const double halfside
= tipSize
/1.73;
488 tipPoints
[0] = wxPoint2DDouble(x
+ halfside
, yBase
);
489 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
490 tipPoints
[2] = wxPoint2DDouble(x
- halfside
, yBase
);
493 pointStart
= RectPoint_BotLeft
;
500 path
.AddRoundedRectangle(0, 0, size
.x
, size
.y
, RADIUS
);
504 wxASSERT_MSG( dy
!= -1, wxS("Unknown tip kind?") );
509 if ( tipKind
!= wxTipKind_None
)
511 path
.MoveToPoint(tipPoints
[0]);
512 path
.AddLineToPoint(tipPoints
[1]);
513 path
.AddLineToPoint(tipPoints
[2]);
515 const double xLeft
= 0.;
516 const double xMid
= size
.x
/2.;
517 const double xRight
= size
.x
;
519 const double yTop
= dy
;
520 const double yMid
= (dy
+ size
.y
)/2.;
521 const double yBot
= dy
+ contentSize
.y
;
523 wxPoint2DDouble rectPoints
[RectPoint_Max
];
524 rectPoints
[RectPoint_TopLeft
] = wxPoint2DDouble(xLeft
, yTop
);
525 rectPoints
[RectPoint_Top
] = wxPoint2DDouble(xMid
, yTop
);
526 rectPoints
[RectPoint_TopRight
] = wxPoint2DDouble(xRight
, yTop
);
527 rectPoints
[RectPoint_Right
] = wxPoint2DDouble(xRight
, yMid
);
528 rectPoints
[RectPoint_BotRight
] = wxPoint2DDouble(xRight
, yBot
);
529 rectPoints
[RectPoint_Bot
] = wxPoint2DDouble(xMid
, yBot
);
530 rectPoints
[RectPoint_BotLeft
] = wxPoint2DDouble(xLeft
, yBot
);
531 rectPoints
[RectPoint_Left
] = wxPoint2DDouble(xLeft
, yMid
);
533 // Iterate over all rectangle rectPoints for the first 3 corners.
534 unsigned n
= pointStart
;
535 for ( unsigned corner
= 0; corner
< 3; corner
++ )
537 const wxPoint2DDouble
& pt1
= rectPoints
[n
];
539 n
= (n
+ 1) % RectPoint_Max
;
541 const wxPoint2DDouble
& pt2
= rectPoints
[n
];
543 path
.AddArcToPoint(pt1
.m_x
, pt1
.m_y
, pt2
.m_x
, pt2
.m_y
, RADIUS
);
545 n
= (n
+ 1) % RectPoint_Max
;
548 // Last one wraps to the first point of the tip.
549 const wxPoint2DDouble
& pt1
= rectPoints
[n
];
550 const wxPoint2DDouble
& pt2
= tipPoints
[0];
552 path
.AddArcToPoint(pt1
.m_x
, pt1
.m_y
, pt2
.m_x
, pt2
.m_y
, RADIUS
);
558 #else // !wxUSE_GRAPHICS_CONTEXT
559 int x
= contentSize
.x
/2,
563 SetSize(contentSize
);
564 #endif // wxUSE_GRAPHICS_CONTEXT/!wxUSE_GRAPHICS_CONTEXT
567 m_anchorPos
.y
= yApex
;
572 // Timer event handler hides the tooltip when the timeout expires.
573 void OnTimer(wxTimerEvent
& WXUNUSED(event
))
577 // Doing "Notify" here ensures that our OnDismiss() is called and so we
578 // also Destroy() ourselves. We could use Dismiss() and call Destroy()
579 // explicitly from here as well.
588 m_timer
.Start(m_timeout
, true);
594 // The anchor point offset if we show a tip or the middle of the top side
598 // The timer counting down the time until we're hidden.
601 // We will need to accesss the timeout period when delaying showing tooltip.
604 // If true, delay showing the tooltip.
607 wxDECLARE_NO_COPY_CLASS(wxRichToolTipPopup
);
610 // ----------------------------------------------------------------------------
611 // wxRichToolTipGenericImpl: generic implementation of wxRichToolTip.
612 // ----------------------------------------------------------------------------
615 wxRichToolTipGenericImpl::SetBackgroundColour(const wxColour
& col
,
616 const wxColour
& colEnd
)
622 void wxRichToolTipGenericImpl::SetCustomIcon(const wxIcon
& icon
)
627 void wxRichToolTipGenericImpl::SetStandardIcon(int icon
)
629 switch ( icon
& wxICON_MASK
)
633 case wxICON_INFORMATION
:
634 // Although we don't use this icon in a list, we need a smallish
635 // icon here and not an icon of a typical message box size so use
636 // wxART_LIST to get it.
637 m_icon
= wxArtProvider::GetIcon
639 wxArtProvider::GetMessageBoxIconId(icon
),
644 case wxICON_QUESTION
:
645 wxFAIL_MSG("Question icon doesn't make sense for a tooltip");
654 void wxRichToolTipGenericImpl::SetTimeout(unsigned millisecondsTimeout
,
655 unsigned millisecondsDelay
)
657 m_delay
= millisecondsDelay
;
658 m_timeout
= millisecondsTimeout
;
661 void wxRichToolTipGenericImpl::SetTipKind(wxTipKind tipKind
)
666 void wxRichToolTipGenericImpl::SetTitleFont(const wxFont
& font
)
671 void wxRichToolTipGenericImpl::ShowFor(wxWindow
* win
)
673 // Set the focus to the window the tooltip refers to to make it look active.
676 wxRichToolTipPopup
* const popup
= new wxRichToolTipPopup
686 popup
->SetBackgroundColours(m_colStart
, m_colEnd
);
688 popup
->SetPosition();
689 // show or start the timer to delay showing the popup
690 popup
->SetTimeoutAndShow( m_timeout
, m_delay
);
693 // Currently only wxMSW provides a native implementation.
698 wxRichToolTipImpl::Create(const wxString
& title
, const wxString
& message
)
700 return new wxRichToolTipGenericImpl(title
, message
);
705 #endif // wxUSE_RICHTOOLTIP