diff --git a/include/wx/gtk/dc.h b/include/wx/gtk/dc.h index 9a1f57b25e..6f6be41438 100644 --- a/include/wx/gtk/dc.h +++ b/include/wx/gtk/dc.h @@ -15,13 +15,16 @@ class wxGTKCairoDCImpl: public wxGCDCImpl { + typedef wxGCDCImpl BaseType; public: wxGTKCairoDCImpl(wxDC* owner); - wxGTKCairoDCImpl(wxDC* owner, double scaleFactor); - wxGTKCairoDCImpl(wxDC* owner, wxWindow* window); + wxGTKCairoDCImpl(wxDC* owner, wxWindow* window, wxLayoutDirection dir = wxLayout_Default, int width = 0); virtual void DoDrawBitmap(const wxBitmap& bitmap, int x, int y, bool useMask) wxOVERRIDE; + virtual void DoDrawCheckMark(int x, int y, int width, int height) wxOVERRIDE; virtual void DoDrawIcon(const wxIcon& icon, int x, int y) wxOVERRIDE; + virtual void DoDrawText(const wxString& text, int x, int y) wxOVERRIDE; + virtual void DoDrawRotatedText(const wxString& text, int x, int y, double angle) wxOVERRIDE; #if wxUSE_IMAGE virtual bool DoFloodFill(int x, int y, const wxColour& col, wxFloodFillStyle style) wxOVERRIDE; #endif @@ -32,12 +35,15 @@ public: virtual void* GetCairoContext() const wxOVERRIDE; virtual wxSize GetPPI() const wxOVERRIDE; + virtual void SetLayoutDirection(wxLayoutDirection dir) wxOVERRIDE; + virtual wxLayoutDirection GetLayoutDirection() const wxOVERRIDE; protected: // Set m_size from the given (valid) GdkWindow. void InitSize(GdkWindow* window); wxSize m_size; + wxLayoutDirection m_layoutDir; wxDECLARE_NO_COPY_CLASS(wxGTKCairoDCImpl); }; @@ -108,7 +114,7 @@ private: class WXDLLIMPEXP_CORE wxGTKCairoDC: public wxDC { public: - wxGTKCairoDC(cairo_t* cr, wxWindow* window); + wxGTKCairoDC(cairo_t* cr, wxWindow* window, wxLayoutDirection dir = wxLayout_LeftToRight, int width = 0); wxDECLARE_NO_COPY_CLASS(wxGTKCairoDC); }; diff --git a/src/gtk/dc.cpp b/src/gtk/dc.cpp index 753da3e76c..7e8579544d 100644 --- a/src/gtk/dc.cpp +++ b/src/gtk/dc.cpp @@ -25,17 +25,14 @@ wxGTKCairoDCImpl::wxGTKCairoDCImpl(wxDC* owner) : wxGCDCImpl(owner) { + m_layoutDir = wxLayout_Default; } -wxGTKCairoDCImpl::wxGTKCairoDCImpl(wxDC* owner, double scaleFactor) - : wxGCDCImpl(owner, 0) -{ - m_contentScaleFactor = scaleFactor; -} - -wxGTKCairoDCImpl::wxGTKCairoDCImpl(wxDC* owner, wxWindow* window) +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; @@ -62,6 +59,12 @@ void wxGTKCairoDCImpl::DoDrawBitmap(const wxBitmap& bitmap, int x, int y, bool u 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); } @@ -81,6 +84,108 @@ bool wxGTKCairoDCImpl::DoFloodFill(int x, int y, const wxColour& col, wxFloodFil } #endif +void wxGTKCairoDCImpl::DoDrawText(const wxString& text, int x, int y) +{ + wxCHECK_RET(IsOk(), "invalid DC"); + + if (text.empty()) + return; + + if (m_layoutDir == wxLayout_RightToLeft && 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(x, y); + CalcBoundingBox(x + w, y + h); + + if (m_layoutDir == wxLayout_RightToLeft) + { + m_graphicContext->PushState(); + // text is not mirrored + m_graphicContext->Scale(-1, 1); + x = -x - w; + } + + 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 (m_layoutDir == wxLayout_RightToLeft) + 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"); @@ -183,6 +288,12 @@ bool wxGTKCairoDCImpl::DoStretchBlit(int xdest, int ydest, int dstWidth, int dst } } 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; @@ -250,6 +361,40 @@ wxSize wxGTKCairoDCImpl::GetPPI() const return wxGCDCImpl::GetPPI(); } +void wxGTKCairoDCImpl::SetLayoutDirection(wxLayoutDirection dir) +{ + if (dir == wxLayout_Default && m_window) + dir = m_window->GetLayoutDirection(); + + if (m_layoutDir != dir) + { + if (m_graphicContext) + { + if (dir == wxLayout_RightToLeft) + { + // wxDC is mirrored for RTL + m_graphicContext->Translate(m_size.x, 0); + m_graphicContext->Scale(-1, 1); + } + else if (m_layoutDir == wxLayout_RightToLeft) + { + m_graphicContext->Scale(-1, 1); + m_graphicContext->Translate(-m_size.x, 0); + } + } + + m_layoutDir = dir; + } +} + +wxLayoutDirection wxGTKCairoDCImpl::GetLayoutDirection() const +{ + // LTR unless explicitly RTL + return + m_layoutDir == wxLayout_RightToLeft + ? wxLayout_RightToLeft + : wxLayout_LeftToRight; +} //----------------------------------------------------------------------------- wxWindowDCImpl::wxWindowDCImpl(wxWindowDC* owner, wxWindow* window) @@ -292,6 +437,8 @@ wxWindowDCImpl::wxWindowDCImpl(wxWindowDC* owner, wxWindow* window) } if (x || y) SetDeviceLocalOrigin(x, y); + + SetLayoutDirection(wxLayout_Default); } else SetGraphicsContext(wxGraphicsContext::Create()); @@ -326,6 +473,7 @@ wxClientDCImpl::wxClientDCImpl(wxClientDC* owner, wxWindow* window) cairo_clip(cr); SetDeviceLocalOrigin(a.x, a.y); } + SetLayoutDirection(wxLayout_Default); } else SetGraphicsContext(wxGraphicsContext::Create()); @@ -342,6 +490,8 @@ wxPaintDCImpl::wxPaintDCImpl(wxPaintDC* owner, wxWindow* window) wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(cr); gc->EnableOffset(m_contentScaleFactor <= 1); SetGraphicsContext(gc); + // context is already adjusted for RTL + m_layoutDir = window->GetLayoutDirection(); } void wxPaintDCImpl::DestroyClippingRegion() @@ -433,15 +583,23 @@ void wxMemoryDCImpl::Setup() gc->EnableOffset(m_contentScaleFactor <= 1); } SetGraphicsContext(gc); + + // re-apply layout direction + const wxLayoutDirection dir = m_layoutDir; + m_layoutDir = wxLayout_Default; + SetLayoutDirection(dir); } //----------------------------------------------------------------------------- -wxGTKCairoDC::wxGTKCairoDC(cairo_t* cr, wxWindow* window) - : wxDC(new wxGTKCairoDCImpl(this, window->GetContentScaleFactor())) +wxGTKCairoDC::wxGTKCairoDC(cairo_t* cr, wxWindow* window, wxLayoutDirection dir, int width) + : wxDC(new wxGTKCairoDCImpl(this, window, dir, width)) { wxGraphicsContext* gc = wxGraphicsContext::CreateFromNative(cr); gc->EnableOffset(window->GetContentScaleFactor() <= 1); SetGraphicsContext(gc); + if (dir == wxLayout_Default) + SetLayoutDirection(window->GetLayoutDirection()); + // else context is already adjusted for RTL } #else diff --git a/src/gtk/renderer.cpp b/src/gtk/renderer.cpp index 2ab0480523..c4adb48166 100644 --- a/src/gtk/renderer.cpp +++ b/src/gtk/renderer.cpp @@ -220,10 +220,6 @@ wxRendererGTK::DrawHeaderButton(wxWindow *win, if (flags & wxCONTROL_DIRTY) button = wxGTKPrivate::GetHeaderButtonWidgetLast(); - int x_diff = 0; - if (win->GetLayoutDirection() == wxLayout_RightToLeft) - x_diff = rect.width; - GtkStateType state = GTK_STATE_NORMAL; if (flags & wxCONTROL_DISABLED) state = GTK_STATE_INSENSITIVE; @@ -252,8 +248,8 @@ wxRendererGTK::DrawHeaderButton(wxWindow *win, sc.AddTreeviewHeaderButton(pos); gtk_style_context_set_state(sc, stateTypeToFlags[state]); - gtk_render_background(sc, cr, rect.x - x_diff, rect.y, rect.width, rect.height); - gtk_render_frame(sc, cr, rect.x - x_diff, rect.y, rect.width, rect.height); + gtk_render_background(sc, cr, rect.x, rect.y, rect.width, rect.height); + gtk_render_frame(sc, cr, rect.x, rect.y, rect.width, rect.height); } else #endif // GTK >= 3.20 @@ -261,11 +257,15 @@ wxRendererGTK::DrawHeaderButton(wxWindow *win, GtkStyleContext* sc = gtk_widget_get_style_context(button); gtk_style_context_save(sc); gtk_style_context_set_state(sc, stateTypeToFlags[state]); - gtk_render_background(sc, cr, rect.x - x_diff, rect.y, rect.width, rect.height); - gtk_render_frame(sc, cr, rect.x - x_diff, rect.y, rect.width, rect.height); + gtk_render_background(sc, cr, rect.x, rect.y, rect.width, rect.height); + gtk_render_frame(sc, cr, rect.x, rect.y, rect.width, rect.height); gtk_style_context_restore(sc); } #else + int x_diff = 0; + if (win->GetLayoutDirection() == wxLayout_RightToLeft) + x_diff = rect.width; + GdkWindow* gdk_window = wxGetGTKDrawable(dc); gtk_paint_box ( @@ -305,7 +305,7 @@ int wxRendererGTK::GetHeaderButtonMargin(wxWindow *WXUNUSED(win)) // draw a ">" or "v" button void -wxRendererGTK::DrawTreeItemButton(wxWindow* win, +wxRendererGTK::DrawTreeItemButton(wxWindow* WXUNUSED_IN_GTK3(win), wxDC& dc, const wxRect& rect, int flags) { wxGTKDrawable* drawable = wxGetGTKDrawable(dc); @@ -314,10 +314,6 @@ wxRendererGTK::DrawTreeItemButton(wxWindow* win, GtkWidget *tree = wxGTKPrivate::GetTreeWidget(); - int x_diff = 0; - if (win->GetLayoutDirection() == wxLayout_RightToLeft) - x_diff = rect.width; - #ifdef __WXGTK3__ int state = GTK_STATE_FLAG_NORMAL; if (flags & wxCONTROL_EXPANDED) @@ -340,9 +336,13 @@ wxRendererGTK::DrawTreeItemButton(wxWindow* win, gtk_style_context_save(sc); gtk_style_context_set_state(sc, GtkStateFlags(state)); gtk_style_context_add_class(sc, GTK_STYLE_CLASS_EXPANDER); - gtk_render_expander(sc, drawable, x - x_diff, y, expander_size, expander_size); + gtk_render_expander(sc, drawable, x, y, expander_size, expander_size); gtk_style_context_restore(sc); #else + int x_diff = 0; + if (win->GetLayoutDirection() == wxLayout_RightToLeft) + x_diff = rect.width; + GtkStateType state; if ( flags & wxCONTROL_CURRENT ) state = GTK_STATE_PRELIGHT; @@ -444,14 +444,10 @@ wxRendererGTK::DrawSplitterSash(wxWindow* win, rect.width = size.x; } - int x_diff = 0; - if (win->GetLayoutDirection() == wxLayout_RightToLeft) - x_diff = rect.width; - #ifdef __WXGTK3__ wxGtkStyleContext sc(dc.GetContentScaleFactor()); sc.AddWindow(); - gtk_render_background(sc, drawable, rect.x - x_diff, rect.y, rect.width, rect.height); + gtk_render_background(sc, drawable, rect.x, rect.y, rect.width, rect.height); sc.Add(GTK_TYPE_PANED, "paned", "pane-separator", NULL); if (gtk_check_version(3,20,0) == NULL) @@ -459,8 +455,12 @@ wxRendererGTK::DrawSplitterSash(wxWindow* win, gtk_style_context_set_state(sc, flags & wxCONTROL_CURRENT ? GTK_STATE_FLAG_PRELIGHT : GTK_STATE_FLAG_NORMAL); - gtk_render_handle(sc, drawable, rect.x - x_diff, rect.y, rect.width, rect.height); + gtk_render_handle(sc, drawable, rect.x, rect.y, rect.width, rect.height); #else + int x_diff = 0; + if (win->GetLayoutDirection() == wxLayout_RightToLeft) + x_diff = rect.width; + GdkWindow* gdk_window = wxGetGTKDrawable(dc); if (gdk_window == NULL) return; @@ -731,8 +731,17 @@ wxRendererGTK::DrawCheckBox(wxWindow*, const int w = info.indicator_width + info.margin_left + info.margin_right; const int h = info.indicator_height + info.margin_top + info.margin_bottom; - const int x = rect.x + (rect.width - w) / 2; - const int y = rect.y + (rect.height - h) / 2; + int x = rect.x + (rect.width - w) / 2; + int y = rect.y + (rect.height - h) / 2; + + const bool isRTL = dc.GetLayoutDirection() == wxLayout_RightToLeft; + if (isRTL) + { + // checkbox is not mirrored + cairo_save(cr); + cairo_scale(cr, -1, 1); + x = -x - w; + } if (gtk_check_version(3,20,0) == NULL) { @@ -756,6 +765,9 @@ wxRendererGTK::DrawCheckBox(wxWindow*, gtk_render_check(sc, cr, x, y, w, h); gtk_style_context_restore(sc); } + if (isRTL) + cairo_restore(cr); + #else // !__WXGTK3__ GtkWidget* button = wxGTKPrivate::GetCheckButtonWidget(); @@ -867,10 +879,6 @@ wxRendererGTK::DrawItemSelectionRect(wxWindow* win, if (flags & wxCONTROL_SELECTED) { - int x_diff = 0; - if (win->GetLayoutDirection() == wxLayout_RightToLeft) - x_diff = rect.width; - GtkWidget* treeWidget = wxGTKPrivate::GetTreeWidget(); #ifdef __WXGTK3__ @@ -881,9 +889,13 @@ wxRendererGTK::DrawItemSelectionRect(wxWindow* win, state |= GTK_STATE_FLAG_FOCUSED; gtk_style_context_set_state(sc, GtkStateFlags(state)); gtk_style_context_add_class(sc, GTK_STYLE_CLASS_CELL); - gtk_render_background(sc, drawable, rect.x - x_diff, rect.y, rect.width, rect.height); + gtk_render_background(sc, drawable, rect.x, rect.y, rect.width, rect.height); gtk_style_context_restore(sc); #else + int x_diff = 0; + if (win->GetLayoutDirection() == wxLayout_RightToLeft) + x_diff = rect.width; + // the wxCONTROL_FOCUSED state is deduced // directly from the m_wxwindow by GTK+ gtk_paint_flat_box(gtk_widget_get_style(treeWidget), diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index 83bc345e2c..f362bf3a23 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -5065,10 +5065,12 @@ bool wxWindowGTK::DoIsExposed( int x, int y ) const bool wxWindowGTK::DoIsExposed( int x, int y, int w, int h ) const { +#ifndef __WXGTK3__ if (GetLayoutDirection() == wxLayout_RightToLeft) return m_updateRegion.Contains(x-w, y, w, h) != wxOutRegion; - else - return m_updateRegion.Contains(x, y, w, h) != wxOutRegion; +#endif + + return m_updateRegion.Contains(x, y, w, h) != wxOutRegion; } #ifdef __WXGTK3__ @@ -5086,6 +5088,13 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region) cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height); cairo_clip(cr); } + if (GetLayoutDirection() == wxLayout_RightToLeft) + { + // wxDC is mirrored for RTL + const int w = gdk_window_get_width(gtk_widget_get_window(m_wxwindow)); + cairo_translate(cr, w, 0); + cairo_scale(cr, -1, 1); + } double x1, y1, x2, y2; cairo_clip_extents(cr, &x1, &y1, &x2, &y2); @@ -5094,7 +5103,6 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region) m_paintContext = cr; m_updateRegion = wxRegion(int(x1), int(y1), int(x2 - x1), int(y2 - y1)); - m_nativeUpdateRegion = m_updateRegion; #else // !__WXGTK3__ m_updateRegion = wxRegion(region); #if wxGTK_HAS_COMPOSITING_SUPPORT @@ -5106,6 +5114,7 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region) m_nativeUpdateRegion = m_updateRegion; +#ifndef __WXGTK3__ if (GetLayoutDirection() == wxLayout_RightToLeft) { // Transform m_updateRegion under RTL @@ -5128,6 +5137,7 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region) ++upd; } } +#endif switch ( GetBackgroundStyle() ) { @@ -5158,7 +5168,7 @@ void wxWindowGTK::GTKSendPaintEvents(const GdkRegion* region) case wxBG_STYLE_ERASE: { #ifdef __WXGTK3__ - wxGTKCairoDC dc(cr, static_cast(this)); + wxGTKCairoDC dc(cr, static_cast(this), GetLayoutDirection()); #else wxWindowDC dc( (wxWindow*)this ); dc.SetDeviceClippingRegion( m_updateRegion );