From ecb1524f8da7901338b263384fed3c612f117b4c Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 17:22:36 +0200 Subject: 5.11 --- wx+/context_menu.h | 4 +- wx+/graph.cpp | 482 +++++++++++++++++++++++++++---------------------- wx+/graph.h | 79 ++++---- wx+/grid.cpp | 64 ++----- wx+/mouse_move_dlg.cpp | 4 +- wx+/no_flicker.h | 3 +- wx+/rtl.h | 57 +++++- 7 files changed, 379 insertions(+), 314 deletions(-) (limited to 'wx+') diff --git a/wx+/context_menu.h b/wx+/context_menu.h index cb6cb86d..2557737a 100644 --- a/wx+/context_menu.h +++ b/wx+/context_menu.h @@ -30,9 +30,9 @@ class ContextMenu : private wxEvtHandler public: ContextMenu() : menu(new wxMenu) {} - void addItem(const wxString& label, const std::function& command, const wxBitmap* bmp = nullptr, bool enabled = true, int id = wxID_ANY) + void addItem(const wxString& label, const std::function& command, const wxBitmap* bmp = nullptr, bool enabled = true) { - wxMenuItem* newItem = new wxMenuItem(menu.get(), id, label); //menu owns item! + wxMenuItem* newItem = new wxMenuItem(menu.get(), wxID_ANY, label); //menu owns item! if (bmp) newItem->SetBitmap(*bmp); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason menu->Append(newItem); if (!enabled) newItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason diff --git a/wx+/graph.cpp b/wx+/graph.cpp index 6fefeb93..30677c80 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -10,6 +10,7 @@ #include #include #include +#include "rtl.h" using namespace zen; using namespace numeric; @@ -17,10 +18,11 @@ using namespace numeric; //todo: support zoom via mouse wheel +warn_static("reviewreviewreviewreviewreview") const wxEventType zen::wxEVT_GRAPH_SELECTION = wxNewEventType(); -const std::shared_ptr Graph2D::GraphAttributes::defaultFormat = std::make_shared(); //for some buggy reason MSVC isn't able to use a temporary as a default argument instead +const std::shared_ptr Graph2D::MainAttributes::defaultFormat = std::make_shared(); //for some buggy reason MSVC isn't able to use a temporary as a default argument namespace { @@ -61,8 +63,7 @@ namespace { wxColor getDefaultColor(size_t pos) { - pos %= 10; - switch (pos) + switch (pos % 10) { case 0: return wxColor(0, 69, 134); //blue @@ -90,137 +91,118 @@ wxColor getDefaultColor(size_t pos) } -void drawYLabel(wxDC& dc, double& yMin, double& yMax, const wxRect& clientArea, int labelWidth, bool drawLeft, const LabelFormatter& labelFmt) //clientArea := y-label + data window +class ConvertCoord //convert between screen and input data coordinates { - //note: DON'T use wxDC::GetSize()! DC may be larger than visible area! - if (clientArea.GetHeight() <= 0 || clientArea.GetWidth() <= 0) - return; - - int optimalBlockHeight = 3 * dc.GetMultiLineTextExtent(L"1").GetHeight(); - - double valRangePerBlock = (yMax - yMin) * optimalBlockHeight / clientArea.GetHeight(); //proposal - valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); - if (isNull(valRangePerBlock)) - return; - - double yMinNew = std::floor(yMin / valRangePerBlock) * valRangePerBlock; - double yMaxNew = std::ceil (yMax / valRangePerBlock) * valRangePerBlock; - int blockCount = numeric::round((yMaxNew - yMinNew) / valRangePerBlock); - if (blockCount == 0) return; +public: + ConvertCoord(double valMin, double valMax, size_t screenSize) : + min_(valMin), + scaleToReal(screenSize == 0 ? 0 : (valMax - valMin) / screenSize), + scaleToScr(isNull((valMax - valMin)) ? 0 : screenSize / (valMax - valMin)) {} - yMin = yMinNew; //inform about adjusted y value range - yMax = yMaxNew; + double screenToReal(double screenPos) const //input value: [0, screenSize - 1] + { + return screenPos * scaleToReal + min_; //come close to valMax, but NEVER reach it! + } + double realToScreen(double realPos) const //return screen position in pixel (but with double precision!) + { + return (realPos - min_) * scaleToScr; + } - //draw labels + int realToScreenRound(double realPos) const //useful to find "proper" y-pixel positions { - wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192))); //light grey - wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels - dc.SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial")); + return numeric::round(realToScreen(realPos)); + } - const int posLabel = drawLeft ? 0 : clientArea.GetWidth() - labelWidth; - const int posDataArea = drawLeft ? labelWidth : 0; - const int widthDataArea = clientArea.GetWidth() - labelWidth; +private: + double min_; + double scaleToReal; + double scaleToScr; +}; - const wxPoint origin = clientArea.GetTopLeft(); - for (int i = 1; i < blockCount; ++i) +//enlarge range to a multiple of a "useful" block size +void widenRange(double& valMin, double& valMax, //in/out + int& blockCount, //out + int graphAreaSize, //in pixel + int optimalBlockSize, // + const LabelFormatter& labelFmt) +{ + if (graphAreaSize > 0) + { + double valRangePerBlock = (valMax - valMin) * optimalBlockSize / graphAreaSize; //proposal + valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); + if (!isNull(valRangePerBlock)) { - //draw grey horizontal lines - const int y = i * static_cast(clientArea.GetHeight()) / blockCount; - if (widthDataArea > 0) - dc.DrawLine(wxPoint(posDataArea, y) + origin, wxPoint(posDataArea + widthDataArea - 1, y) + origin); - - //draw y axis labels - const wxString label = labelFmt.formatText(yMaxNew - i * valRangePerBlock ,valRangePerBlock); - wxSize labelExtent = dc.GetMultiLineTextExtent(label); - - labelExtent.x = std::max(labelExtent.x, labelWidth); //enlarge if possible to center horizontally - - dc.DrawLabel(label, wxRect(wxPoint(posLabel, y - labelExtent.GetHeight() / 2) + origin, labelExtent), wxALIGN_CENTRE); + valMin = std::floor(valMin / valRangePerBlock) * valRangePerBlock; + valMax = std::ceil (valMax / valRangePerBlock) * valRangePerBlock; + blockCount = numeric::round((valMax - valMin) / valRangePerBlock); //"round" to avoid IEEE 754 surprises + return; } } + blockCount = 0; } -void drawXLabel(wxDC& dc, double& xMin, double& xMax, const wxRect& clientArea, int labelHeight, bool drawBottom, const LabelFormatter& labelFmt) //clientArea := x-label + data window +void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const ConvertCoord& cvrtX, const wxRect& graphArea, const wxRect& labelArea, const LabelFormatter& labelFmt) { - //note: DON'T use wxDC::GetSize()! DC may be larger than visible area! - if (clientArea.GetHeight() <= 0 || clientArea.GetWidth() <= 0) - return; - - const int optimalBlockWidth = dc.GetMultiLineTextExtent(L"100000000000000").GetWidth(); - - double valRangePerBlock = (xMax - xMin) * optimalBlockWidth / clientArea.GetWidth(); //proposal - valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); - if (isNull(valRangePerBlock)) + assert(graphArea.width == labelArea.width && graphArea.x == labelArea.x); + if (blockCount <= 0) return; - double xMinNew = std::floor(xMin / valRangePerBlock) * valRangePerBlock; - double xMaxNew = std::ceil (xMax / valRangePerBlock) * valRangePerBlock; - int blockCount = numeric::round((xMaxNew - xMinNew) / valRangePerBlock); - if (blockCount == 0) - return; + wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192))); //light grey + wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels + dc.SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial")); - xMin = xMinNew; //inform about adjusted x value range - xMax = xMaxNew; + const double valRangePerBlock = (xMax - xMin) / blockCount; - //draw labels + for (int i = 1; i < blockCount; ++i) { - wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192))); //light grey - wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels - dc.SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial")); + //draw grey vertical lines + const double valX = xMin + i * valRangePerBlock; //step over raw data, not graph area pixels, to not lose precision + const int x = graphArea.x + cvrtX.realToScreenRound(valX); - const int posLabel = drawBottom ? clientArea.GetHeight() - labelHeight : 0; - const int posDataArea = drawBottom ? 0 : labelHeight; - const int heightDataArea = clientArea.GetHeight() - labelHeight; + if (graphArea.height > 0) + dc.DrawLine(wxPoint(x, graphArea.y), wxPoint(x, graphArea.y + graphArea.height)); - const wxPoint origin = clientArea.GetTopLeft(); - - for (int i = 1; i < blockCount; ++i) - { - //draw grey vertical lines - const int x = i * static_cast(clientArea.GetWidth()) / blockCount; - if (heightDataArea > 0) - dc.DrawLine(wxPoint(x, posDataArea) + origin, wxPoint(x, posDataArea + heightDataArea - 1) + origin); - - //draw x axis labels - const wxString label = labelFmt.formatText(xMin + i * valRangePerBlock ,valRangePerBlock); - wxSize labelExtent = dc.GetMultiLineTextExtent(label); - - labelExtent.y = std::max(labelExtent.y, labelHeight); //enlarge if possible to center vertically - - dc.DrawLabel(label, wxRect(wxPoint(x - labelExtent.GetWidth() / 2, posLabel) + origin, labelExtent), wxALIGN_CENTRE); - } + //draw x axis labels + const wxString label = labelFmt.formatText(xMin + i * valRangePerBlock, valRangePerBlock); + wxSize labelExtent = dc.GetMultiLineTextExtent(label); + dc.DrawText(label, wxPoint(x - labelExtent.GetWidth() / 2, labelArea.y + (labelArea.height - labelExtent.GetHeight()) / 2)); //center } } -class ConvertCoord //convert between screen and actual coordinates +void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const ConvertCoord& cvrtY, const wxRect& graphArea, const wxRect& labelArea, const LabelFormatter& labelFmt) { -public: - ConvertCoord(double valMin, double valMax, size_t screenSize) : - min_(valMin), - scaleToReal(screenSize == 0 ? 0 : (valMax - valMin) / screenSize), - scaleToScr(isNull(valMax - valMin) ? 0 : screenSize / (valMax - valMin)) {} + assert(graphArea.height == labelArea.height && graphArea.y == labelArea.y); + if (blockCount <= 0) + return; - double screenToReal(double screenPos) const //input value: [0, screenSize - 1] - { - return screenPos * scaleToReal + min_; //come close to valMax, but NEVER reach it! - } - double realToScreen(double realPos) const //return screen position in pixel (but with double precision!) + wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192))); //light grey + wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels + dc.SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial")); + + const double valRangePerBlock = (yMax - yMin) / blockCount; + + for (int i = 1; i < blockCount; ++i) { - return (realPos - min_) * scaleToScr; - } + //draw grey horizontal lines + const double valY = yMin + i * valRangePerBlock; //step over raw data, not graph area pixels, to not lose precision + const int y = graphArea.y + cvrtY.realToScreenRound(valY); -private: - const double min_; - const double scaleToReal; - const double scaleToScr; -}; + if (graphArea.width > 0) + dc.DrawLine(wxPoint(graphArea.x, y), wxPoint(graphArea.x + graphArea.width, y)); + + //draw y axis labels + const wxString label = labelFmt.formatText(valY, valRangePerBlock); + wxSize labelExtent = dc.GetMultiLineTextExtent(label); + dc.DrawText(label, wxPoint(labelArea.x + (labelArea.width - labelExtent.GetWidth()) / 2, y - labelExtent.GetHeight() / 2)); //center + } +} -template -void subsample(StdCont& cont, size_t factor) +template +void subsample(StdContainter& cont, size_t factor) { if (factor <= 1) return; @@ -238,8 +220,7 @@ Graph2D::Graph2D(wxWindow* parent, const wxPoint& pos, const wxSize& size, long style, - const wxString& name) : - wxPanel(parent, winid, pos, size, style, name) + const wxString& name) : wxPanel(parent, winid, pos, size, style, name) { Connect(wxEVT_PAINT, wxPaintEventHandler(Graph2D::onPaintEvent), nullptr, this); Connect(wxEVT_SIZE, wxSizeEventHandler (Graph2D::onSizeEvent ), nullptr, this); @@ -261,6 +242,14 @@ Graph2D::Graph2D(wxWindow* parent, } +void Graph2D::onPaintEvent(wxPaintEvent& event) +{ + //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)! + BufferedPaintDC dc(*this, doubleBuffer); + render(dc); +} + + void Graph2D::OnMouseLeftDown(wxMouseEvent& event) { activeSel.reset(new MouseSelection(*this, event.GetPosition())); @@ -309,16 +298,16 @@ void Graph2D::OnMouseCaptureLost(wxMouseCaptureLostEvent& event) } -void Graph2D::setData(const std::shared_ptr& data, const LineAttributes& la) +void Graph2D::setData(const std::shared_ptr& data, const CurveAttributes& la) { curves_.clear(); addData(data, la); } -void Graph2D::addData(const std::shared_ptr& data, const LineAttributes& la) +void Graph2D::addData(const std::shared_ptr& data, const CurveAttributes& la) { - LineAttributes newAttr = la; + CurveAttributes newAttr = la; if (newAttr.autoColor) newAttr.setColor(getDefaultColor(curves_.size())); curves_.push_back(std::make_pair(data, newAttr)); @@ -348,34 +337,32 @@ void Graph2D::render(wxDC& dc) const // wxPanel::GetClassDefaultAttributes().colBg : // wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); const wxColor backColor = GetBackgroundColour(); //user-configurable! - - //wxDCBrushChanger dummy(dc, *wxTRANSPARENT_BRUSH); //sigh, who *invents* this stuff??? -> workaround for issue with wxBufferedPaintDC - DcBackgroundChanger dummy(dc, backColor); + DcBackgroundChanger dummy(dc, backColor); //use wxDC::SetBackground instead of wxDC::SetBrush dc.Clear(); } //note: DON'T use wxDC::GetSize()! DC may be larger than visible area! /* ----------------------- - |y-label |data window | - |---------------------- | | x-label | ----------------------- + |y-label | graph area | + |---------------------- */ - wxRect dataArea = GetClientSize(); //data window only - wxRect yLabelArea = GetClientSize(); //y-label + data window - wxRect xLabelArea = GetClientSize(); //x-label + data window + const wxRect clientRect = GetClientSize(); //data window only + wxRect graphArea = clientRect; //data window only + int xLabelPosY = clientRect.y; + int yLabelPosX = clientRect.x; switch (attr.labelposX) { case X_LABEL_TOP: - dataArea.y += attr.labelHeightX; - dataArea.height -= attr.labelHeightX; - yLabelArea = dataArea; + graphArea.y += attr.xLabelHeight; + graphArea.height -= attr.xLabelHeight; break; case X_LABEL_BOTTOM: - dataArea.height -= attr.labelHeightX; - yLabelArea = dataArea; + xLabelPosY += clientRect.height - attr.xLabelHeight; + graphArea.height -= attr.xLabelHeight; break; case X_LABEL_NONE: break; @@ -384,14 +371,12 @@ void Graph2D::render(wxDC& dc) const switch (attr.labelposY) { case Y_LABEL_LEFT: - dataArea .x += attr.labelWidthY; - xLabelArea.x += attr.labelWidthY; - dataArea .width -= attr.labelWidthY; - xLabelArea.width -= attr.labelWidthY; + graphArea.x += attr.yLabelWidth; + graphArea.width -= attr.yLabelWidth; break; case Y_LABEL_RIGHT: - dataArea .width -= attr.labelWidthY; - xLabelArea.width -= attr.labelWidthY; + yLabelPosX += clientRect.width - attr.yLabelWidth; + graphArea.width -= attr.yLabelWidth; break; case Y_LABEL_NONE: break; @@ -401,123 +386,189 @@ void Graph2D::render(wxDC& dc) const //paint actual graph background (without labels) DcBackgroundChanger dummy(dc, *wxWHITE); //accessibility: we have to set both back- and foreground colors or none at all! wxDCPenChanger dummy2(dc, wxColour(130, 135, 144)); //medium grey, the same Win7 uses for other frame borders - //dc.DrawRectangle(static_cast(dataArea).Inflate(1, 1)); //correct wxWidgets design mistakes - dc.DrawRectangle(dataArea); - dataArea.Deflate(1, 1); //do not draw on border + //dc.DrawRectangle(static_cast(graphArea).Inflate(1, 1)); //correct wxWidgets design mistakes + dc.DrawRectangle(graphArea); + graphArea.Deflate(1, 1); //do not draw on border } + //set label areas respecting graph area border! + wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, attr.xLabelHeight); + wxRect yLabelArea(yLabelPosX, graphArea.y, attr.yLabelWidth, graphArea.height); + + const wxPoint graphAreaOrigin = graphArea.GetTopLeft(); + //detect x value range - double minWndX = attr.minXauto ? std::numeric_limits::infinity() : attr.minX; //automatic: ensure values are initialized by first curve - double maxWndX = attr.maxXauto ? -std::numeric_limits::infinity() : attr.maxX; // + double minX = attr.minXauto ? std::numeric_limits::infinity() : attr.minX; //automatic: ensure values are initialized by first curve + double maxX = attr.maxXauto ? -std::numeric_limits::infinity() : attr.maxX; // if (!curves_.empty()) - for (auto iter = curves_.begin(); iter != curves_.end(); ++iter) - if (iter->first.get()) + for (auto it = curves_.begin(); it != curves_.end(); ++it) + if (it->first.get()) { - const GraphData& graph = *iter->first; + const GraphData& graph = *it->first; assert(graph.getXBegin() <= graph.getXEnd() + 1.0e-9); - //GCC fucks up bad when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18 + //GCC fucks up badly when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18 if (attr.minXauto) - minWndX = std::min(minWndX, graph.getXBegin()); + minX = std::min(minX, graph.getXBegin()); if (attr.maxXauto) - maxWndX = std::max(maxWndX, graph.getXEnd()); + maxX = std::max(maxX, graph.getXEnd()); } - if (minWndX < maxWndX && maxWndX - minWndX < std::numeric_limits::infinity()) //valid x-range + if (minX < maxX && maxX - minX < std::numeric_limits::infinity()) //valid x-range { - if (attr.labelposX != X_LABEL_NONE && //minWndX, maxWndX are just a suggestion, drawXLabel may enlarge them! - attr.labelFmtX.get()) - drawXLabel(dc, minWndX, maxWndX, xLabelArea, attr.labelHeightX, attr.labelposX == X_LABEL_BOTTOM, *attr.labelFmtX); - + int blockCountX = 0; + //enlarge minX, maxX to a multiple of a "useful" block size + if (attr.labelposX != X_LABEL_NONE && attr.labelFmtX.get()) + widenRange(minX, maxX, //in/out + blockCountX, //out + graphArea.width, + dc.GetTextExtent(L"100000000000000").GetWidth(), + *attr.labelFmtX); //detect y value range std::vector, int>> yValuesList(curves_.size()); - double minWndY = attr.minYauto ? std::numeric_limits::infinity() : attr.minY; //automatic: ensure values are initialized by first curve - double maxWndY = attr.maxYauto ? -std::numeric_limits::infinity() : attr.maxY; // - if (!curves_.empty()) + double minY = attr.minYauto ? std::numeric_limits::infinity() : attr.minY; //automatic: ensure values are initialized by first curve + double maxY = attr.maxYauto ? -std::numeric_limits::infinity() : attr.maxY; // { const int AVG_FACTOR = 2; //some averaging of edgy input data to smoothen behavior on window resize - const ConvertCoord cvrtX(minWndX, maxWndX, dataArea.width * AVG_FACTOR); - - for (GraphList::const_iterator j = curves_.begin(); j != curves_.end(); ++j) - { - if (!j->first) continue; - const GraphData& graph = *j->first; + const ConvertCoord cvrtX(minX, maxX, graphArea.width * AVG_FACTOR); - std::vector& yValues = yValuesList[j - curves_.begin()].first; //actual y-values - int& offset = yValuesList[j - curves_.begin()].second; //x-value offset in pixel + for (auto it = curves_.begin(); it != curves_.end(); ++it) + if (it->first.get()) { - const double xBegin = graph.getXBegin(); - const double xEnd = graph.getXEnd(); + const size_t index = it - curves_.begin(); + const GraphData& graph = *it->first; - const int posFirst = std::ceil (cvrtX.realToScreen(std::max(xBegin, minWndX))); //evaluate visible area only and make sure to not step one pixel before xbegin()! - const int postLast = std::floor(cvrtX.realToScreen(std::min(xEnd, maxWndX))); //apply min/max *before* calling realToScreen()! + std::vector& yValues = yValuesList[index].first; //actual y-values + int& offsetX = yValuesList[index].second; //x-value offset in pixel + { + const double xBegin = graph.getXBegin(); + const double xEnd = graph.getXEnd(); - for (int i = posFirst; i < postLast; ++i) - yValues.push_back(graph.getValue(cvrtX.screenToReal(i))); + const int posFirst = std::ceil(cvrtX.realToScreen(std::max(xBegin, minX))); //apply min/max *before* calling realToScreen()! + const int posLast = std::ceil(cvrtX.realToScreen(std::min(xEnd, maxX))); //do not step outside [xBegin, xEnd) range => 2 x ceil! + //conversion from std::ceil double to int is loss-free for full value range of int! tested successfully on MSVC - subsample(yValues, AVG_FACTOR); - offset = posFirst / AVG_FACTOR; - } + for (int i = posFirst; i < posLast; ++i) + yValues.push_back(graph.getValue(cvrtX.screenToReal(i))); - if (!yValues.empty()) - { - if (attr.minYauto) - minWndY = std::min(minWndY, *std::min_element(yValues.begin(), yValues.end())); - if (attr.maxYauto) - maxWndY = std::max(maxWndY, *std::max_element(yValues.begin(), yValues.end())); + subsample(yValues, AVG_FACTOR); + offsetX = posFirst / AVG_FACTOR; + } + + if (!yValues.empty()) + { + if (attr.minYauto) + minY = std::min(minY, *std::min_element(yValues.begin(), yValues.end())); + if (attr.maxYauto) + maxY = std::max(maxY, *std::max_element(yValues.begin(), yValues.end())); + } } - } } - if (minWndY < maxWndY) //valid y-range + if (minY < maxY) //valid y-range { - if (attr.labelposY != Y_LABEL_NONE && //minWnd, maxWndY are just a suggestion, drawYLabel may enlarge them! - attr.labelFmtY.get()) - drawYLabel(dc, minWndY, maxWndY, yLabelArea, attr.labelWidthY, attr.labelposY == Y_LABEL_LEFT, *attr.labelFmtY); - - const ConvertCoord cvrtY(minWndY, maxWndY, dataArea.height <= 0 ? 0 : dataArea.height - 1); //both minY/maxY values will be actually evaluated in contrast to maxX => - 1 - const ConvertCoord cvrtX(minWndX, maxWndX, dataArea.width); + int blockCountY = 0; + //enlarge minY, maxY to a multiple of a "useful" block size + if (attr.labelposY != Y_LABEL_NONE && attr.labelFmtY.get()) + widenRange(minY, maxY, //in/out + blockCountY, //out + graphArea.height, + 3 * dc.GetTextExtent(L"1").GetHeight(), + *attr.labelFmtY); + + const ConvertCoord cvrtX(minX, maxX, graphArea.width); //map [minX, maxX) to [0, graphWidth) + const ConvertCoord cvrtY(maxY, minY, graphArea.height <= 0 ? 0 : graphArea.height - 1); //map [minY, maxY] to [graphHeight - 1, 0] + + //calculate curve coordinates on graph area + auto getCurvePoints = [&](size_t index, std::vector& points) + { + if (index < yValuesList.size()) + { + const std::vector& yValues = yValuesList[index].first; //actual y-values + const int offsetX = yValuesList[index].second; //x-value offset in pixel - const wxPoint dataOrigin = dataArea.GetTopLeft(); + for (auto i = yValues.begin(); i != yValues.end(); ++i) + points.push_back(wxPoint(offsetX + (i - yValues.begin()), + cvrtY.realToScreenRound(*i)) + graphAreaOrigin); + } + }; //update active mouse selection if (activeSel.get() && - dataArea.width > 0 && - dataArea.height > 0) + graphArea.width > 0 && graphArea.height > 0) { - wxPoint startPos = activeSel->getStartPos() - dataOrigin; //pos relative to dataArea - wxPoint currentPos = activeSel->refCurrentPos() - dataOrigin; + wxPoint startPos = activeSel->getStartPos() - graphAreaOrigin; //pos relative to graphArea + wxPoint currentPos = activeSel->refCurrentPos() - graphAreaOrigin; - //normalize positions - confine(startPos .x, 0, dataArea.width); //allow for one past the end(!) to enable "full range selections" - confine(currentPos.x, 0, dataArea.width); // + //normalize positions: a mouse selection is symmetric and *not* an half-open range! + confine(startPos .x, 0, graphArea.width - 1); + confine(currentPos.x, 0, graphArea.width - 1); + confine(startPos .y, 0, graphArea.height - 1); + confine(currentPos.y, 0, graphArea.height - 1); - confine(startPos .y, 0, dataArea.height); // - confine(currentPos.y, 0, dataArea.height); // + auto& from = activeSel->refSelection().from; + auto& to = activeSel->refSelection().to; //save current selection as double coordinates - activeSel->refSelection().from = SelectionBlock::Point(cvrtX.screenToReal(startPos.x + 0.5), //+0.5 start selection in the middle of a pixel - cvrtY.screenToReal(startPos.y + 0.5)); - activeSel->refSelection().to = SelectionBlock::Point(cvrtX.screenToReal(currentPos.x + 0.5), - cvrtY.screenToReal(currentPos.y + 0.5)); + from.x = cvrtX.screenToReal(startPos .x + (startPos.x <= currentPos.x ? 0 : 1)); // use full pixel range for selection! + to .x = cvrtX.screenToReal(currentPos.x + (startPos.x <= currentPos.x ? 1 : 0)); + + from.y = cvrtY.screenToReal(startPos .y + (startPos.y <= currentPos.y ? 0 : 1)); + to .y = cvrtY.screenToReal(currentPos.y + (startPos.y <= currentPos.y ? 1 : 0)); } - //draw all currently set mouse selections (including active selection) + + //#################### begin drawing #################### + //1. draw colored area under curves + for (auto it = curves_.begin(); it != curves_.end(); ++it) + if (it->second.drawCurveArea) + { + std::vector points; + getCurvePoints(it - curves_.begin(), points); + if (!points.empty()) + { + points.push_back(wxPoint(points.back ().x, graphArea.GetBottom())); //add lower right and left corners + points.push_back(wxPoint(points.front().x, graphArea.GetBottom())); // + + wxDCBrushChanger dummy(dc, it->second.fillColor); + wxDCPenChanger dummy2(dc, it->second.fillColor); + dc.DrawPolygon(static_cast(points.size()), &points[0]); + } + } + + //2. draw all currently set mouse selections (including active selection) std::vector allSelections = oldSel; if (activeSel) allSelections.push_back(activeSel->refSelection()); { - wxColor colSelect(168, 202, 236); //light blue - //wxDCBrushChanger dummy(dc, *wxTRANSPARENT_BRUSH); - wxDCBrushChanger dummy(dc, colSelect); //alpha channel (not yet) supported on wxMSW, so draw selection before graphs - - wxDCPenChanger dummy2(dc, colSelect); + //alpha channel (not yet) supported on wxMSW, so draw selection before curves + wxDCBrushChanger dummy(dc, wxColor(168, 202, 236)); //light blue + wxDCPenChanger dummy2(dc, wxColor(51, 153, 255)); //dark blue for (auto i = allSelections.begin(); i != allSelections.end(); ++i) { - const wxPoint pixelFrom = wxPoint(cvrtX.realToScreen(i->from.x), - cvrtY.realToScreen(i->from.y)) + dataOrigin; - const wxPoint pixelTo = wxPoint(cvrtX.realToScreen(i->to.x), - cvrtY.realToScreen(i->to.y)) + dataOrigin; + //harmonize with active mouse selection above! + wxPoint pixelFrom(cvrtX.realToScreenRound(i->from.x), + cvrtY.realToScreenRound(i->from.y)); + wxPoint pixelTo(cvrtX.realToScreenRound(i->to.x), + cvrtY.realToScreenRound(i->to.y)); + //convert half-open to inclusive ranges for use with wxDC::DrawRectangle + if (pixelFrom.x != pixelTo.x) //no matter how small the selection, always draw at least one pixel! + { + pixelFrom.x -= pixelFrom.x < pixelTo.x ? 0 : 1; + pixelTo .x -= pixelFrom.x < pixelTo.x ? 1 : 0; + } + if (pixelFrom.y != pixelTo.y) + { + pixelFrom.y -= pixelFrom.y < pixelTo.y ? 0 : 1; + pixelTo .y -= pixelFrom.y < pixelTo.y ? 1 : 0; + } + confine(pixelFrom.x, 0, graphArea.width - 1); + confine(pixelTo .x, 0, graphArea.width - 1); + confine(pixelFrom.y, 0, graphArea.height - 1); + confine(pixelTo .y, 0, graphArea.height - 1); + + pixelFrom += graphAreaOrigin; + pixelTo += graphAreaOrigin; switch (attr.mouseSelMode) { @@ -527,30 +578,29 @@ void Graph2D::render(wxDC& dc) const dc.DrawRectangle(wxRect(pixelFrom, pixelTo)); break; case SELECT_X_AXIS: - dc.DrawRectangle(wxRect(wxPoint(pixelFrom.x, dataArea.y), wxPoint(pixelTo.x, dataArea.y + dataArea.height - 1))); + dc.DrawRectangle(wxRect(wxPoint(pixelFrom.x, graphArea.y), wxPoint(pixelTo.x, graphArea.y + graphArea.height - 1))); break; case SELECT_Y_AXIS: - dc.DrawRectangle(wxRect(wxPoint(dataArea.x, pixelFrom.y), wxPoint(dataArea.x + dataArea.width - 1, pixelTo.y))); + dc.DrawRectangle(wxRect(wxPoint(graphArea.x, pixelFrom.y), wxPoint(graphArea.x + graphArea.width - 1, pixelTo.y))); break; } } } - //finally draw curves - for (GraphList::const_iterator j = curves_.begin(); j != curves_.end(); ++j) - { - std::vector& yValues = yValuesList[j - curves_.begin()].first; //actual y-values - int offsetX = yValuesList[j - curves_.begin()].second; //x-value offset in pixel - - std::vector curve; - for (std::vector::const_iterator i = yValues.begin(); i != yValues.end(); ++i) - curve.push_back(wxPoint(i - yValues.begin() + offsetX, - dataArea.height - 1 - cvrtY.realToScreen(*i)) + dataOrigin); //screen y axis starts upper left + //3. draw labels and background grid + drawXLabel(dc, minX, maxX, blockCountX, cvrtX, graphArea, xLabelArea, *attr.labelFmtX); + drawYLabel(dc, minY, maxY, blockCountY, cvrtY, graphArea, yLabelArea, *attr.labelFmtY); - if (!curve.empty()) + //4. finally draw curves + for (auto it = curves_.begin(); it != curves_.end(); ++it) + { + std::vector points; + getCurvePoints(it - curves_.begin(), points); + if (!points.empty()) { - dc.SetPen(wxPen(j->second.color, j->second.lineWidth)); - dc.DrawLines(static_cast(curve.size()), &curve[0]); + wxDCPenChanger dummy(dc, wxPen(it->second.color, it->second.lineWidth)); + dc.DrawLines(static_cast(points.size()), &points[0]); + dc.DrawPoint(points.back()); //last pixel omitted by DrawLines } } } diff --git a/wx+/graph.h b/wx+/graph.h index ddcc1e33..f5e38851 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -20,12 +20,12 @@ namespace zen /* Example: //init graph (optional) - m_panelGraph->setAttributes(Graph2D::GraphAttributes(). + m_panelGraph->setAttributes(Graph2D::MainAttributes(). setLabelX(Graph2D::POSLX_BOTTOM, 20, std::make_shared()). setLabelY(Graph2D::POSLY_RIGHT, 60, std::make_shared())); //set graph data std::shared_ptr graphDataBytes = ... - m_panelGraph->setData(graphDataBytes, Graph2D::LineAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); + m_panelGraph->setData(graphDataBytes, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0))); */ //------------------------------------------------------------------------------------------------------------ @@ -148,24 +148,29 @@ public: long style = wxTAB_TRAVERSAL | wxNO_BORDER, const wxString& name = wxPanelNameStr); - class LineAttributes + class CurveAttributes { public: - LineAttributes() : autoColor(true), lineWidth(2) {} + CurveAttributes() : autoColor(true), drawCurveArea(false), lineWidth(2) {} - LineAttributes& setColor(const wxColour& col) { color = col; autoColor = false; return *this; } - LineAttributes& setLineWidth(size_t width) { lineWidth = static_cast(width); return *this; } + CurveAttributes& setColor (const wxColour& col) { color = col; autoColor = false; return *this; } + CurveAttributes& fillCurveArea(const wxColour& col) { fillColor = col; drawCurveArea = true; return *this; } + CurveAttributes& setLineWidth(size_t width) { lineWidth = static_cast(width); return *this; } private: friend class Graph2D; - bool autoColor; + bool autoColor; wxColour color; - int lineWidth; + + bool drawCurveArea; + wxColour fillColor; + + int lineWidth; }; - void setData(const std::shared_ptr& data, const LineAttributes& attr = LineAttributes()); - void addData(const std::shared_ptr& data, const LineAttributes& attr = LineAttributes()); + void setData(const std::shared_ptr& data, const CurveAttributes& attr = CurveAttributes()); + void addData(const std::shared_ptr& data, const CurveAttributes& attr = CurveAttributes()); enum PosLabelY { @@ -189,10 +194,10 @@ public: SELECT_Y_AXIS, }; - class GraphAttributes + class MainAttributes { public: - GraphAttributes() : + MainAttributes() : minXauto(true), maxXauto(true), minX(0), @@ -202,40 +207,40 @@ public: minY(0), maxY(0), labelposX(X_LABEL_BOTTOM), - labelHeightX(25), - labelFmtX(new DecimalNumberFormatter()), + xLabelHeight(25), + labelFmtX(std::make_shared()), labelposY(Y_LABEL_LEFT), - labelWidthY(60), - labelFmtY(new DecimalNumberFormatter()), + yLabelWidth(60), + labelFmtY(std::make_shared()), mouseSelMode(SELECT_RECTANGLE) {} - GraphAttributes& setMinX(double newMinX) { minX = newMinX; minXauto = false; return *this; } - GraphAttributes& setMaxX(double newMaxX) { maxX = newMaxX; maxXauto = false; return *this; } + MainAttributes& setMinX(double newMinX) { minX = newMinX; minXauto = false; return *this; } + MainAttributes& setMaxX(double newMaxX) { maxX = newMaxX; maxXauto = false; return *this; } - GraphAttributes& setMinY(double newMinY) { minY = newMinY; minYauto = false; return *this; } - GraphAttributes& setMaxY(double newMaxY) { maxY = newMaxY; maxYauto = false; return *this; } + MainAttributes& setMinY(double newMinY) { minY = newMinY; minYauto = false; return *this; } + MainAttributes& setMaxY(double newMaxY) { maxY = newMaxY; maxYauto = false; return *this; } - GraphAttributes& setAutoSize() { minXauto = true; maxXauto = true; minYauto = true; maxYauto = true; return *this; } + MainAttributes& setAutoSize() { minXauto = true; maxXauto = true; minYauto = true; maxYauto = true; return *this; } static const std::shared_ptr defaultFormat; - GraphAttributes& setLabelX(PosLabelX posX, size_t height = 25, const std::shared_ptr& newLabelFmt = defaultFormat) + MainAttributes& setLabelX(PosLabelX posX, size_t height = 25, const std::shared_ptr& newLabelFmt = defaultFormat) { labelposX = posX; - labelHeightX = static_cast(height); + xLabelHeight = static_cast(height); labelFmtX = newLabelFmt; return *this; } - GraphAttributes& setLabelY(PosLabelY posY, size_t width = 60, const std::shared_ptr& newLabelFmt = defaultFormat) + MainAttributes& setLabelY(PosLabelY posY, size_t width = 60, const std::shared_ptr& newLabelFmt = defaultFormat) { labelposY = posY; - labelWidthY = static_cast(width); + yLabelWidth = static_cast(width); labelFmtY = newLabelFmt; return *this; } - GraphAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; } + MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; } private: friend class Graph2D; @@ -251,17 +256,17 @@ public: double maxY; PosLabelX labelposX; - int labelHeightX; + int xLabelHeight; std::shared_ptr labelFmtX; PosLabelY labelposY; - int labelWidthY; + int yLabelWidth; std::shared_ptr labelFmtY; SelMode mouseSelMode; }; - void setAttributes(const GraphAttributes& newAttr) { attr = newAttr; Refresh(); } - GraphAttributes getAttributes() const { return attr; } + void setAttributes(const MainAttributes& newAttr) { attr = newAttr; Refresh(); } + MainAttributes getAttributes() const { return attr; } std::vector getSelections() const { return oldSel; } @@ -279,13 +284,7 @@ private: void OnMouseLeftUp (wxMouseEvent& event); void OnMouseCaptureLost(wxMouseCaptureLostEvent& event); - void onPaintEvent(wxPaintEvent& event) - { - wxAutoBufferedPaintDC dc(this); //this one happily fucks up for RTL layout by not drawing the first column (x = 0)! - //wxPaintDC dc(this); - render(dc); - } - + void onPaintEvent(wxPaintEvent& event); void onSizeEvent(wxSizeEvent& event) { Refresh(); event.Skip(); } void onEraseBackGround(wxEraseEvent& event) {} @@ -311,9 +310,11 @@ private: std::vector oldSel; //applied selections std::shared_ptr activeSel; //set during mouse selection - GraphAttributes attr; //global attributes + MainAttributes attr; //global attributes + + std::unique_ptr doubleBuffer; - typedef std::vector, LineAttributes>> GraphList; + typedef std::vector, CurveAttributes>> GraphList; GraphList curves_; }; } diff --git a/wx+/grid.cpp b/wx+/grid.cpp index ff45224d..5c9d3dc8 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -7,7 +7,6 @@ #include "grid.h" #include #include -#include //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER #include #include #include @@ -19,6 +18,7 @@ #include #include #include "image_tools.h" +#include "rtl.h" #ifdef FFS_LINUX #include @@ -63,48 +63,8 @@ const wxColor COLOR_LABEL_GRADIENT_TO_FOCUS = COLOR_LABEL_GRADIENT_TO; wxColor getColorMainWinBackground() { return wxListBox::GetClassDefaultAttributes().colBg; } //cannot be initialized statically on wxGTK! const wxColor colorGridLine = wxColour(192, 192, 192); //light grey -//------------------------------------------------------------ - - -//a fix for a poor wxWidgets implementation (wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active) -#ifndef wxALWAYS_NATIVE_DOUBLE_BUFFER -#error we need this one! -#endif - -#if wxALWAYS_NATIVE_DOUBLE_BUFFER -struct BufferedPaintDC : public wxPaintDC { BufferedPaintDC(wxWindow& wnd, std::unique_ptr& buffer) : wxPaintDC(&wnd) {} }; - -#else -class BufferedPaintDC : public wxMemoryDC -{ -public: - BufferedPaintDC(wxWindow& wnd, std::unique_ptr& buffer) : buffer_(buffer), paintDc(&wnd) - { - const wxSize clientSize = wnd.GetClientSize(); - if (!buffer_ || clientSize != wxSize(buffer->GetWidth(), buffer->GetHeight())) - buffer.reset(new wxBitmap(clientSize.GetWidth(), clientSize.GetHeight())); - - SelectObject(*buffer); - - if (paintDc.IsOk()) - SetLayoutDirection(paintDc.GetLayoutDirection()); - } - - ~BufferedPaintDC() - { - paintDc.SetLayoutDirection(wxLayout_LeftToRight); //workaround bug in wxDC::Blit() - SetLayoutDirection(wxLayout_LeftToRight); // - - const wxPoint origin = GetDeviceOrigin(); - paintDc.Blit(0, 0, buffer_->GetWidth(), buffer_->GetHeight(), this, -origin.x, -origin.y); - } - -private: - std::unique_ptr& buffer_; - wxPaintDC paintDc; -}; -#endif +//------------------------------------------------------------ //another fix for yet another poor wxWidgets implementation (wxDCClipper does *not* stack) hash_map clippingAreas; //associate "active" clipping area with each DC @@ -114,15 +74,15 @@ class DcClipper public: DcClipper(wxDC& dc, const wxRect& r) : dc_(dc) { - auto iter = clippingAreas.find(&dc); - if (iter != clippingAreas.end()) + auto it = clippingAreas.find(&dc); + if (it != clippingAreas.end()) { - oldRect.reset(new wxRect(iter->second)); + oldRect.reset(new wxRect(it->second)); wxRect tmp = r; tmp.Intersect(*oldRect); //better safe than sorry dc_.SetClippingRegion(tmp); // - iter->second = tmp; + it->second = tmp; } else { @@ -426,13 +386,13 @@ private: void onPaintEvent(wxPaintEvent& event) { //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)! - BufferedPaintDC dc(*this, buffer); + BufferedPaintDC dc(*this, doubleBuffer); assert(GetSize() == GetClientSize()); const wxRegion& updateReg = GetUpdateRegion(); - for (wxRegionIterator iter = updateReg; iter; ++iter) - render(dc, iter.GetRect()); + for (wxRegionIterator it = updateReg; it; ++it) + render(dc, it.GetRect()); } void onSizeEvent(wxSizeEvent& event) @@ -444,7 +404,7 @@ private: void onEraseBackGround(wxEraseEvent& event) {} Grid& parent_; - std::unique_ptr buffer; + std::unique_ptr doubleBuffer; }; //---------------------------------------------------------------------------------------------------------------- @@ -2206,9 +2166,9 @@ void Grid::autoSizeColumns(size_t compPos) if (compPos < comp.size() && comp[compPos].allowColumnResize) { auto& visibleCols = comp[compPos].visibleCols; - for (auto iter = visibleCols.begin(); iter != visibleCols.end(); ++iter) + for (auto it = visibleCols.begin(); it != visibleCols.end(); ++it) { - const size_t col = iter - visibleCols.begin(); + const size_t col = it - visibleCols.begin(); const ptrdiff_t bestWidth = getBestColumnSize(col, compPos); //return -1 on error if (bestWidth >= 0) setColWidthAndNotify(bestWidth, col, compPos, true); diff --git a/wx+/mouse_move_dlg.cpp b/wx+/mouse_move_dlg.cpp index e64e5da1..5c2a0a97 100644 --- a/wx+/mouse_move_dlg.cpp +++ b/wx+/mouse_move_dlg.cpp @@ -26,9 +26,9 @@ template inline void forEachChild(wxWindow& parent, Fun f) { wxWindowList& wl = parent.GetChildren(); - for (auto iter = wl.begin(); iter != wl.end(); ++iter) //yet another wxWidgets bug keeps us from using std::for_each + for (auto it = wl.begin(); it != wl.end(); ++it) //yet another wxWidgets bug keeps us from using std::for_each { - wxWindow& wnd = **iter; + wxWindow& wnd = **it; f(wnd); forEachChild(wnd, f); } diff --git a/wx+/no_flicker.h b/wx+/no_flicker.h index fb315de0..ea9efb4d 100644 --- a/wx+/no_flicker.h +++ b/wx+/no_flicker.h @@ -7,7 +7,8 @@ #ifndef NO_FLICKER_HEADER_893421590321532 #define NO_FLICKER_HEADER_893421590321532 -#include +#include +#include namespace zen { diff --git a/wx+/rtl.h b/wx+/rtl.h index f89bb86e..84009a86 100644 --- a/wx+/rtl.h +++ b/wx+/rtl.h @@ -13,6 +13,7 @@ #include #include #include +#include //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER namespace zen { @@ -30,15 +31,25 @@ void drawBitmapRtlNoMirror(wxDC& dc, //wxDC::DrawLabel does already NOT mirror int alignment, std::unique_ptr& buffer); -void drawIconRtlNoMirror(wxDC& dc, - const wxIcon& icon, //wxDC::DrawIcon DOES mirror by default +void drawIconRtlNoMirror(wxDC& dc, //wxDC::DrawIcon DOES mirror by default + const wxIcon& icon, const wxPoint& pt, std::unique_ptr& buffer); wxBitmap mirrorIfRtl(const wxBitmap& bmp); +/* +class BufferedPaintDC +{ +public: + BufferedPaintDC(wxWindow& wnd, std::unique_ptr& buffer); +}; +*/ +//a fix for a poor wxWidgets implementation (wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active) + +//manual text flow correction: http://www.w3.org/International/articles/inline-bidi-markup/ @@ -116,6 +127,48 @@ wxBitmap mirrorIfRtl(const wxBitmap& bmp) else return bmp; } + + +#ifndef wxALWAYS_NATIVE_DOUBLE_BUFFER +#error we need this one! +#endif + +#if wxALWAYS_NATIVE_DOUBLE_BUFFER +struct BufferedPaintDC : public wxPaintDC { BufferedPaintDC(wxWindow& wnd, std::unique_ptr& buffer) : wxPaintDC(&wnd) {} }; + +#else +class BufferedPaintDC : public wxMemoryDC +{ +public: + BufferedPaintDC(wxWindow& wnd, std::unique_ptr& buffer) : buffer_(buffer), paintDc(&wnd) + { + const wxSize clientSize = wnd.GetClientSize(); + if (!buffer_ || clientSize != wxSize(buffer->GetWidth(), buffer->GetHeight())) + buffer.reset(new wxBitmap(clientSize.GetWidth(), clientSize.GetHeight())); + + SelectObject(*buffer); + + if (paintDc.IsOk() && paintDc.GetLayoutDirection() == wxLayout_RightToLeft) + SetLayoutDirection(wxLayout_RightToLeft); + } + + ~BufferedPaintDC() + { + if (GetLayoutDirection() == wxLayout_RightToLeft) + { + paintDc.SetLayoutDirection(wxLayout_LeftToRight); //workaround bug in wxDC::Blit() + SetLayoutDirection(wxLayout_LeftToRight); // + } + + const wxPoint origin = GetDeviceOrigin(); + paintDc.Blit(0, 0, buffer_->GetWidth(), buffer_->GetHeight(), this, -origin.x, -origin.y); + } + +private: + std::unique_ptr& buffer_; + wxPaintDC paintDc; +}; +#endif } #endif //RTL_H_0183487180058718273432148 -- cgit