diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:27:42 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:27:42 +0200 |
commit | b916407a2a06f8452e82b74dc44c54acbcc572b0 (patch) | |
tree | 46358e0bb035fca0f42edb4b5b8aa5f1613814af /wx+ | |
parent | 5.20 (diff) | |
download | FreeFileSync-b916407a2a06f8452e82b74dc44c54acbcc572b0.tar.gz FreeFileSync-b916407a2a06f8452e82b74dc44c54acbcc572b0.tar.bz2 FreeFileSync-b916407a2a06f8452e82b74dc44c54acbcc572b0.zip |
5.21
Diffstat (limited to 'wx+')
-rw-r--r-- | wx+/bitmap_button.h | 81 | ||||
-rw-r--r-- | wx+/button.cpp | 305 | ||||
-rw-r--r-- | wx+/button.h | 53 | ||||
-rw-r--r-- | wx+/dc.h | 4 | ||||
-rw-r--r-- | wx+/graph.cpp | 185 | ||||
-rw-r--r-- | wx+/graph.h | 11 | ||||
-rw-r--r-- | wx+/grid.cpp | 317 | ||||
-rw-r--r-- | wx+/grid.h | 46 | ||||
-rw-r--r-- | wx+/image_tools.cpp | 185 | ||||
-rw-r--r-- | wx+/image_tools.h | 55 | ||||
-rw-r--r-- | wx+/no_flicker.h | 10 |
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 @@ -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; }; - @@ -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 |