]> git.saurik.com Git - wxWidgets.git/blob - src/generic/richtooltipg.cpp
Add check for destroying window with mouse capture in wxGTK.
[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()
236 {
237 wxPoint pos = GetTipPoint();
238
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.
241 pos -= m_anchorPos;
242
243 Move(pos, wxSIZE_NO_ADJUSTMENTS);
244 }
245
246 void DoShow()
247 {
248 Popup();
249 }
250
251 void SetTimeoutAndShow(unsigned timeout, unsigned delay)
252 {
253 if ( !timeout && !delay )
254 {
255 DoShow();
256 return;
257 }
258
259 Connect(wxEVT_TIMER, wxTimerEventHandler(wxRichToolTipPopup::OnTimer));
260
261 m_timeout = timeout; // set for use in OnTimer if we have a delay
262 m_delayShow = delay != 0;
263
264 if ( !m_delayShow )
265 DoShow();
266
267 m_timer.Start((delay ? delay : timeout), true /* one shot */);
268 }
269
270 protected:
271 virtual void OnDismiss()
272 {
273 Destroy();
274 }
275
276 private:
277 #ifdef __WXMSW__
278 // Returns non-NULL theme only if we're using Win7-style tooltips.
279 static wxUxThemeEngine* GetTooltipTheme()
280 {
281 // Even themed applications under XP still use "classic" tooltips.
282 if ( wxGetWinVersion() <= wxWinVersion_XP )
283 return NULL;
284
285 return wxUxThemeEngine::GetIfActive();
286 }
287 #endif // __WXMSW__
288
289 // For now we just hard code the tip height, would be nice to do something
290 // smarter in the future.
291 static int GetTipHeight()
292 {
293 #ifdef __WXMSW__
294 if ( GetTooltipTheme() )
295 return 20;
296 #endif // __WXMSW__
297
298 return 15;
299 }
300
301 // Get the point to which our tip should point.
302 wxPoint GetTipPoint() const
303 {
304 // Currently we always use the middle of the window. It seems that MSW
305 // native tooltips use a different point but it's not really clear how
306 // do they determine it nor whether it's worth the trouble to emulate
307 // their behaviour.
308 const wxRect r = GetParent()->GetScreenRect();
309 return wxPoint(r.x + r.width/2, r.y + r.height/2);
310 }
311
312 // Choose the correct orientation depending on the window position.
313 //
314 // Also use the tip kind appropriate for the current environment. For MSW
315 // the right triangles are used and for Mac the equilateral ones as this is
316 // the prevailing kind under these systems. For everything else we go with
317 // right triangles as well but without any real rationale so this could be
318 // tweaked in the future.
319 wxTipKind GetBestTipKind() const
320 {
321 const wxPoint pos = GetTipPoint();
322
323 // Use GetFromWindow() and not GetFromPoint() here to try to get the
324 // correct display even if the tip point itself is not visible.
325 int dpy = wxDisplay::GetFromWindow(GetParent());
326 if ( dpy == wxNOT_FOUND )
327 dpy = 0; // What else can we do?
328
329 const wxRect rectDpy = wxDisplay(dpy).GetClientArea();
330
331 #ifdef __WXMAC__
332 return pos.y > rectDpy.height/2 ? wxTipKind_Bottom : wxTipKind_Top;
333 #else // !__WXMAC__
334 return pos.y > rectDpy.height/2
335 ? pos.x > rectDpy.width/2
336 ? wxTipKind_BottomRight
337 : wxTipKind_BottomLeft
338 : pos.x > rectDpy.width/2
339 ? wxTipKind_TopRight
340 : wxTipKind_TopLeft;
341 #endif // __WXMAC__/!__WXMAC__
342 }
343
344 // Set the size and shape of the tip window and returns the offset of its
345 // content area from the top (horizontal offset is always 0 currently).
346 int SetTipShapeAndSize(wxTipKind tipKind, const wxSize& contentSize)
347 {
348 #if wxUSE_GRAPHICS_CONTEXT
349 wxSize size = contentSize;
350
351 // The size is the vertical size and the offset is the distance from
352 // edge for asymmetric tips, currently hard-coded to be the same as the
353 // size.
354 const int tipSize = GetTipHeight();
355 const int tipOffset = tipSize;
356
357 // The horizontal position of the tip.
358 int x = -1;
359
360 // The vertical coordinates of the tip base and apex.
361 int yBase = -1,
362 yApex = -1;
363
364 // The offset of the content part of the window.
365 int dy = -1;
366
367 // Define symbolic names for the rectangle corners and mid-way points
368 // that we use below in an attempt to make the code more clear. Notice
369 // that these values must be consecutive as we iterate over them.
370 enum RectPoint
371 {
372 RectPoint_TopLeft,
373 RectPoint_Top,
374 RectPoint_TopRight,
375 RectPoint_Right,
376 RectPoint_BotRight,
377 RectPoint_Bot,
378 RectPoint_BotLeft,
379 RectPoint_Left,
380 RectPoint_Max
381 };
382
383 // The starting point for AddArcToPoint() calls below, we iterate over
384 // all RectPoints from it.
385 RectPoint pointStart = RectPoint_Max;
386
387
388 // Hard-coded radius of the round main rectangle corners.
389 const double RADIUS = 5;
390
391 // Create a path defining the shape of the tooltip window.
392 wxGraphicsPath
393 path = wxGraphicsRenderer::GetDefaultRenderer()->CreatePath();
394
395 if ( tipKind == wxTipKind_Auto )
396 tipKind = GetBestTipKind();
397
398 // Points defining the tip shape (in clockwise order as we must end at
399 // tipPoints[0] after drawing the rectangle outline in this order).
400 wxPoint2DDouble tipPoints[3];
401
402 switch ( tipKind )
403 {
404 case wxTipKind_Auto:
405 wxFAIL_MSG( "Impossible kind value" );
406 break;
407
408 case wxTipKind_TopLeft:
409 x = tipOffset;
410 yApex = 0;
411 yBase = tipSize;
412 dy = tipSize;
413
414 tipPoints[0] = wxPoint2DDouble(x, yBase);
415 tipPoints[1] = wxPoint2DDouble(x, yApex);
416 tipPoints[2] = wxPoint2DDouble(x + tipSize, yBase);
417
418 pointStart = RectPoint_TopRight;
419 break;
420
421 case wxTipKind_TopRight:
422 x = size.x - tipOffset;
423 yApex = 0;
424 yBase = tipSize;
425 dy = tipSize;
426
427 tipPoints[0] = wxPoint2DDouble(x - tipSize, yBase);
428 tipPoints[1] = wxPoint2DDouble(x, yApex);
429 tipPoints[2] = wxPoint2DDouble(x, yBase);
430
431 pointStart = RectPoint_TopRight;
432 break;
433
434 case wxTipKind_BottomLeft:
435 x = tipOffset;
436 yApex = size.y + tipSize;
437 yBase = size.y;
438 dy = 0;
439
440 tipPoints[0] = wxPoint2DDouble(x + tipSize, yBase);
441 tipPoints[1] = wxPoint2DDouble(x, yApex);
442 tipPoints[2] = wxPoint2DDouble(x, yBase);
443
444 pointStart = RectPoint_BotLeft;
445 break;
446
447 case wxTipKind_BottomRight:
448 x = size.x - tipOffset;
449 yApex = size.y + tipSize;
450 yBase = size.y;
451 dy = 0;
452
453 tipPoints[0] = wxPoint2DDouble(x, yBase);
454 tipPoints[1] = wxPoint2DDouble(x, yApex);
455 tipPoints[2] = wxPoint2DDouble(x - tipSize, yBase);
456
457 pointStart = RectPoint_BotLeft;
458 break;
459
460 case wxTipKind_Top:
461 x = size.x/2;
462 yApex = 0;
463 yBase = tipSize;
464 dy = tipSize;
465
466 {
467 // A half-side of an equilateral triangle is its altitude
468 // divided by sqrt(3) ~= 1.73.
469 const double halfside = tipSize/1.73;
470
471 tipPoints[0] = wxPoint2DDouble(x - halfside, yBase);
472 tipPoints[1] = wxPoint2DDouble(x, yApex);
473 tipPoints[2] = wxPoint2DDouble(x + halfside, yBase);
474 }
475
476 pointStart = RectPoint_TopRight;
477 break;
478
479 case wxTipKind_Bottom:
480 x = size.x/2;
481 yApex = size.y + tipSize;
482 yBase = size.y;
483 dy = 0;
484
485 {
486 const double halfside = tipSize/1.73;
487
488 tipPoints[0] = wxPoint2DDouble(x + halfside, yBase);
489 tipPoints[1] = wxPoint2DDouble(x, yApex);
490 tipPoints[2] = wxPoint2DDouble(x - halfside, yBase);
491 }
492
493 pointStart = RectPoint_BotLeft;
494 break;
495
496 case wxTipKind_None:
497 x = size.x/2;
498 dy = 0;
499
500 path.AddRoundedRectangle(0, 0, size.x, size.y, RADIUS);
501 break;
502 }
503
504 wxASSERT_MSG( dy != -1, wxS("Unknown tip kind?") );
505
506 size.y += tipSize;
507 SetSize(size);
508
509 if ( tipKind != wxTipKind_None )
510 {
511 path.MoveToPoint(tipPoints[0]);
512 path.AddLineToPoint(tipPoints[1]);
513 path.AddLineToPoint(tipPoints[2]);
514
515 const double xLeft = 0.;
516 const double xMid = size.x/2.;
517 const double xRight = size.x;
518
519 const double yTop = dy;
520 const double yMid = (dy + size.y)/2.;
521 const double yBot = dy + contentSize.y;
522
523 wxPoint2DDouble rectPoints[RectPoint_Max];
524 rectPoints[RectPoint_TopLeft] = wxPoint2DDouble(xLeft, yTop);
525 rectPoints[RectPoint_Top] = wxPoint2DDouble(xMid, yTop);
526 rectPoints[RectPoint_TopRight] = wxPoint2DDouble(xRight, yTop);
527 rectPoints[RectPoint_Right] = wxPoint2DDouble(xRight, yMid);
528 rectPoints[RectPoint_BotRight] = wxPoint2DDouble(xRight, yBot);
529 rectPoints[RectPoint_Bot] = wxPoint2DDouble(xMid, yBot);
530 rectPoints[RectPoint_BotLeft] = wxPoint2DDouble(xLeft, yBot);
531 rectPoints[RectPoint_Left] = wxPoint2DDouble(xLeft, yMid);
532
533 // Iterate over all rectangle rectPoints for the first 3 corners.
534 unsigned n = pointStart;
535 for ( unsigned corner = 0; corner < 3; corner++ )
536 {
537 const wxPoint2DDouble& pt1 = rectPoints[n];
538
539 n = (n + 1) % RectPoint_Max;
540
541 const wxPoint2DDouble& pt2 = rectPoints[n];
542
543 path.AddArcToPoint(pt1.m_x, pt1.m_y, pt2.m_x, pt2.m_y, RADIUS);
544
545 n = (n + 1) % RectPoint_Max;
546 }
547
548 // Last one wraps to the first point of the tip.
549 const wxPoint2DDouble& pt1 = rectPoints[n];
550 const wxPoint2DDouble& pt2 = tipPoints[0];
551
552 path.AddArcToPoint(pt1.m_x, pt1.m_y, pt2.m_x, pt2.m_y, RADIUS);
553
554 path.CloseSubpath();
555 }
556
557 SetShape(path);
558 #else // !wxUSE_GRAPHICS_CONTEXT
559 int x = contentSize.x/2,
560 yApex = 0,
561 dy = 0;
562
563 SetSize(contentSize);
564 #endif // wxUSE_GRAPHICS_CONTEXT/!wxUSE_GRAPHICS_CONTEXT
565
566 m_anchorPos.x = x;
567 m_anchorPos.y = yApex;
568
569 return dy;
570 }
571
572 // Timer event handler hides the tooltip when the timeout expires.
573 void OnTimer(wxTimerEvent& WXUNUSED(event))
574 {
575 if ( !m_delayShow )
576 {
577 // Doing "Notify" here ensures that our OnDismiss() is called and so we
578 // also Destroy() ourselves. We could use Dismiss() and call Destroy()
579 // explicitly from here as well.
580 DismissAndNotify();
581
582 return;
583 }
584
585 m_delayShow = false;
586
587 if ( m_timeout )
588 m_timer.Start(m_timeout, true);
589
590 DoShow();
591 }
592
593
594 // The anchor point offset if we show a tip or the middle of the top side
595 // otherwise.
596 wxPoint m_anchorPos;
597
598 // The timer counting down the time until we're hidden.
599 wxTimer m_timer;
600
601 // We will need to accesss the timeout period when delaying showing tooltip.
602 int m_timeout;
603
604 // If true, delay showing the tooltip.
605 bool m_delayShow;
606
607 wxDECLARE_NO_COPY_CLASS(wxRichToolTipPopup);
608 };
609
610 // ----------------------------------------------------------------------------
611 // wxRichToolTipGenericImpl: generic implementation of wxRichToolTip.
612 // ----------------------------------------------------------------------------
613
614 void
615 wxRichToolTipGenericImpl::SetBackgroundColour(const wxColour& col,
616 const wxColour& colEnd)
617 {
618 m_colStart = col;
619 m_colEnd = colEnd;
620 }
621
622 void wxRichToolTipGenericImpl::SetCustomIcon(const wxIcon& icon)
623 {
624 m_icon = icon;
625 }
626
627 void wxRichToolTipGenericImpl::SetStandardIcon(int icon)
628 {
629 switch ( icon & wxICON_MASK )
630 {
631 case wxICON_WARNING:
632 case wxICON_ERROR:
633 case wxICON_INFORMATION:
634 // Although we don't use this icon in a list, we need a smallish
635 // icon here and not an icon of a typical message box size so use
636 // wxART_LIST to get it.
637 m_icon = wxArtProvider::GetIcon
638 (
639 wxArtProvider::GetMessageBoxIconId(icon),
640 wxART_LIST
641 );
642 break;
643
644 case wxICON_QUESTION:
645 wxFAIL_MSG("Question icon doesn't make sense for a tooltip");
646 break;
647
648 case wxICON_NONE:
649 m_icon = wxNullIcon;
650 break;
651 }
652 }
653
654 void wxRichToolTipGenericImpl::SetTimeout(unsigned millisecondsTimeout,
655 unsigned millisecondsDelay)
656 {
657 m_delay = millisecondsDelay;
658 m_timeout = millisecondsTimeout;
659 }
660
661 void wxRichToolTipGenericImpl::SetTipKind(wxTipKind tipKind)
662 {
663 m_tipKind = tipKind;
664 }
665
666 void wxRichToolTipGenericImpl::SetTitleFont(const wxFont& font)
667 {
668 m_titleFont = font;
669 }
670
671 void wxRichToolTipGenericImpl::ShowFor(wxWindow* win)
672 {
673 // Set the focus to the window the tooltip refers to to make it look active.
674 win->SetFocus();
675
676 wxRichToolTipPopup* const popup = new wxRichToolTipPopup
677 (
678 win,
679 m_title,
680 m_message,
681 m_icon,
682 m_tipKind,
683 m_titleFont
684 );
685
686 popup->SetBackgroundColours(m_colStart, m_colEnd);
687
688 popup->SetPosition();
689 // show or start the timer to delay showing the popup
690 popup->SetTimeoutAndShow( m_timeout, m_delay );
691 }
692
693 // Currently only wxMSW provides a native implementation.
694 #ifndef __WXMSW__
695
696 /* static */
697 wxRichToolTipImpl*
698 wxRichToolTipImpl::Create(const wxString& title, const wxString& message)
699 {
700 return new wxRichToolTipGenericImpl(title, message);
701 }
702
703 #endif // !__WXMSW__
704
705 #endif // wxUSE_RICHTOOLTIP