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
);
235 void SetPosition(const wxRect
* rect
)
239 if ( !rect
|| rect
->IsEmpty() )
242 pos
= GetParent()->ClientToScreen( wxPoint( rect
->x
+ rect
->width
/ 2, rect
->y
+ rect
->height
/ 2 ) );
244 // We want our anchor point to coincide with this position so offset
245 // the position of the top left corner passed to Move() accordingly.
248 Move(pos
, wxSIZE_NO_ADJUSTMENTS
);
256 void SetTimeoutAndShow(unsigned timeout
, unsigned delay
)
258 if ( !timeout
&& !delay
)
264 Connect(wxEVT_TIMER
, wxTimerEventHandler(wxRichToolTipPopup::OnTimer
));
266 m_timeout
= timeout
; // set for use in OnTimer if we have a delay
267 m_delayShow
= delay
!= 0;
272 m_timer
.Start((delay
? delay
: timeout
), true /* one shot */);
276 virtual void OnDismiss()
283 // Returns non-NULL theme only if we're using Win7-style tooltips.
284 static wxUxThemeEngine
* GetTooltipTheme()
286 // Even themed applications under XP still use "classic" tooltips.
287 if ( wxGetWinVersion() <= wxWinVersion_XP
)
290 return wxUxThemeEngine::GetIfActive();
294 // For now we just hard code the tip height, would be nice to do something
295 // smarter in the future.
296 static int GetTipHeight()
299 if ( GetTooltipTheme() )
306 // Get the point to which our tip should point.
307 wxPoint
GetTipPoint() const
309 // Currently we always use the middle of the window. It seems that MSW
310 // native tooltips use a different point but it's not really clear how
311 // do they determine it nor whether it's worth the trouble to emulate
313 const wxRect r
= GetParent()->GetScreenRect();
314 return wxPoint(r
.x
+ r
.width
/2, r
.y
+ r
.height
/2);
317 // Choose the correct orientation depending on the window position.
319 // Also use the tip kind appropriate for the current environment. For MSW
320 // the right triangles are used and for Mac the equilateral ones as this is
321 // the prevailing kind under these systems. For everything else we go with
322 // right triangles as well but without any real rationale so this could be
323 // tweaked in the future.
324 wxTipKind
GetBestTipKind() const
326 const wxPoint pos
= GetTipPoint();
328 // Use GetFromWindow() and not GetFromPoint() here to try to get the
329 // correct display even if the tip point itself is not visible.
330 int dpy
= wxDisplay::GetFromWindow(GetParent());
331 if ( dpy
== wxNOT_FOUND
)
332 dpy
= 0; // What else can we do?
334 const wxRect rectDpy
= wxDisplay(dpy
).GetClientArea();
337 return pos
.y
> rectDpy
.height
/2 ? wxTipKind_Bottom
: wxTipKind_Top
;
339 return pos
.y
> rectDpy
.height
/2
340 ? pos
.x
> rectDpy
.width
/2
341 ? wxTipKind_BottomRight
342 : wxTipKind_BottomLeft
343 : pos
.x
> rectDpy
.width
/2
346 #endif // __WXMAC__/!__WXMAC__
349 // Set the size and shape of the tip window and returns the offset of its
350 // content area from the top (horizontal offset is always 0 currently).
351 int SetTipShapeAndSize(wxTipKind tipKind
, const wxSize
& contentSize
)
353 #if wxUSE_GRAPHICS_CONTEXT
354 wxSize size
= contentSize
;
356 // The size is the vertical size and the offset is the distance from
357 // edge for asymmetric tips, currently hard-coded to be the same as the
359 const int tipSize
= GetTipHeight();
360 const int tipOffset
= tipSize
;
362 // The horizontal position of the tip.
365 // The vertical coordinates of the tip base and apex.
369 // The offset of the content part of the window.
372 // Define symbolic names for the rectangle corners and mid-way points
373 // that we use below in an attempt to make the code more clear. Notice
374 // that these values must be consecutive as we iterate over them.
388 // The starting point for AddArcToPoint() calls below, we iterate over
389 // all RectPoints from it.
390 RectPoint pointStart
= RectPoint_Max
;
393 // Hard-coded radius of the round main rectangle corners.
394 const double RADIUS
= 5;
396 // Create a path defining the shape of the tooltip window.
398 path
= wxGraphicsRenderer::GetDefaultRenderer()->CreatePath();
400 if ( tipKind
== wxTipKind_Auto
)
401 tipKind
= GetBestTipKind();
403 // Points defining the tip shape (in clockwise order as we must end at
404 // tipPoints[0] after drawing the rectangle outline in this order).
405 wxPoint2DDouble tipPoints
[3];
410 wxFAIL_MSG( "Impossible kind value" );
413 case wxTipKind_TopLeft
:
419 tipPoints
[0] = wxPoint2DDouble(x
, yBase
);
420 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
421 tipPoints
[2] = wxPoint2DDouble(x
+ tipSize
, yBase
);
423 pointStart
= RectPoint_TopRight
;
426 case wxTipKind_TopRight
:
427 x
= size
.x
- tipOffset
;
432 tipPoints
[0] = wxPoint2DDouble(x
- tipSize
, yBase
);
433 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
434 tipPoints
[2] = wxPoint2DDouble(x
, yBase
);
436 pointStart
= RectPoint_TopRight
;
439 case wxTipKind_BottomLeft
:
441 yApex
= size
.y
+ tipSize
;
445 tipPoints
[0] = wxPoint2DDouble(x
+ tipSize
, yBase
);
446 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
447 tipPoints
[2] = wxPoint2DDouble(x
, yBase
);
449 pointStart
= RectPoint_BotLeft
;
452 case wxTipKind_BottomRight
:
453 x
= size
.x
- tipOffset
;
454 yApex
= size
.y
+ tipSize
;
458 tipPoints
[0] = wxPoint2DDouble(x
, yBase
);
459 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
460 tipPoints
[2] = wxPoint2DDouble(x
- tipSize
, yBase
);
462 pointStart
= RectPoint_BotLeft
;
472 // A half-side of an equilateral triangle is its altitude
473 // divided by sqrt(3) ~= 1.73.
474 const double halfside
= tipSize
/1.73;
476 tipPoints
[0] = wxPoint2DDouble(x
- halfside
, yBase
);
477 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
478 tipPoints
[2] = wxPoint2DDouble(x
+ halfside
, yBase
);
481 pointStart
= RectPoint_TopRight
;
484 case wxTipKind_Bottom
:
486 yApex
= size
.y
+ tipSize
;
491 const double halfside
= tipSize
/1.73;
493 tipPoints
[0] = wxPoint2DDouble(x
+ halfside
, yBase
);
494 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
495 tipPoints
[2] = wxPoint2DDouble(x
- halfside
, yBase
);
498 pointStart
= RectPoint_BotLeft
;
505 path
.AddRoundedRectangle(0, 0, size
.x
, size
.y
, RADIUS
);
509 wxASSERT_MSG( dy
!= -1, wxS("Unknown tip kind?") );
514 if ( tipKind
!= wxTipKind_None
)
516 path
.MoveToPoint(tipPoints
[0]);
517 path
.AddLineToPoint(tipPoints
[1]);
518 path
.AddLineToPoint(tipPoints
[2]);
520 const double xLeft
= 0.;
521 const double xMid
= size
.x
/2.;
522 const double xRight
= size
.x
;
524 const double yTop
= dy
;
525 const double yMid
= (dy
+ size
.y
)/2.;
526 const double yBot
= dy
+ contentSize
.y
;
528 wxPoint2DDouble rectPoints
[RectPoint_Max
];
529 rectPoints
[RectPoint_TopLeft
] = wxPoint2DDouble(xLeft
, yTop
);
530 rectPoints
[RectPoint_Top
] = wxPoint2DDouble(xMid
, yTop
);
531 rectPoints
[RectPoint_TopRight
] = wxPoint2DDouble(xRight
, yTop
);
532 rectPoints
[RectPoint_Right
] = wxPoint2DDouble(xRight
, yMid
);
533 rectPoints
[RectPoint_BotRight
] = wxPoint2DDouble(xRight
, yBot
);
534 rectPoints
[RectPoint_Bot
] = wxPoint2DDouble(xMid
, yBot
);
535 rectPoints
[RectPoint_BotLeft
] = wxPoint2DDouble(xLeft
, yBot
);
536 rectPoints
[RectPoint_Left
] = wxPoint2DDouble(xLeft
, yMid
);
538 // Iterate over all rectangle rectPoints for the first 3 corners.
539 unsigned n
= pointStart
;
540 for ( unsigned corner
= 0; corner
< 3; corner
++ )
542 const wxPoint2DDouble
& pt1
= rectPoints
[n
];
544 n
= (n
+ 1) % RectPoint_Max
;
546 const wxPoint2DDouble
& pt2
= rectPoints
[n
];
548 path
.AddArcToPoint(pt1
.m_x
, pt1
.m_y
, pt2
.m_x
, pt2
.m_y
, RADIUS
);
550 n
= (n
+ 1) % RectPoint_Max
;
553 // Last one wraps to the first point of the tip.
554 const wxPoint2DDouble
& pt1
= rectPoints
[n
];
555 const wxPoint2DDouble
& pt2
= tipPoints
[0];
557 path
.AddArcToPoint(pt1
.m_x
, pt1
.m_y
, pt2
.m_x
, pt2
.m_y
, RADIUS
);
563 #else // !wxUSE_GRAPHICS_CONTEXT
564 wxUnusedVar(tipKind
);
566 int x
= contentSize
.x
/2,
570 SetSize(contentSize
);
571 #endif // wxUSE_GRAPHICS_CONTEXT/!wxUSE_GRAPHICS_CONTEXT
574 m_anchorPos
.y
= yApex
;
579 // Timer event handler hides the tooltip when the timeout expires.
580 void OnTimer(wxTimerEvent
& WXUNUSED(event
))
584 // Doing "Notify" here ensures that our OnDismiss() is called and so we
585 // also Destroy() ourselves. We could use Dismiss() and call Destroy()
586 // explicitly from here as well.
595 m_timer
.Start(m_timeout
, true);
601 // The anchor point offset if we show a tip or the middle of the top side
605 // The timer counting down the time until we're hidden.
608 // We will need to accesss the timeout period when delaying showing tooltip.
611 // If true, delay showing the tooltip.
614 wxDECLARE_NO_COPY_CLASS(wxRichToolTipPopup
);
617 // ----------------------------------------------------------------------------
618 // wxRichToolTipGenericImpl: generic implementation of wxRichToolTip.
619 // ----------------------------------------------------------------------------
622 wxRichToolTipGenericImpl::SetBackgroundColour(const wxColour
& col
,
623 const wxColour
& colEnd
)
629 void wxRichToolTipGenericImpl::SetCustomIcon(const wxIcon
& icon
)
634 void wxRichToolTipGenericImpl::SetStandardIcon(int icon
)
636 switch ( icon
& wxICON_MASK
)
640 case wxICON_INFORMATION
:
641 // Although we don't use this icon in a list, we need a smallish
642 // icon here and not an icon of a typical message box size so use
643 // wxART_LIST to get it.
644 m_icon
= wxArtProvider::GetIcon
646 wxArtProvider::GetMessageBoxIconId(icon
),
651 case wxICON_QUESTION
:
652 wxFAIL_MSG("Question icon doesn't make sense for a tooltip");
661 void wxRichToolTipGenericImpl::SetTimeout(unsigned millisecondsTimeout
,
662 unsigned millisecondsDelay
)
664 m_delay
= millisecondsDelay
;
665 m_timeout
= millisecondsTimeout
;
668 void wxRichToolTipGenericImpl::SetTipKind(wxTipKind tipKind
)
673 void wxRichToolTipGenericImpl::SetTitleFont(const wxFont
& font
)
678 void wxRichToolTipGenericImpl::ShowFor(wxWindow
* win
, const wxRect
* rect
)
680 // Set the focus to the window the tooltip refers to to make it look active.
683 wxRichToolTipPopup
* const popup
= new wxRichToolTipPopup
693 popup
->SetBackgroundColours(m_colStart
, m_colEnd
);
695 popup
->SetPosition(rect
);
696 // show or start the timer to delay showing the popup
697 popup
->SetTimeoutAndShow( m_timeout
, m_delay
);
700 // Currently only wxMSW provides a native implementation.
705 wxRichToolTipImpl::Create(const wxString
& title
, const wxString
& message
)
707 return new wxRichToolTipGenericImpl(title
, message
);
712 #endif // wxUSE_RICHTOOLTIP