summaryrefslogtreecommitdiff
path: root/wx+
diff options
context:
space:
mode:
Diffstat (limited to 'wx+')
-rw-r--r--wx+/bitmap_button.h81
-rw-r--r--wx+/button.cpp305
-rw-r--r--wx+/button.h53
-rw-r--r--wx+/dc.h4
-rw-r--r--wx+/graph.cpp185
-rw-r--r--wx+/graph.h11
-rw-r--r--wx+/grid.cpp317
-rw-r--r--wx+/grid.h46
-rw-r--r--wx+/image_tools.cpp185
-rw-r--r--wx+/image_tools.h55
-rw-r--r--wx+/no_flicker.h10
11 files changed, 609 insertions, 643 deletions
diff --git a/wx+/bitmap_button.h b/wx+/bitmap_button.h
new file mode 100644
index 00000000..5674f66b
--- /dev/null
+++ b/wx+/bitmap_button.h
@@ -0,0 +1,81 @@
+// **************************************************************************
+// * 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 BUTTON_HEADER_83415718945878341563415
+#define BUTTON_HEADER_83415718945878341563415
+
+#include <wx/bmpbuttn.h>
+#include "image_tools.h"
+
+namespace zen
+{
+//zen::BitmapTextButton is identical to wxBitmapButton, but preserves the label via SetLabel(), which wxFormbuilder would ditch!
+class BitmapTextButton : public wxBitmapButton
+{
+public:
+ BitmapTextButton(wxWindow* parent,
+ wxWindowID id,
+ const wxString& label,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = 0,
+ const wxValidator& validator = wxDefaultValidator,
+ const wxString& name = wxButtonNameStr) :
+ wxBitmapButton(parent, id, wxNullBitmap, pos, size, style | wxBU_AUTODRAW, validator, name) { SetLabel(label); }
+};
+
+void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& text, int gap = 5, int border = 5);
+
+//set bitmap label flicker free:
+void setImage(wxBitmapButton& button, const wxBitmap& bmp);
+
+
+
+
+
+
+
+
+
+
+
+//################################### implementation ###################################
+inline
+void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& text, int gap, int border)
+{
+ assert(gap >= 0 && border >= 0);
+ gap = std::max(0, gap);
+ border = std::max(0, border);
+
+ wxImage dynImage = createImageFromText(text, btn.GetFont(), btn.GetForegroundColour());
+ if (img.IsOk())
+ {
+ if (btn.GetLayoutDirection() != wxLayout_RightToLeft)
+ dynImage = stackImages(img, dynImage, ImageStackLayout::HORIZONTAL, ImageStackAlignment::CENTER, gap);
+ else
+ dynImage = stackImages(dynImage, img, ImageStackLayout::HORIZONTAL, ImageStackAlignment::CENTER, gap);
+ }
+
+ //SetMinSize() instead of SetSize() is needed here for wxWindows layout determination to work corretly
+ wxSize minSize = btn.GetMinSize();
+ btn.SetMinSize(wxSize(std::max(dynImage.GetWidth () + 2 * border, minSize.GetWidth()),
+ std::max(dynImage.GetHeight() + 2 * border, minSize.GetHeight())));
+
+ btn.SetBitmapLabel(wxBitmap(dynImage));
+ //SetLabel() calls confuse wxBitmapButton in the disabled state and it won't show the image! workaround:
+ btn.SetBitmapDisabled(wxBitmap(dynImage.ConvertToDisabled()));
+}
+
+
+inline
+void setImage(wxBitmapButton& button, const wxBitmap& bmp)
+{
+ if (!isEqual(button.GetBitmapLabel(), bmp))
+ button.SetBitmapLabel(bmp);
+}
+}
+
+#endif //BUTTON_HEADER_83415718945878341563415
diff --git a/wx+/button.cpp b/wx+/button.cpp
deleted file mode 100644
index 806c0969..00000000
--- a/wx+/button.cpp
+++ /dev/null
@@ -1,305 +0,0 @@
-// **************************************************************************
-// * 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 *
-// **************************************************************************
-
-#include "button.h"
-#include <algorithm>
-#include <limits>
-#include <cmath>
-#include <zen/string_tools.h>
-#include <wx/dcmemory.h>
-#include <wx/image.h>
-#include "image_tools.h"
-
-using namespace zen;
-
-
-void zen::setImage(wxBitmapButton& button, const wxBitmap& bmp)
-{
- if (!isEqual(button.GetBitmapLabel(), bmp))
- button.SetBitmapLabel(bmp);
-}
-
-
-BitmapButton::BitmapButton(wxWindow* parent,
- wxWindowID id,
- const wxString& label,
- const wxPoint& pos,
- const wxSize& size,
- long style,
- const wxValidator& validator,
- const wxString& name) :
- wxBitmapButton(parent, id, wxNullBitmap, pos, size, style | wxBU_AUTODRAW, validator, name),
- spaceAfter_(0),
- spaceBefore_(0),
- innerBorderSize(5)
-{
- SetLabel(label);
-}
-
-
-void BitmapButton::setBitmapFront(const wxBitmap& bitmap, int spaceAfter)
-{
- if (!isEqual(bitmap, bitmapFront) || spaceAfter_ != spaceAfter) //avoid flicker
- {
- bitmapFront = bitmap;
- spaceAfter_ = spaceAfter;
- refreshButtonLabel();
- }
-}
-
-
-void BitmapButton::SetLabel(const wxString& label)
-{
- if (wxBitmapButton::GetLabel() != label) //avoid flicker
- {
- wxBitmapButton::SetLabel(label);
- refreshButtonLabel();
- }
-}
-
-void BitmapButton::setBitmapBack(const wxBitmap& bitmap, int spaceBefore)
-{
- if (!isEqual(bitmap, bitmapBack) || spaceBefore_ != spaceBefore) //avoid flicker
- {
- bitmapBack = bitmap;
- spaceBefore_ = spaceBefore;
- refreshButtonLabel();
- }
-}
-
-
-namespace
-{
-void makeWhiteTransparent(wxImage& image) //assume black text on white background
-{
- if (unsigned char* alphaFirst = image.GetAlpha())
- {
- unsigned char* alphaLast = alphaFirst + image.GetWidth() * image.GetHeight();
-
- //dist(black, white)
- const double distBlackWhite = 255 * std::sqrt(3.0);
-
- const unsigned char* bytePos = image.GetData();
-
- for (unsigned char* j = alphaFirst; j != alphaLast; ++j)
- {
- unsigned char r = *bytePos++; //
- unsigned char g = *bytePos++; //each pixel consists of three bytes
- unsigned char b = *bytePos++; //
-
- //dist((r,g,b), white)
- double distColWhite = std::sqrt((255.0 - r) * (255.0 - r) +
- (255.0 - g) * (255.0 - g) +
- (255.0 - b) * (255.0 - b));
-
- //black(0,0,0) becomes fully opaque(255), while white(255,255,255) becomes transparent(0)
- *j = distColWhite / distBlackWhite * wxIMAGE_ALPHA_OPAQUE;
- }
- }
-}
-
-
-wxSize getSizeNeeded(const wxString& text, wxFont& font)
-{
- wxCoord width = 0;
- wxCoord height = 0;
-
- //the context used for bitmaps...
- wxMemoryDC().GetMultiLineTextExtent(replaceCpy(text, L"&", L"", false), //remove accelerator
- &width, &height, nullptr, &font);
-
- return wxSize(width, height);
-}
-}
-
-
-wxBitmap BitmapButton::createBitmapFromText(const wxString& text)
-{
- //wxDC::DrawLabel() doesn't respect alpha channel at all => apply workaround to calculate alpha values manually:
-
- if (text.empty())
- return wxBitmap();
-
- wxFont currentFont = wxBitmapButton::GetFont();
- wxColor textColor = wxBitmapButton::GetForegroundColour();
-
- wxSize sizeNeeded = getSizeNeeded(text, currentFont);
- wxBitmap newBitmap(sizeNeeded.GetWidth(), sizeNeeded.GetHeight());
-
- {
- wxMemoryDC dc(newBitmap);
-
- //set up white background
- dc.SetBackground(*wxWHITE_BRUSH);
- dc.Clear();
-
- //find position of accelerator
- const size_t accelPos = text.find(L"&");
- const int indexAccel = accelPos != wxString::npos ? static_cast<int>(accelPos) : -1;
-
- dc.SetTextForeground(*wxBLACK); //for use in makeWhiteTransparent
- dc.SetTextBackground(*wxWHITE); //
- dc.SetFont(currentFont);
-
- dc.DrawLabel(replaceCpy(text, L"&", L"", false), //remove accelerator
- wxNullBitmap, wxRect(0, 0, newBitmap.GetWidth(), newBitmap.GetHeight()), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, indexAccel);
- }
-
- //add alpha channel to image
- wxImage finalImage(newBitmap.ConvertToImage());
- finalImage.SetAlpha();
-
- //set values for alpha channel
- makeWhiteTransparent(finalImage);
-
- //now apply real text color
- unsigned char* bytePos = finalImage.GetData();
- const int pixelCount = finalImage.GetWidth() * finalImage.GetHeight();
- for (int i = 0; i < pixelCount; ++ i)
- {
- *bytePos++ = textColor.Red();
- *bytePos++ = textColor.Green();
- *bytePos++ = textColor.Blue();
- }
-
- return wxBitmap(finalImage);
-}
-
-
-//copy one image into another, allowing arbitrary overlapping! (pos may contain negative numbers)
-void writeToImage(const wxImage& source, const wxPoint& pos, wxImage& target)
-{
- //determine startpositions in source and target image, as well as width and height to be copied
- wxPoint posSrc, posTrg;
- int width, height;
-
- //X-axis
- if (pos.x < 0)
- {
- posSrc.x = -pos.x;
- posTrg.x = 0;
- width = std::min(pos.x + source.GetWidth(), target.GetWidth());
- }
- else
- {
- posSrc.x = 0;
- posTrg.x = pos.x;
- width = std::min(target.GetWidth() - pos.x, source.GetWidth());
- }
-
- //Y-axis
- if (pos.y < 0)
- {
- posSrc.y = -pos.y;
- posTrg.y = 0;
- height = std::min(pos.y + source.GetHeight(), target.GetHeight());
- }
- else
- {
- posSrc.y = 0;
- posTrg.y = pos.y;
- height = std::min(target.GetHeight() - pos.y, source.GetHeight());
- }
-
-
- if (width > 0 && height > 0)
- {
- {
- //copy source to target respecting overlapping parts
- const unsigned char* sourcePtr = source.GetData() + 3 * (posSrc.x + posSrc.y * source.GetWidth());
- const unsigned char* const sourcePtrEnd = source.GetData() + 3 * (posSrc.x + (posSrc.y + height) * source.GetWidth());
- unsigned char* targetPtr = target.GetData() + 3 * (posTrg.x + posTrg.y * target.GetWidth());
-
- while (sourcePtr < sourcePtrEnd)
- {
- memcpy(targetPtr, sourcePtr, 3 * width);
- sourcePtr += 3 * source.GetWidth();
- targetPtr += 3 * target.GetWidth();
- }
- }
-
- //handle different cases concerning alpha channel
- if (source.HasAlpha())
- {
- if (!target.HasAlpha())
- {
- target.SetAlpha();
- memset(target.GetAlpha(), wxIMAGE_ALPHA_OPAQUE, target.GetWidth() * target.GetHeight());
- }
-
- //copy alpha channel
- const unsigned char* sourcePtr = source.GetAlpha() + (posSrc.x + posSrc.y * source.GetWidth());
- const unsigned char* const sourcePtrEnd = source.GetAlpha() + (posSrc.x + (posSrc.y + height) * source.GetWidth());
- unsigned char* targetPtr = target.GetAlpha() + (posTrg.x + posTrg.y * target.GetWidth());
-
- while (sourcePtr < sourcePtrEnd)
- {
- memcpy(targetPtr, sourcePtr, width);
- sourcePtr += source.GetWidth();
- targetPtr += target.GetWidth();
- }
- }
- else if (target.HasAlpha())
- {
- unsigned char* targetPtr = target.GetAlpha() + (posTrg.x + posTrg.y * target.GetWidth());
- const unsigned char* const targetPtrEnd = target.GetAlpha() + (posTrg.x + (posTrg.y + height) * target.GetWidth());
-
- while (targetPtr < targetPtrEnd)
- {
- memset(targetPtr, wxIMAGE_ALPHA_OPAQUE, width);
- targetPtr += target.GetWidth();
- }
- }
- }
-}
-
-
-void BitmapButton::refreshButtonLabel()
-{
- wxBitmap bitmapText = createBitmapFromText(GetLabel());
-
- auto getSize = [](const wxBitmap& bmp) { return bmp.IsOk() ? wxSize(bmp.GetWidth(), bmp.GetHeight()) : wxSize(0, 0); };
-
- wxSize szFront = getSize(bitmapFront); //
- wxSize szText = getSize(bitmapText); //make sure to NOT access null-bitmaps!
- wxSize szBack = getSize(bitmapBack); //
-
- //calculate dimensions of new button
- const int height = std::max(std::max(szFront.GetHeight(), szText.GetHeight()), szBack.GetHeight());
- const int width = szFront.GetWidth() + spaceAfter_ + szText.GetWidth() + spaceBefore_ + szBack.GetWidth();
-
- //create a transparent image
- wxImage transparentImage(width, height, false);
- transparentImage.SetAlpha();
- unsigned char* alpha = transparentImage.GetAlpha();
- ::memset(alpha, wxIMAGE_ALPHA_TRANSPARENT, width * height);
-
- //wxDC::DrawLabel() unfortunately isn't working for transparent images on Linux, so we need to use custom image-concatenation
- if (bitmapFront.IsOk())
- writeToImage(bitmapFront.ConvertToImage(),
- wxPoint(0, (transparentImage.GetHeight() - bitmapFront.GetHeight()) / 2),
- transparentImage);
-
- if (bitmapText.IsOk())
- writeToImage(bitmapText.ConvertToImage(),
- wxPoint(szFront.GetWidth() + spaceAfter_, (transparentImage.GetHeight() - bitmapText.GetHeight()) / 2),
- transparentImage);
-
- if (bitmapBack.IsOk())
- writeToImage(bitmapBack.ConvertToImage(),
- wxPoint(szFront.GetWidth() + spaceAfter_ + szText.GetWidth() + spaceBefore_, (transparentImage.GetHeight() - bitmapBack.GetHeight()) / 2),
- transparentImage);
-
- //adjust button size
- wxSize minSize = GetMinSize();
-
- //SetMinSize() instead of SetSize() is needed here for wxWindows layout determination to work corretly
- wxBitmapButton::SetMinSize(wxSize(std::max(width + 2 * innerBorderSize, minSize.GetWidth()),
- std::max(height + 2 * innerBorderSize, minSize.GetHeight())));
-
- //finally set bitmap
- wxBitmapButton::SetBitmapLabel(wxBitmap(transparentImage));
-}
diff --git a/wx+/button.h b/wx+/button.h
deleted file mode 100644
index 42174a44..00000000
--- a/wx+/button.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// **************************************************************************
-// * 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 CUSTOMBUTTON_H_INCLUDED
-#define CUSTOMBUTTON_H_INCLUDED
-
-#include <wx/bmpbuttn.h>
-
-namespace zen
-{
-//zen::BitmapButton behaves like wxButton but optionally adds bitmap labels
-class BitmapButton : public wxBitmapButton
-{
-public:
- BitmapButton(wxWindow* parent,
- wxWindowID id,
- const wxString& label,
- const wxPoint& pos = wxDefaultPosition,
- const wxSize& size = wxDefaultSize,
- long style = 0,
- const wxValidator& validator = wxDefaultValidator,
- const wxString& name = wxButtonNameStr);
-
- void setBitmapFront(const wxBitmap& bitmap, int spaceAfter = 0); //...and enlarge button if required!
- void setBitmapBack (const wxBitmap& bitmap, int spaceBefore = 0); //
-
- void setInnerBorderSize(int sz) { innerBorderSize = sz; refreshButtonLabel(); }
-
- virtual void SetLabel(const wxString& label);
-
- void refreshButtonLabel(); //e.g. after font change
-
-private:
- wxBitmap createBitmapFromText(const wxString& text);
-
- wxBitmap bitmapFront;
- int spaceAfter_;
- ///wxString textLabel;
- int spaceBefore_;
- wxBitmap bitmapBack;
-
- int innerBorderSize;
-};
-
-//set bitmap label flicker free!
-void setImage(wxBitmapButton& button, const wxBitmap& bmp);
-}
-
-
-#endif // CUSTOMBUTTON_H_INCLUDED
diff --git a/wx+/dc.h b/wx+/dc.h
index d0f5c805..059db018 100644
--- a/wx+/dc.h
+++ b/wx+/dc.h
@@ -74,8 +74,8 @@ public:
}
private:
- //associate "active" clipping area with each DC
- static hash_map<wxDC*, wxRect>& refDcToAreaMap() { static hash_map<wxDC*, wxRect> clippingAreas; return clippingAreas; }
+ //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_;
diff --git a/wx+/graph.cpp b/wx+/graph.cpp
index cbedfa53..29ec4f36 100644
--- a/wx+/graph.cpp
+++ b/wx+/graph.cpp
@@ -16,7 +16,7 @@
using namespace zen;
-//todo: support zoom via mouse wheel
+//todo: support zoom via mouse wheel?
const wxEventType zen::wxEVT_GRAPH_SELECTION = wxNewEventType();
@@ -218,40 +218,38 @@ void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Grap
}
-warn_static("review")
-
+//calculate intersection of polygon with half-plane
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; };
+ auto isMarkedOob = [&](size_t index) { return oobMarker[index] != 0; }; //test if point is start of a OOB line
std::vector<CurvePoint> curvePointsTmp;
- std::vector<char> oobMarkerTmp;
- auto savePoint = [&](const CurvePoint& pt, bool markedOob) { curvePointsTmp.push_back(pt); oobMarkerTmp.push_back(markedOob); };
+ std::vector<char> oobMarkerTmp;
+ curvePointsTmp.reserve(curvePoints.size()); //allocating memory for these containers is one
+ oobMarkerTmp .reserve(oobMarker .size()); //of the more expensive operations of Graph2D!
- warn_static("perf: avoid these push_backs")
+ auto savePoint = [&](const CurvePoint& pt, bool markedOob) { curvePointsTmp.push_back(pt); oobMarkerTmp.push_back(markedOob); };
- bool lastPointInside = isInside(curvePoints[0]);
- if (lastPointInside)
+ bool pointInside = isInside(curvePoints[0]);
+ if (pointInside)
savePoint(curvePoints[0], isMarkedOob(0));
- for (auto it = curvePoints.begin() + 1; it != curvePoints.end(); ++it)
+ for (size_t index = 1; index < curvePoints.size(); ++index)
{
- const size_t index = it - curvePoints.begin();
-
- const bool pointInside = isInside(*it);
- if (pointInside != lastPointInside)
+ if (isInside(curvePoints[index]) != pointInside)
{
- lastPointInside = pointInside;
-
- const CurvePoint is = getIntersection(*(it - 1), *it); //getIntersection returns *it when delta is zero
+ pointInside = !pointInside;
+ const CurvePoint is = getIntersection(curvePoints[index - 1],
+ curvePoints[index]); //getIntersection returns *it when delta is zero
savePoint(is, !pointInside || isMarkedOob(index - 1));
}
if (pointInside)
- savePoint(*it, isMarkedOob(index));
+ savePoint(curvePoints[index], isMarkedOob(index));
}
+
curvePointsTmp.swap(curvePoints);
oobMarkerTmp .swap(oobMarker);
}
@@ -264,7 +262,7 @@ struct GetIntersectionX
{
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;
+ return numeric::isNull(deltaX) ? to : CurvePoint(x_, from.y + (x_ - from.x) / deltaX * deltaY);
};
private:
double x_;
@@ -277,7 +275,7 @@ struct GetIntersectionY
{
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;
+ return numeric::isNull(deltaY) ? to : CurvePoint(from.x + (y_ - from.y) / deltaY * deltaX, y_);
};
private:
double y_;
@@ -285,6 +283,7 @@ private:
void cutPointsOutsideX(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, double minX, double maxX)
{
+ assert(std::find(oobMarker.begin(), oobMarker.end(), true) == oobMarker.end());
cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x >= minX; }, GetIntersectionX(minX));
cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x <= maxX; }, GetIntersectionX(maxX));
}
@@ -297,27 +296,28 @@ void cutPointsOutsideY(std::vector<CurvePoint>& curvePoints, std::vector<char>&
}
-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
+ const std::pair<double, double> rangeX = getRangeX();
- for (int i = posFrom; i <= posTo; ++i)
+ const double screenLow = cvrtX.realToScreen(std::max(rangeX.first, minX)); //=> xLow >= 0
+ const double screenHigh = cvrtX.realToScreen(std::min(rangeX.second, maxX)); //=> xHigh <= pixelWidth - 1
+ //if double is larger than what int can represent => undefined behavior!
+ //=> convert to int not before checking value range!
+ if (screenLow <= screenHigh)
{
- const double x = cvrtX.screenToReal(i);
- points.push_back(CurvePoint(x, getValue(x)));
+ const int posFrom = std::ceil (screenLow ); //do not step outside [minX, maxX] in loop below!
+ const int posTo = std::floor(screenHigh); //
+ //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)));
+ }
}
}
@@ -330,10 +330,15 @@ void SparseCurveData::getPoints(double minX, double maxX, int pixelWidth, std::v
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));
+ if (!points.empty())
+ {
+ if (pt.x <= points.back().x) //allow ascending x-positions only! algorithm below may cause double-insertion after empty x-ranges!
+ return;
+
+ if (addSteps_)
+ if (pt.y != points.back().y)
+ points.push_back(CurvePoint(pt.x, points.back().y));
+ }
points.push_back(pt);
};
@@ -345,13 +350,37 @@ void SparseCurveData::getPoints(double minX, double maxX, int pixelWidth, std::v
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!
+ //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
+ /*
+ Breakdown of all combinations of posLe, posGe and expected action (n >= 1)
+ Note: For every empty x-range of at least one pixel, both next and previous points must be saved to keep the interpolating line stable!!!
+
+ posLe | posGe | action
+ +-------+-------+--------
+ | none | none | break
+ | i | none | save ptLe; break
+ | i - n | none | break;
+ +-------+-------+--------
+ | none | i | save ptGe; continue
+ | i | i | save one of ptLe, ptGe; continue
+ | i - n | i | save ptGe; continue
+ +-------+-------+--------
+ | none | i + n | save ptGe; jump to position posGe + 1
+ | i | i + n | save ptLe; if n == 1: continue; else: save ptGe; jump to position posGe + 1
+ | i - n | i + n | save ptLe, ptGe; jump to position posGe + 1
+ +-------+-------+--------
+ */
+ if (posGe < i)
+ {
+ if (posLe == i)
+ addPoint(*ptLe);
+ break;
+ }
+ else 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);
@@ -360,32 +389,14 @@ void SparseCurveData::getPoints(double minX, double maxX, int pixelWidth, std::v
}
else
{
- if (posLe == i)
+ 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 (posLe != i || posGe > i + 1)
{
- if (i == posTo && posLe == i) //no need for outside point if last position was already set above
- break;
-
addPoint(*ptGe);
- break;
+ i = posGe; //skip sparse area: +1 will be added by for-loop!
}
- if (posGe > i) //skip sparse area
- i = posGe - 1;
}
}
}
@@ -397,7 +408,7 @@ Graph2D::Graph2D(wxWindow* parent,
const wxSize& size,
long style,
const wxString& name) : wxPanel(parent, winid, pos, size, style, name),
- labelFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial")
+ 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);
@@ -405,11 +416,8 @@ Graph2D::Graph2D(wxWindow* parent,
Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Graph2D::onEraseBackGround), nullptr, this);
//SetDoubleBuffered(true); slow as hell!
-#if wxCHECK_VERSION(2, 9, 1)
+
SetBackgroundStyle(wxBG_STYLE_PAINT);
-#else
- SetBackgroundStyle(wxBG_STYLE_CUSTOM);
-#endif
Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(Graph2D::OnMouseLeftDown), nullptr, this);
Connect(wxEVT_MOTION, wxMouseEventHandler(Graph2D::OnMouseMovement), nullptr, this);
@@ -493,7 +501,7 @@ 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!
+ //set label font right at the start so that it is considered by wxDC::GetTextExtent() below!
dc.SetFont(labelFont);
const wxRect clientRect = GetClientRect(); //DON'T use wxDC::GetSize()! DC may be larger than visible area!
@@ -590,18 +598,17 @@ void Graph2D::render(wxDC& dc) const
double maxY = attr.maxYauto ? -std::numeric_limits<double>::infinity() : attr.maxY; //
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
+ std::vector<std::vector<char>> oobMarker (curves_.size()); //effectively a std::vector<bool> marking points that start an out-of-bounds line
- for (auto it = curves_.begin(); it != curves_.end(); ++it)
- if (const CurveData* curve = it->first.get())
+ for (size_t index = 0; index < curves_.size(); ++index)
+ if (const CurveData* curve = curves_[index].first.get())
{
- const size_t index = it - curves_.begin();
std::vector<CurvePoint>& points = curvePoints[index];
- auto& marker = oobMarker[index];
+ auto& marker = oobMarker [index];
curve->getPoints(minX, maxX, graphArea.width, points);
- //cut points outside visible x-range now in order to calculate height of visible points only!
+ //cut points outside visible x-range now in order to calculate height of visible line fragments only!
marker.resize(points.size()); //default value: false
cutPointsOutsideX(points, marker, minX, maxX);
@@ -637,18 +644,17 @@ void Graph2D::render(wxDC& dc) const
{
//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)
+ //2. pixels that are grossly out of range can be 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])
+ for (const CurvePoint& 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)
+ if (activeSel.get() && graphArea.width > 0 && graphArea.height > 0)
{
auto widen = [](double* low, double* high)
{
@@ -766,30 +772,29 @@ void Graph2D::render(wxDC& dc) const
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())
+ //draw all parts of the curve except for the out-of-bounds fragments
+ size_t drawIndexFirst = 0;
+ while (drawIndexFirst < points.size())
{
- size_t pointsIndexLast = std::find(marker.begin() + pointsIndexFirst, marker.end(), true) - marker.begin();
- if (pointsIndexLast < points.size()) ++ pointsIndexLast;
+ size_t drawIndexLast = std::find(marker.begin() + drawIndexFirst, marker.end(), true) - marker.begin();
+ if (drawIndexLast < points.size()) ++ drawIndexLast;
- const int pointCount = static_cast<int>(pointsIndexLast - pointsIndexFirst);
+ const int pointCount = static_cast<int>(drawIndexLast - drawIndexFirst);
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
+ dc.DrawLines(pointCount, &points[drawIndexFirst]);
+ dc.DrawPoint(points[drawIndexLast - 1]); //wxDC::DrawLines() doesn't draw last pixel
}
- pointsIndexFirst = std::find(marker.begin() + pointsIndexLast, marker.end(), false) - marker.begin();
+ drawIndexFirst = std::find(marker.begin() + drawIndexLast, 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);
+ for (const auto& ct : attr.cornerTexts)
+ drawCornerText(dc, graphArea, ct.second, ct.first);
}
}
}
diff --git a/wx+/graph.h b/wx+/graph.h
index fe008a38..a752959b 100644
--- a/wx+/graph.h
+++ b/wx+/graph.h
@@ -11,7 +11,6 @@
#include <vector>
#include <memory>
#include <wx/panel.h>
-//#include <wx/dcbuffer.h>
#include <zen/string_tools.h>
#include <zen/optional.h>
@@ -98,11 +97,11 @@ private:
struct VectorCurveData : public ArrayCurveData
{
- std::vector<double>& refData() { return data; }
+ 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;
+ 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;
};
//------------------------------------------------------------------------------------------------------------
@@ -347,7 +346,7 @@ private:
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()!
+ 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 c8d418ee..c9dfbbe9 100644
--- a/wx+/grid.cpp
+++ b/wx+/grid.cpp
@@ -17,8 +17,6 @@
#include <zen/scope_guard.h>
#include <zen/utf.h>
#include <zen/format_unit.h>
-//#include "image_tools.h"
-//#include "rtl.h"
#include "dc.h"
#ifdef ZEN_LINUX
@@ -90,11 +88,11 @@ void GridData::renderCell(Grid& grid, wxDC& dc, const wxRect& rect, size_t row,
rectTmp.x += COLUMN_BORDER_LEFT;
rectTmp.width -= COLUMN_BORDER_LEFT;
- drawCellText(dc, rectTmp, getValue(row, colType), grid.IsEnabled());
+ drawCellText(dc, rectTmp, getValue(row, colType), true);
}
-size_t GridData::getBestSize(wxDC& dc, size_t row, ColumnType colType)
+int GridData::getBestSize(wxDC& dc, size_t row, ColumnType colType)
{
return dc.GetTextExtent(getValue(row, colType)).GetWidth() + 2 * COLUMN_BORDER_LEFT; //some border on left and right side
}
@@ -131,9 +129,6 @@ void GridData::drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bo
namespace
{
-#ifdef _MSC_VER
-#pragma warning(disable:4428) // VC wrongly issues warning C4428: universal-character-name encountered in source
-#endif
const wchar_t ELLIPSIS = L'\u2026'; //...
template <class Function> inline
@@ -262,11 +257,7 @@ public:
//SetDoubleBuffered(true); slow as hell!
-#if wxCHECK_VERSION(2, 9, 1)
SetBackgroundStyle(wxBG_STYLE_PAINT);
-#else
- SetBackgroundStyle(wxBG_STYLE_CUSTOM);
-#endif
Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(SubWindow::onFocus), nullptr, this);
Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(SubWindow::onFocus), nullptr, this);
@@ -434,7 +425,7 @@ public:
wxClientDC dc(this);
wxFont labelFont = GetFont();
- labelFont.SetWeight(wxFONTWEIGHT_BOLD);
+ //labelFont.SetWeight(wxFONTWEIGHT_BOLD);
dc.SetFont(labelFont); //harmonize with RowLabelWin::render()!
int bestWidth = 0;
@@ -455,8 +446,8 @@ public:
return -1;
}
- ptrdiff_t getRowHeight() const { return std::max<ptrdiff_t>(1, rowHeight); } //guarantees to return size >= 1 !
- void setRowHeight(size_t height) { rowHeight = height; }
+ int getRowHeight() const { return std::max(1, rowHeight); } //guarantees to return size >= 1 !
+ void setRowHeight(int height) { rowHeight = height; }
wxRect getRowLabelArea(ptrdiff_t row) const
{
@@ -470,7 +461,7 @@ public:
const int yFrom = refParent().CalcUnscrolledPosition(clientRect.GetTopLeft ()).y;
const int yTo = refParent().CalcUnscrolledPosition(clientRect.GetBottomRight()).y;
- return std::make_pair(std::max<ptrdiff_t>(yFrom / rowHeight, 0),
+ return std::make_pair(std::max(yFrom / rowHeight, 0),
std::min<ptrdiff_t>((yTo / rowHeight) + 1, refParent().getRowCount()));
}
@@ -481,13 +472,32 @@ private:
virtual void render(wxDC& dc, const wxRect& rect)
{
- if (IsEnabled())
+
+ /*
+ IsEnabled() vs IsThisEnabled() since wxWidgets 2.9.5:
+
+ void wxWindowBase::NotifyWindowOnEnableChange(), called from bool wxWindowBase::Enable(), has this buggy exception of NOT
+ refreshing child elements when disabling a IsTopLevel() dialog, e.g. when showing a modal dialog.
+ The unfortunate effect on XP for using IsEnabled() when rendering the grid is that the user can move the modal dialog
+ and *draw* with it on the background while the grid refreshes as disabled incrementally!
+
+ => Don't use IsEnabled() since it considers the top level window. The brittle wxWidgets implementation is right in their intention,
+ but wrong when not refreshing child-windows: the control designer decides how his control should be rendered!
+
+ => IsThisEnabled() OTOH is too shallow and does not consider parent windows which are not top level.
+
+ The perfect solution would be a bool ShouldBeDrawnActive() { return "IsEnabled() but ignore effects of showing a modal dialog"; }
+
+ However "IsThisEnabled()" is good enough (same like the old IsEnabled() on wxWidgets 2.8.12) and it avoids this pathetic behavior on XP.
+ (Similar problem on Win 7: e.g. directly click sync button without comparing first)
+ */
+ if (IsThisEnabled())
clearArea(dc, rect, getColorMainWinBackground());
else
clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
wxFont labelFont = GetFont();
- labelFont.SetWeight(wxFONTWEIGHT_BOLD);
+ //labelFont.SetWeight(wxFONTWEIGHT_BOLD);
dc.SetFont(labelFont); //harmonize with RowLabelWin::getBestWidth()!
auto rowRange = getRowsOnClient(rect); //returns range [begin, end)
@@ -533,7 +543,7 @@ private:
virtual void onMouseMovement(wxMouseEvent& event) { refParent().redirectRowLabelEvent(event); }
virtual void onMouseLeftUp (wxMouseEvent& event) { refParent().redirectRowLabelEvent(event); }
- ptrdiff_t rowHeight;
+ int rowHeight;
};
@@ -542,7 +552,7 @@ namespace
class ColumnResizing
{
public:
- ColumnResizing(wxWindow& wnd, size_t col, size_t compPos, ptrdiff_t startWidth, int clientPosX) :
+ ColumnResizing(wxWindow& wnd, size_t col, size_t compPos, int startWidth, int clientPosX) :
wnd_(wnd), col_(col), compPos_(compPos), startWidth_(startWidth), clientPosX_(clientPosX)
{
wnd_.CaptureMouse();
@@ -553,17 +563,17 @@ public:
wnd_.ReleaseMouse();
}
- size_t getColumn () const { return col_; }
- size_t getComponentPos() const { return compPos_; }
- ptrdiff_t getStartWidth () const { return startWidth_; }
- int getStartPosX () const { return clientPosX_; }
+ size_t getColumn () const { return col_; }
+ size_t getComponentPos() const { return compPos_; }
+ int getStartWidth () const { return startWidth_; }
+ int getStartPosX () const { return clientPosX_; }
private:
wxWindow& wnd_;
- const size_t col_;
- const size_t compPos_;
- const ptrdiff_t startWidth_;
- const int clientPosX_;
+ const size_t col_;
+ const size_t compPos_;
+ const int startWidth_;
+ const int clientPosX_;
};
@@ -609,7 +619,7 @@ private:
virtual void render(wxDC& dc, const wxRect& rect)
{
- if (IsEnabled())
+ if (IsThisEnabled())
clearArea(dc, rect, getColorMainWinBackground());
else
clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
@@ -676,7 +686,7 @@ private:
if (action->wantResize)
{
if (!event.LeftDClick()) //double-clicks never seem to arrive here; why is this checked at all???
- if (Opt<ptrdiff_t> colWidth = refParent().getColWidth(action->col, action->compPos))
+ if (Opt<int> colWidth = refParent().getColWidth(action->col, action->compPos))
activeResizing.reset(new ColumnResizing(*this, action->col, action->compPos, *colWidth, event.GetPosition().x));
}
else //a move or single click
@@ -732,7 +742,7 @@ private:
if (action->wantResize)
{
//auto-size visible range on double-click
- const ptrdiff_t bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error
+ const int bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error
if (bestWidth >= 0)
{
refParent().setColWidthAndNotify(bestWidth, action->col, action->compPos);
@@ -748,8 +758,7 @@ private:
{
const auto col = activeResizing->getColumn();
const auto compPos = activeResizing->getComponentPos();
-
- const ptrdiff_t newWidth = activeResizing->getStartWidth() + event.GetPosition().x - activeResizing->getStartPosX();
+ const int newWidth = activeResizing->getStartWidth() + event.GetPosition().x - activeResizing->getStartPosX();
//set width tentatively
refParent().setColWidthAndNotify(newWidth, col, compPos);
@@ -904,7 +913,7 @@ public:
private:
virtual void render(wxDC& dc, const wxRect& rect)
{
- if (IsEnabled())
+ if (IsThisEnabled())
clearArea(dc, rect, getColorMainWinBackground());
else
clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
@@ -927,8 +936,9 @@ private:
std::vector<std::vector<ColumnWidth>> compAbsWidths = refParent().getColWidths(); //resolve stretched widths
for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp)
{
- const ptrdiff_t compWidth = std::accumulate(iterComp->begin(), iterComp->end(), static_cast<ptrdiff_t>(0),
- [](ptrdiff_t sum, const ColumnWidth& cw) { return sum + cw.width_; });
+ int compWidth = 0;
+ for (const ColumnWidth& cw : *iterComp)
+ compWidth += cw.width_;
const size_t compPos = iterComp - compAbsWidths.begin();
if (auto prov = refParent().getDataProvider(compPos))
@@ -941,21 +951,19 @@ private:
}
//draw single cells, column by column
- for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol)
+ for (const ColumnWidth& cw : *iterComp)
{
- const int width = iterCol->width_; //don't use unsigned for calculations!
-
if (cellAreaTL.x > rect.GetRight())
return; //done
- if (cellAreaTL.x + width > rect.x)
+ if (cellAreaTL.x + cw.width_ > rect.x)
for (int row = rowFirst; row < rowLast; ++row)
{
- const wxRect& cellRect = wxRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, width, rowHeight);
+ const wxRect& cellRect = wxRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width_, rowHeight);
RecursiveDcClipper clip(dc, cellRect);
- prov->renderCell(refParent(), dc, cellRect, row, iterCol->type_);
+ prov->renderCell(refParent(), dc, cellRect, row, cw.type_);
}
- cellAreaTL.x += width;
+ cellAreaTL.x += cw.width_;
}
}
else
@@ -977,7 +985,7 @@ private:
drawSelection = activeSelection->isPositiveSelect(); //overwrite default
}
- prov.renderRowBackgound(dc, rect, row, grid.IsEnabled(), drawSelection, wxWindow::FindFocus() == &grid.getMainWin());
+ prov.renderRowBackgound(dc, rect, row, grid.IsThisEnabled(), drawSelection, wxWindow::FindFocus() == &grid.getMainWin());
}
virtual void onMouseLeftDown (wxMouseEvent& event) { onMouseDown(event); }
@@ -1158,8 +1166,8 @@ private:
numeric::confine<ptrdiff_t>(row, 0, rowCount - 1);
- auto& comp = refParent().comp;
- std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); //clear selection, do NOT fire event
+ for (Grid::Component& c : refParent().comp)
+ c.selection.clear(); //clear selection, do NOT fire event
refParent().selectRangeAndNotify(selectionAnchor, row, cursor.second); //set new selection + fire event
cursor.first = row; //don't call setCursor() since it writes to "selectionAnchor"!
@@ -1418,7 +1426,7 @@ Grid::Grid(wxWindow* parent,
colLabelHeight(DEFAULT_COL_LABEL_HEIGHT),
drawRowLabel(true),
comp(1),
- colSizeOld(0)
+ rowCountOld(0)
{
Connect(wxEVT_PAINT, wxPaintEventHandler(Grid::onPaintEvent ), nullptr, this);
Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Grid::onEraseBackGround), nullptr, this);
@@ -1654,17 +1662,18 @@ size_t Grid::getRowCount() const
void Grid::Refresh(bool eraseBackground, const wxRect* rect)
{
const size_t rowCountNew = getRowCount();
- if (colSizeOld != rowCountNew)
+ if (rowCountOld != rowCountNew)
{
- colSizeOld = rowCountNew;
- std::for_each(comp.begin(), comp.end(), [&](Component& c) { c.selection.init(rowCountNew); });
+ rowCountOld = rowCountNew;
+ for (Component& c : comp)
+ c.selection.init(rowCountNew);
updateWindowSizes();
}
wxScrolledWindow::Refresh(eraseBackground, rect);
}
-void Grid::setRowHeight(size_t height)
+void Grid::setRowHeight(int height)
{
rowLabelWin_->setRowHeight(height);
updateWindowSizes();
@@ -1680,12 +1689,9 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr, size_
comp[compPos].oldColAttributes = attr;
std::vector<VisibleColumn> visibleCols;
- std::for_each(attr.begin(), attr.end(),
- [&](const ColumnAttribute& ca)
- {
+ for (const ColumnAttribute& ca : attr)
if (ca.visible_)
- visibleCols.push_back(Grid::VisibleColumn(ca.type_, ca.offset_, ca.stretch_));
- });
+ visibleCols.push_back(VisibleColumn(ca.type_, ca.offset_, ca.stretch_));
//"ownership" of visible columns is now within Grid
comp[compPos].visibleCols = visibleCols;
@@ -1707,9 +1713,7 @@ std::vector<Grid::ColumnAttribute> Grid::getColumnConfig(size_t compPos) const
auto iterVcolsend = comp[compPos].visibleCols.end();
//update visible columns but keep order of non-visible ones!
- std::for_each(output.begin(), output.end(),
- [&](ColumnAttribute& ca)
- {
+ for (ColumnAttribute& ca : output)
if (ca.visible_)
{
if (iterVcols != iterVcolsend)
@@ -1722,7 +1726,6 @@ std::vector<Grid::ColumnAttribute> Grid::getColumnConfig(size_t compPos) const
else
assert(false);
}
- });
assert(iterVcols == iterVcolsend);
return output;
@@ -1810,7 +1813,7 @@ void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range,
break;
case SB_SHOW_ALWAYS:
- if (range <= 1) //scrollbars hidden if range == 0 or 1
+ if (range <= 1) //scrollbars would be hidden for range == 0 or 1!
wxScrolledWindow::SetScrollbar(orientation, 0, 199999, 200000, refresh);
else
wxScrolledWindow::SetScrollbar(orientation, position, thumbSize, range, refresh);
@@ -1825,10 +1828,6 @@ void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range,
//get rid of scrollbars, but preserve scrolling behavior!
#ifdef ZEN_WIN
-#ifdef __MINGW32__ //MinGW is clueless...
-#define WM_MOUSEHWHEEL 0x020E
-#endif
-
WXLRESULT Grid::MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
//we land here if wxWindowMSW::MSWWindowProc() couldn't handle the message
@@ -1873,15 +1872,13 @@ wxRect Grid::getColumnLabelArea(ColumnType colType, size_t compPos) const
auto iterCol = std::find_if(iterComp->begin(), iterComp->end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; });
if (iterCol != iterComp->end())
{
- ptrdiff_t posX = std::accumulate(compAbsWidths.begin(), iterComp, static_cast<ptrdiff_t>(0),
- [](ptrdiff_t sum, const std::vector<ColumnWidth>& cols)
- {
- return sum + std::accumulate(cols.begin(), cols.end(), static_cast<ptrdiff_t>(0),
- [](ptrdiff_t val2, const ColumnWidth& cw) { return val2 + cw.width_; });
- });
+ ptrdiff_t posX = 0;
+ for (auto it = compAbsWidths.begin(); it != iterComp; ++it)
+ for (const ColumnWidth& cw : *it)
+ posX += cw.width_;
- posX += std::accumulate(iterComp->begin(), iterCol, static_cast<ptrdiff_t>(0),
- [](ptrdiff_t sum, const ColumnWidth& cw) { return sum + cw.width_; });
+ for (auto it = iterComp->begin(); it != iterCol; ++it)
+ posX += it->width_;
return wxRect(wxPoint(posX, 0), wxSize(iterCol->width_, colLabelHeight));
}
@@ -1895,19 +1892,16 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const
const int absPosX = CalcUnscrolledPosition(pos).x;
if (absPosX >= 0)
{
- ptrdiff_t accuWidth = 0;
+ int accuWidth = 0;
std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve stretched widths
- for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp)
+ for (size_t compPos = 0; compPos < compAbsWidths.size(); ++compPos)
{
- const size_t compPos = iterComp - compAbsWidths.begin();
const int resizeTolerance = columnResizeAllowed(compPos) ? COLUMN_RESIZE_TOLERANCE : 0;
- for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol)
+ for (size_t col = 0; col < compAbsWidths[compPos].size(); ++col)
{
- const size_t col = iterCol - iterComp->begin();
-
- accuWidth += iterCol->width_;
+ accuWidth += compAbsWidths[compPos][col].width_;
if (std::abs(absPosX - accuWidth) < resizeTolerance)
{
ColAction out = {};
@@ -1956,16 +1950,14 @@ ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos, size_t compPos)
auto iterComp = compAbsWidths.begin() + compPos;
const int absPosX = CalcUnscrolledPosition(pos).x;
- ptrdiff_t accuWidth = std::accumulate(compAbsWidths.begin(), iterComp, static_cast<ptrdiff_t>(0),
- [](ptrdiff_t sum, const std::vector<ColumnWidth>& cols)
- {
- return sum + std::accumulate(cols.begin(), cols.end(), static_cast<ptrdiff_t>(0),
- [](ptrdiff_t sum2, const ColumnWidth& cw) { return sum2 + cw.width_; });
- });
+ int accuWidth = 0;
+ for (auto it = compAbsWidths.begin(); it != iterComp; ++it)
+ for (const ColumnWidth& cw : *it)
+ accuWidth += cw.width_;
for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol)
{
- const ptrdiff_t width = iterCol->width_; //beware dreaded unsigned conversions!
+ const int width = iterCol->width_; //beware dreaded unsigned conversions!
accuWidth += width;
if (absPosX < accuWidth - width / 2)
@@ -1994,16 +1986,13 @@ Opt<std::pair<ColumnType, size_t>> Grid::getColumnAtPos(int posX) const
{
std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve negative/stretched widths
- ptrdiff_t accWidth = 0;
- for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp)
- for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol)
+ int accWidth = 0;
+ for (size_t compPos = 0; compPos < compAbsWidths.size(); ++compPos)
+ for (const ColumnWidth& cw : compAbsWidths[compPos])
{
- accWidth += iterCol->width_;
+ accWidth += cw.width_;
if (posX < accWidth)
- {
- const size_t compPos = iterComp - compAbsWidths.begin();
- return std::make_pair(iterCol->type_, compPos);
- }
+ return std::make_pair(cw.type_, compPos);
}
}
return NoValue();
@@ -2025,7 +2014,8 @@ void Grid::setGridCursor(size_t row, size_t compPos)
mainWin_->setCursor(row, compPos);
mainWin_->makeRowVisible(row);
- std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); //clear selection, do NOT fire event
+ for (Grid::Component& c : comp)
+ c.selection.clear(); //clear selection, do NOT fire event
selectRangeAndNotify(row, row, compPos); //set new selection + fire event
mainWin_->Refresh();
@@ -2114,7 +2104,7 @@ std::pair<size_t, size_t> Grid::getGridCursor() const
}
-ptrdiff_t Grid::getBestColumnSize(size_t col, size_t compPos) const
+int Grid::getBestColumnSize(size_t col, size_t compPos) const
{
if (compPos < comp.size())
{
@@ -2127,7 +2117,7 @@ ptrdiff_t Grid::getBestColumnSize(size_t col, size_t compPos) const
wxClientDC dc(mainWin_);
dc.SetFont(mainWin_->GetFont()); //harmonize with MainWin::render()
- size_t maxSize = 0;
+ int maxSize = 0;
auto rowRange = rowLabelWin_->getRowsOnClient(mainWin_->GetClientRect()); //returns range [begin, end)
for (auto row = rowRange.first; row < rowRange.second; ++row)
@@ -2140,22 +2130,25 @@ ptrdiff_t Grid::getBestColumnSize(size_t col, size_t compPos) const
}
-void Grid::setColWidthAndNotify(ptrdiff_t width, size_t col, size_t compPos, bool notifyAsync)
+void Grid::setColWidthAndNotify(int width, size_t col, size_t compPos, bool notifyAsync)
{
if (compPos < comp.size() && col < comp[compPos].visibleCols.size())
{
VisibleColumn& vcRs = comp[compPos].visibleCols[col];
- const int mainWinWidth = mainWin_->GetClientSize().GetWidth();
- const ptrdiff_t stretchTotal = getStretchTotal();
- const ptrdiff_t stretchedWithCol = getColStretchedWidth(vcRs.stretch_, stretchTotal, mainWinWidth);
-
- vcRs.offset_ = width - stretchedWithCol; //width := stretchedWidth + offset
+ const std::vector<std::vector<int>> stretchedWidths = getColStretchedWidths(mainWin_->GetClientSize().GetWidth());
+ if (stretchedWidths.size() != comp.size() || stretchedWidths[compPos].size() != comp[compPos].visibleCols.size())
+ {
+ assert(false);
+ return;
+ }
//CAVEATS:
//I. fixed-size columns: normalize offset so that resulting width is at least COLUMN_MIN_WIDTH: this is NOT enforced by getColWidths()!
//II. stretched columns: do not allow user to set offsets so small that they result in negative (non-normalized) widths: this gives an
//unusual delay when enlarging the column again later
- vcRs.offset_ = std::max(vcRs.offset_, COLUMN_MIN_WIDTH - stretchedWithCol);
+ width = std::max(width, COLUMN_MIN_WIDTH);
+
+ vcRs.offset_ = width - stretchedWidths[compPos][col]; //width := stretchedWidth + offset
//III. resizing any column should normalize *all* other stretched columns' offsets considering current mainWinWidth!
// test case:
@@ -2163,17 +2156,13 @@ void Grid::setColWidthAndNotify(ptrdiff_t width, size_t col, size_t compPos, boo
//2. shrink main window width so that horizontal scrollbars are shown despite the streched column
//3. shrink a fixed-size column so that the scrollbars vanish and columns cover full width again
//4. now verify that the stretched column is resizing immediately if main window is enlarged again
- std::for_each(comp.begin(), comp.end(), [&](Component& c)
+ for (size_t compPos2 = 0; compPos2 < comp.size(); ++compPos2)
{
- std::for_each(c.visibleCols.begin(), c.visibleCols.end(), [&](VisibleColumn& vc)
- {
- if (vc.stretch_ > 0) //normalize stretched columns only
- {
- const ptrdiff_t stretchedWidth = Grid::getColStretchedWidth(vc.stretch_, stretchTotal, mainWinWidth);
- vc.offset_ = std::max(vc.offset_, COLUMN_MIN_WIDTH - stretchedWidth);
- }
- });
- });
+ auto& visibleCols = comp[compPos2].visibleCols;
+ for (size_t col2 = 0; col2 < visibleCols.size(); ++col2)
+ if (visibleCols[col2].stretch_ > 0) //normalize stretched columns only
+ visibleCols[col2].offset_ = std::max(visibleCols[col2].offset_, COLUMN_MIN_WIDTH - stretchedWidths[compPos2][col2]);
+ }
GridColumnResizeEvent sizeEvent(vcRs.offset_, vcRs.type_, compPos);
if (wxEvtHandler* evtHandler = GetEventHandler())
@@ -2194,10 +2183,9 @@ void Grid::autoSizeColumns(size_t compPos)
if (compPos < comp.size() && comp[compPos].allowColumnResize)
{
auto& visibleCols = comp[compPos].visibleCols;
- for (auto it = visibleCols.begin(); it != visibleCols.end(); ++it)
+ for (size_t col = 0; col < visibleCols.size(); ++col)
{
- const size_t col = it - visibleCols.begin();
- const ptrdiff_t bestWidth = getBestColumnSize(col, compPos); //return -1 on error
+ const int bestWidth = getBestColumnSize(col, compPos); //return -1 on error
if (bestWidth >= 0)
setColWidthAndNotify(bestWidth, col, compPos, true);
}
@@ -2207,20 +2195,55 @@ void Grid::autoSizeColumns(size_t compPos)
}
-ptrdiff_t Grid::getStretchTotal() const //sum of all stretch factors
+std::vector<std::vector<int>> Grid::getColStretchedWidths(int clientWidth) const //final width = (normalized) (stretchedWidth + offset)
{
- return std::accumulate(comp.begin(), comp.end(), static_cast<ptrdiff_t>(0),
- [](ptrdiff_t sum, const Component& c)
+ assert(clientWidth >= 0);
+ clientWidth = std::max(clientWidth, 0);
+ int stretchTotal = 0;
+ for (const Component& c : comp)
+ for (const VisibleColumn& vc : c.visibleCols)
+ {
+ assert(vc.stretch_ >= 0);
+ stretchTotal += vc.stretch_;
+ }
+
+ int remainingWidth = clientWidth;
+
+ std::vector<std::vector<int>> output;
+ for (const Component& c : comp)
{
- return sum + std::accumulate(c.visibleCols.begin(), c.visibleCols.end(), static_cast<ptrdiff_t>(0),
- [](ptrdiff_t val2, const Grid::VisibleColumn& vc) { return val2 + vc.stretch_; });
- });
-}
+ output.push_back(std::vector<int>());
+ auto& compWidths = output.back();
+ if (stretchTotal <= 0)
+ compWidths.resize(c.visibleCols.size()); //fill with zeros
+ else
+ for (const VisibleColumn& vc : c.visibleCols)
+ {
+ const int width = clientWidth * vc.stretch_ / stretchTotal; //rounds down!
+ compWidths.push_back(width);
+ remainingWidth -= width;
+ }
+ }
-ptrdiff_t Grid::getColStretchedWidth(ptrdiff_t stretch, ptrdiff_t stretchTotal, int mainWinWidth) //final width := stretchedWidth + (normalized) offset
-{
- return stretchTotal > 0 ? mainWinWidth * stretch / stretchTotal : 0; //rounds down! => not all of clientWidth is correctly distributed according to stretch factors
+ //distribute *all* of clientWidth: should suffice to enlarge the first few stretched columns; no need to minimize total absolute error of distribution
+ if (stretchTotal > 0)
+ if (remainingWidth > 0)
+ {
+ for (size_t compPos2 = 0; compPos2 < comp.size(); ++compPos2)
+ {
+ auto& visibleCols = comp[compPos2].visibleCols;
+ for (size_t col2 = 0; col2 < visibleCols.size(); ++col2)
+ if (visibleCols[col2].stretch_ > 0)
+ {
+ ++output[compPos2][col2];
+ if (--remainingWidth == 0)
+ return output;
+ }
+ }
+ assert(false);
+ }
+ return output;
}
@@ -2232,39 +2255,41 @@ std::vector<std::vector<Grid::ColumnWidth>> Grid::getColWidths() const
std::vector<std::vector<Grid::ColumnWidth>> Grid::getColWidths(int mainWinWidth) const //evaluate stretched columns; structure matches "comp"
{
- std::vector<std::vector<ColumnWidth>> output;
+ const std::vector<std::vector<int>> stretchedWidths = getColStretchedWidths(mainWinWidth);
+ assert(stretchedWidths.size() == comp.size());
- const ptrdiff_t stretchTotal = getStretchTotal();
+ std::vector<std::vector<ColumnWidth>> output;
- std::for_each(comp.begin(), comp.end(), [&](const Component& c)
+ for (size_t compPos2 = 0; compPos2 < comp.size(); ++compPos2)
{
+ assert(stretchedWidths[compPos2].size() == comp[compPos2].visibleCols.size());
+
output.push_back(std::vector<ColumnWidth>());
auto& compWidths = output.back();
- std::for_each(c.visibleCols.begin(), c.visibleCols.end(), [&](const VisibleColumn& vc)
+ auto& visibleCols = comp[compPos2].visibleCols;
+ for (size_t col2 = 0; col2 < visibleCols.size(); ++col2)
{
- ptrdiff_t widthNormalized = Grid::getColStretchedWidth(vc.stretch_, stretchTotal, mainWinWidth) + vc.offset_;
+ const auto& vc = visibleCols[col2];
+ int width = stretchedWidths[compPos2][col2] + vc.offset_;
if (vc.stretch_ > 0)
- widthNormalized = std::max(widthNormalized, static_cast<ptrdiff_t>(COLUMN_MIN_WIDTH)); //normalization really needed here: e.g. smaller main window would result in negative width
+ width = std::max(width, COLUMN_MIN_WIDTH); //normalization really needed here: e.g. smaller main window would result in negative width
else
- widthNormalized = std::max(widthNormalized, static_cast<ptrdiff_t>(0)); //support smaller width than COLUMN_MIN_WIDTH if set via configuration
+ width = std::max(width, 0); //support smaller width than COLUMN_MIN_WIDTH if set via configuration
- compWidths.push_back(Grid::ColumnWidth(vc.type_, widthNormalized));
- });
- });
+ compWidths.push_back(ColumnWidth(vc.type_, width));
+ }
+ }
return output;
}
-ptrdiff_t Grid::getColWidthsSum(int mainWinWidth) const
+int Grid::getColWidthsSum(int mainWinWidth) const
{
- auto widths = getColWidths(mainWinWidth);
- return std::accumulate(widths.begin(), widths.end(), static_cast<ptrdiff_t>(0),
- [](ptrdiff_t sum, const std::vector<Grid::ColumnWidth>& cols)
- {
- return sum + std::accumulate(cols.begin(), cols.end(), static_cast<ptrdiff_t>(0),
- [](ptrdiff_t val2, const Grid::ColumnWidth& cw) { return val2 + cw.width_; });
- });
+ int sum = 0;
+ for (const std::vector<ColumnWidth>& cols : getColWidths(mainWinWidth))
+ for (const ColumnWidth& cw : cols)
+ sum += cw.width_;
+ return sum;
};
-
diff --git a/wx+/grid.h b/wx+/grid.h
index 1faa0bce..f93c0d3b 100644
--- a/wx+/grid.h
+++ b/wx+/grid.h
@@ -47,11 +47,11 @@ struct GridClickEvent : public wxMouseEvent
struct GridColumnResizeEvent : public wxCommandEvent
{
- GridColumnResizeEvent(ptrdiff_t offset, ColumnType colType, size_t compPos) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset), compPos_(compPos) {}
+ GridColumnResizeEvent(int offset, ColumnType colType, size_t compPos) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset), compPos_(compPos) {}
virtual wxEvent* Clone() const { return new GridColumnResizeEvent(*this); }
const ColumnType colType_;
- const ptrdiff_t offset_;
+ const int offset_;
const size_t compPos_;
};
@@ -96,7 +96,7 @@ public:
virtual wxString getValue(size_t row, ColumnType colType) const = 0;
virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, bool hasFocus); //default implementation
virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, size_t row, ColumnType colType); //
- virtual size_t getBestSize(wxDC& dc, size_t row, ColumnType colType); //must correspond to renderCell()!
+ virtual int getBestSize(wxDC& dc, size_t row, ColumnType colType); //must correspond to renderCell()!
virtual wxString getToolTip(size_t row, ColumnType colType) const { return wxString(); }
//label area
@@ -127,7 +127,7 @@ public:
size_t getRowCount() const;
- void setRowHeight(size_t height);
+ void setRowHeight(int height);
//grid component := a grid is divided into multiple components each of which is essentially a set of connected columns
void setComponentCount(size_t count) { comp.resize(count); updateWindowSizes(); }
@@ -135,13 +135,13 @@ public:
struct ColumnAttribute
{
- ColumnAttribute(ColumnType type, ptrdiff_t offset, ptrdiff_t stretch, bool visible = true) : type_(type), visible_(visible), stretch_(std::max<ptrdiff_t>(stretch, 0)), offset_(offset) {}
+ ColumnAttribute(ColumnType type, int offset, int stretch, bool visible = true) : type_(type), visible_(visible), stretch_(std::max(stretch, 0)), offset_(offset) { assert(stretch >=0 ); }
ColumnType type_;
bool visible_;
- //first client width is partitioned according to all available stretch factors, then "offset_" is added
+ //first, client width is partitioned according to all available stretch factors, then "offset_" is added
//universal model: a non-stretched column has stretch factor 0 with the "offset" becoming identical to final width!
- ptrdiff_t stretch_; //>= 0
- ptrdiff_t offset_;
+ int stretch_; //>= 0
+ int offset_;
};
void setColumnConfig(const std::vector<ColumnAttribute>& attr, size_t compPos = 0); //set column count + widths
@@ -212,7 +212,7 @@ private:
virtual WXLRESULT MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam); //support horizontal mouse wheel
#endif
- ptrdiff_t getBestColumnSize(size_t col, size_t compPos) const; //return -1 on error
+ int getBestColumnSize(size_t col, size_t compPos) const; //return -1 on error
friend class GridData;
class SubWindow;
@@ -229,9 +229,9 @@ private:
std::vector<size_t> get() const
{
std::vector<size_t> selection;
- for (auto iter = rowSelectionValue.begin(); iter != rowSelectionValue.end(); ++iter)
- if (*iter != 0)
- selection.push_back(iter - rowSelectionValue.begin());
+ for (size_t row = 0; row < rowSelectionValue.size(); ++row)
+ if (rowSelectionValue[row] != 0)
+ selection.push_back(row);
return selection;
}
@@ -257,10 +257,10 @@ private:
struct VisibleColumn
{
- VisibleColumn(ColumnType type, ptrdiff_t offset, ptrdiff_t stretch) : type_(type), stretch_(stretch), offset_(offset) {}
+ VisibleColumn(ColumnType type, int offset, int stretch) : type_(type), stretch_(stretch), offset_(offset) {}
ColumnType type_;
- ptrdiff_t stretch_; //>= 0
- ptrdiff_t offset_;
+ int stretch_; //>= 0
+ int offset_;
};
struct Component
@@ -278,15 +278,16 @@ private:
struct ColumnWidth
{
- ColumnWidth(ColumnType type, ptrdiff_t width) : type_(type), width_(width) {}
+ ColumnWidth(ColumnType type, int width) : type_(type), width_(width) {}
ColumnType type_;
- ptrdiff_t width_;
+ int width_;
};
std::vector<std::vector<ColumnWidth>> getColWidths() const; //
std::vector<std::vector<ColumnWidth>> getColWidths(int mainWinWidth) const; //evaluate stretched columns; structure matches "comp"
- ptrdiff_t getColWidthsSum(int mainWinWidth) const;
+ int getColWidthsSum(int mainWinWidth) const;
+ std::vector<std::vector<int>> getColStretchedWidths(int clientWidth) const; //final width = (normalized) (stretchedWidth + offset)
- Opt<ptrdiff_t> getColWidth(size_t col, size_t compPos) const
+ Opt<int> getColWidth(size_t col, size_t compPos) const
{
const auto& widths = getColWidths();
if (compPos < widths.size() && col < widths[compPos].size())
@@ -294,10 +295,7 @@ private:
return NoValue();
}
- ptrdiff_t getStretchTotal() const; //sum of all stretch factors
- static ptrdiff_t getColStretchedWidth(ptrdiff_t stretch, ptrdiff_t stretchTotal, int mainWinWidth); //final width = stretchedWidth + (normalized) offset
-
- void setColWidthAndNotify(ptrdiff_t width, size_t col, size_t compPos, bool notifyAsync = false);
+ void setColWidthAndNotify(int width, size_t col, size_t compPos, bool notifyAsync = false);
wxRect getColumnLabelArea(ColumnType colType, size_t compPos) const; //returns empty rect if column not found
@@ -342,7 +340,7 @@ private:
bool drawRowLabel;
std::vector<Component> comp;
- size_t colSizeOld; //at the time of last Grid::Refresh()
+ size_t rowCountOld; //at the time of last Grid::Refresh()
};
}
diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp
new file mode 100644
index 00000000..97fe3660
--- /dev/null
+++ b/wx+/image_tools.cpp
@@ -0,0 +1,185 @@
+// **************************************************************************
+// * 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 *
+// **************************************************************************
+
+#include "image_tools.h"
+#include <zen/string_tools.h>
+#include <wx/app.h>
+
+
+using namespace zen;
+
+namespace
+{
+void writeToImage(const wxImage& source, wxImage& target, const wxPoint& pos)
+{
+ const int srcWidth = source.GetWidth ();
+ const int srcHeight = source.GetHeight();
+ const int trgWidth = target.GetWidth ();
+
+ if (srcWidth > 0 && srcHeight > 0)
+ {
+ assert(0 <= pos.x && pos.x + srcWidth <= trgWidth ); //draw area must be a
+ assert(0 <= pos.y && pos.y + srcHeight <= target.GetHeight()); //subset of target image!
+ assert(target.HasAlpha());
+
+ {
+ const unsigned char* sourcePtr = source.GetData();
+ unsigned char* targetPtr = target.GetData() + 3 * (pos.x + pos.y * trgWidth);
+
+ for (int row = 0; row < srcHeight; ++row)
+ ::memcpy(targetPtr + 3 * row * trgWidth, sourcePtr + 3 * row * srcWidth, 3 * srcWidth);
+ }
+
+ //handle alpha channel
+ {
+ unsigned char* targetPtr = target.GetAlpha() + pos.x + pos.y * trgWidth;
+ if (source.HasAlpha())
+ {
+ const unsigned char* sourcePtr = source.GetAlpha();
+ for (int row = 0; row < srcHeight; ++row)
+ ::memcpy(targetPtr + row * trgWidth, sourcePtr + row * srcWidth, srcWidth);
+ }
+ else
+ for (int row = 0; row < srcHeight; ++row)
+ ::memset(targetPtr + row * trgWidth, wxIMAGE_ALPHA_OPAQUE, srcWidth);
+ }
+ }
+}
+}
+
+
+wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLayout dir, ImageStackAlignment align, int gap)
+{
+ assert(gap >= 0);
+ gap = std::max(0, gap);
+
+ const int img1Width = img1.GetWidth ();
+ const int img1Height = img1.GetHeight();
+ const int img2Width = img2.GetWidth ();
+ const int img2Height = img2.GetHeight();
+
+ int width = std::max(img1Width, img2Width);
+ int height = std::max(img1Height, img2Height);
+ switch (dir)
+ {
+ case ImageStackLayout::HORIZONTAL:
+ width = img1Width + gap + img2Width;
+ break;
+
+ case ImageStackLayout::VERTICAL:
+ height = img1Height + gap + img2Height;
+ break;
+ }
+ wxImage output(width, height);
+ output.SetAlpha();
+ ::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, width * height);
+ ::memset(output.GetData (), 0, 3 * width * height); //redundant due to transparent alpha
+
+ auto calcPos = [&](int imageExtent, int totalExtent)
+ {
+ switch (align)
+ {
+ case ImageStackAlignment::CENTER:
+ return (totalExtent - imageExtent) / 2;
+ case ImageStackAlignment::LEFT:
+ return 0;
+ case ImageStackAlignment::RIGHT:
+ return totalExtent - imageExtent;
+ }
+ assert(false);
+ return 0;
+ };
+
+ switch (dir)
+ {
+ case ImageStackLayout::HORIZONTAL:
+ writeToImage(img1, output, wxPoint(0, calcPos(img1Height, height)));
+ writeToImage(img2, output, wxPoint(img1Width + gap, calcPos(img2Height, height)));
+ break;
+
+ case ImageStackLayout::VERTICAL:
+ writeToImage(img1, output, wxPoint(calcPos(img1Width, width), 0));
+ writeToImage(img2, output, wxPoint(calcPos(img2Width, width), img1Height + gap));
+ break;
+ }
+ return output;
+}
+
+
+namespace
+{
+void makeWhiteTransparent(wxImage& image) //assume black text on white background
+{
+ assert(image.HasAlpha());
+ if (unsigned char* alphaPtr = image.GetAlpha())
+ {
+ const int pixelCount = image.GetWidth() * image.GetHeight();
+ const unsigned char* dataPtr = image.GetData();
+ for (int i = 0; i < pixelCount; ++ i)
+ {
+ const unsigned char r = *dataPtr++;
+ const unsigned char g = *dataPtr++;
+ const unsigned char b = *dataPtr++;
+
+ //black(0,0,0) becomes fully opaque(255), while white(255,255,255) becomes transparent(0)
+ alphaPtr[i] = static_cast<unsigned char>((255 - r + 255 - g + 255 - b) / 3); //mixed mode arithmetics!
+ }
+ }
+}
+
+
+wxSize getTextExtent(const wxString& text, const wxFont& font)
+{
+ wxMemoryDC dc; //the context used for bitmaps
+ dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evalated on OS X, wxWidgets 2.9.5, so apply it to the DC directly!
+ return dc.GetMultiLineTextExtent(replaceCpy(text, L"&", L"", false)); //remove accelerator
+}
+}
+
+wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const wxColor& col)
+{
+ //wxDC::DrawLabel() doesn't respect alpha channel => calculate alpha values manually:
+
+ if (text.empty())
+ return wxImage();
+
+ wxBitmap newBitmap(getTextExtent(text, font));
+ {
+ wxMemoryDC dc(newBitmap);
+ dc.SetBackground(*wxWHITE_BRUSH);
+ dc.Clear();
+
+ dc.SetTextForeground(*wxBLACK); //for use in makeWhiteTransparent
+ dc.SetTextBackground(*wxWHITE); //
+ dc.SetFont(font);
+
+ wxString textFmt = replaceCpy(text, L"&", L"", false);
+ //for some reason wxDC::DrawText messes up "weak" bidi characters even when wxLayout_RightToLeft is set! (--> arrows in hebrew/arabic)
+ //=> use mark characters instead:
+ const wchar_t rtlMark = L'\u200F';
+ if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
+ textFmt = rtlMark + textFmt + rtlMark;
+
+ dc.DrawText(textFmt, wxPoint());
+ }
+
+ wxImage output(newBitmap.ConvertToImage());
+ output.SetAlpha();
+
+ //calculate alpha channel
+ makeWhiteTransparent(output);
+
+ //apply actual text color
+ unsigned char* dataPtr = output.GetData();
+ const int pixelCount = output.GetWidth() * output.GetHeight();
+ for (int i = 0; i < pixelCount; ++ i)
+ {
+ *dataPtr++ = col.Red();
+ *dataPtr++ = col.Green();
+ *dataPtr++ = col.Blue();
+ }
+ return output;
+}
diff --git a/wx+/image_tools.h b/wx+/image_tools.h
index ec9e34d4..d3a20a45 100644
--- a/wx+/image_tools.h
+++ b/wx+/image_tools.h
@@ -15,19 +15,39 @@
namespace zen
{
-wxBitmap greyScale(const wxBitmap& bmp); //greyscale + brightness adaption
+enum class ImageStackLayout
+{
+ HORIZONTAL,
+ VERTICAL
+};
+
+enum class ImageStackAlignment
+{
+ CENTER,
+ LEFT,
+ RIGHT,
+ TOP = LEFT,
+ BOTTOM = RIGHT,
+};
+wxImage stackImages(const wxImage& img1, const wxImage& img2, ImageStackLayout dir, ImageStackAlignment align, int gap = 0);
+
+wxImage createImageFromText(const wxString& text, const wxFont& font, const wxColor& col);
+
+
+wxImage greyScale(const wxImage& img); //greyscale + brightness adaption
+wxBitmap greyScale(const wxBitmap& bmp); //
wxBitmap layOver(const wxBitmap& foreground, const wxBitmap& background); //merge
-void move(wxImage& img, int up, int left = 0);
+//void moveImage(wxImage& img, int right, int up);
void adjustBrightness(wxImage& img, int targetLevel);
double getAvgBrightness(const wxImage& img); //in [0, 255]
void brighten(wxImage& img, int level); //level: delta per channel in points
bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs); //pixel-wise equality (respecting alpha channel)
-wxColor gradient(const wxColor& from, const wxColor& to, double fraction); //maps fraction within [0, 1] to an intermediate color
+//wxColor gradient(const wxColor& from, const wxColor& to, double fraction); //maps fraction within [0, 1] to an intermediate color
-wxColour hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1]
+//wxColour hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1]
@@ -43,20 +63,20 @@ wxColour hsvColor(double h, double s, double v); //h within [0, 360), s, v withi
//################################### implementation ###################################
+/*
inline
-void move(wxImage& img, int up, int left)
+void moveImage(wxImage& img, int right, int up)
{
- img = img.GetSubImage(wxRect(std::max(0, left), std::max(0, up), img.GetWidth() - abs(left), img.GetHeight() - abs(up)));
- img.Resize(wxSize(img.GetWidth() + abs(left), img.GetHeight() + abs(up)), wxPoint(-std::min(0, left), -std::min(0, up)));
+ img = img.GetSubImage(wxRect(std::max(0, -right), std::max(0, up), img.GetWidth() - abs(right), img.GetHeight() - abs(up)));
+ img.Resize(wxSize(img.GetWidth() + abs(right), img.GetHeight() + abs(up)), wxPoint(std::max(0, right), std::max(0, -up)));
}
+*/
inline
-wxBitmap greyScale(const wxBitmap& bmp)
+wxImage greyScale(const wxImage& img)
{
- assert(!bmp.GetMask()); //wxWidgets screws up for the gazillionth time applying a mask instead of alpha channel if the .png image has only 0 and 0xff opacity values!!!
-
- wxImage output = bmp.ConvertToImage().ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3); //treat all channels equally!
+ wxImage output = img.ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3); //treat all channels equally!
//wxImage output = bmp.ConvertToImage().ConvertToGreyscale();
adjustBrightness(output, 160);
return output;
@@ -64,6 +84,14 @@ wxBitmap greyScale(const wxBitmap& bmp)
inline
+wxBitmap greyScale(const wxBitmap& bmp)
+{
+ assert(!bmp.GetMask()); //wxWidgets screws up for the gazillionth time applying a mask instead of alpha channel if the .png image has only 0 and 0xff opacity values!!!
+ return greyScale(bmp.ConvertToImage());
+}
+
+
+inline
double getAvgBrightness(const wxImage& img)
{
const int pixelCount = img.GetWidth() * img.GetHeight();
@@ -160,6 +188,7 @@ bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs)
return std::equal(imLhs.GetData(), imLhs.GetData() + pixelCount * 3, imRhs.GetData());
}
+/*
inline
wxColor gradient(const wxColor& from, const wxColor& to, double fraction)
{
@@ -170,8 +199,9 @@ wxColor gradient(const wxColor& from, const wxColor& to, double fraction)
from.Blue () + (to.Blue () - from.Blue ()) * fraction,
from.Alpha() + (to.Alpha() - from.Alpha()) * fraction);
}
+*/
-
+/*
inline
wxColour hsvColor(double h, double s, double v) //h within [0, 360), s, v within [0, 1]
{
@@ -218,6 +248,7 @@ wxColour hsvColor(double h, double s, double v) //h within [0, 360), s, v within
assert(false);
return *wxBLACK;
}
+*/
}
#endif //IMAGE_TOOLS_HEADER_45782456427634254
diff --git a/wx+/no_flicker.h b/wx+/no_flicker.h
index fd64628f..c5b0d238 100644
--- a/wx+/no_flicker.h
+++ b/wx+/no_flicker.h
@@ -15,7 +15,7 @@ namespace zen
inline
void setText(wxTextCtrl& control, const wxString& newText, bool* additionalLayoutChange = nullptr)
{
- const wxString& label = control.GetValue(); //perf: don't call twice!
+ const wxString& label = control.GetValue(); //perf: don't call twice!
if (additionalLayoutChange && !*additionalLayoutChange) //never revert from true to false!
*additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
@@ -27,11 +27,11 @@ inline
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
+ //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!
+ const wxString& label = control.GetLabel(); //perf: don't call twice!
if (additionalLayoutChange && !*additionalLayoutChange)
*additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
bgstack15