1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/richtooltipg.cpp
3 // Purpose: Implementation of wxRichToolTip.
4 // Author: Vadim Zeitlin
6 // Copyright: (c) 2011 Vadim Zeitlin <vadim@wxwidgets.org>
7 // Licence: wxWindows licence
8 ///////////////////////////////////////////////////////////////////////////////
10 // ============================================================================
12 // ============================================================================
14 // ----------------------------------------------------------------------------
16 // ----------------------------------------------------------------------------
18 // for compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
28 #include "wx/dcmemory.h"
30 #include "wx/region.h"
31 #include "wx/settings.h"
33 #include "wx/statbmp.h"
34 #include "wx/stattext.h"
39 #include "wx/private/richtooltip.h"
40 #include "wx/generic/private/richtooltip.h"
42 #include "wx/artprov.h"
43 #include "wx/custombgwin.h"
44 #include "wx/display.h"
45 #include "wx/graphics.h"
46 #include "wx/popupwin.h"
47 #include "wx/textwrapper.h"
50 #include "wx/msw/uxtheme.h"
52 static const int TTP_BALLOONTITLE
= 4;
54 static const int TMT_TEXTCOLOR
= 3803;
55 static const int TMT_GRADIENTCOLOR1
= 3810;
56 static const int TMT_GRADIENTCOLOR2
= 3811;
59 // ----------------------------------------------------------------------------
60 // wxRichToolTipPopup: the popup window used by wxRichToolTip.
61 // ----------------------------------------------------------------------------
63 class wxRichToolTipPopup
:
64 public wxCustomBackgroundWindow
<wxPopupTransientWindow
>
67 wxRichToolTipPopup(wxWindow
* parent
,
68 const wxString
& title
,
69 const wxString
& message
,
72 const wxFont
& titleFont_
) :
75 Create(parent
, wxFRAME_SHAPED
);
78 wxBoxSizer
* const sizerTitle
= new wxBoxSizer(wxHORIZONTAL
);
81 sizerTitle
->Add(new wxStaticBitmap(this, wxID_ANY
, icon
),
82 wxSizerFlags().Centre().Border(wxRIGHT
));
84 //else: Simply don't show any icon.
86 wxStaticText
* const labelTitle
= new wxStaticText(this, wxID_ANY
, "");
87 labelTitle
->SetLabelText(title
);
89 wxFont
titleFont(titleFont_
);
90 if ( !titleFont
.IsOk() )
92 // Determine the appropriate title font for the current platform.
93 titleFont
= labelTitle
->GetFont();
96 // When using themes MSW tooltips use larger bluish version of the
98 wxUxThemeEngine
* const theme
= GetTooltipTheme();
101 titleFont
.MakeLarger();
104 if ( FAILED(theme
->GetThemeColor
106 wxUxThemeHandle(parent
, L
"TOOLTIP"),
113 // Use the standard value of this colour as fallback.
117 labelTitle
->SetForegroundColour(wxRGBToColour(c
));
122 // Everything else, including "classic" MSW look uses just the
123 // bold version of the base font.
124 titleFont
.MakeBold();
128 labelTitle
->SetFont(titleFont
);
129 sizerTitle
->Add(labelTitle
, wxSizerFlags().Centre());
131 wxBoxSizer
* const sizerTop
= new wxBoxSizer(wxVERTICAL
);
132 sizerTop
->Add(sizerTitle
,
133 wxSizerFlags().DoubleBorder(wxLEFT
|wxRIGHT
|wxTOP
));
135 // Use a spacer as we don't want to have a double border between the
136 // elements, just a simple one will do.
137 sizerTop
->AddSpacer(wxSizerFlags::GetDefaultBorder());
139 wxTextSizerWrapper
wrapper(this);
140 wxSizer
* sizerText
= wrapper
.CreateSizer(message
, -1 /* No wrapping */);
143 if ( icon
.IsOk() && GetTooltipTheme() )
145 // Themed tooltips under MSW align the text with the title, not
146 // with the icon, so use a helper horizontal sizer in this case.
147 wxBoxSizer
* const sizerTextIndent
= new wxBoxSizer(wxHORIZONTAL
);
148 sizerTextIndent
->AddSpacer(icon
.GetWidth());
149 sizerTextIndent
->Add(sizerText
,
150 wxSizerFlags().Border(wxLEFT
).Centre());
152 sizerText
= sizerTextIndent
;
155 sizerTop
->Add(sizerText
,
156 wxSizerFlags().DoubleBorder(wxLEFT
|wxRIGHT
|wxBOTTOM
)
161 const int offsetY
= SetTipShapeAndSize(tipKind
, GetBestSize());
164 // Offset our contents by the tip height to make it appear in the
166 sizerTop
->PrependSpacer(offsetY
);
172 void SetBackgroundColours(wxColour colStart
, wxColour colEnd
)
174 if ( !colStart
.IsOk() )
176 // Determine the best colour(s) to use on our own.
178 wxUxThemeEngine
* const theme
= GetTooltipTheme();
181 wxUxThemeHandle
hTheme(GetParent(), L
"TOOLTIP");
184 if ( FAILED(theme
->GetThemeColor
192 FAILED(theme
->GetThemeColor
205 colStart
= wxRGBToColour(c1
);
206 colEnd
= wxRGBToColour(c2
);
211 colStart
= wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK
);
217 // Use gradient-filled background bitmap.
218 const wxSize size
= GetClientSize();
223 dc
.GradientFillLinear(size
, colStart
, colEnd
, wxDOWN
);
226 SetBackgroundBitmap(bmp
);
228 else // Use solid colour.
230 SetBackgroundColour(colStart
);
234 void SetPosition(const wxRect
* rect
)
238 if ( !rect
|| rect
->IsEmpty() )
241 pos
= GetParent()->ClientToScreen( wxPoint( rect
->x
+ rect
->width
/ 2, rect
->y
+ rect
->height
/ 2 ) );
243 // We want our anchor point to coincide with this position so offset
244 // the position of the top left corner passed to Move() accordingly.
247 Move(pos
, wxSIZE_NO_ADJUSTMENTS
);
255 void SetTimeoutAndShow(unsigned timeout
, unsigned delay
)
257 if ( !timeout
&& !delay
)
263 Connect(wxEVT_TIMER
, wxTimerEventHandler(wxRichToolTipPopup::OnTimer
));
265 m_timeout
= timeout
; // set for use in OnTimer if we have a delay
266 m_delayShow
= delay
!= 0;
271 m_timer
.Start((delay
? delay
: timeout
), true /* one shot */);
275 virtual void OnDismiss()
282 // Returns non-NULL theme only if we're using Win7-style tooltips.
283 static wxUxThemeEngine
* GetTooltipTheme()
285 // Even themed applications under XP still use "classic" tooltips.
286 if ( wxGetWinVersion() <= wxWinVersion_XP
)
289 return wxUxThemeEngine::GetIfActive();
293 // For now we just hard code the tip height, would be nice to do something
294 // smarter in the future.
295 static int GetTipHeight()
298 if ( GetTooltipTheme() )
305 // Get the point to which our tip should point.
306 wxPoint
GetTipPoint() const
308 // Currently we always use the middle of the window. It seems that MSW
309 // native tooltips use a different point but it's not really clear how
310 // do they determine it nor whether it's worth the trouble to emulate
312 const wxRect r
= GetParent()->GetScreenRect();
313 return wxPoint(r
.x
+ r
.width
/2, r
.y
+ r
.height
/2);
316 // Choose the correct orientation depending on the window position.
318 // Also use the tip kind appropriate for the current environment. For MSW
319 // the right triangles are used and for Mac the equilateral ones as this is
320 // the prevailing kind under these systems. For everything else we go with
321 // right triangles as well but without any real rationale so this could be
322 // tweaked in the future.
323 wxTipKind
GetBestTipKind() const
325 const wxPoint pos
= GetTipPoint();
327 // Use GetFromWindow() and not GetFromPoint() here to try to get the
328 // correct display even if the tip point itself is not visible.
329 int dpy
= wxDisplay::GetFromWindow(GetParent());
330 if ( dpy
== wxNOT_FOUND
)
331 dpy
= 0; // What else can we do?
333 const wxRect rectDpy
= wxDisplay(dpy
).GetClientArea();
336 return pos
.y
> rectDpy
.height
/2 ? wxTipKind_Bottom
: wxTipKind_Top
;
338 return pos
.y
> rectDpy
.height
/2
339 ? pos
.x
> rectDpy
.width
/2
340 ? wxTipKind_BottomRight
341 : wxTipKind_BottomLeft
342 : pos
.x
> rectDpy
.width
/2
345 #endif // __WXMAC__/!__WXMAC__
348 // Set the size and shape of the tip window and returns the offset of its
349 // content area from the top (horizontal offset is always 0 currently).
350 int SetTipShapeAndSize(wxTipKind tipKind
, const wxSize
& contentSize
)
352 #if wxUSE_GRAPHICS_CONTEXT
353 wxSize size
= contentSize
;
355 // The size is the vertical size and the offset is the distance from
356 // edge for asymmetric tips, currently hard-coded to be the same as the
358 const int tipSize
= GetTipHeight();
359 const int tipOffset
= tipSize
;
361 // The horizontal position of the tip.
364 // The vertical coordinates of the tip base and apex.
368 // The offset of the content part of the window.
371 // Define symbolic names for the rectangle corners and mid-way points
372 // that we use below in an attempt to make the code more clear. Notice
373 // that these values must be consecutive as we iterate over them.
387 // The starting point for AddArcToPoint() calls below, we iterate over
388 // all RectPoints from it.
389 RectPoint pointStart
= RectPoint_Max
;
392 // Hard-coded radius of the round main rectangle corners.
393 const double RADIUS
= 5;
395 // Create a path defining the shape of the tooltip window.
397 path
= wxGraphicsRenderer::GetDefaultRenderer()->CreatePath();
399 if ( tipKind
== wxTipKind_Auto
)
400 tipKind
= GetBestTipKind();
402 // Points defining the tip shape (in clockwise order as we must end at
403 // tipPoints[0] after drawing the rectangle outline in this order).
404 wxPoint2DDouble tipPoints
[3];
409 wxFAIL_MSG( "Impossible kind value" );
412 case wxTipKind_TopLeft
:
418 tipPoints
[0] = wxPoint2DDouble(x
, yBase
);
419 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
420 tipPoints
[2] = wxPoint2DDouble(x
+ tipSize
, yBase
);
422 pointStart
= RectPoint_TopRight
;
425 case wxTipKind_TopRight
:
426 x
= size
.x
- tipOffset
;
431 tipPoints
[0] = wxPoint2DDouble(x
- tipSize
, yBase
);
432 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
433 tipPoints
[2] = wxPoint2DDouble(x
, yBase
);
435 pointStart
= RectPoint_TopRight
;
438 case wxTipKind_BottomLeft
:
440 yApex
= size
.y
+ tipSize
;
444 tipPoints
[0] = wxPoint2DDouble(x
+ tipSize
, yBase
);
445 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
446 tipPoints
[2] = wxPoint2DDouble(x
, yBase
);
448 pointStart
= RectPoint_BotLeft
;
451 case wxTipKind_BottomRight
:
452 x
= size
.x
- tipOffset
;
453 yApex
= size
.y
+ tipSize
;
457 tipPoints
[0] = wxPoint2DDouble(x
, yBase
);
458 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
459 tipPoints
[2] = wxPoint2DDouble(x
- tipSize
, yBase
);
461 pointStart
= RectPoint_BotLeft
;
471 // A half-side of an equilateral triangle is its altitude
472 // divided by sqrt(3) ~= 1.73.
473 const double halfside
= tipSize
/1.73;
475 tipPoints
[0] = wxPoint2DDouble(x
- halfside
, yBase
);
476 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
477 tipPoints
[2] = wxPoint2DDouble(x
+ halfside
, yBase
);
480 pointStart
= RectPoint_TopRight
;
483 case wxTipKind_Bottom
:
485 yApex
= size
.y
+ tipSize
;
490 const double halfside
= tipSize
/1.73;
492 tipPoints
[0] = wxPoint2DDouble(x
+ halfside
, yBase
);
493 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
494 tipPoints
[2] = wxPoint2DDouble(x
- halfside
, yBase
);
497 pointStart
= RectPoint_BotLeft
;
504 path
.AddRoundedRectangle(0, 0, size
.x
, size
.y
, RADIUS
);
508 wxASSERT_MSG( dy
!= -1, wxS("Unknown tip kind?") );
513 if ( tipKind
!= wxTipKind_None
)
515 path
.MoveToPoint(tipPoints
[0]);
516 path
.AddLineToPoint(tipPoints
[1]);
517 path
.AddLineToPoint(tipPoints
[2]);
519 const double xLeft
= 0.;
520 const double xMid
= size
.x
/2.;
521 const double xRight
= size
.x
;
523 const double yTop
= dy
;
524 const double yMid
= (dy
+ size
.y
)/2.;
525 const double yBot
= dy
+ contentSize
.y
;
527 wxPoint2DDouble rectPoints
[RectPoint_Max
];
528 rectPoints
[RectPoint_TopLeft
] = wxPoint2DDouble(xLeft
, yTop
);
529 rectPoints
[RectPoint_Top
] = wxPoint2DDouble(xMid
, yTop
);
530 rectPoints
[RectPoint_TopRight
] = wxPoint2DDouble(xRight
, yTop
);
531 rectPoints
[RectPoint_Right
] = wxPoint2DDouble(xRight
, yMid
);
532 rectPoints
[RectPoint_BotRight
] = wxPoint2DDouble(xRight
, yBot
);
533 rectPoints
[RectPoint_Bot
] = wxPoint2DDouble(xMid
, yBot
);
534 rectPoints
[RectPoint_BotLeft
] = wxPoint2DDouble(xLeft
, yBot
);
535 rectPoints
[RectPoint_Left
] = wxPoint2DDouble(xLeft
, yMid
);
537 // Iterate over all rectangle rectPoints for the first 3 corners.
538 unsigned n
= pointStart
;
539 for ( unsigned corner
= 0; corner
< 3; corner
++ )
541 const wxPoint2DDouble
& pt1
= rectPoints
[n
];
543 n
= (n
+ 1) % RectPoint_Max
;
545 const wxPoint2DDouble
& pt2
= rectPoints
[n
];
547 path
.AddArcToPoint(pt1
.m_x
, pt1
.m_y
, pt2
.m_x
, pt2
.m_y
, RADIUS
);
549 n
= (n
+ 1) % RectPoint_Max
;
552 // Last one wraps to the first point of the tip.
553 const wxPoint2DDouble
& pt1
= rectPoints
[n
];
554 const wxPoint2DDouble
& pt2
= tipPoints
[0];
556 path
.AddArcToPoint(pt1
.m_x
, pt1
.m_y
, pt2
.m_x
, pt2
.m_y
, RADIUS
);
562 #else // !wxUSE_GRAPHICS_CONTEXT
563 wxUnusedVar(tipKind
);
565 int x
= contentSize
.x
/2,
569 SetSize(contentSize
);
570 #endif // wxUSE_GRAPHICS_CONTEXT/!wxUSE_GRAPHICS_CONTEXT
573 m_anchorPos
.y
= yApex
;
578 // Timer event handler hides the tooltip when the timeout expires.
579 void OnTimer(wxTimerEvent
& WXUNUSED(event
))
583 // Doing "Notify" here ensures that our OnDismiss() is called and so we
584 // also Destroy() ourselves. We could use Dismiss() and call Destroy()
585 // explicitly from here as well.
594 m_timer
.Start(m_timeout
, true);
600 // The anchor point offset if we show a tip or the middle of the top side
604 // The timer counting down the time until we're hidden.
607 // We will need to accesss the timeout period when delaying showing tooltip.
610 // If true, delay showing the tooltip.
613 wxDECLARE_NO_COPY_CLASS(wxRichToolTipPopup
);
616 // ----------------------------------------------------------------------------
617 // wxRichToolTipGenericImpl: generic implementation of wxRichToolTip.
618 // ----------------------------------------------------------------------------
621 wxRichToolTipGenericImpl::SetBackgroundColour(const wxColour
& col
,
622 const wxColour
& colEnd
)
628 void wxRichToolTipGenericImpl::SetCustomIcon(const wxIcon
& icon
)
633 void wxRichToolTipGenericImpl::SetStandardIcon(int icon
)
635 switch ( icon
& wxICON_MASK
)
639 case wxICON_INFORMATION
:
640 // Although we don't use this icon in a list, we need a smallish
641 // icon here and not an icon of a typical message box size so use
642 // wxART_LIST to get it.
643 m_icon
= wxArtProvider::GetIcon
645 wxArtProvider::GetMessageBoxIconId(icon
),
650 case wxICON_QUESTION
:
651 wxFAIL_MSG("Question icon doesn't make sense for a tooltip");
660 void wxRichToolTipGenericImpl::SetTimeout(unsigned millisecondsTimeout
,
661 unsigned millisecondsDelay
)
663 m_delay
= millisecondsDelay
;
664 m_timeout
= millisecondsTimeout
;
667 void wxRichToolTipGenericImpl::SetTipKind(wxTipKind tipKind
)
672 void wxRichToolTipGenericImpl::SetTitleFont(const wxFont
& font
)
677 void wxRichToolTipGenericImpl::ShowFor(wxWindow
* win
, const wxRect
* rect
)
679 // Set the focus to the window the tooltip refers to to make it look active.
682 wxRichToolTipPopup
* const popup
= new wxRichToolTipPopup
692 popup
->SetBackgroundColours(m_colStart
, m_colEnd
);
694 popup
->SetPosition(rect
);
695 // show or start the timer to delay showing the popup
696 popup
->SetTimeoutAndShow( m_timeout
, m_delay
);
699 // Currently only wxMSW provides a native implementation.
704 wxRichToolTipImpl::Create(const wxString
& title
, const wxString
& message
)
706 return new wxRichToolTipGenericImpl(title
, message
);
711 #endif // wxUSE_RICHTOOLTIP