]> git.saurik.com Git - wxWidgets.git/blobdiff - src/generic/richtooltipg.cpp
Rename src/generic/richtooltip.cpp to src/generic/richtooltipg.cpp.
[wxWidgets.git] / src / generic / richtooltipg.cpp
diff --git a/src/generic/richtooltipg.cpp b/src/generic/richtooltipg.cpp
new file mode 100644 (file)
index 0000000..2cbce1e
--- /dev/null
@@ -0,0 +1,673 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        src/generic/richtooltipg.cpp
+// Purpose:     Implementation of wxRichToolTip.
+// Author:      Vadim Zeitlin
+// Created:     2011-10-07
+// RCS-ID:      $Id: wxhead.cpp,v 1.11 2010-04-22 12:44:51 zeitlin Exp $
+// Copyright:   (c) 2011 Vadim Zeitlin <vadim@wxwidgets.org>
+// Licence:     wxWindows licence
+///////////////////////////////////////////////////////////////////////////////
+
+// ============================================================================
+// declarations
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// headers
+// ----------------------------------------------------------------------------
+
+// for compilers that support precompilation, includes "wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+    #pragma hdrstop
+#endif
+
+#if wxUSE_RICHTOOLTIP
+
+#ifndef WX_PRECOMP
+    #include "wx/dcmemory.h"
+    #include "wx/icon.h"
+    #include "wx/region.h"
+    #include "wx/settings.h"
+    #include "wx/sizer.h"
+    #include "wx/statbmp.h"
+    #include "wx/stattext.h"
+    #include "wx/timer.h"
+    #include "wx/utils.h"
+#endif // WX_PRECOMP
+
+#include "wx/private/richtooltip.h"
+#include "wx/generic/private/richtooltip.h"
+
+#include "wx/artprov.h"
+#include "wx/custombgwin.h"
+#include "wx/display.h"
+#include "wx/graphics.h"
+#include "wx/popupwin.h"
+#include "wx/textwrapper.h"
+
+#ifdef __WXMSW__
+    #include "wx/msw/uxtheme.h"
+
+    static const int TTP_BALLOONTITLE = 4;
+
+    static const int TMT_TEXTCOLOR = 3803;
+    static const int TMT_GRADIENTCOLOR1 = 3810;
+    static const int TMT_GRADIENTCOLOR2 = 3811;
+#endif
+
+// ----------------------------------------------------------------------------
+// wxRichToolTipPopup: the popup window used by wxRichToolTip.
+// ----------------------------------------------------------------------------
+
+class wxRichToolTipPopup :
+    public wxCustomBackgroundWindow<wxPopupTransientWindow>
+{
+public:
+    wxRichToolTipPopup(wxWindow* parent,
+                       const wxString& title,
+                       const wxString& message,
+                       const wxIcon& icon,
+                       wxTipKind tipKind,
+                       const wxFont& titleFont_) :
+        m_timer(this)
+    {
+        Create(parent, wxFRAME_SHAPED);
+
+
+        wxBoxSizer* const sizerTitle = new wxBoxSizer(wxHORIZONTAL);
+        if ( icon.IsOk() )
+        {
+            sizerTitle->Add(new wxStaticBitmap(this, wxID_ANY, icon),
+                            wxSizerFlags().Centre().Border(wxRIGHT));
+        }
+        //else: Simply don't show any icon.
+
+        wxStaticText* const labelTitle = new wxStaticText(this, wxID_ANY, "");
+        labelTitle->SetLabelText(title);
+
+        wxFont titleFont(titleFont_);
+        if ( !titleFont.IsOk() )
+        {
+            // Determine the appropriate title font for the current platform.
+            titleFont = labelTitle->GetFont();
+
+#ifdef __WXMSW__
+            // When using themes MSW tooltips use larger bluish version of the
+            // normal font.
+            wxUxThemeEngine* const theme = GetTooltipTheme();
+            if ( theme )
+            {
+                titleFont.MakeLarger();
+
+                COLORREF c;
+                if ( FAILED(theme->GetThemeColor
+                                   (
+                                        wxUxThemeHandle(parent, L"TOOLTIP"),
+                                        TTP_BALLOONTITLE,
+                                        0,
+                                        TMT_TEXTCOLOR,
+                                        &c
+                                    )) )
+                {
+                    // Use the standard value of this colour as fallback.
+                    c = 0x993300;
+                }
+
+                labelTitle->SetForegroundColour(wxRGBToColour(c));
+            }
+            else
+#endif // __WXMSW__
+            {
+                // Everything else, including "classic" MSW look uses just the
+                // bold version of the base font.
+                titleFont.MakeBold();
+            }
+        }
+
+        labelTitle->SetFont(titleFont);
+        sizerTitle->Add(labelTitle, wxSizerFlags().Centre());
+
+        wxBoxSizer* const sizerTop = new wxBoxSizer(wxVERTICAL);
+        sizerTop->Add(sizerTitle,
+                        wxSizerFlags().DoubleBorder(wxLEFT|wxRIGHT|wxTOP));
+
+        // Use a spacer as we don't want to have a double border between the
+        // elements, just a simple one will do.
+        sizerTop->AddSpacer(wxSizerFlags::GetDefaultBorder());
+
+        wxTextSizerWrapper wrapper(this);
+        wxSizer* sizerText = wrapper.CreateSizer(message, -1 /* No wrapping */);
+
+#ifdef __WXMSW__
+        if ( icon.IsOk() && GetTooltipTheme() )
+        {
+            // Themed tooltips under MSW align the text with the title, not
+            // with the icon, so use a helper horizontal sizer in this case.
+            wxBoxSizer* const sizerTextIndent = new wxBoxSizer(wxHORIZONTAL);
+            sizerTextIndent->AddSpacer(icon.GetWidth());
+            sizerTextIndent->Add(sizerText,
+                                    wxSizerFlags().Border(wxLEFT).Centre());
+
+            sizerText = sizerTextIndent;
+        }
+#endif // !__WXMSW__
+        sizerTop->Add(sizerText,
+                        wxSizerFlags().DoubleBorder(wxLEFT|wxRIGHT|wxBOTTOM)
+                                      .Centre());
+
+        SetSizer(sizerTop);
+
+        const int offsetY = SetTipShapeAndSize(tipKind, GetBestSize());
+        if ( offsetY > 0 )
+        {
+            // Offset our contents by the tip height to make it appear in the
+            // main rectangle.
+            sizerTop->PrependSpacer(offsetY);
+        }
+
+        Layout();
+    }
+
+    void SetBackground(wxColour colStart, wxColour colEnd)
+    {
+        if ( !colStart.IsOk() )
+        {
+            // Determine the best colour(s) to use on our own.
+#ifdef __WXMSW__
+            wxUxThemeEngine* const theme = GetTooltipTheme();
+            if ( theme )
+            {
+                wxUxThemeHandle hTheme(GetParent(), L"TOOLTIP");
+
+                COLORREF c1, c2;
+                if ( FAILED(theme->GetThemeColor
+                                   (
+                                        hTheme,
+                                        TTP_BALLOONTITLE,
+                                        0,
+                                        TMT_GRADIENTCOLOR1,
+                                        &c1
+                                    )) ||
+                    FAILED(theme->GetThemeColor
+                                  (
+                                        hTheme,
+                                        TTP_BALLOONTITLE,
+                                        0,
+                                        TMT_GRADIENTCOLOR2,
+                                        &c2
+                                  )) )
+                {
+                    c1 = 0xffffff;
+                    c2 = 0xf0e5e4;
+                }
+
+                colStart = wxRGBToColour(c1);
+                colEnd = wxRGBToColour(c2);
+            }
+            else
+#endif // __WXMSW__
+            {
+                colStart = wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK);
+            }
+        }
+
+        if ( colEnd.IsOk() )
+        {
+            // Use gradient-filled background bitmap.
+            const wxSize size = GetClientSize();
+            wxBitmap bmp(size);
+            {
+                wxMemoryDC dc(bmp);
+                dc.Clear();
+                dc.GradientFillLinear(size, colStart, colEnd, wxDOWN);
+            }
+
+            SetBackgroundBitmap(bmp);
+        }
+        else // Use solid colour.
+        {
+            SetBackgroundColour(colStart);
+        }
+    }
+
+    void DoShow()
+    {
+        wxPoint pos = GetTipPoint();
+
+        // We want our anchor point to coincide with this position so offset
+        // the position of the top left corner passed to Move() accordingly.
+        pos -= m_anchorPos;
+
+        Move(pos, wxSIZE_NO_ADJUSTMENTS);
+
+        Popup();
+    }
+
+    void SetTimeout(unsigned timeout)
+    {
+        if ( !timeout )
+            return;
+
+        Connect(wxEVT_TIMER, wxTimerEventHandler(wxRichToolTipPopup::OnTimer));
+
+        m_timer.Start(timeout, true /* one shot */);
+    }
+
+protected:
+    virtual void OnDismiss()
+    {
+        Destroy();
+    }
+
+private:
+#ifdef __WXMSW__
+    // Returns non-NULL theme only if we're using Win7-style tooltips.
+    static wxUxThemeEngine* GetTooltipTheme()
+    {
+        // Even themed applications under XP still use "classic" tooltips.
+        if ( wxGetWinVersion() <= wxWinVersion_XP )
+            return NULL;
+
+        return wxUxThemeEngine::GetIfActive();
+    }
+#endif // __WXMSW__
+
+    // For now we just hard code the tip height, would be nice to do something
+    // smarter in the future.
+    static int GetTipHeight()
+    {
+#ifdef __WXMSW__
+        if ( GetTooltipTheme() )
+            return 20;
+#endif // __WXMSW__
+
+        return 15;
+    }
+
+    // Get the point to which our tip should point.
+    wxPoint GetTipPoint() const
+    {
+        // Currently we always use the middle of the window. It seems that MSW
+        // native tooltips use a different point but it's not really clear how
+        // do they determine it nor whether it's worth the trouble to emulate
+        // their behaviour.
+        const wxRect r = GetParent()->GetScreenRect();
+        return wxPoint(r.x + r.width/2, r.y + r.height/2);
+    }
+
+    // Choose the correct orientation depending on the window position.
+    //
+    // Also use the tip kind appropriate for the current environment. For MSW
+    // the right triangles are used and for Mac the equilateral ones as this is
+    // the prevailing kind under these systems. For everything else we go with
+    // right triangles as well but without any real rationale so this could be
+    // tweaked in the future.
+    wxTipKind GetBestTipKind() const
+    {
+        const wxPoint pos = GetTipPoint();
+
+        // Use GetFromWindow() and not GetFromPoint() here to try to get the
+        // correct display even if the tip point itself is not visible.
+        int dpy = wxDisplay::GetFromWindow(GetParent());
+        if ( dpy == wxNOT_FOUND )
+            dpy = 0; // What else can we do?
+
+        const wxRect rectDpy = wxDisplay(dpy).GetClientArea();
+
+#ifdef __WXMAC__
+        return pos.y > rectDpy.height/2 ? wxTipKind_Bottom : wxTipKind_Top;
+#else // !__WXMAC__
+        return pos.y > rectDpy.height/2
+                    ? pos.x > rectDpy.width/2
+                        ? wxTipKind_BottomRight
+                        : wxTipKind_BottomLeft
+                    : pos.x > rectDpy.width/2
+                        ? wxTipKind_TopRight
+                        : wxTipKind_TopLeft;
+#endif // __WXMAC__/!__WXMAC__
+    }
+
+    // Set the size and shape of the tip window and returns the offset of its
+    // content area from the top (horizontal offset is always 0 currently).
+    int SetTipShapeAndSize(wxTipKind tipKind, const wxSize& contentSize)
+    {
+#if wxUSE_GRAPHICS_CONTEXT
+        wxSize size = contentSize;
+
+        // The size is the vertical size and the offset is the distance from
+        // edge for asymmetric tips, currently hard-coded to be the same as the
+        // size.
+        const int tipSize = GetTipHeight();
+        const int tipOffset = tipSize;
+
+        // The horizontal position of the tip.
+        int x = -1;
+
+        // The vertical coordinates of the tip base and apex.
+        int yBase = -1,
+            yApex = -1;
+
+        // The offset of the content part of the window.
+        int dy = -1;
+
+        // Define symbolic names for the rectangle corners and mid-way points
+        // that we use below in an attempt to make the code more clear. Notice
+        // that these values must be consecutive as we iterate over them.
+        enum RectPoint
+        {
+            RectPoint_TopLeft,
+            RectPoint_Top,
+            RectPoint_TopRight,
+            RectPoint_Right,
+            RectPoint_BotRight,
+            RectPoint_Bot,
+            RectPoint_BotLeft,
+            RectPoint_Left,
+            RectPoint_Max
+        };
+
+        // The starting point for AddArcToPoint() calls below, we iterate over
+        // all RectPoints from it.
+        RectPoint pointStart = RectPoint_Max;
+
+
+        // Hard-coded radius of the round main rectangle corners.
+        const double RADIUS = 5;
+
+        // Create a path defining the shape of the tooltip window.
+        wxGraphicsPath
+            path = wxGraphicsRenderer::GetDefaultRenderer()->CreatePath();
+
+        if ( tipKind == wxTipKind_Auto )
+            tipKind = GetBestTipKind();
+
+        // Points defining the tip shape (in clockwise order as we must end at
+        // tipPoints[0] after drawing the rectangle outline in this order).
+        wxPoint2DDouble tipPoints[3];
+
+        switch ( tipKind )
+        {
+            case wxTipKind_Auto:
+                wxFAIL_MSG( "Impossible kind value" );
+                break;
+
+            case wxTipKind_TopLeft:
+                x = tipOffset;
+                yApex = 0;
+                yBase = tipSize;
+                dy = tipSize;
+
+                tipPoints[0] = wxPoint2DDouble(x, yBase);
+                tipPoints[1] = wxPoint2DDouble(x, yApex);
+                tipPoints[2] = wxPoint2DDouble(x + tipSize, yBase);
+
+                pointStart = RectPoint_TopRight;
+                break;
+
+            case wxTipKind_TopRight:
+                x = size.x - tipOffset;
+                yApex = 0;
+                yBase = tipSize;
+                dy = tipSize;
+
+                tipPoints[0] = wxPoint2DDouble(x - tipSize, yBase);
+                tipPoints[1] = wxPoint2DDouble(x, yApex);
+                tipPoints[2] = wxPoint2DDouble(x, yBase);
+
+                pointStart = RectPoint_TopRight;
+                break;
+
+            case wxTipKind_BottomLeft:
+                x = tipOffset;
+                yApex = size.y + tipSize;
+                yBase = size.y;
+                dy = 0;
+
+                tipPoints[0] = wxPoint2DDouble(x + tipSize, yBase);
+                tipPoints[1] = wxPoint2DDouble(x, yApex);
+                tipPoints[2] = wxPoint2DDouble(x, yBase);
+
+                pointStart = RectPoint_BotLeft;
+                break;
+
+            case wxTipKind_BottomRight:
+                x = size.x - tipOffset;
+                yApex = size.y + tipSize;
+                yBase = size.y;
+                dy = 0;
+
+                tipPoints[0] = wxPoint2DDouble(x, yBase);
+                tipPoints[1] = wxPoint2DDouble(x, yApex);
+                tipPoints[2] = wxPoint2DDouble(x - tipSize, yBase);
+
+                pointStart = RectPoint_BotLeft;
+                break;
+
+            case wxTipKind_Top:
+                x = size.x/2;
+                yApex = 0;
+                yBase = tipSize;
+                dy = tipSize;
+
+                {
+                    // A half-side of an equilateral triangle is its altitude
+                    // divided by sqrt(3) ~= 1.73.
+                    const double halfside = tipSize/1.73;
+
+                    tipPoints[0] = wxPoint2DDouble(x - halfside, yBase);
+                    tipPoints[1] = wxPoint2DDouble(x, yApex);
+                    tipPoints[2] = wxPoint2DDouble(x + halfside, yBase);
+                }
+
+                pointStart = RectPoint_TopRight;
+                break;
+
+            case wxTipKind_Bottom:
+                x = size.x/2;
+                yApex = size.y + tipSize;
+                yBase = size.y;
+                dy = 0;
+
+                {
+                    const double halfside = tipSize/1.73;
+
+                    tipPoints[0] = wxPoint2DDouble(x + halfside, yBase);
+                    tipPoints[1] = wxPoint2DDouble(x, yApex);
+                    tipPoints[2] = wxPoint2DDouble(x - halfside, yBase);
+                }
+
+                pointStart = RectPoint_BotLeft;
+                break;
+
+            case wxTipKind_None:
+                x = size.x/2;
+                dy = 0;
+
+                path.AddRoundedRectangle(0, 0, size.x, size.y, RADIUS);
+                break;
+        }
+
+        wxASSERT_MSG( dy != -1, wxS("Unknown tip kind?") );
+
+        size.y += tipSize;
+        SetSize(size);
+
+        if ( tipKind != wxTipKind_None )
+        {
+            path.MoveToPoint(tipPoints[0]);
+            path.AddLineToPoint(tipPoints[1]);
+            path.AddLineToPoint(tipPoints[2]);
+
+            const double xLeft = 0.;
+            const double xMid = size.x/2.;
+            const double xRight = size.x;
+
+            const double yTop = dy;
+            const double yMid = (dy + size.y)/2.;
+            const double yBot = dy + contentSize.y;
+
+            wxPoint2DDouble rectPoints[RectPoint_Max];
+            rectPoints[RectPoint_TopLeft]  = wxPoint2DDouble(xLeft,  yTop);
+            rectPoints[RectPoint_Top]      = wxPoint2DDouble(xMid,   yTop);
+            rectPoints[RectPoint_TopRight] = wxPoint2DDouble(xRight, yTop);
+            rectPoints[RectPoint_Right]    = wxPoint2DDouble(xRight, yMid);
+            rectPoints[RectPoint_BotRight] = wxPoint2DDouble(xRight, yBot);
+            rectPoints[RectPoint_Bot]      = wxPoint2DDouble(xMid,   yBot);
+            rectPoints[RectPoint_BotLeft]  = wxPoint2DDouble(xLeft,  yBot);
+            rectPoints[RectPoint_Left]     = wxPoint2DDouble(xLeft,  yMid);
+
+            // Iterate over all rectangle rectPoints for the first 3 corners.
+            unsigned n = pointStart;
+            for ( unsigned corner = 0; corner < 3; corner++ )
+            {
+                const wxPoint2DDouble& pt1 = rectPoints[n];
+
+                n = (n + 1) % RectPoint_Max;
+
+                const wxPoint2DDouble& pt2 = rectPoints[n];
+
+                path.AddArcToPoint(pt1.m_x, pt1.m_y, pt2.m_x, pt2.m_y, RADIUS);
+
+                n = (n + 1) % RectPoint_Max;
+            }
+
+            // Last one wraps to the first point of the tip.
+            const wxPoint2DDouble& pt1 = rectPoints[n];
+            const wxPoint2DDouble& pt2 = tipPoints[0];
+
+            path.AddArcToPoint(pt1.m_x, pt1.m_y, pt2.m_x, pt2.m_y, RADIUS);
+
+            path.CloseSubpath();
+        }
+
+        SetShape(path);
+#else // !wxUSE_GRAPHICS_CONTEXT
+        int x = contentSize.x/2,
+            yApex = 0,
+            dy = 0;
+
+        SetSize(contentSize);
+#endif // wxUSE_GRAPHICS_CONTEXT/!wxUSE_GRAPHICS_CONTEXT
+
+        m_anchorPos.x = x;
+        m_anchorPos.y = yApex;
+
+        return dy;
+    }
+
+    // Timer event handler hides the tooltip when the timeout expires.
+    void OnTimer(wxTimerEvent& WXUNUSED(event))
+    {
+        // Doing "Notify" here ensures that our OnDismiss() is called and so we
+        // also Destroy() ourselves. We could use Dismiss() and call Destroy()
+        // explicitly from here as well.
+        DismissAndNotify();
+    }
+
+
+    // The anchor point offset if we show a tip or the middle of the top side
+    // otherwise.
+    wxPoint m_anchorPos;
+
+    // The timer counting down the time until we're hidden.
+    wxTimer m_timer;
+
+    wxDECLARE_NO_COPY_CLASS(wxRichToolTipPopup);
+};
+
+// ----------------------------------------------------------------------------
+// wxRichToolTipGenericImpl: generic implementation of wxRichToolTip.
+// ----------------------------------------------------------------------------
+
+void
+wxRichToolTipGenericImpl::SetBackgroundColour(const wxColour& col,
+                                              const wxColour& colEnd)
+{
+    m_colStart = col;
+    m_colEnd = colEnd;
+}
+
+void wxRichToolTipGenericImpl::SetCustomIcon(const wxIcon& icon)
+{
+    m_icon = icon;
+}
+
+void wxRichToolTipGenericImpl::SetStandardIcon(int icon)
+{
+    switch ( icon & wxICON_MASK )
+    {
+        case wxICON_WARNING:
+        case wxICON_ERROR:
+        case wxICON_INFORMATION:
+            // Although we don't use this icon in a list, we need a smallish
+            // icon here and not an icon of a typical message box size so use
+            // wxART_LIST to get it.
+            m_icon = wxArtProvider::GetIcon
+                     (
+                        wxArtProvider::GetMessageBoxIconId(icon),
+                        wxART_LIST
+                     );
+            break;
+
+        case wxICON_QUESTION:
+            wxFAIL_MSG("Question icon doesn't make sense for a tooltip");
+            break;
+
+        case wxICON_NONE:
+            m_icon = wxNullIcon;
+            break;
+    }
+}
+
+void wxRichToolTipGenericImpl::SetTimeout(unsigned milliseconds)
+{
+    m_timeout = milliseconds;
+}
+
+void wxRichToolTipGenericImpl::SetTipKind(wxTipKind tipKind)
+{
+    m_tipKind = tipKind;
+}
+
+void wxRichToolTipGenericImpl::SetTitleFont(const wxFont& font)
+{
+    m_titleFont = font;
+}
+
+void wxRichToolTipGenericImpl::ShowFor(wxWindow* win)
+{
+    // Set the focus to the window the tooltip refers to to make it look active.
+    win->SetFocus();
+
+    wxRichToolTipPopup* const popup = new wxRichToolTipPopup
+                                          (
+                                            win,
+                                            m_title,
+                                            m_message,
+                                            m_icon,
+                                            m_tipKind,
+                                            m_titleFont
+                                          );
+
+    popup->SetBackground(m_colStart, m_colEnd);
+
+    popup->DoShow();
+
+    popup->SetTimeout(m_timeout);
+}
+
+// Currently only wxMSW provides a native implementation.
+#ifndef __WXMSW__
+
+/* static */
+wxRichToolTipImpl*
+wxRichToolTipImpl::Create(const wxString& title, const wxString& message)
+{
+    return new wxRichToolTipGenericImpl(title, message);
+}
+
+#endif // !__WXMSW__
+
+#endif // wxUSE_RICHTOOLTIP