1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/richtooltipg.cpp
3 // Purpose: Implementation of wxRichToolTip.
4 // Author: Vadim Zeitlin
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 ///////////////////////////////////////////////////////////////////////////////
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
);
248 void SetTimeout(unsigned timeout
)
253 Connect(wxEVT_TIMER
, wxTimerEventHandler(wxRichToolTipPopup::OnTimer
));
255 m_timer
.Start(timeout
, true /* one shot */);
259 virtual void OnDismiss()
266 // Returns non-NULL theme only if we're using Win7-style tooltips.
267 static wxUxThemeEngine
* GetTooltipTheme()
269 // Even themed applications under XP still use "classic" tooltips.
270 if ( wxGetWinVersion() <= wxWinVersion_XP
)
273 return wxUxThemeEngine::GetIfActive();
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()
282 if ( GetTooltipTheme() )
289 // Get the point to which our tip should point.
290 wxPoint
GetTipPoint() const
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
296 const wxRect r
= GetParent()->GetScreenRect();
297 return wxPoint(r
.x
+ r
.width
/2, r
.y
+ r
.height
/2);
300 // Choose the correct orientation depending on the window position.
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
309 const wxPoint pos
= GetTipPoint();
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?
317 const wxRect rectDpy
= wxDisplay(dpy
).GetClientArea();
320 return pos
.y
> rectDpy
.height
/2 ? wxTipKind_Bottom
: wxTipKind_Top
;
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
329 #endif // __WXMAC__/!__WXMAC__
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
)
336 #if wxUSE_GRAPHICS_CONTEXT
337 wxSize size
= contentSize
;
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
342 const int tipSize
= GetTipHeight();
343 const int tipOffset
= tipSize
;
345 // The horizontal position of the tip.
348 // The vertical coordinates of the tip base and apex.
352 // The offset of the content part of the window.
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.
371 // The starting point for AddArcToPoint() calls below, we iterate over
372 // all RectPoints from it.
373 RectPoint pointStart
= RectPoint_Max
;
376 // Hard-coded radius of the round main rectangle corners.
377 const double RADIUS
= 5;
379 // Create a path defining the shape of the tooltip window.
381 path
= wxGraphicsRenderer::GetDefaultRenderer()->CreatePath();
383 if ( tipKind
== wxTipKind_Auto
)
384 tipKind
= GetBestTipKind();
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];
393 wxFAIL_MSG( "Impossible kind value" );
396 case wxTipKind_TopLeft
:
402 tipPoints
[0] = wxPoint2DDouble(x
, yBase
);
403 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
404 tipPoints
[2] = wxPoint2DDouble(x
+ tipSize
, yBase
);
406 pointStart
= RectPoint_TopRight
;
409 case wxTipKind_TopRight
:
410 x
= size
.x
- tipOffset
;
415 tipPoints
[0] = wxPoint2DDouble(x
- tipSize
, yBase
);
416 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
417 tipPoints
[2] = wxPoint2DDouble(x
, yBase
);
419 pointStart
= RectPoint_TopRight
;
422 case wxTipKind_BottomLeft
:
424 yApex
= size
.y
+ tipSize
;
428 tipPoints
[0] = wxPoint2DDouble(x
+ tipSize
, yBase
);
429 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
430 tipPoints
[2] = wxPoint2DDouble(x
, yBase
);
432 pointStart
= RectPoint_BotLeft
;
435 case wxTipKind_BottomRight
:
436 x
= size
.x
- tipOffset
;
437 yApex
= size
.y
+ tipSize
;
441 tipPoints
[0] = wxPoint2DDouble(x
, yBase
);
442 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
443 tipPoints
[2] = wxPoint2DDouble(x
- tipSize
, yBase
);
445 pointStart
= RectPoint_BotLeft
;
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;
459 tipPoints
[0] = wxPoint2DDouble(x
- halfside
, yBase
);
460 tipPoints
[1] = wxPoint2DDouble(x
, yApex
);
461 tipPoints
[2] = wxPoint2DDouble(x
+ halfside
, yBase
);
464 pointStart
= RectPoint_TopRight
;
467 case wxTipKind_Bottom
:
469 yApex
= size
.y
+ tipSize
;
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_BotLeft
;
488 path
.AddRoundedRectangle(0, 0, size
.x
, size
.y
, RADIUS
);
492 wxASSERT_MSG( dy
!= -1, wxS("Unknown tip kind?") );
497 if ( tipKind
!= wxTipKind_None
)
499 path
.MoveToPoint(tipPoints
[0]);
500 path
.AddLineToPoint(tipPoints
[1]);
501 path
.AddLineToPoint(tipPoints
[2]);
503 const double xLeft
= 0.;
504 const double xMid
= size
.x
/2.;
505 const double xRight
= size
.x
;
507 const double yTop
= dy
;
508 const double yMid
= (dy
+ size
.y
)/2.;
509 const double yBot
= dy
+ contentSize
.y
;
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
);
521 // Iterate over all rectangle rectPoints for the first 3 corners.
522 unsigned n
= pointStart
;
523 for ( unsigned corner
= 0; corner
< 3; corner
++ )
525 const wxPoint2DDouble
& pt1
= rectPoints
[n
];
527 n
= (n
+ 1) % RectPoint_Max
;
529 const wxPoint2DDouble
& pt2
= rectPoints
[n
];
531 path
.AddArcToPoint(pt1
.m_x
, pt1
.m_y
, pt2
.m_x
, pt2
.m_y
, RADIUS
);
533 n
= (n
+ 1) % RectPoint_Max
;
536 // Last one wraps to the first point of the tip.
537 const wxPoint2DDouble
& pt1
= rectPoints
[n
];
538 const wxPoint2DDouble
& pt2
= tipPoints
[0];
540 path
.AddArcToPoint(pt1
.m_x
, pt1
.m_y
, pt2
.m_x
, pt2
.m_y
, RADIUS
);
546 #else // !wxUSE_GRAPHICS_CONTEXT
547 int x
= contentSize
.x
/2,
551 SetSize(contentSize
);
552 #endif // wxUSE_GRAPHICS_CONTEXT/!wxUSE_GRAPHICS_CONTEXT
555 m_anchorPos
.y
= yApex
;
560 // Timer event handler hides the tooltip when the timeout expires.
561 void OnTimer(wxTimerEvent
& WXUNUSED(event
))
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.
570 // The anchor point offset if we show a tip or the middle of the top side
574 // The timer counting down the time until we're hidden.
577 wxDECLARE_NO_COPY_CLASS(wxRichToolTipPopup
);
580 // ----------------------------------------------------------------------------
581 // wxRichToolTipGenericImpl: generic implementation of wxRichToolTip.
582 // ----------------------------------------------------------------------------
585 wxRichToolTipGenericImpl::SetBackgroundColour(const wxColour
& col
,
586 const wxColour
& colEnd
)
592 void wxRichToolTipGenericImpl::SetCustomIcon(const wxIcon
& icon
)
597 void wxRichToolTipGenericImpl::SetStandardIcon(int icon
)
599 switch ( icon
& wxICON_MASK
)
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
609 wxArtProvider::GetMessageBoxIconId(icon
),
614 case wxICON_QUESTION
:
615 wxFAIL_MSG("Question icon doesn't make sense for a tooltip");
624 void wxRichToolTipGenericImpl::SetTimeout(unsigned milliseconds
)
626 m_timeout
= milliseconds
;
629 void wxRichToolTipGenericImpl::SetTipKind(wxTipKind tipKind
)
634 void wxRichToolTipGenericImpl::SetTitleFont(const wxFont
& font
)
639 void wxRichToolTipGenericImpl::ShowFor(wxWindow
* win
)
641 // Set the focus to the window the tooltip refers to to make it look active.
644 wxRichToolTipPopup
* const popup
= new wxRichToolTipPopup
654 popup
->SetBackgroundColours(m_colStart
, m_colEnd
);
658 popup
->SetTimeout(m_timeout
);
661 // Currently only wxMSW provides a native implementation.
666 wxRichToolTipImpl::Create(const wxString
& title
, const wxString
& message
)
668 return new wxRichToolTipGenericImpl(title
, message
);
673 #endif // wxUSE_RICHTOOLTIP