]> git.saurik.com Git - wxWidgets.git/blob - src/generic/richtooltipg.cpp
3d6f0211a51ed86d490088908aaaab04984d3e8c
[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$
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 SetPosition(const wxRect* rect)
236 {
237 wxPoint pos;
238
239 if ( !rect || rect->IsEmpty() )
240 pos = GetTipPoint();
241 else
242 pos = GetParent()->ClientToScreen( wxPoint( rect->x + rect->width / 2, rect->y + rect->height / 2 ) );
243
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.
246 pos -= m_anchorPos;
247
248 Move(pos, wxSIZE_NO_ADJUSTMENTS);
249 }
250
251 void DoShow()
252 {
253 Popup();
254 }
255
256 void SetTimeoutAndShow(unsigned timeout, unsigned delay)
257 {
258 if ( !timeout && !delay )
259 {
260 DoShow();
261 return;
262 }
263
264 Connect(wxEVT_TIMER, wxTimerEventHandler(wxRichToolTipPopup::OnTimer));
265
266 m_timeout = timeout; // set for use in OnTimer if we have a delay
267 m_delayShow = delay != 0;
268
269 if ( !m_delayShow )
270 DoShow();
271
272 m_timer.Start((delay ? delay : timeout), true /* one shot */);
273 }
274
275 protected:
276 virtual void OnDismiss()
277 {
278 Destroy();
279 }
280
281 private:
282 #ifdef __WXMSW__
283 // Returns non-NULL theme only if we're using Win7-style tooltips.
284 static wxUxThemeEngine* GetTooltipTheme()
285 {
286 // Even themed applications under XP still use "classic" tooltips.
287 if ( wxGetWinVersion() <= wxWinVersion_XP )
288 return NULL;
289
290 return wxUxThemeEngine::GetIfActive();
291 }
292 #endif // __WXMSW__
293
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()
297 {
298 #ifdef __WXMSW__
299 if ( GetTooltipTheme() )
300 return 20;
301 #endif // __WXMSW__
302
303 return 15;
304 }
305
306 // Get the point to which our tip should point.
307 wxPoint GetTipPoint() const
308 {
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
312 // their behaviour.
313 const wxRect r = GetParent()->GetScreenRect();
314 return wxPoint(r.x + r.width/2, r.y + r.height/2);
315 }
316
317 // Choose the correct orientation depending on the window position.
318 //
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
325 {
326 const wxPoint pos = GetTipPoint();
327
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?
333
334 const wxRect rectDpy = wxDisplay(dpy).GetClientArea();
335
336 #ifdef __WXMAC__
337 return pos.y > rectDpy.height/2 ? wxTipKind_Bottom : wxTipKind_Top;
338 #else // !__WXMAC__
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
344 ? wxTipKind_TopRight
345 : wxTipKind_TopLeft;
346 #endif // __WXMAC__/!__WXMAC__
347 }
348
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)
352 {
353 #if wxUSE_GRAPHICS_CONTEXT
354 wxSize size = contentSize;
355
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
358 // size.
359 const int tipSize = GetTipHeight();
360 const int tipOffset = tipSize;
361
362 // The horizontal position of the tip.
363 int x = -1;
364
365 // The vertical coordinates of the tip base and apex.
366 int yBase = -1,
367 yApex = -1;
368
369 // The offset of the content part of the window.
370 int dy = -1;
371
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.
375 enum RectPoint
376 {
377 RectPoint_TopLeft,
378 RectPoint_Top,
379 RectPoint_TopRight,
380 RectPoint_Right,
381 RectPoint_BotRight,
382 RectPoint_Bot,
383 RectPoint_BotLeft,
384 RectPoint_Left,
385 RectPoint_Max
386 };
387
388 // The starting point for AddArcToPoint() calls below, we iterate over
389 // all RectPoints from it.
390 RectPoint pointStart = RectPoint_Max;
391
392
393 // Hard-coded radius of the round main rectangle corners.
394 const double RADIUS = 5;
395
396 // Create a path defining the shape of the tooltip window.
397 wxGraphicsPath
398 path = wxGraphicsRenderer::GetDefaultRenderer()->CreatePath();
399
400 if ( tipKind == wxTipKind_Auto )
401 tipKind = GetBestTipKind();
402
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];
406
407 switch ( tipKind )
408 {
409 case wxTipKind_Auto:
410 wxFAIL_MSG( "Impossible kind value" );
411 break;
412
413 case wxTipKind_TopLeft:
414 x = tipOffset;
415 yApex = 0;
416 yBase = tipSize;
417 dy = tipSize;
418
419 tipPoints[0] = wxPoint2DDouble(x, yBase);
420 tipPoints[1] = wxPoint2DDouble(x, yApex);
421 tipPoints[2] = wxPoint2DDouble(x + tipSize, yBase);
422
423 pointStart = RectPoint_TopRight;
424 break;
425
426 case wxTipKind_TopRight:
427 x = size.x - tipOffset;
428 yApex = 0;
429 yBase = tipSize;
430 dy = tipSize;
431
432 tipPoints[0] = wxPoint2DDouble(x - tipSize, yBase);
433 tipPoints[1] = wxPoint2DDouble(x, yApex);
434 tipPoints[2] = wxPoint2DDouble(x, yBase);
435
436 pointStart = RectPoint_TopRight;
437 break;
438
439 case wxTipKind_BottomLeft:
440 x = tipOffset;
441 yApex = size.y + tipSize;
442 yBase = size.y;
443 dy = 0;
444
445 tipPoints[0] = wxPoint2DDouble(x + tipSize, yBase);
446 tipPoints[1] = wxPoint2DDouble(x, yApex);
447 tipPoints[2] = wxPoint2DDouble(x, yBase);
448
449 pointStart = RectPoint_BotLeft;
450 break;
451
452 case wxTipKind_BottomRight:
453 x = size.x - tipOffset;
454 yApex = size.y + tipSize;
455 yBase = size.y;
456 dy = 0;
457
458 tipPoints[0] = wxPoint2DDouble(x, yBase);
459 tipPoints[1] = wxPoint2DDouble(x, yApex);
460 tipPoints[2] = wxPoint2DDouble(x - tipSize, yBase);
461
462 pointStart = RectPoint_BotLeft;
463 break;
464
465 case wxTipKind_Top:
466 x = size.x/2;
467 yApex = 0;
468 yBase = tipSize;
469 dy = tipSize;
470
471 {
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;
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_TopRight;
482 break;
483
484 case wxTipKind_Bottom:
485 x = size.x/2;
486 yApex = size.y + tipSize;
487 yBase = size.y;
488 dy = 0;
489
490 {
491 const double halfside = tipSize/1.73;
492
493 tipPoints[0] = wxPoint2DDouble(x + halfside, yBase);
494 tipPoints[1] = wxPoint2DDouble(x, yApex);
495 tipPoints[2] = wxPoint2DDouble(x - halfside, yBase);
496 }
497
498 pointStart = RectPoint_BotLeft;
499 break;
500
501 case wxTipKind_None:
502 x = size.x/2;
503 dy = 0;
504
505 path.AddRoundedRectangle(0, 0, size.x, size.y, RADIUS);
506 break;
507 }
508
509 wxASSERT_MSG( dy != -1, wxS("Unknown tip kind?") );
510
511 size.y += tipSize;
512 SetSize(size);
513
514 if ( tipKind != wxTipKind_None )
515 {
516 path.MoveToPoint(tipPoints[0]);
517 path.AddLineToPoint(tipPoints[1]);
518 path.AddLineToPoint(tipPoints[2]);
519
520 const double xLeft = 0.;
521 const double xMid = size.x/2.;
522 const double xRight = size.x;
523
524 const double yTop = dy;
525 const double yMid = (dy + size.y)/2.;
526 const double yBot = dy + contentSize.y;
527
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);
537
538 // Iterate over all rectangle rectPoints for the first 3 corners.
539 unsigned n = pointStart;
540 for ( unsigned corner = 0; corner < 3; corner++ )
541 {
542 const wxPoint2DDouble& pt1 = rectPoints[n];
543
544 n = (n + 1) % RectPoint_Max;
545
546 const wxPoint2DDouble& pt2 = rectPoints[n];
547
548 path.AddArcToPoint(pt1.m_x, pt1.m_y, pt2.m_x, pt2.m_y, RADIUS);
549
550 n = (n + 1) % RectPoint_Max;
551 }
552
553 // Last one wraps to the first point of the tip.
554 const wxPoint2DDouble& pt1 = rectPoints[n];
555 const wxPoint2DDouble& pt2 = tipPoints[0];
556
557 path.AddArcToPoint(pt1.m_x, pt1.m_y, pt2.m_x, pt2.m_y, RADIUS);
558
559 path.CloseSubpath();
560 }
561
562 SetShape(path);
563 #else // !wxUSE_GRAPHICS_CONTEXT
564 wxUnusedVar(tipKind);
565
566 int x = contentSize.x/2,
567 yApex = 0,
568 dy = 0;
569
570 SetSize(contentSize);
571 #endif // wxUSE_GRAPHICS_CONTEXT/!wxUSE_GRAPHICS_CONTEXT
572
573 m_anchorPos.x = x;
574 m_anchorPos.y = yApex;
575
576 return dy;
577 }
578
579 // Timer event handler hides the tooltip when the timeout expires.
580 void OnTimer(wxTimerEvent& WXUNUSED(event))
581 {
582 if ( !m_delayShow )
583 {
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.
587 DismissAndNotify();
588
589 return;
590 }
591
592 m_delayShow = false;
593
594 if ( m_timeout )
595 m_timer.Start(m_timeout, true);
596
597 DoShow();
598 }
599
600
601 // The anchor point offset if we show a tip or the middle of the top side
602 // otherwise.
603 wxPoint m_anchorPos;
604
605 // The timer counting down the time until we're hidden.
606 wxTimer m_timer;
607
608 // We will need to accesss the timeout period when delaying showing tooltip.
609 int m_timeout;
610
611 // If true, delay showing the tooltip.
612 bool m_delayShow;
613
614 wxDECLARE_NO_COPY_CLASS(wxRichToolTipPopup);
615 };
616
617 // ----------------------------------------------------------------------------
618 // wxRichToolTipGenericImpl: generic implementation of wxRichToolTip.
619 // ----------------------------------------------------------------------------
620
621 void
622 wxRichToolTipGenericImpl::SetBackgroundColour(const wxColour& col,
623 const wxColour& colEnd)
624 {
625 m_colStart = col;
626 m_colEnd = colEnd;
627 }
628
629 void wxRichToolTipGenericImpl::SetCustomIcon(const wxIcon& icon)
630 {
631 m_icon = icon;
632 }
633
634 void wxRichToolTipGenericImpl::SetStandardIcon(int icon)
635 {
636 switch ( icon & wxICON_MASK )
637 {
638 case wxICON_WARNING:
639 case wxICON_ERROR:
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
645 (
646 wxArtProvider::GetMessageBoxIconId(icon),
647 wxART_LIST
648 );
649 break;
650
651 case wxICON_QUESTION:
652 wxFAIL_MSG("Question icon doesn't make sense for a tooltip");
653 break;
654
655 case wxICON_NONE:
656 m_icon = wxNullIcon;
657 break;
658 }
659 }
660
661 void wxRichToolTipGenericImpl::SetTimeout(unsigned millisecondsTimeout,
662 unsigned millisecondsDelay)
663 {
664 m_delay = millisecondsDelay;
665 m_timeout = millisecondsTimeout;
666 }
667
668 void wxRichToolTipGenericImpl::SetTipKind(wxTipKind tipKind)
669 {
670 m_tipKind = tipKind;
671 }
672
673 void wxRichToolTipGenericImpl::SetTitleFont(const wxFont& font)
674 {
675 m_titleFont = font;
676 }
677
678 void wxRichToolTipGenericImpl::ShowFor(wxWindow* win, const wxRect* rect)
679 {
680 // Set the focus to the window the tooltip refers to to make it look active.
681 win->SetFocus();
682
683 wxRichToolTipPopup* const popup = new wxRichToolTipPopup
684 (
685 win,
686 m_title,
687 m_message,
688 m_icon,
689 m_tipKind,
690 m_titleFont
691 );
692
693 popup->SetBackgroundColours(m_colStart, m_colEnd);
694
695 popup->SetPosition(rect);
696 // show or start the timer to delay showing the popup
697 popup->SetTimeoutAndShow( m_timeout, m_delay );
698 }
699
700 // Currently only wxMSW provides a native implementation.
701 #ifndef __WXMSW__
702
703 /* static */
704 wxRichToolTipImpl*
705 wxRichToolTipImpl::Create(const wxString& title, const wxString& message)
706 {
707 return new wxRichToolTipGenericImpl(title, message);
708 }
709
710 #endif // !__WXMSW__
711
712 #endif // wxUSE_RICHTOOLTIP