]> git.saurik.com Git - wxWidgets.git/blob - src/generic/richtooltipg.cpp
Ensure there is valid context for DrawRectangle
[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 int x = contentSize.x/2,
565 yApex = 0,
566 dy = 0;
567
568 SetSize(contentSize);
569 #endif // wxUSE_GRAPHICS_CONTEXT/!wxUSE_GRAPHICS_CONTEXT
570
571 m_anchorPos.x = x;
572 m_anchorPos.y = yApex;
573
574 return dy;
575 }
576
577 // Timer event handler hides the tooltip when the timeout expires.
578 void OnTimer(wxTimerEvent& WXUNUSED(event))
579 {
580 if ( !m_delayShow )
581 {
582 // Doing "Notify" here ensures that our OnDismiss() is called and so we
583 // also Destroy() ourselves. We could use Dismiss() and call Destroy()
584 // explicitly from here as well.
585 DismissAndNotify();
586
587 return;
588 }
589
590 m_delayShow = false;
591
592 if ( m_timeout )
593 m_timer.Start(m_timeout, true);
594
595 DoShow();
596 }
597
598
599 // The anchor point offset if we show a tip or the middle of the top side
600 // otherwise.
601 wxPoint m_anchorPos;
602
603 // The timer counting down the time until we're hidden.
604 wxTimer m_timer;
605
606 // We will need to accesss the timeout period when delaying showing tooltip.
607 int m_timeout;
608
609 // If true, delay showing the tooltip.
610 bool m_delayShow;
611
612 wxDECLARE_NO_COPY_CLASS(wxRichToolTipPopup);
613 };
614
615 // ----------------------------------------------------------------------------
616 // wxRichToolTipGenericImpl: generic implementation of wxRichToolTip.
617 // ----------------------------------------------------------------------------
618
619 void
620 wxRichToolTipGenericImpl::SetBackgroundColour(const wxColour& col,
621 const wxColour& colEnd)
622 {
623 m_colStart = col;
624 m_colEnd = colEnd;
625 }
626
627 void wxRichToolTipGenericImpl::SetCustomIcon(const wxIcon& icon)
628 {
629 m_icon = icon;
630 }
631
632 void wxRichToolTipGenericImpl::SetStandardIcon(int icon)
633 {
634 switch ( icon & wxICON_MASK )
635 {
636 case wxICON_WARNING:
637 case wxICON_ERROR:
638 case wxICON_INFORMATION:
639 // Although we don't use this icon in a list, we need a smallish
640 // icon here and not an icon of a typical message box size so use
641 // wxART_LIST to get it.
642 m_icon = wxArtProvider::GetIcon
643 (
644 wxArtProvider::GetMessageBoxIconId(icon),
645 wxART_LIST
646 );
647 break;
648
649 case wxICON_QUESTION:
650 wxFAIL_MSG("Question icon doesn't make sense for a tooltip");
651 break;
652
653 case wxICON_NONE:
654 m_icon = wxNullIcon;
655 break;
656 }
657 }
658
659 void wxRichToolTipGenericImpl::SetTimeout(unsigned millisecondsTimeout,
660 unsigned millisecondsDelay)
661 {
662 m_delay = millisecondsDelay;
663 m_timeout = millisecondsTimeout;
664 }
665
666 void wxRichToolTipGenericImpl::SetTipKind(wxTipKind tipKind)
667 {
668 m_tipKind = tipKind;
669 }
670
671 void wxRichToolTipGenericImpl::SetTitleFont(const wxFont& font)
672 {
673 m_titleFont = font;
674 }
675
676 void wxRichToolTipGenericImpl::ShowFor(wxWindow* win, const wxRect* rect)
677 {
678 // Set the focus to the window the tooltip refers to to make it look active.
679 win->SetFocus();
680
681 wxRichToolTipPopup* const popup = new wxRichToolTipPopup
682 (
683 win,
684 m_title,
685 m_message,
686 m_icon,
687 m_tipKind,
688 m_titleFont
689 );
690
691 popup->SetBackgroundColours(m_colStart, m_colEnd);
692
693 popup->SetPosition(rect);
694 // show or start the timer to delay showing the popup
695 popup->SetTimeoutAndShow( m_timeout, m_delay );
696 }
697
698 // Currently only wxMSW provides a native implementation.
699 #ifndef __WXMSW__
700
701 /* static */
702 wxRichToolTipImpl*
703 wxRichToolTipImpl::Create(const wxString& title, const wxString& message)
704 {
705 return new wxRichToolTipGenericImpl(title, message);
706 }
707
708 #endif // !__WXMSW__
709
710 #endif // wxUSE_RICHTOOLTIP