Files
dolphin/Externals/wxWidgets3/src/generic/richtooltipg.cpp
2012-03-17 21:47:47 -07:00

674 lines
21 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/generic/richtooltipg.cpp
// Purpose: Implementation of wxRichToolTip.
// Author: Vadim Zeitlin
// Created: 2011-10-07
// RCS-ID: $Id: richtooltipg.cpp 69968 2011-12-09 15:58:40Z VZ $
// 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 SetBackgroundColours(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->SetBackgroundColours(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