summaryrefslogtreecommitdiff
path: root/wx+
diff options
context:
space:
mode:
Diffstat (limited to 'wx+')
-rw-r--r--wx+/dc.h127
-rw-r--r--wx+/graph.cpp495
-rw-r--r--wx+/graph.h137
-rw-r--r--wx+/grid.cpp59
-rw-r--r--wx+/no_flicker.h17
-rw-r--r--wx+/rtl.h54
-rw-r--r--wx+/shell_execute.h11
-rw-r--r--wx+/string_conv.h1
8 files changed, 600 insertions, 301 deletions
diff --git a/wx+/dc.h b/wx+/dc.h
new file mode 100644
index 00000000..d0f5c805
--- /dev/null
+++ b/wx+/dc.h
@@ -0,0 +1,127 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
+// **************************************************************************
+
+#ifndef DC_3813704987123956832143243214
+#define DC_3813704987123956832143243214
+
+#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER
+
+namespace zen
+{
+/*
+1. wxDCClipper does *not* stack: another fix for yet another poor wxWidgets implementation
+
+class RecursiveDcClipper
+{
+ RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc)
+};
+
+------------------------------------------------------------------------------------------------
+
+2. wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active: a fix for a poor wxWidgets implementation
+class BufferedPaintDC
+{
+ BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer);
+};
+*/
+
+
+
+
+
+
+
+
+
+
+
+//---------------------- implementation ------------------------
+class RecursiveDcClipper
+{
+public:
+ RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc)
+ {
+ auto it = refDcToAreaMap().find(&dc);
+ if (it != refDcToAreaMap().end())
+ {
+ oldRect.reset(new wxRect(it->second));
+
+ wxRect tmp = r;
+ tmp.Intersect(*oldRect); //better safe than sorry
+ dc_.SetClippingRegion(tmp); //
+ it->second = tmp;
+ }
+ else
+ {
+ dc_.SetClippingRegion(r);
+ refDcToAreaMap().insert(std::make_pair(&dc_, r));
+ }
+ }
+
+ ~RecursiveDcClipper()
+ {
+ dc_.DestroyClippingRegion();
+ if (oldRect.get() != nullptr)
+ {
+ dc_.SetClippingRegion(*oldRect);
+ refDcToAreaMap()[&dc_] = *oldRect;
+ }
+ else
+ refDcToAreaMap().erase(&dc_);
+ }
+
+private:
+ //associate "active" clipping area with each DC
+ static hash_map<wxDC*, wxRect>& refDcToAreaMap() { static hash_map<wxDC*, wxRect> clippingAreas; return clippingAreas; }
+
+ std::unique_ptr<wxRect> oldRect;
+ wxDC& dc_;
+};
+
+
+#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<wxBitmap>& buffer) : wxPaintDC(&wnd) {} };
+
+#else
+class BufferedPaintDC : public wxMemoryDC
+{
+public:
+ BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& 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<wxBitmap>& buffer_;
+ wxPaintDC paintDc;
+};
+#endif
+}
+
+#endif //DC_3813704987123956832143243214
diff --git a/wx+/graph.cpp b/wx+/graph.cpp
index 0d14ae69..cbedfa53 100644
--- a/wx+/graph.cpp
+++ b/wx+/graph.cpp
@@ -9,8 +9,9 @@
#include <algorithm>
#include <numeric>
#include <zen/basic_math.h>
+#include <zen/scope_guard.h>
#include <wx/settings.h>
-#include "rtl.h"
+#include "dc.h"
using namespace zen;
@@ -31,8 +32,8 @@ double zen::nextNiceNumber(double blockSize) //round to next number which is a c
const double e = std::pow(10, k);
if (numeric::isNull(e))
return 0;
- const double a = blockSize / e; //blockSize = a * 10^k with a in (1, 10)
- assert(1 < a && a < 10);
+ const double a = blockSize / e; //blockSize = a * 10^k with a in [1, 10)
+ assert(1 <= a && a < 10);
//have a look at leading two digits: "nice" numbers start with 1, 2, 2.5 and 5
const double steps[] = { 1, 2, 2.5, 5, 10 };
@@ -78,18 +79,22 @@ public:
ConvertCoord(double valMin, double valMax, size_t screenSize) :
min_(valMin),
scaleToReal(screenSize == 0 ? 0 : (valMax - valMin) / screenSize),
- scaleToScr(numeric::isNull((valMax - valMin)) ? 0 : screenSize / (valMax - valMin)) {}
+ scaleToScr(numeric::isNull((valMax - valMin)) ? 0 : screenSize / (valMax - valMin)),
+ outOfBoundsLow (-1 * scaleToReal + valMin),
+ outOfBoundsHigh((screenSize + 1) * scaleToReal + valMin) { if (outOfBoundsLow > outOfBoundsHigh) std::swap(outOfBoundsLow, outOfBoundsHigh); }
double screenToReal(double screenPos) const //input value: [0, screenSize - 1]
{
- return screenPos * scaleToReal + min_; //come close to valMax, but NEVER reach it!
+ return screenPos * scaleToReal + min_;
}
double realToScreen(double realPos) const //return screen position in pixel (but with double precision!)
{
return (realPos - min_) * scaleToScr;
}
- int realToScreenRound(double realPos) const //useful to find "proper" y-pixel positions
+ int realToScreenRound(double realPos) const //returns -1 and screenSize + 1 if out of bounds!
{
+ //catch large double values: if double is larger than what int can represent => undefined behavior!
+ numeric::confine(realPos , outOfBoundsLow, outOfBoundsHigh);
return numeric::round(realToScreen(realPos));
}
@@ -97,19 +102,22 @@ private:
double min_;
double scaleToReal;
double scaleToScr;
+
+ double outOfBoundsLow;
+ double outOfBoundsHigh;
};
//enlarge value range to display 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, //
+ int graphAreaSize, //in pixel
+ int optimalBlockSizePx, //
const LabelFormatter& labelFmt)
{
if (graphAreaSize > 0)
{
- double valRangePerBlock = (valMax - valMin) * optimalBlockSize / graphAreaSize; //proposal
+ double valRangePerBlock = (valMax - valMin) * optimalBlockSizePx / graphAreaSize; //proposal
valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock);
if (!numeric::isNull(valRangePerBlock))
{
@@ -145,7 +153,7 @@ void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const Conver
//draw x axis labels
const wxString label = labelFmt.formatText(valX, valRangePerBlock);
- wxSize labelExtent = dc.GetMultiLineTextExtent(label);
+ const wxSize labelExtent = dc.GetMultiLineTextExtent(label);
dc.DrawText(label, wxPoint(x - labelExtent.GetWidth() / 2, labelArea.y + (labelArea.height - labelExtent.GetHeight()) / 2)); //center
}
}
@@ -173,31 +181,223 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver
//draw y axis labels
const wxString label = labelFmt.formatText(valY, valRangePerBlock);
- wxSize labelExtent = dc.GetMultiLineTextExtent(label);
+ const wxSize labelExtent = dc.GetMultiLineTextExtent(label);
dc.DrawText(label, wxPoint(labelArea.x + (labelArea.width - labelExtent.GetWidth()) / 2, y - labelExtent.GetHeight() / 2)); //center
}
}
-template <class StdContainter>
-void subsample(StdContainter& cont, size_t factor)
+void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos)
{
- if (factor <= 1) return;
+ if (txt.empty()) return;
+ const int borderX = 5;
+ const int borderY = 2; //it looks like wxDC::GetMultiLineTextExtent() precisely returns width, but too large a height: maybe they consider "text row height"?
- auto itOut = cont.begin();
- for (auto itIn = cont.begin(); cont.end() - itIn >= static_cast<ptrdiff_t>(factor); itIn += factor) //don't even let iterator point out of range!
- *itOut++ = std::accumulate(itIn, itIn + factor, 0.0) / static_cast<double>(factor);
+ wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
+ wxSize txtExtent = dc.GetMultiLineTextExtent(txt);
+ txtExtent.x += 2 * borderX;
+ txtExtent.y += 2 * borderY;
- cont.erase(itOut, cont.end());
+ wxPoint drawPos = graphArea.GetTopLeft();
+ switch (pos)
+ {
+ case Graph2D::CORNER_TOP_LEFT:
+ break;
+ case Graph2D::CORNER_TOP_RIGHT:
+ drawPos.x += graphArea.width - txtExtent.GetWidth();
+ break;
+ case Graph2D::CORNER_BOTTOM_LEFT:
+ drawPos.y += graphArea.height - txtExtent.GetHeight();
+ break;
+ case Graph2D::CORNER_BOTTOM_RIGHT:
+ drawPos.x += graphArea.width - txtExtent.GetWidth();
+ drawPos.y += graphArea.height - txtExtent.GetHeight();
+ break;
+ }
+ dc.DrawText(txt, drawPos + wxPoint(borderX, borderY));
}
+
+
+warn_static("review")
+
+template <class Function, class Function2>
+void cutPoints(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, Function isInside, Function2 getIntersection)
+{
+ assert(curvePoints.size() == oobMarker.size());
+ if (curvePoints.size() != oobMarker.size() || curvePoints.empty()) return;
+ auto isMarkedOob = [&](size_t index) { return oobMarker[index] != 0; };
+
+ std::vector<CurvePoint> curvePointsTmp;
+ std::vector<char> oobMarkerTmp;
+ auto savePoint = [&](const CurvePoint& pt, bool markedOob) { curvePointsTmp.push_back(pt); oobMarkerTmp.push_back(markedOob); };
+
+ warn_static("perf: avoid these push_backs")
+
+ bool lastPointInside = isInside(curvePoints[0]);
+ if (lastPointInside)
+ savePoint(curvePoints[0], isMarkedOob(0));
+
+ for (auto it = curvePoints.begin() + 1; it != curvePoints.end(); ++it)
+ {
+ const size_t index = it - curvePoints.begin();
+
+ const bool pointInside = isInside(*it);
+ if (pointInside != lastPointInside)
+ {
+ lastPointInside = pointInside;
+
+ const CurvePoint is = getIntersection(*(it - 1), *it); //getIntersection returns *it when delta is zero
+ savePoint(is, !pointInside || isMarkedOob(index - 1));
+ }
+ if (pointInside)
+ savePoint(*it, isMarkedOob(index));
+ }
+ curvePointsTmp.swap(curvePoints);
+ oobMarkerTmp .swap(oobMarker);
+}
+
+
+struct GetIntersectionX
+{
+ GetIntersectionX(double x) : x_(x) {}
+ CurvePoint operator()(const CurvePoint& from, const CurvePoint& to) const
+ {
+ const double deltaX = to.x - from.x;
+ const double deltaY = to.y - from.y;
+ return !numeric::isNull(deltaX) ? CurvePoint(x_, from.y + (x_ - from.x) / deltaX * deltaY) : to;
+ };
+private:
+ double x_;
+};
+
+struct GetIntersectionY
+{
+ GetIntersectionY(double y) : y_(y) {}
+ CurvePoint operator()(const CurvePoint& from, const CurvePoint& to) const
+ {
+ const double deltaX = to.x - from.x;
+ const double deltaY = to.y - from.y;
+ return !numeric::isNull(deltaY) ? CurvePoint(from.x + (y_ - from.y) / deltaY * deltaX, y_) : to;
+ };
+private:
+ double y_;
+};
+
+void cutPointsOutsideX(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, double minX, double maxX)
+{
+ cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x >= minX; }, GetIntersectionX(minX));
+ cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x <= maxX; }, GetIntersectionX(maxX));
+}
+
+void cutPointsOutsideY(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, double minY, double maxY)
+{
+ cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.y >= minY; }, GetIntersectionY(minY));
+ cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.y <= maxY; }, GetIntersectionY(maxY));
+}
+}
+
+
+warn_static("review")
+void ContinuousCurveData::getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const
+{
+ if (pixelWidth <= 1) return;
+ const ConvertCoord cvrtX(minX, maxX, pixelWidth - 1); //map [minX, maxX] to [0, pixelWidth - 1]
+
+ std::pair<double, double> rangeX = getRangeX();
+ //catch large double values: if double is larger than what int can represent => undefined behavior!
+ const double xOutOfBoundsLow = cvrtX.screenToReal(-1);
+ const double xOutOfBoundsHigh = cvrtX.screenToReal(pixelWidth);
+ numeric::confine(rangeX.first , xOutOfBoundsLow, xOutOfBoundsHigh); //don't confine to [minX, maxX] which
+ numeric::confine(rangeX.second, xOutOfBoundsLow, xOutOfBoundsHigh); //would prevent empty ranges
+
+ const int posFrom = std::ceil (cvrtX.realToScreen(std::max(rangeX.first, minX))); //do not step outside [minX, maxX]
+ const int posTo = std::floor(cvrtX.realToScreen(std::min(rangeX.second, maxX))); //
+ //conversion from std::floor/std::ceil double return value to int is loss-free for full value range of 32-bit int! tested successfully on MSVC
+
+ for (int i = posFrom; i <= posTo; ++i)
+ {
+ const double x = cvrtX.screenToReal(i);
+ points.push_back(CurvePoint(x, getValue(x)));
+ }
+}
+
+
+void SparseCurveData::getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const
+{
+ if (pixelWidth <= 1) return;
+ const ConvertCoord cvrtX(minX, maxX, pixelWidth - 1); //map [minX, maxX] to [0, pixelWidth - 1]
+ const std::pair<double, double> rangeX = getRangeX();
+
+ auto addPoint = [&](const CurvePoint& pt)
+ {
+ warn_static("verify steps")
+ if (addSteps_ && !points.empty())
+ if (pt.y != points.back().y)
+ points.push_back(CurvePoint(pt.x, points.back().y));
+ points.push_back(pt);
+ };
+
+ const int posFrom = cvrtX.realToScreenRound(std::max(rangeX.first, minX));
+ const int posTo = cvrtX.realToScreenRound(std::min(rangeX.second, maxX));
+
+ for (int i = posFrom; i <= posTo; ++i)
+ {
+ const double x = cvrtX.screenToReal(i);
+ Opt<CurvePoint> ptLe = getLessEq(x);
+ Opt<CurvePoint> ptGe = getGreaterEq(x);
+ //both non-existent and invalid return values are mapped to out of expected range: => check on posLe/posGe NOT ptLe/ptGE in the following!
+ const int posLe = ptLe ? cvrtX.realToScreenRound(ptLe->x) : i + 1;
+ const int posGe = ptGe ? cvrtX.realToScreenRound(ptGe->x) : i - 1;
+ assert(!ptLe || posLe <= i); //check for invalid return values
+ assert(!ptGe || posGe >= i); //
+
+ if (posGe == i) //test if point would be mapped to pixel x-position i
+ {
+ if (posLe == i) //
+ addPoint(x - ptLe->x < ptGe->x - x ? *ptLe : *ptGe);
+ else
+ addPoint(*ptGe);
+ }
+ else
+ {
+ if (posLe == i)
+ addPoint(*ptLe);
+ else //no point for x-position i
+ {
+ if (i == posFrom && posGe > i)
+ {
+ if (posLe < i)
+ addPoint(*ptLe); //use first point outside display area!
+ else if (posGe > posTo) //curve starts outside the draw range!
+ break;
+ }
+ }
+
+ if (posGe < i)
+ break;
+
+ if (posGe > posTo) //last point outside the display area!
+ {
+ if (i == posTo && posLe == i) //no need for outside point if last position was already set above
+ break;
+
+ addPoint(*ptGe);
+ break;
+ }
+ if (posGe > i) //skip sparse area
+ i = posGe - 1;
+ }
+ }
}
+
Graph2D::Graph2D(wxWindow* parent,
wxWindowID winid,
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),
+ labelFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial")
{
Connect(wxEVT_PAINT, wxPaintEventHandler(Graph2D::onPaintEvent), nullptr, this);
Connect(wxEVT_SIZE, wxSizeEventHandler (Graph2D::onSizeEvent ), nullptr, this);
@@ -272,14 +472,14 @@ void Graph2D::OnMouseCaptureLost(wxMouseCaptureLostEvent& event)
}
-void Graph2D::setData(const std::shared_ptr<GraphData>& data, const CurveAttributes& ca)
+void Graph2D::setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca)
{
curves_.clear();
- addData(data, ca);
+ addCurve(data, ca);
}
-void Graph2D::addData(const std::shared_ptr<GraphData>& data, const CurveAttributes& ca)
+void Graph2D::addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca)
{
CurveAttributes newAttr = ca;
if (newAttr.autoColor)
@@ -288,22 +488,13 @@ void Graph2D::addData(const std::shared_ptr<GraphData>& data, const CurveAttribu
Refresh();
}
-namespace //putting this into function scope makes MSVC crash...
-{
-struct CurveSamples
-{
- CurveSamples() : offsetX(0) {}
- std::vector<double> yValues; //actual y-values at each screen pixel position
- int offsetX; //x-value offset in pixels
-};
-}
void Graph2D::render(wxDC& dc) const
{
using namespace numeric;
//set label font right at the start so that it is considered by wxDC::GetTextExtent below!
- dc.SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial"));
+ dc.SetFont(labelFont);
const wxRect clientRect = GetClientRect(); //DON'T use wxDC::GetSize()! DC may be larger than visible area!
{
@@ -365,27 +556,25 @@ void Graph2D::render(wxDC& dc) const
//set label areas respecting graph area border!
const wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, attr.xLabelHeight);
const wxRect yLabelArea(yLabelPosX, graphArea.y, attr.yLabelWidth, graphArea.height);
-
const wxPoint graphAreaOrigin = graphArea.GetTopLeft();
//detect x value range
double minX = attr.minXauto ? std::numeric_limits<double>::infinity() : attr.minX; //automatic: ensure values are initialized by first curve
double maxX = attr.maxXauto ? -std::numeric_limits<double>::infinity() : attr.maxX; //
- if (!curves_.empty())
- for (auto it = curves_.begin(); it != curves_.end(); ++it)
- if (it->first.get())
- {
- const GraphData& graph = *it->first;
- assert(graph.getXBegin() <= graph.getXEnd() + 1.0e-9);
- //GCC fucks up badly when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18
-
- if (attr.minXauto)
- minX = std::min(minX, graph.getXBegin());
- if (attr.maxXauto)
- maxX = std::max(maxX, graph.getXEnd());
- }
+ for (auto it = curves_.begin(); it != curves_.end(); ++it)
+ if (const CurveData* curve = it->first.get())
+ {
+ const std::pair<double, double> rangeX = curve->getRangeX();
+ assert(rangeX.first <= rangeX.second + 1.0e-9);
+ //GCC fucks up badly when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18
+
+ if (attr.minXauto)
+ minX = std::min(minX, rangeX.first);
+ if (attr.maxXauto)
+ maxX = std::max(maxX, rangeX.second);
+ }
- if (minX < maxX && maxX - minX < std::numeric_limits<double>::infinity()) //valid x-range
+ if (minX <= maxX && maxX - minX < std::numeric_limits<double>::infinity()) //valid x-range
{
int blockCountX = 0;
//enlarge minX, maxX to a multiple of a "useful" block size
@@ -396,39 +585,37 @@ void Graph2D::render(wxDC& dc) const
dc.GetTextExtent(L"100000000000000").GetWidth(),
*attr.labelFmtX);
- //detect y value range
- std::vector<CurveSamples> yValuesList(curves_.size());
+ //get raw values + detect y value range
double minY = attr.minYauto ? std::numeric_limits<double>::infinity() : attr.minY; //automatic: ensure values are initialized by first curve
double maxY = attr.maxYauto ? -std::numeric_limits<double>::infinity() : attr.maxY; //
- {
- const int AVG_FACTOR = 2; //some averaging of edgy input data to smoothen behavior on window resize
- const ConvertCoord cvrtX(minX, maxX, graphArea.width * AVG_FACTOR);
- for (auto it = curves_.begin(); it != curves_.end(); ++it)
- if (const GraphData* graph = it->first.get())
- {
- CurveSamples& samples = yValuesList[it - curves_.begin()];
- {
- const int posFirst = std::ceil(cvrtX.realToScreen(std::max(graph->getXBegin(), minX))); //do not step outside [xBegin, xEnd) range => 2 x ceil!
- const int posLast = std::ceil(cvrtX.realToScreen(std::min(graph->getXEnd (), maxX))); //
- //conversion from std::ceil double return valute to int is loss-free for full value range of 32-bit int! tested successfully on MSVC
+ std::vector<std::vector<CurvePoint>> curvePoints(curves_.size());
+ std::vector<std::vector<char>> oobMarker (curves_.size()); //effectively a std::vector<bool> marking points that start a out of bounds line
- for (int i = posFirst; i < posLast; ++i)
- samples.yValues.push_back(graph->getValue(cvrtX.screenToReal(i)));
+ for (auto it = curves_.begin(); it != curves_.end(); ++it)
+ if (const CurveData* curve = it->first.get())
+ {
+ const size_t index = it - curves_.begin();
+ std::vector<CurvePoint>& points = curvePoints[index];
+ auto& marker = oobMarker[index];
- subsample(samples.yValues, AVG_FACTOR);
- samples.offsetX = posFirst / AVG_FACTOR;
- }
- if (!samples.yValues.empty())
- {
- if (attr.minYauto)
- minY = std::min(minY, *std::min_element(samples.yValues.begin(), samples.yValues.end()));
- if (attr.maxYauto)
- maxY = std::max(maxY, *std::max_element(samples.yValues.begin(), samples.yValues.end()));
- }
+ curve->getPoints(minX, maxX, graphArea.width, points);
+
+ //cut points outside visible x-range now in order to calculate height of visible points only!
+ marker.resize(points.size()); //default value: false
+ cutPointsOutsideX(points, marker, minX, maxX);
+
+ if ((attr.minYauto || attr.maxYauto) && !points.empty())
+ {
+ auto itPair = std::minmax_element(points.begin(), points.end(), [](const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.y < rhs.y; });
+ if (attr.minYauto)
+ minY = std::min(minY, itPair.first->y);
+ if (attr.maxYauto)
+ maxY = std::max(maxY, itPair.second->y);
}
- }
- if (minY < maxY) //valid y-range
+ }
+
+ if (minY <= maxY) //valid y-range
{
int blockCountY = 0;
//enlarge minY, maxY to a multiple of a "useful" block size
@@ -439,44 +626,55 @@ void Graph2D::render(wxDC& dc) const
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]
+ if (graphArea.width <= 1 || graphArea.height <= 1) return;
+ const ConvertCoord cvrtX(minX, maxX, graphArea.width - 1); //map [minX, maxX] to [0, pixelWidth - 1]
+ const ConvertCoord cvrtY(maxY, minY, graphArea.height - 1); //map [minY, maxY] to [pixelHeight - 1, 0]
//calculate curve coordinates on graph area
- auto getCurvePoints = [&](size_t index, std::vector<wxPoint>& points)
- {
- if (index < yValuesList.size())
- {
- CurveSamples& samples = yValuesList[index];
+ std::vector<std::vector<wxPoint>> drawPoints(curves_.size());
- for (auto it = samples.yValues.begin(); it != samples.yValues.end(); ++it)
- points.push_back(wxPoint(samples.offsetX + (it - samples.yValues.begin()),
- cvrtY.realToScreenRound(*it)) + graphAreaOrigin);
- }
- };
+ for (size_t index = 0; index < curves_.size(); ++index)
+ {
+ //cut points outside visible y-range before calculating pixels:
+ //1. realToScreenRound() deforms out-of-range values!
+ //2. pixels that are grossly out of range may become a severe performance problem when drawing on the DC (Windows)
+ cutPointsOutsideY(curvePoints[index], oobMarker[index], minY, maxY);
+
+ auto& points = drawPoints[index];
+ for (const auto& pt : curvePoints[index])
+ points.push_back(wxPoint(cvrtX.realToScreenRound(pt.x),
+ cvrtY.realToScreenRound(pt.y)) + graphAreaOrigin);
+ }
//update active mouse selection
if (activeSel.get() &&
graphArea.width > 0 && graphArea.height > 0)
{
- wxPoint startPos = activeSel->getStartPos() - graphAreaOrigin; //make relative to graphArea
- wxPoint currentPos = activeSel->refCurrentPos() - graphAreaOrigin;
-
- //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);
-
- auto& from = activeSel->refSelection().from;
- auto& to = activeSel->refSelection().to;
+ auto widen = [](double* low, double* high)
+ {
+ if (*low > *high)
+ std::swap(low, high);
+ *low -= 0.5;
+ *high += 0.5;
+ };
- //save current selection as double coordinates
- 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));
+ const wxPoint screenStart = activeSel->getStartPos() - graphAreaOrigin; //make relative to graphArea
+ const wxPoint screenCurrent = activeSel->refCurrentPos() - graphAreaOrigin;
- from.y = cvrtY.screenToReal(startPos .y + (startPos.y <= currentPos.y ? 0 : 1));
- to .y = cvrtY.screenToReal(currentPos.y + (startPos.y <= currentPos.y ? 1 : 0));
+ //normalize positions: a mouse selection is symmetric and *not* an half-open range!
+ double screenFromX = confineCpy(screenStart .x, 0, graphArea.width - 1);
+ double screenFromY = confineCpy(screenStart .y, 0, graphArea.height - 1);
+ double screenToX = confineCpy(screenCurrent.x, 0, graphArea.width - 1);
+ double screenToY = confineCpy(screenCurrent.y, 0, graphArea.height - 1);
+ widen(&screenFromX, &screenToX); //use full pixel range for selection!
+ widen(&screenFromY, &screenToY);
+
+ //save current selection as "double" coordinates
+ activeSel->refSelection().from = CurvePoint(cvrtX.screenToReal(screenFromX),
+ cvrtY.screenToReal(screenFromY));
+
+ activeSel->refSelection().to = CurvePoint(cvrtX.screenToReal(screenToX),
+ cvrtY.screenToReal(screenToY));
}
//#################### begin drawing ####################
@@ -484,8 +682,7 @@ void Graph2D::render(wxDC& dc) const
for (auto it = curves_.begin(); it != curves_.end(); ++it)
if (it->second.drawCurveArea)
{
- std::vector<wxPoint> points;
- getCurvePoints(it - curves_.begin(), points);
+ std::vector<wxPoint> points = drawPoints[it - curves_.begin()];
if (!points.empty())
{
points.push_back(wxPoint(points.back ().x, graphArea.GetBottom())); //add lower right and left corners
@@ -502,36 +699,39 @@ void Graph2D::render(wxDC& dc) const
if (activeSel)
allSelections.push_back(activeSel->refSelection());
{
- //alpha channel (not yet) supported on wxMSW, so draw selection before curves
+ //alpha channel not 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 it = allSelections.begin(); it != allSelections.end(); ++it)
+ auto shrink = [](double* low, double* high)
{
- //harmonize with active mouse selection above!
- wxPoint pixelFrom(cvrtX.realToScreenRound(it->from.x),
- cvrtY.realToScreenRound(it->from.y));
- wxPoint pixelTo(cvrtX.realToScreenRound(it->to.x),
- cvrtY.realToScreenRound(it->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;
+ if (*low > *high)
+ std::swap(low, high);
+ *low += 0.5;
+ *high -= 0.5;
+ if (*low > *high)
+ *low = *high = (*low + *high) / 2;
+ };
+ for (auto it = allSelections.begin(); it != allSelections.end(); ++it)
+ {
+ //harmonize with active mouse selection above
+ double screenFromX = cvrtX.realToScreen(it->from.x);
+ double screenFromY = cvrtY.realToScreen(it->from.y);
+ double screenToX = cvrtX.realToScreen(it->to.x);
+ double screenToY = cvrtY.realToScreen(it->to.y);
+ shrink(&screenFromX, &screenToX);
+ shrink(&screenFromY, &screenToY);
+
+ confine(screenFromX, 0.0, graphArea.width - 1.0);
+ confine(screenFromY, 0.0, graphArea.height - 1.0);
+ confine(screenToX, 0.0, graphArea.width - 1.0);
+ confine(screenToY, 0.0, graphArea.height - 1.0);
+
+ const wxPoint pixelFrom = wxPoint(numeric::round(screenFromX),
+ numeric::round(screenFromY)) + graphAreaOrigin;
+ const wxPoint pixelTo = wxPoint(numeric::round(screenToX),
+ numeric::round(screenToY)) + graphAreaOrigin;
switch (attr.mouseSelMode)
{
case SELECT_NONE:
@@ -554,17 +754,42 @@ void Graph2D::render(wxDC& dc) const
drawYLabel(dc, minY, maxY, blockCountY, cvrtY, graphArea, yLabelArea, *attr.labelFmtY);
//4. finally draw curves
- for (auto it = curves_.begin(); it != curves_.end(); ++it)
{
- std::vector<wxPoint> points;
- getCurvePoints(it - curves_.begin(), points);
- if (!points.empty())
+ dc.SetClippingRegion(graphArea); //prevent thick curves from drawing slightly outside
+ ZEN_ON_SCOPE_EXIT(dc.DestroyClippingRegion());
+
+ for (auto it = curves_.begin(); it != curves_.end(); ++it)
{
wxDCPenChanger dummy(dc, wxPen(it->second.color, it->second.lineWidth));
- dc.DrawLines(static_cast<int>(points.size()), &points[0]);
- dc.DrawPoint(points.back()); //wxDC::DrawLines() doesn't draw last pixel
+
+ const size_t index = it - curves_.begin();
+ std::vector<wxPoint>& points = drawPoints[index]; //alas wxDC::DrawLines() is not const-correct!!!
+ auto& marker = oobMarker [index];
+ assert(points.size() == marker.size());
+ warn_static("review")
+
+ //draw all parts of the curve except for the out-of-bounds ranges
+ size_t pointsIndexFirst = 0;
+ while (pointsIndexFirst < points.size())
+ {
+ size_t pointsIndexLast = std::find(marker.begin() + pointsIndexFirst, marker.end(), true) - marker.begin();
+ if (pointsIndexLast < points.size()) ++ pointsIndexLast;
+
+ const int pointCount = static_cast<int>(pointsIndexLast - pointsIndexFirst);
+ if (pointCount > 0)
+ {
+ if (pointCount >= 2) //on OS X wxWidgets has a nasty assert on this
+ dc.DrawLines(pointCount, &points[pointsIndexFirst]);
+ dc.DrawPoint(points[pointsIndexLast - 1]); //wxDC::DrawLines() doesn't draw last pixel
+ }
+ pointsIndexFirst = std::find(marker.begin() + pointsIndexLast, marker.end(), false) - marker.begin();
+ }
}
}
+
+ //5. draw corner texts
+ for (auto it = attr.cornerTexts.begin(); it != attr.cornerTexts.end(); ++it)
+ drawCornerText(dc, graphArea, it->second, it->first);
}
}
}
diff --git a/wx+/graph.h b/wx+/graph.h
index 8f816b08..fe008a38 100644
--- a/wx+/graph.h
+++ b/wx+/graph.h
@@ -7,13 +7,15 @@
#ifndef WX_PLOT_HEADER_2344252459
#define WX_PLOT_HEADER_2344252459
+#include <map>
#include <vector>
#include <memory>
#include <wx/panel.h>
-#include <wx/dcbuffer.h>
+//#include <wx/dcbuffer.h>
#include <zen/string_tools.h>
+#include <zen/optional.h>
-//simple 2D graph as wxPanel specialization
+//elegant 2D graph as wxPanel specialization
namespace zen
{
@@ -21,62 +23,90 @@ namespace zen
Example:
//init graph (optional)
m_panelGraph->setAttributes(Graph2D::MainAttributes().
- setLabelX(Graph2D::POSLX_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()).
- setLabelY(Graph2D::POSLY_RIGHT, 60, std::make_shared<LabelFormatterBytes>()));
+ setLabelX(Graph2D::X_LABEL_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()).
+ setLabelY(Graph2D::Y_LABEL_RIGHT, 60, std::make_shared<LabelFormatterBytes>()));
//set graph data
- std::shared_ptr<GraphData> graphDataBytes = ...
- m_panelGraph->setData(graphDataBytes, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0)));
+ std::shared_ptr<CurveData> curveDataBytes = ...
+ m_panelGraph->setCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0)));
*/
-//------------------------------------------------------------------------------------------------------------
-struct GraphData
+struct CurvePoint
{
- virtual ~GraphData() {}
- virtual double getValue (double x) const = 0;
- virtual double getXBegin() const = 0;
- virtual double getXEnd () const = 0; //upper bound for x, getValue() is NOT evaluated at this position! Similar to std::vector::end()
+ CurvePoint() : x(0), y(0) {}
+ CurvePoint(double xVal, double yVal) : x(xVal), y(yVal) {}
+ double x;
+ double y;
};
+inline bool operator==(const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; }
+inline bool operator!=(const CurvePoint& lhs, const CurvePoint& rhs) { return !(lhs == rhs); }
+struct CurveData
+{
+ virtual ~CurveData() {}
-//reference data implementation
-class RangeData : public GraphData
+ virtual std::pair<double, double> getRangeX() const = 0;
+ virtual void getPoints(double minX, double maxX, int pixelWidth,
+ std::vector<CurvePoint>& points) const = 0; //points outside the draw area are automatically trimmed!
+};
+
+//special curve types:
+struct ContinuousCurveData : public CurveData
{
-public:
- std::vector<double>& refData() { return data; }
+ virtual double getValue(double x) const = 0;
private:
- virtual double getValue(double x) const
- {
- const size_t pos = static_cast<size_t>(x);
- return pos < data.size() ? data[pos] : 0;
- }
- virtual double getXBegin() const { return 0; }
- virtual double getXEnd() const { return data.size(); } //example: two-element range is accessible within [0, 2)
+ virtual void getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const final;
+};
- std::vector<double> data;
+struct SparseCurveData : public CurveData
+{
+ SparseCurveData(bool addSteps = false) : addSteps_(addSteps) {} //addSteps: add points to get a staircase effect or connect points via a direct line
+
+ virtual Opt<CurvePoint> getLessEq (double x) const = 0;
+ virtual Opt<CurvePoint> getGreaterEq(double x) const = 0;
+
+private:
+ virtual void getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const final;
+ bool addSteps_;
};
-/*
-//reference data implementation
-class VectorData : public GraphData
+struct ArrayCurveData : public SparseCurveData
{
-public:
- operator std::vector<double>& () { return data; }
+ virtual double getValue(size_t pos) const = 0;
+ virtual size_t getSize() const = 0;
private:
- virtual double getValue(double x) const
+ virtual std::pair<double, double> getRangeX() const final { const size_t sz = getSize(); return std::make_pair(0.0, sz == 0 ? 0.0 : sz - 1.0); }
+
+ virtual Opt<CurvePoint> getLessEq(double x) const final
{
- const size_t pos = static_cast<size_t>(x);
- return pos < data.size() ? data[pos] : 0;
+ const size_t sz = getSize();
+ const size_t pos = std::min<ptrdiff_t>(std::floor(x), sz - 1); //[!] expect unsigned underflow if empty!
+ if (pos < sz)
+ return CurvePoint(pos, getValue(pos));
+ return NoValue();
}
- virtual double getXBegin() const { return 0; }
- virtual double getXEnd() const { return data.size(); } //example: two-element range is accessible within [0, 2)
- std::vector<double> data;
+ virtual Opt<CurvePoint> getGreaterEq(double x) const final
+ {
+ const size_t pos = std::max<ptrdiff_t>(std::ceil(x), 0); //[!] use std::max with signed type!
+ if (pos < getSize())
+ return CurvePoint(pos, getValue(pos));
+ return NoValue();
+ }
+};
+
+struct VectorCurveData : public ArrayCurveData
+{
+ std::vector<double>& refData() { return data; }
+private:
+ virtual double getValue(size_t pos) const final { return pos < data.size() ? data[pos] : 0; }
+ virtual size_t getSize() const final { return data.size(); }
+ std::vector<double> data;
};
-*/
//------------------------------------------------------------------------------------------------------------
+
struct LabelFormatter
{
virtual ~LabelFormatter() {}
@@ -106,16 +136,8 @@ extern const wxEventType wxEVT_GRAPH_SELECTION;
struct SelectionBlock
{
- struct Point
- {
- Point() : x(0), y(0) {}
- Point(double xVal, double yVal) : x(xVal), y(yVal) {}
- double x;
- double y;
- };
-
- Point from;
- Point to;
+ CurvePoint from;
+ CurvePoint to;
};
class GraphSelectEvent : public wxCommandEvent
@@ -135,8 +157,8 @@ typedef void (wxEvtHandler::*GraphSelectEventFunction)(GraphSelectEvent&);
#define GraphSelectEventHandler(func) \
(wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GraphSelectEventFunction, &func)
-
//------------------------------------------------------------------------------------------------------------
+
class Graph2D : public wxPanel
{
public:
@@ -168,8 +190,8 @@ public:
int lineWidth;
};
- void setData(const std::shared_ptr<GraphData>& data, const CurveAttributes& ca = CurveAttributes());
- void addData(const std::shared_ptr<GraphData>& data, const CurveAttributes& ca = CurveAttributes());
+ void setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes());
+ void addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes());
enum PosLabelY
{
@@ -185,6 +207,14 @@ public:
X_LABEL_NONE
};
+ enum PosCorner
+ {
+ CORNER_TOP_LEFT,
+ CORNER_TOP_RIGHT,
+ CORNER_BOTTOM_LEFT,
+ CORNER_BOTTOM_RIGHT,
+ };
+
enum SelMode
{
SELECT_NONE,
@@ -238,6 +268,8 @@ public:
return *this;
}
+ MainAttributes& setCornerText(const wxString& txt, PosCorner pos) { cornerTexts[pos] = txt; return *this; }
+
MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; }
private:
@@ -261,6 +293,8 @@ public:
int yLabelWidth;
std::shared_ptr<LabelFormatter> labelFmtY;
+ std::map<PosCorner, wxString> cornerTexts;
+
SelMode mouseSelMode;
};
void setAttributes(const MainAttributes& newAttr) { attr = newAttr; Refresh(); }
@@ -311,8 +345,9 @@ private:
std::unique_ptr<wxBitmap> doubleBuffer;
- typedef std::vector<std::pair<std::shared_ptr<GraphData>, CurveAttributes>> GraphList;
- GraphList curves_;
+ typedef std::vector<std::pair<std::shared_ptr<CurveData>, CurveAttributes>> CurveList;
+ CurveList curves_;
+ wxFont labelFont; //perf!!! generating the font is *very* expensive! don't do this repeatedly in Graph2D::render()!
};
}
diff --git a/wx+/grid.cpp b/wx+/grid.cpp
index 46c0c406..c8d418ee 100644
--- a/wx+/grid.cpp
+++ b/wx+/grid.cpp
@@ -17,8 +17,9 @@
#include <zen/scope_guard.h>
#include <zen/utf.h>
#include <zen/format_unit.h>
-#include "image_tools.h"
-#include "rtl.h"
+//#include "image_tools.h"
+//#include "rtl.h"
+#include "dc.h"
#ifdef ZEN_LINUX
#include <gtk/gtk.h>
@@ -63,50 +64,6 @@ 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
-
-//------------------------------------------------------------
-
-//another fix for yet another poor wxWidgets implementation (wxDCClipper does *not* stack)
-hash_map<wxDC*, wxRect> clippingAreas; //associate "active" clipping area with each DC
-
-class DcClipper
-{
-public:
- DcClipper(wxDC& dc, const wxRect& r) : dc_(dc)
- {
- auto it = clippingAreas.find(&dc);
- if (it != clippingAreas.end())
- {
- oldRect.reset(new wxRect(it->second));
-
- wxRect tmp = r;
- tmp.Intersect(*oldRect); //better safe than sorry
- dc_.SetClippingRegion(tmp); //
- it->second = tmp;
- }
- else
- {
- dc_.SetClippingRegion(r);
- clippingAreas.insert(std::make_pair(&dc_, r));
- }
- }
-
- ~DcClipper()
- {
- dc_.DestroyClippingRegion();
- if (oldRect.get() != nullptr)
- {
- dc_.SetClippingRegion(*oldRect);
- clippingAreas[&dc_] = *oldRect;
- }
- else
- clippingAreas.erase(&dc_);
- }
-
-private:
- std::unique_ptr<wxRect> oldRect;
- wxDC& dc_;
-};
}
//----------------------------------------------------------------------------------------------------------------
@@ -208,7 +165,7 @@ wxString getTruncatedText(const wxString& text, Function textFits)
void drawTextLabelFitting(wxDC& dc, const wxString& text, const wxRect& rect, int alignment)
{
- DcClipper clip(dc, rect); //wxDC::DrawLabel doesn't care about width, WTF?
+ RecursiveDcClipper clip(dc, rect); //wxDC::DrawLabel doesn't care about width, WTF?
/*
performance notes:
@@ -555,7 +512,7 @@ private:
wxRect textRect = rect;
textRect.Deflate(1);
{
- DcClipper clip(dc, textRect); //wxDC::DrawLabel doesn't care about with, WTF?
+ RecursiveDcClipper clip(dc, textRect); //wxDC::DrawLabel doesn't care about with, WTF?
dc.DrawLabel(formatRow(row), textRect, wxALIGN_CENTRE);
}
@@ -691,7 +648,7 @@ private:
highlight ? col == highlight->first && compPos == highlight->second :
false;
- DcClipper clip(dc, rect);
+ RecursiveDcClipper clip(dc, rect);
dataView->renderColumnLabel(refParent(), dc, rect, colType, isHighlighted);
//draw move target location
@@ -978,7 +935,7 @@ private:
{
//draw background lines
{
- DcClipper dummy2(dc, rect); //solve issues with drawBackground() painting in area outside of rect (which is not also refreshed by renderCell()) -> keep small scope!
+ RecursiveDcClipper dummy2(dc, rect); //solve issues with drawBackground() painting in area outside of rect (which is not also refreshed by renderCell()) -> keep small scope!
for (int row = rowFirst; row < rowLast; ++row)
drawBackground(*prov, dc, wxRect(cellAreaTL + wxPoint(0, row * rowHeight), wxSize(compWidth, rowHeight)), row, compPos);
}
@@ -995,7 +952,7 @@ private:
for (int row = rowFirst; row < rowLast; ++row)
{
const wxRect& cellRect = wxRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, width, rowHeight);
- DcClipper clip(dc, cellRect);
+ RecursiveDcClipper clip(dc, cellRect);
prov->renderCell(refParent(), dc, cellRect, row, iterCol->type_);
}
cellAreaTL.x += width;
diff --git a/wx+/no_flicker.h b/wx+/no_flicker.h
index ea9efb4d..fd64628f 100644
--- a/wx+/no_flicker.h
+++ b/wx+/no_flicker.h
@@ -15,20 +15,27 @@ namespace zen
inline
void setText(wxTextCtrl& control, const wxString& newText, bool* additionalLayoutChange = nullptr)
{
+ const wxString& label = control.GetValue(); //perf: don't call twice!
if (additionalLayoutChange && !*additionalLayoutChange) //never revert from true to false!
- *additionalLayoutChange = control.GetValue().length() != newText.length(); //avoid screen flicker: update layout only when necessary
+ *additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
- if (control.GetValue() != newText)
+ if (label != newText)
control.ChangeValue(newText);
}
inline
-void setText(wxStaticText& control, const wxString& newText, bool* additionalLayoutChange = nullptr)
+void setText(wxStaticText& control, wxString newText, bool* additionalLayoutChange = nullptr)
{
+#ifdef ZEN_WIN
+ //wxStaticText handles ampersands incorrectly: https://sourceforge.net/p/freefilesync/bugs/279/
+ replace(newText, L'&', L"&&");
+#endif
+
+ const wxString& label = control.GetLabel(); //perf: don't call twice!
if (additionalLayoutChange && !*additionalLayoutChange)
- *additionalLayoutChange = control.GetLabel().length() != newText.length(); //avoid screen flicker: update layout only when necessary
+ *additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
- if (control.GetLabel() != newText)
+ if (label != newText)
control.SetLabel(newText);
}
}
diff --git a/wx+/rtl.h b/wx+/rtl.h
index 84009a86..4caf19ea 100644
--- a/wx+/rtl.h
+++ b/wx+/rtl.h
@@ -13,7 +13,6 @@
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/app.h>
-#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER
namespace zen
{
@@ -38,17 +37,6 @@ void drawIconRtlNoMirror(wxDC& dc, //wxDC::DrawIcon DOES mirror by default
wxBitmap mirrorIfRtl(const wxBitmap& bmp);
-
-/*
-class BufferedPaintDC
-{
-public:
- BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& 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/
@@ -127,48 +115,6 @@ 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<wxBitmap>& buffer) : wxPaintDC(&wnd) {} };
-
-#else
-class BufferedPaintDC : public wxMemoryDC
-{
-public:
- BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& 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<wxBitmap>& buffer_;
- wxPaintDC paintDc;
-};
-#endif
}
#endif //RTL_H_0183487180058718273432148
diff --git a/wx+/shell_execute.h b/wx+/shell_execute.h
index 8965186e..1d67aa21 100644
--- a/wx+/shell_execute.h
+++ b/wx+/shell_execute.h
@@ -42,12 +42,15 @@ void shellExecute(const Zstring& command, ExecutionType type = EXEC_TYPE_ASYNC)
{
#ifdef ZEN_WIN
//parse commandline
+ Zstring commandTmp = command;
+ trim(commandTmp, true, false); //CommandLineToArgvW() does not like leading spaces
+
std::vector<std::wstring> argv;
int argc = 0;
- if (LPWSTR* tmp = ::CommandLineToArgvW(command.c_str(), &argc))
+ if (LPWSTR* tmp = ::CommandLineToArgvW(commandTmp.c_str(), &argc))
{
+ ZEN_ON_SCOPE_EXIT(::LocalFree(tmp));
std::copy(tmp, tmp + argc, std::back_inserter(argv));
- ::LocalFree(tmp);
}
std::wstring filename;
@@ -74,7 +77,7 @@ void shellExecute(const Zstring& command, ExecutionType type = EXEC_TYPE_ASYNC)
if (!::ShellExecuteEx(&execInfo)) //__inout LPSHELLEXECUTEINFO lpExecInfo
{
wxString cmdFmt = L"File: " + filename + L"\nArg: " + arguments;
- wxMessageBox(_("Invalid command line:") + L"\n" + cmdFmt + L"\n\n" + formatSystemError(L"ShellExecuteEx", getLastError()));
+ wxMessageBox(_("Invalid command line:") + L"\n" + cmdFmt + L"\n\n" + formatSystemError(L"ShellExecuteEx", getLastError()), /*L"FreeFileSync - " + */_("Error"), wxOK | wxICON_ERROR);
return;
}
@@ -99,7 +102,7 @@ void shellExecute(const Zstring& command, ExecutionType type = EXEC_TYPE_ASYNC)
//Posix::system - execute a shell command
int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect...
if (rv == -1 || WEXITSTATUS(rv) == 127) //http://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)"
- wxMessageBox(_("Invalid command line:") + L"\n" + utfCvrtTo<wxString>(command));
+ wxMessageBox(_("Invalid command line:") + L"\n" + utfCvrtTo<wxString>(command), /*L"FreeFileSync - " +*/ _("Error"), wxOK | wxICON_ERROR);
}
else
async([=] { int rv = ::system(command.c_str()); (void)rv; });
diff --git a/wx+/string_conv.h b/wx+/string_conv.h
index b919a3ee..e9150223 100644
--- a/wx+/string_conv.h
+++ b/wx+/string_conv.h
@@ -23,7 +23,6 @@ inline std::vector<Zstring> toZ(const std::vector<wxString>& strList)
std::transform(strList.begin(), strList.end(), std::back_inserter(tmp), [](const wxString& str) { return toZ(str); });
return tmp;
}
-
}
#endif // STRINGCONV_H_INCLUDED
bgstack15