Files
wxWidgets/src/gtk/dc.cpp
Vadim Zeitlin 6383bc39ff Add convenient wxDCImpl::CalcBoundingBox() overloads and use them
No real changes, just make the code updating the bounding box slightly
shorter by providing convenient and slightly higher-level overloads.

For now these functions are only in wxDCImpl, it's not clear if we
really need them in wxDC, so don't add them to the public API.
2022-04-30 20:51:53 +01:00

685 lines
20 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/gtk/dc.cpp
// Purpose:
// Author: Robert Roebling
// Copyright: (c) 1998 Robert Roebling
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __WXGTK3__
#include "wx/window.h"
#include "wx/dcclient.h"
#include "wx/dcmemory.h"
#include "wx/dcscreen.h"
#include "wx/display.h"
#include "wx/gdicmn.h"
#include "wx/icon.h"
#include "wx/gtk/dc.h"
#include "wx/gtk/private/wrapgtk.h"
wxGTKCairoDCImpl::wxGTKCairoDCImpl(wxDC* owner)
: wxGCDCImpl(owner)
{
m_layoutDir = wxLayout_Default;
}
wxGTKCairoDCImpl::wxGTKCairoDCImpl(wxDC* owner, wxWindow* window, wxLayoutDirection dir, int width)
: wxGCDCImpl(owner, 0)
, m_size(width, 0)
{
m_layoutDir = dir;
if ( window )
{
m_window = window;
m_font = window->GetFont();
m_textForegroundColour = window->GetForegroundColour();
m_textBackgroundColour = window->GetBackgroundColour();
m_contentScaleFactor = window->GetContentScaleFactor();
}
}
void wxGTKCairoDCImpl::InitSize(GdkWindow* window)
{
m_size.x = gdk_window_get_width(window);
m_size.y = gdk_window_get_height(window);
}
void wxGTKCairoDCImpl::DoDrawBitmap(const wxBitmap& bitmap, int x, int y, bool useMask)
{
wxCHECK_RET(IsOk(), "invalid DC");
cairo_t* cr = NULL;
if (m_graphicContext)
cr = static_cast<cairo_t*>(m_graphicContext->GetNativeContext());
if (cr)
{
cairo_save(cr);
if (m_layoutDir == wxLayout_RightToLeft)
{
// bitmap is not mirrored
cairo_scale(cr, -1, 1);
x = -x - bitmap.GetWidth();
}
bitmap.Draw(cr, x, y, useMask, &m_textForegroundColour, &m_textBackgroundColour);
cairo_restore(cr);
}
}
void wxGTKCairoDCImpl::DoDrawIcon(const wxIcon& icon, int x, int y)
{
DoDrawBitmap(icon, x, y, true);
}
#if wxUSE_IMAGE
bool wxDoFloodFill(wxDC* dc, int x, int y, const wxColour& col, wxFloodFillStyle style);
bool wxGTKCairoDCImpl::DoFloodFill(int x, int y, const wxColour& col, wxFloodFillStyle style)
{
return wxDoFloodFill(GetOwner(), x, y, col, style);
}
#endif
void wxGTKCairoDCImpl::DoDrawText(const wxString& text, int x, int y)
{
wxCHECK_RET(IsOk(), "invalid DC");
if (text.empty())
return;
const bool xInverted = m_signX < 0 || m_layoutDir == wxLayout_RightToLeft;
if (xInverted && text.find('\n') != wxString::npos)
{
// RTL needs each line separately to position text properly.
// DrawLabel() will split the text and call back for each line.
GetOwner()->DrawLabel(text, wxRect(x, y, 0, 0));
return;
}
int w, h;
DoGetTextExtent(text, &w, &h);
CalcBoundingBox(wxPoint(x, y), wxSize(w, h));
const bool yInverted = m_signY < 0;
if (xInverted || yInverted)
m_graphicContext->PushState();
if (xInverted)
{
// text is not mirrored
m_graphicContext->Scale(-1, 1);
x = -x - w;
}
if (yInverted)
{
m_graphicContext->Scale(1, -1);
y = -y - h;
}
wxCompositionMode curMode = m_graphicContext->GetCompositionMode();
m_graphicContext->SetCompositionMode(wxCOMPOSITION_OVER);
if (m_backgroundMode == wxBRUSHSTYLE_TRANSPARENT)
m_graphicContext->DrawText(text, x, y);
else
m_graphicContext->DrawText(text, x, y, m_graphicContext->CreateBrush(m_textBackgroundColour));
m_graphicContext->SetCompositionMode(curMode);
if (xInverted || yInverted)
m_graphicContext->PopState();
}
void wxGTKCairoDCImpl::DoDrawRotatedText(const wxString& text, int x, int y, double angle)
{
wxCHECK_RET(IsOk(), "invalid DC");
// save current bounding box
// rotation will cause DoDrawText() to update it incorrectly
const bool isBBoxValid = m_isBBoxValid;
const int minX = m_minX;
const int minY = m_minY;
const int maxX = m_maxX;
const int maxY = m_maxY;
const double rad = wxDegToRad(-angle);
m_graphicContext->PushState();
m_graphicContext->Translate(x, y);
m_graphicContext->Rotate(rad);
DoDrawText(text, 0, 0);
m_graphicContext->PopState();
// restore bounding box and update it correctly
m_isBBoxValid = isBBoxValid;
m_minX = minX;
m_minY = minY;
m_maxX = maxX;
m_maxY = maxY;
CalcBoundingBox(x, y);
int w, h;
DoGetTextExtent(text, &w, &h);
cairo_matrix_t m;
cairo_matrix_init_translate(&m, x, y);
cairo_matrix_rotate(&m, rad);
double xx = w, yy = 0;
cairo_matrix_transform_point(&m, &xx, &yy);
CalcBoundingBox(int(xx), int(yy));
xx = w; yy = h;
cairo_matrix_transform_point(&m, &xx, &yy);
CalcBoundingBox(int(xx), int(yy));
xx = 0; yy = h;
cairo_matrix_transform_point(&m, &xx, &yy);
CalcBoundingBox(int(xx), int(yy));
}
void wxGTKCairoDCImpl::DoDrawCheckMark(int x, int y, int width, int height)
{
if (m_layoutDir == wxLayout_RightToLeft)
{
wxCHECK_RET(IsOk(), "invalid DC");
// checkmark is not mirrored
m_graphicContext->PushState();
m_graphicContext->Scale(-1, 1);
BaseType::DoDrawCheckMark(-x - width, y, width, height);
m_graphicContext->PopState();
}
else
BaseType::DoDrawCheckMark(x, y, width, height);
}
wxBitmap wxGTKCairoDCImpl::DoGetAsBitmap(const wxRect* /*subrect*/) const
{
wxFAIL_MSG("DoGetAsBitmap not implemented");
return wxBitmap();
}
bool wxGTKCairoDCImpl::DoGetPixel(int x, int y, wxColour* col) const
{
if (col)
{
cairo_t* cr = NULL;
if (m_graphicContext)
cr = static_cast<cairo_t*>(m_graphicContext->GetNativeContext());
if (cr)
{
cairo_surface_t* surface = cairo_get_target(cr);
x = LogicalToDeviceX(x);
y = LogicalToDeviceY(y);
GdkPixbuf* pixbuf = gdk_pixbuf_get_from_surface(surface, x, y, 1, 1);
if (pixbuf)
{
const guchar* src = gdk_pixbuf_get_pixels(pixbuf);
col->Set(src[0], src[1], src[2]);
g_object_unref(pixbuf);
return true;
}
*col = wxColour();
}
}
return false;
}
void wxGTKCairoDCImpl::DoGetSize(int* width, int* height) const
{
if (width)
*width = m_size.x;
if (height)
*height = m_size.y;
}
bool wxGTKCairoDCImpl::DoStretchBlit(int xdest, int ydest, int dstWidth, int dstHeight, wxDC* source, int xsrc, int ysrc, int srcWidth, int srcHeight, wxRasterOperationMode rop, bool useMask, int xsrcMask, int ysrcMask)
{
wxCHECK_MSG(IsOk(), false, "invalid DC");
wxCHECK_MSG(source && source->IsOk(), false, "invalid source DC");
cairo_t* cr = NULL;
if (m_graphicContext)
cr = static_cast<cairo_t*>(m_graphicContext->GetNativeContext());
cairo_t* cr_src = NULL;
wxGraphicsContext* gc_src = source->GetGraphicsContext();
if (gc_src)
cr_src = static_cast<cairo_t*>(gc_src->GetNativeContext());
if (cr == NULL || cr_src == NULL)
return false;
const int xsrc_dev = source->LogicalToDeviceX(xsrc);
const int ysrc_dev = source->LogicalToDeviceY(ysrc);
cairo_surface_t* surfaceSrc = cairo_get_target(cr_src);
cairo_surface_flush(surfaceSrc);
cairo_surface_t* surfaceTmp = NULL;
// If destination (this) and source wxDC refer to the same Cairo context
// it means that we operate on one surface and results of drawing
// can be invalid if destination and source regions overlap.
// In such situation we have to copy source surface to the temporary
// surface and use this copy in the drawing operations.
if ( cr == cr_src )
{
// Check if destination and source regions overlap.
// If necessary, copy source surface to the temporary one.
if (wxRect(xdest, ydest, dstWidth, dstHeight)
.Intersects(wxRect(xsrc, ysrc, srcWidth, srcHeight)))
{
const int w = cairo_image_surface_get_width(surfaceSrc);
const int h = cairo_image_surface_get_height(surfaceSrc);
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
if ( cairo_version() >= CAIRO_VERSION_ENCODE(1, 12, 0) )
{
surfaceTmp = cairo_surface_create_similar_image(surfaceSrc,
cairo_image_surface_get_format(surfaceSrc),
w, h);
}
else
#endif // Cairo 1.12
{
surfaceTmp = cairo_surface_create_similar(surfaceSrc,
CAIRO_CONTENT_COLOR_ALPHA,
w, h);
}
cairo_t* crTmp = cairo_create(surfaceTmp);
cairo_set_source_surface(crTmp, surfaceSrc, 0, 0);
cairo_rectangle(crTmp, 0.0, 0.0, w, h);
cairo_set_operator(crTmp, CAIRO_OPERATOR_SOURCE);
cairo_fill(crTmp);
cairo_destroy(crTmp);
cairo_surface_flush(surfaceTmp);
surfaceSrc = surfaceTmp;
}
}
cairo_save(cr);
if (m_layoutDir == wxLayout_RightToLeft)
{
// blit is not mirrored
cairo_scale(cr, -1, 1);
xdest = -xdest - dstWidth;
}
cairo_translate(cr, xdest, ydest);
cairo_rectangle(cr, 0, 0, dstWidth, dstHeight);
double sx, sy;
source->GetUserScale(&sx, &sy);
const wxBitmap& bitmap = source->GetImpl()->GetSelectedBitmap();
const double bmpScale = bitmap.IsOk() ? bitmap.GetScaleFactor() : 1.0;
cairo_scale(cr, dstWidth / (sx * srcWidth * bmpScale), dstHeight / (sy * srcHeight * bmpScale));
cairo_set_source_surface(cr, surfaceSrc, -xsrc_dev, -ysrc_dev);
const wxRasterOperationMode rop_save = m_logicalFunction;
SetLogicalFunction(rop);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
cairo_surface_t* maskSurf = NULL;
if (useMask)
{
if (bitmap.IsOk())
{
wxMask* mask = bitmap.GetMask();
if (mask)
maskSurf = *mask;
}
}
if (maskSurf)
{
int xsrcMask_dev = xsrc_dev;
int ysrcMask_dev = ysrc_dev;
if (xsrcMask != -1)
xsrcMask_dev = source->LogicalToDeviceX(xsrcMask);
if (ysrcMask != -1)
ysrcMask_dev = source->LogicalToDeviceY(ysrcMask);
cairo_clip(cr);
cairo_mask_surface(cr, maskSurf, -xsrcMask_dev, -ysrcMask_dev);
}
else
{
cairo_fill(cr);
}
cairo_restore(cr);
if ( surfaceTmp )
{
cairo_surface_destroy(surfaceTmp);
}
m_logicalFunction = rop_save;
return true;
}
void* wxGTKCairoDCImpl::GetCairoContext() const
{
cairo_t* cr = NULL;
if (m_graphicContext)
cr = static_cast<cairo_t*>(m_graphicContext->GetNativeContext());
return cr;
}
wxSize wxGTKCairoDCImpl::GetPPI() const
{
if ( m_window )
{
return wxDisplay(m_window).GetPPI();
}
// For a non-window-based DC the concept of PPI doesn't make much sense
// anyhow, so just return the hardcoded value used by the base class.
return wxGCDCImpl::GetPPI();
}
void wxGTKCairoDCImpl::SetLayoutDirection(wxLayoutDirection dir)
{
if (dir == wxLayout_Default && m_window)
dir = m_window->GetLayoutDirection();
m_layoutDir = dir;
}
wxLayoutDirection wxGTKCairoDCImpl::GetLayoutDirection() const
{
// LTR unless explicitly RTL
return
m_layoutDir == wxLayout_RightToLeft
? wxLayout_RightToLeft
: wxLayout_LeftToRight;
}
void wxGTKCairoDCImpl::AdjustForRTL(cairo_t* cr)
{
if (m_layoutDir == wxLayout_RightToLeft)
{
cairo_translate(cr, m_size.x, 0);
cairo_scale(cr, -1, 1);
}
}
//-----------------------------------------------------------------------------
wxWindowDCImpl::wxWindowDCImpl(wxWindowDC* owner, wxWindow* window)
: wxGTKCairoDCImpl(owner, window)
{
GtkWidget* widget = window->m_wxwindow;
if (widget == NULL)
widget = window->m_widget;
GdkWindow* gdkWindow = NULL;
if (widget)
{
gdkWindow = gtk_widget_get_window(widget);
m_ok = true;
}
if (gdkWindow)
{
cairo_t* cr = gdk_cairo_create(gdkWindow);
SetLayoutDirection(wxLayout_Default);
AdjustForRTL(cr);
wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(cr);
cairo_destroy(cr);
gc->SetContentScaleFactor(m_contentScaleFactor);
SetGraphicsContext(gc);
GtkAllocation a;
gtk_widget_get_allocation(widget, &a);
int x, y;
if (gtk_widget_get_has_window(widget))
{
m_size.x = gdk_window_get_width(gdkWindow);
m_size.y = gdk_window_get_height(gdkWindow);
x = m_size.x - a.width;
y = m_size.y - a.height;
}
else
{
m_size.x = a.width;
m_size.y = a.height;
x = a.x;
y = a.y;
cairo_rectangle(cr, a.x, a.y, a.width, a.height);
cairo_clip(cr);
}
if (x || y)
SetDeviceLocalOrigin(x, y);
}
else
SetGraphicsContext(wxGraphicsContext::Create());
}
//-----------------------------------------------------------------------------
wxClientDCImpl::wxClientDCImpl(wxClientDC* owner, wxWindow* window)
: wxGTKCairoDCImpl(owner, window)
{
GtkWidget* widget = window->m_wxwindow;
if (widget == NULL)
widget = window->m_widget;
GdkWindow* gdkWindow = NULL;
if (widget)
{
window->GetClientSize(&m_size.x, &m_size.y);
gdkWindow = gtk_widget_get_window(widget);
m_ok = true;
}
if (gdkWindow)
{
cairo_t* cr = gdk_cairo_create(gdkWindow);
SetLayoutDirection(wxLayout_Default);
AdjustForRTL(cr);
wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(cr);
cairo_destroy(cr);
gc->SetContentScaleFactor(m_contentScaleFactor);
SetGraphicsContext(gc);
if (!gtk_widget_get_has_window(widget))
{
GtkAllocation a;
gtk_widget_get_allocation(widget, &a);
cairo_rectangle(cr, a.x, a.y, a.width, a.height);
cairo_clip(cr);
SetDeviceLocalOrigin(a.x, a.y);
}
}
else
SetGraphicsContext(wxGraphicsContext::Create());
}
//-----------------------------------------------------------------------------
wxPaintDCImpl::wxPaintDCImpl(wxPaintDC* owner, wxWindow* window)
: wxGTKCairoDCImpl(owner, window)
, m_clip(window->m_nativeUpdateRegion)
{
cairo_t* cr = window->GTKPaintContext();
wxCHECK_RET(cr, "using wxPaintDC without being in a native paint event");
InitSize(gtk_widget_get_window(window->m_wxwindow));
wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(cr);
gc->SetContentScaleFactor(m_contentScaleFactor);
SetGraphicsContext(gc);
// context is already adjusted for RTL
m_layoutDir = window->GetLayoutDirection();
}
void wxPaintDCImpl::DestroyClippingRegion()
{
BaseType::DestroyClippingRegion();
// re-establish clip for paint update area
int x, y, w, h;
m_clip.GetBox(x, y, w, h);
cairo_t* cr = static_cast<cairo_t*>(GetCairoContext());
cairo_rectangle(cr,
DeviceToLogicalX(x), DeviceToLogicalY(y),
DeviceToLogicalXRel(w), DeviceToLogicalYRel(h));
cairo_clip(cr);
}
//-----------------------------------------------------------------------------
wxScreenDCImpl::wxScreenDCImpl(wxScreenDC* owner)
: wxGTKCairoDCImpl(owner, static_cast<wxWindow*>(NULL))
{
GdkWindow* window = gdk_get_default_root_window();
InitSize(window);
cairo_t* cr = gdk_cairo_create(window);
wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(cr);
cairo_destroy(cr);
gc->SetContentScaleFactor(m_contentScaleFactor);
SetGraphicsContext(gc);
}
wxSize wxScreenDCImpl::GetPPI() const
{
return wxGetDisplayPPI();
}
//-----------------------------------------------------------------------------
wxMemoryDCImpl::wxMemoryDCImpl(wxMemoryDC* owner)
: wxGTKCairoDCImpl(owner)
{
m_ok = false;
}
wxMemoryDCImpl::wxMemoryDCImpl(wxMemoryDC* owner, wxBitmap& bitmap)
: wxGTKCairoDCImpl(owner, static_cast<wxWindow*>(NULL))
, m_bitmap(bitmap)
{
Setup();
}
wxMemoryDCImpl::wxMemoryDCImpl(wxMemoryDC* owner, wxDC*)
: wxGTKCairoDCImpl(owner)
{
m_ok = false;
}
wxBitmap wxMemoryDCImpl::DoGetAsBitmap(const wxRect* subrect) const
{
return subrect ? m_bitmap.GetSubBitmap(*subrect) : m_bitmap;
}
void wxMemoryDCImpl::DoSelect(const wxBitmap& bitmap)
{
m_bitmap = bitmap;
Setup();
}
const wxBitmap& wxMemoryDCImpl::GetSelectedBitmap() const
{
return m_bitmap;
}
wxBitmap& wxMemoryDCImpl::GetSelectedBitmap()
{
return m_bitmap;
}
void wxMemoryDCImpl::Setup()
{
wxGraphicsContext* gc = NULL;
m_ok = m_bitmap.IsOk();
if (m_ok)
{
m_size = m_bitmap.GetLogicalSize();
m_contentScaleFactor = m_bitmap.GetScaleFactor();
cairo_t* cr = m_bitmap.CairoCreate();
AdjustForRTL(cr);
gc = wxGraphicsContext::CreateFromNative(cr);
cairo_destroy(cr);
gc->SetContentScaleFactor(m_contentScaleFactor);
}
SetGraphicsContext(gc);
}
//-----------------------------------------------------------------------------
wxGTKCairoDC::wxGTKCairoDC(cairo_t* cr, wxWindow* window, wxLayoutDirection dir, int width)
: wxDC(new wxGTKCairoDCImpl(this, window, dir, width))
{
wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(cr);
gc->SetContentScaleFactor(window->GetContentScaleFactor());
SetGraphicsContext(gc);
if (dir == wxLayout_Default)
SetLayoutDirection(window->GetLayoutDirection());
// else context is already adjusted for RTL
}
#else
#include "wx/gtk/dc.h"
//-----------------------------------------------------------------------------
// wxGTKDCImpl
//-----------------------------------------------------------------------------
wxIMPLEMENT_ABSTRACT_CLASS(wxGTKDCImpl, wxDCImpl);
wxGTKDCImpl::wxGTKDCImpl( wxDC *owner )
: wxDCImpl( owner )
{
m_ok = FALSE;
m_pen = *wxBLACK_PEN;
m_font = *wxNORMAL_FONT;
m_brush = *wxWHITE_BRUSH;
}
wxGTKDCImpl::~wxGTKDCImpl()
{
}
void wxGTKDCImpl::DoSetClippingRegion( wxCoord x, wxCoord y, wxCoord width, wxCoord height )
{
wxASSERT_MSG( width >= 0 && height >= 0,
"Clipping box size values cannot be negative" );
wxRect newRegion(x, y, width, height);
wxRect clipRegion;
if ( m_clipping )
{
// New clipping box is an intersection
// of required clipping box and the current one.
wxRect curRegion(m_clipX1, m_clipY1, m_clipX2 - m_clipX1, m_clipY2 - m_clipY1);
clipRegion = curRegion.Intersect(newRegion);
}
else
{
// Effective clipping box is an intersection
// of required clipping box and DC surface.
int dcWidth, dcHeight;
DoGetSize(&dcWidth, &dcHeight);
wxRect dcRect(DeviceToLogicalX(0), DeviceToLogicalY(0),
DeviceToLogicalXRel(dcWidth), DeviceToLogicalYRel(dcHeight));
clipRegion = dcRect.Intersect(newRegion);
m_clipping = true;
}
if ( clipRegion.IsEmpty() )
{
m_clipX1 = m_clipY1 = m_clipX2 = m_clipY2 = 0;
}
else
{
m_clipX1 = clipRegion.GetLeftTop().x;
m_clipY1 = clipRegion.GetLeftTop().y;
m_clipX2 = clipRegion.GetBottomRight().x + 1;
m_clipY2 = clipRegion.GetBottomRight().y + 1;
}
}
// ---------------------------------------------------------------------------
// get DC capabilities
// ---------------------------------------------------------------------------
void wxGTKDCImpl::DoGetSizeMM( int* width, int* height ) const
{
int w = 0;
int h = 0;
GetOwner()->GetSize( &w, &h );
if (width) *width = int( double(w) / (m_userScaleX*GetMMToPXx()) );
if (height) *height = int( double(h) / (m_userScaleY*GetMMToPXy()) );
}
// Resolution in pixels per logical inch
wxSize wxGTKDCImpl::GetPPI() const
{
// TODO (should probably be pure virtual)
return wxSize(0, 0);
}
#endif