diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2018-05-09 00:11:35 +0200 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2018-05-09 00:11:35 +0200 |
commit | 015bb675d6eb177900c8ac94a6d35edc5ad90576 (patch) | |
tree | edde4153ce9b2ba6bdaf9d3c0af0966ed6dfd717 /wx+ | |
parent | 9.8 (diff) | |
download | FreeFileSync-015bb675d6eb177900c8ac94a6d35edc5ad90576.tar.gz FreeFileSync-015bb675d6eb177900c8ac94a6d35edc5ad90576.tar.bz2 FreeFileSync-015bb675d6eb177900c8ac94a6d35edc5ad90576.zip |
9.9
Diffstat (limited to 'wx+')
-rwxr-xr-x | wx+/bitmap_button.h | 3 | ||||
-rwxr-xr-x | wx+/choice_enum.h | 2 | ||||
-rwxr-xr-x | wx+/dc.h | 21 | ||||
-rwxr-xr-x | wx+/font_size.h | 1 | ||||
-rwxr-xr-x | wx+/graph.cpp | 72 | ||||
-rwxr-xr-x | wx+/graph.h | 56 | ||||
-rwxr-xr-x | wx+/grid.cpp | 82 | ||||
-rwxr-xr-x | wx+/grid.h | 8 | ||||
-rwxr-xr-x | wx+/image_holder.h | 52 | ||||
-rwxr-xr-x | wx+/image_resources.cpp | 258 | ||||
-rwxr-xr-x | wx+/image_resources.h | 2 | ||||
-rwxr-xr-x | wx+/image_tools.cpp | 2 | ||||
-rwxr-xr-x | wx+/popup_dlg.cpp | 23 | ||||
-rwxr-xr-x | wx+/popup_dlg_generated.cpp | 5 | ||||
-rwxr-xr-x | wx+/popup_dlg_generated.h | 4 | ||||
-rwxr-xr-x | wx+/std_button_layout.h | 9 | ||||
-rwxr-xr-x | wx+/tooltip.cpp | 22 | ||||
-rwxr-xr-x | wx+/zlib_wrap.cpp | 4 |
18 files changed, 457 insertions, 169 deletions
diff --git a/wx+/bitmap_button.h b/wx+/bitmap_button.h index 379cf52d..dc7615c1 100755 --- a/wx+/bitmap_button.h +++ b/wx+/bitmap_button.h @@ -9,6 +9,7 @@ #include <wx/bmpbuttn.h> #include "image_tools.h" +#include "dc.h" namespace zen @@ -28,7 +29,7 @@ public: 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); +void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& text, int gap = fastFromDIP(5), int border = fastFromDIP(5)); //set bitmap label flicker free: void setImage(wxBitmapButton& button, const wxBitmap& bmp); diff --git a/wx+/choice_enum.h b/wx+/choice_enum.h index 6b2edd01..97c40b68 100755 --- a/wx+/choice_enum.h +++ b/wx+/choice_enum.h @@ -107,8 +107,6 @@ template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mappi if (item.first == value) ctrl.SetToolTip(item.second.second); } - } - #endif //CHOICE_ENUM_H_132413545345687 @@ -9,7 +9,9 @@ #include <unordered_map> #include <zen/optional.h> +#include <zen/basic_math.h> #include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER +#include <wx/dcscreen.h> namespace zen @@ -41,6 +43,25 @@ void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) } +/* +Standard DPI: + Windows/Ubuntu: 96 x 96 + macOS: wxWidgets uses DIP (note: wxScreenDC().GetPPI() returns 72 x 72 which is a lie; looks like 96 x 96) +*/ +inline +int fastFromDIP(int d) //like wxWindow::FromDIP (but tied to primary monitor and buffered) +{ + +#ifdef wxHAVE_DPI_INDEPENDENT_PIXELS //pulled from wx/window.h + return d; //e.g. macOS, GTK3 +#else + assert(wxTheApp); //only call after wxWidgets was initalized! + static const int dpiY = wxScreenDC().GetPPI().y; //perf: buffering for calls to ::GetDeviceCaps() needed!? + const int defaultDpi = 96; + return numeric::round(1.0 * d * dpiY / defaultDpi); +#endif +} + diff --git a/wx+/font_size.h b/wx+/font_size.h index 636b07aa..2f2d377c 100755 --- a/wx+/font_size.h +++ b/wx+/font_size.h @@ -10,6 +10,7 @@ #include <zen/basic_math.h> #include <wx/window.h> #include <zen/scope_guard.h> +#include "dc.h" namespace zen diff --git a/wx+/graph.cpp b/wx+/graph.cpp index dbce3769..b006cda0 100755 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -203,12 +203,11 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos, const wxColor& backgroundColor) { if (txt.empty()) return; - const int borderX = 5; - const int borderY = 2; //it looks like wxDC::GetMultiLineTextExtent() precisely returns width, but too large a height: maybe they consider "text row height"? - wxSize txtExtent = dc.GetMultiLineTextExtent(txt); - txtExtent.x += 2 * borderX; - txtExtent.y += 2 * borderY; + const wxSize border(fastFromDIP(5), fastFromDIP(2)); + //it looks like wxDC::GetMultiLineTextExtent() precisely returns width, but too large a height: maybe they consider "text row height"? + + const wxSize boxExtent = dc.GetMultiLineTextExtent(txt) + 2 * border; wxPoint drawPos = graphArea.GetTopLeft(); switch (pos) @@ -216,24 +215,24 @@ void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Grap case Graph2D::CORNER_TOP_LEFT: break; case Graph2D::CORNER_TOP_RIGHT: - drawPos.x += graphArea.width - txtExtent.GetWidth(); + drawPos.x += graphArea.width - boxExtent.GetWidth(); break; case Graph2D::CORNER_BOTTOM_LEFT: - drawPos.y += graphArea.height - txtExtent.GetHeight(); + drawPos.y += graphArea.height - boxExtent.GetHeight(); break; case Graph2D::CORNER_BOTTOM_RIGHT: - drawPos.x += graphArea.width - txtExtent.GetWidth(); - drawPos.y += graphArea.height - txtExtent.GetHeight(); + drawPos.x += graphArea.width - boxExtent.GetWidth(); + drawPos.y += graphArea.height - boxExtent.GetHeight(); break; } { //add text shadow to improve readability: wxDCTextColourChanger dummy(dc, backgroundColor); - dc.DrawText(txt, drawPos + wxPoint(borderX + 1, borderY + 1)); + dc.DrawText(txt, drawPos + border + wxSize(fastFromDIP(1), fastFromDIP(1))); } wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); - dc.DrawText(txt, drawPos + wxPoint(borderX, borderY)); + dc.DrawText(txt, drawPos + border); } @@ -452,8 +451,7 @@ Graph2D::Graph2D(wxWindow* parent, { Connect(wxEVT_PAINT, wxPaintEventHandler(Graph2D::onPaintEvent), nullptr, this); Connect(wxEVT_SIZE, wxSizeEventHandler (Graph2D::onSizeEvent ), nullptr, this); - //http://wiki.wxwidgets.org/Flicker-Free_Drawing - Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Graph2D::onEraseBackGround), nullptr, this); + Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //http://wiki.wxwidgets.org/Flicker-Free_Drawing //SetDoubleBuffered(true); slow as hell! @@ -548,6 +546,9 @@ void Graph2D::render(wxDC& dc) const //wxPanel::GetClassDefaultAttributes().colBg : //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); + const int xLabelHeight = attr_.xLabelHeight ? *attr_.xLabelHeight : GetCharHeight() + fastFromDIP(2) /*margin*/; + const int yLabelWidth = attr_.yLabelWidth ? *attr_.yLabelWidth : dc.GetTextExtent(L"1,23457e+07").x; + /* ----------------------- | | x-label | @@ -562,12 +563,12 @@ void Graph2D::render(wxDC& dc) const switch (attr_.labelposX) { case LABEL_X_TOP: - graphArea.y += attr_.xLabelHeight; - graphArea.height -= attr_.xLabelHeight; + graphArea.y += xLabelHeight; + graphArea.height -= xLabelHeight; break; case LABEL_X_BOTTOM: - xLabelPosY += clientRect.height - attr_.xLabelHeight; - graphArea.height -= attr_.xLabelHeight; + xLabelPosY += clientRect.height - xLabelHeight; + graphArea.height -= xLabelHeight; break; case LABEL_X_NONE: break; @@ -575,17 +576,20 @@ void Graph2D::render(wxDC& dc) const switch (attr_.labelposY) { case LABEL_Y_LEFT: - graphArea.x += attr_.yLabelWidth; - graphArea.width -= attr_.yLabelWidth; + graphArea.x += yLabelWidth; + graphArea.width -= yLabelWidth; break; case LABEL_Y_RIGHT: - yLabelPosX += clientRect.width - attr_.yLabelWidth; - graphArea.width -= attr_.yLabelWidth; + yLabelPosX += clientRect.width - yLabelWidth; + graphArea.width -= yLabelWidth; break; case LABEL_Y_NONE: break; } + assert(attr_.labelposX == LABEL_X_NONE || attr_.labelFmtX); + assert(attr_.labelposY == LABEL_Y_NONE || attr_.labelFmtY); + { //paint graph background (excluding label area) wxDCPenChanger dummy (dc, getBorderColor()); @@ -597,13 +601,13 @@ void Graph2D::render(wxDC& dc) const } //set label areas respecting graph area border! - const wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, attr_.xLabelHeight); - const wxRect yLabelArea(yLabelPosX, graphArea.y, attr_.yLabelWidth, graphArea.height); + const wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, xLabelHeight); + const wxRect yLabelArea(yLabelPosX, graphArea.y, yLabelWidth, graphArea.height); const wxPoint graphAreaOrigin = graphArea.GetTopLeft(); //detect x value range - double minX = attr_.minXauto ? std::numeric_limits<double>::infinity() : attr_.minX; //automatic: ensure values are initialized by first curve - double maxX = attr_.maxXauto ? -std::numeric_limits<double>::infinity() : attr_.maxX; // + double minX = attr_.minX ? *attr_.minX : std::numeric_limits<double>::infinity(); //automatic: ensure values are initialized by first curve + double maxX = attr_.maxX ? *attr_.maxX : -std::numeric_limits<double>::infinity(); // for (auto it = curves_.begin(); it != curves_.end(); ++it) if (const CurveData* curve = it->first.get()) { @@ -611,9 +615,9 @@ void Graph2D::render(wxDC& dc) const assert(rangeX.first <= rangeX.second + 1.0e-9); //GCC fucks up badly when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18 - if (attr_.minXauto) + if (!attr_.minX) minX = std::min(minX, rangeX.first); - if (attr_.maxXauto) + if (!attr_.maxX) maxX = std::max(maxX, rangeX.second); } @@ -630,8 +634,8 @@ void Graph2D::render(wxDC& dc) const *attr_.labelFmtX); //get raw values + detect y value range - double minY = attr_.minYauto ? std::numeric_limits<double>::infinity() : attr_.minY; //automatic: ensure values are initialized by first curve - double maxY = attr_.maxYauto ? -std::numeric_limits<double>::infinity() : attr_.maxY; // + double minY = attr_.minY ? *attr_.minY : std::numeric_limits<double>::infinity(); //automatic: ensure values are initialized by first curve + double maxY = attr_.maxY ? *attr_.maxY : -std::numeric_limits<double>::infinity(); // 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 an out-of-bounds line @@ -650,12 +654,12 @@ void Graph2D::render(wxDC& dc) const const bool doPolygonCut = curves_[index].second.fillMode == CurveAttributes::FILL_POLYGON; //impacts auto minY/maxY!! cutPointsOutsideX(points, marker, minX, maxX, doPolygonCut); - if (attr_.minYauto || attr_.maxYauto) + if (!attr_.minY || !attr_.maxY) { auto itPair = std::minmax_element(points.begin(), points.end(), [](const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.y < rhs.y; }); - if (attr_.minYauto) + if (!attr_.minY) minY = std::min(minY, itPair.first->y); - if (attr_.maxYauto) + if (!attr_.maxY) maxY = std::max(maxY, itPair.second->y); } } @@ -805,8 +809,8 @@ void Graph2D::render(wxDC& dc) const } //3. draw labels and background grid - drawXLabel(dc, minX, maxX, blockCountX, cvrtX, graphArea, xLabelArea, *attr_.labelFmtX); - drawYLabel(dc, minY, maxY, blockCountY, cvrtY, graphArea, yLabelArea, *attr_.labelFmtY); + if (attr_.labelFmtX) drawXLabel(dc, minX, maxX, blockCountX, cvrtX, graphArea, xLabelArea, *attr_.labelFmtX); + if (attr_.labelFmtY) drawYLabel(dc, minY, maxY, blockCountY, cvrtY, graphArea, yLabelArea, *attr_.labelFmtY); //4. finally draw curves { diff --git a/wx+/graph.h b/wx+/graph.h index 45129c4b..bf0e7a70 100755 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -101,12 +101,12 @@ private: struct VectorCurveData : public ArrayCurveData { - std::vector<double>& refData() { return data; } + std::vector<double>& refData() { return data_; } private: - double getValue(size_t pos) const override { return pos < data.size() ? data[pos] : 0; } - size_t getSize() const override { return data.size(); } + double getValue(size_t pos) const override { return pos < data_.size() ? data_[pos] : 0; } + size_t getSize() const override { return data_.size(); } - std::vector<double> data; + std::vector<double> data_; }; //------------------------------------------------------------------------------------------------------------ @@ -128,7 +128,7 @@ double nextNiceNumber(double blockSize); //round to next number which is conveni struct DecimalNumberFormatter : public LabelFormatter { double getOptimalBlockSize(double sizeProposed ) const override { return nextNiceNumber(sizeProposed); } - wxString formatText (double value, double optimalBlockSize) const override { return zen::numberTo<wxString>(value); } + wxString formatText (double value, double optimalBlockSize) const override { return numberTo<wxString>(value); } }; //------------------------------------------------------------------------------------------------------------ @@ -178,7 +178,7 @@ public: { public: CurveAttributes() {} //required by GCC - CurveAttributes& setColor (const wxColor& col) { color = col; autoColor = false; return *this; } + CurveAttributes& setColor (const wxColor& col) { color = col; autoColor = false; return *this; } CurveAttributes& fillCurveArea (const wxColor& col) { fillColor = col; fillMode = FILL_CURVE; return *this; } CurveAttributes& fillPolygonArea(const wxColor& col) { fillColor = col; fillMode = FILL_POLYGON; return *this; } CurveAttributes& setLineWidth(size_t width) { lineWidth = static_cast<int>(width); return *this; } @@ -240,26 +240,26 @@ public: class MainAttributes { public: - MainAttributes& setMinX(double newMinX) { minX = newMinX; minXauto = false; return *this; } - MainAttributes& setMaxX(double newMaxX) { maxX = newMaxX; maxXauto = false; return *this; } + MainAttributes& setMinX(double newMinX) { minX = newMinX; return *this; } + MainAttributes& setMaxX(double newMaxX) { maxX = newMaxX; return *this; } - MainAttributes& setMinY(double newMinY) { minY = newMinY; minYauto = false; return *this; } - MainAttributes& setMaxY(double newMaxY) { maxY = newMaxY; maxYauto = false; return *this; } + MainAttributes& setMinY(double newMinY) { minY = newMinY; return *this; } + MainAttributes& setMaxY(double newMaxY) { maxY = newMaxY; return *this; } - MainAttributes& setAutoSize() { minXauto = maxXauto = minYauto = maxYauto = true; return *this; } + MainAttributes& setAutoSize() { minX = maxX = minY = maxY = NoValue(); return *this; } - MainAttributes& setLabelX(PosLabelX posX, size_t height = 25, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>()) + MainAttributes& setLabelX(PosLabelX posX, int height = -1, std::shared_ptr<LabelFormatter> newLabelFmt = nullptr) { - labelposX = posX; - xLabelHeight = static_cast<int>(height); - labelFmtX = newLabelFmt; + labelposX = posX; + if (height >= 0) xLabelHeight = height; + if (newLabelFmt) labelFmtX = newLabelFmt; return *this; } - MainAttributes& setLabelY(PosLabelY posY, size_t width = 60, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>()) + MainAttributes& setLabelY(PosLabelY posY, int width = -1, std::shared_ptr<LabelFormatter> newLabelFmt = nullptr) { - labelposY = posY; - yLabelWidth = static_cast<int>(width); - labelFmtY = newLabelFmt; + labelposY = posY; + if (width >= 0) yLabelWidth = width; + if (newLabelFmt) labelFmtY = newLabelFmt; return *this; } @@ -272,22 +272,18 @@ public: private: friend class Graph2D; - bool minXauto = true; //autodetect range for X value - bool maxXauto = true; - double minX = 0; //x-range to visualize - double maxX = 0; // + Opt<double> minX; //x-range to visualize + Opt<double> maxX; // - bool minYauto = true; //autodetect range for Y value - bool maxYauto = true; - double minY = 0; //y-range to visualize - double maxY = 0; // + Opt<double> minY; //y-range to visualize + Opt<double> maxY; // PosLabelX labelposX = LABEL_X_BOTTOM; - int xLabelHeight = 25; + Opt<int> xLabelHeight; std::shared_ptr<LabelFormatter> labelFmtX = std::make_shared<DecimalNumberFormatter>(); PosLabelY labelposY = LABEL_Y_LEFT; - int yLabelWidth = 60; + Opt<int> yLabelWidth; std::shared_ptr<LabelFormatter> labelFmtY = std::make_shared<DecimalNumberFormatter>(); std::map<PosCorner, wxString> cornerTexts; @@ -295,6 +291,7 @@ public: wxColor backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SelMode mouseSelMode = SELECT_RECTANGLE; }; + void setAttributes(const MainAttributes& newAttr) { attr_ = newAttr; Refresh(); } MainAttributes getAttributes() const { return attr_; } @@ -315,7 +312,6 @@ private: void onPaintEvent(wxPaintEvent& event); void onSizeEvent(wxSizeEvent& event) { Refresh(); event.Skip(); } - void onEraseBackGround(wxEraseEvent& event) {} void render(wxDC& dc) const; diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 64f7f4a6..a1beee01 100755 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -25,15 +25,15 @@ using namespace zen; -wxColor Grid::getColorSelectionGradientFrom() { return { 137, 172, 255 }; } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 -wxColor Grid::getColorSelectionGradientTo () { return { 225, 234, 255 }; } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 +//let's NOT create wxWidgets objects statically: +wxColor GridData::getColorSelectionGradientFrom() { return { 137, 172, 255 }; } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 +wxColor GridData::getColorSelectionGradientTo () { return { 225, 234, 255 }; } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 -const int GridData::COLUMN_GAP_LEFT = 4; +int GridData::getColumnGapLeft() { return fastFromDIP(4); } namespace { -//let's NOT create wxWidgets objects statically: //------------------------------ Grid Parameters -------------------------------- inline wxColor getColorLabelText() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); } inline wxColor getColorGridLine() { return { 192, 192, 192 }; } //light grey @@ -42,15 +42,16 @@ inline wxColor getColorLabelGradientFrom() { return wxSystemSettings::GetColour( inline wxColor getColorLabelGradientTo () { return { 200, 200, 200 }; } //light grey inline wxColor getColorLabelGradientFocusFrom() { return getColorLabelGradientFrom(); } -inline wxColor getColorLabelGradientFocusTo () { return Grid::getColorSelectionGradientFrom(); } +inline wxColor getColorLabelGradientFocusTo () { return GridData::getColorSelectionGradientFrom(); } -const double MOUSE_DRAG_ACCELERATION = 1.5; //unit: [rows / (pixel * sec)] -> same value like Explorer! -const int DEFAULT_COL_LABEL_BORDER = 6; //top + bottom border in addition to label height -const int COLUMN_MOVE_DELAY = 5; //unit: [pixel] (from Explorer) -const int COLUMN_MIN_WIDTH = 40; //only honored when resizing manually! -const int ROW_LABEL_BORDER = 3; -const int COLUMN_RESIZE_TOLERANCE = 6; //unit [pixel] -const int COLUMN_FILL_GAP_TOLERANCE = 10; //enlarge column to fill full width when resizing +const double MOUSE_DRAG_ACCELERATION_DIP = 1.5; //unit: [rows / (DIP * sec)] -> same value like Explorer! +const int DEFAULT_COL_LABEL_BORDER_DIP = 6; //top + bottom border in addition to label height +const int COLUMN_MOVE_DELAY_DIP = 5; //unit: [pixel] (from Explorer) +const int COLUMN_MIN_WIDTH_DIP = 40; //only honored when resizing manually! +const int ROW_LABEL_BORDER_DIP = 3; +const int COLUMN_RESIZE_TOLERANCE_DIP = 6; //unit [pixel] +const int COLUMN_FILL_GAP_TOLERANCE_DIP = 10; //enlarge column to fill full width when resizing +const int COLUMN_MOVE_MARKER_WIDTH_DIP = 3; const bool fillGapAfterColumns = true; //draw rows/column label to fill full window width; may become an instance variable some time? } @@ -77,15 +78,15 @@ void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType c { wxRect rectTmp = drawCellBorder(dc, rect); - rectTmp.x += COLUMN_GAP_LEFT; - rectTmp.width -= COLUMN_GAP_LEFT; + rectTmp.x += getColumnGapLeft(); + rectTmp.width -= getColumnGapLeft(); drawCellText(dc, rectTmp, getValue(row, colType)); } int GridData::getBestSize(wxDC& dc, size_t row, ColumnType colType) { - return dc.GetTextExtent(getValue(row, colType)).GetWidth() + 2 * COLUMN_GAP_LEFT + 1; //gap on left and right side + border + return dc.GetTextExtent(getValue(row, colType)).GetWidth() + 2 * getColumnGapLeft() + 1; //gap on left and right side + border } @@ -104,7 +105,7 @@ void GridData::drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bo if (enabled) { if (selected) - dc.GradientFillLinear(rect, Grid::getColorSelectionGradientFrom(), Grid::getColorSelectionGradientTo(), wxEAST); + dc.GradientFillLinear(rect, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST); else clearArea(dc, rect, backgroundColor); } @@ -186,8 +187,8 @@ void GridData::renderColumnLabel(Grid& grid, wxDC& dc, const wxRect& rect, Colum wxRect rectTmp = drawColumnLabelBorder(dc, rect); drawColumnLabelBackground(dc, rectTmp, highlighted); - rectTmp.x += COLUMN_GAP_LEFT; - rectTmp.width -= COLUMN_GAP_LEFT; + rectTmp.x += getColumnGapLeft(); + rectTmp.width -= getColumnGapLeft(); drawColumnLabelText(dc, rectTmp, getColumnLabel(colType)); } @@ -245,15 +246,14 @@ public: { Connect(wxEVT_PAINT, wxPaintEventHandler(SubWindow::onPaintEvent), nullptr, this); Connect(wxEVT_SIZE, wxSizeEventHandler (SubWindow::onSizeEvent), nullptr, this); - //http://wiki.wxwidgets.org/Flicker-Free_Drawing - Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(SubWindow::onEraseBackGround), nullptr, this); + Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //http://wiki.wxwidgets.org/Flicker-Free_Drawing //SetDoubleBuffered(true); slow as hell! SetBackgroundStyle(wxBG_STYLE_PAINT); - Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(SubWindow::onFocus), nullptr, this); - Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(SubWindow::onFocus), nullptr, this); + Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(SubWindow::onFocus), nullptr, this); + Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(SubWindow::onFocus), nullptr, this); Connect(wxEVT_CHILD_FOCUS, wxEventHandler(SubWindow::onChildFocus), nullptr, this); Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(SubWindow::onMouseLeftDown ), nullptr, this); @@ -364,8 +364,6 @@ private: event.Skip(); } - void onEraseBackGround(wxEraseEvent& event) {} - Grid& parent_; Opt<wxBitmap> doubleBuffer_; }; @@ -429,7 +427,7 @@ public: int bestWidth = 0; for (ptrdiff_t i = rowFrom; i <= rowTo; ++i) - bestWidth = std::max(bestWidth, dc.GetTextExtent(formatRow(i)).GetWidth() + 2 * ROW_LABEL_BORDER); + bestWidth = std::max(bestWidth, dc.GetTextExtent(formatRow(i)).GetWidth() + fastFromDIP(2 * ROW_LABEL_BORDER_DIP)); return bestWidth; } @@ -671,10 +669,12 @@ private: if (refParent().allowColumnMove_) if (activeClickOrMove_ && activeClickOrMove_->isRealMove()) { + const int markerWidth = fastFromDIP(COLUMN_MOVE_MARKER_WIDTH_DIP); + if (col + 1 == activeClickOrMove_->refColumnTo()) //handle pos 1, 2, .. up to "at end" position - dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight() + wxPoint(-2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); + dc.GradientFillLinear(wxRect(rect.x + rect.width - markerWidth, rect.y, markerWidth, rect.height), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); else if (col == activeClickOrMove_->refColumnTo() && col == 0) //pos 0 - dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); + dc.GradientFillLinear(wxRect(rect.GetTopLeft(), wxSize(markerWidth, rect.height)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); } } } @@ -769,7 +769,7 @@ private: //check if there's a small gap after last column, if yes, fill it const int gapWidth = GetClientSize().GetWidth() - refParent().getColWidthsSum(GetClientSize().GetWidth()); - if (std::abs(gapWidth) < COLUMN_FILL_GAP_TOLERANCE) + if (std::abs(gapWidth) < fastFromDIP(COLUMN_FILL_GAP_TOLERANCE_DIP)) refParent().setColumnWidth(newWidth + gapWidth, col, ALLOW_GRID_EVENT); refParent().Refresh(); //refresh columns on main grid as well! @@ -777,7 +777,7 @@ private: else if (activeClickOrMove_) { const int clientPosX = event.GetPosition().x; - if (std::abs(clientPosX - activeClickOrMove_->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) + if (std::abs(clientPosX - activeClickOrMove_->getStartPosX()) > fastFromDIP(COLUMN_MOVE_DELAY_DIP)) //real move (not a single click) { activeClickOrMove_->setRealMove(); @@ -1163,13 +1163,13 @@ private: wnd_.refParent().GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); if (pixelsPerUnitY <= 0) return; - const double mouseDragSpeedIncScrollU = pixelsPerUnitY > 0 ? MOUSE_DRAG_ACCELERATION * wnd_.rowLabelWin_.getRowHeight() / pixelsPerUnitY : 0; //unit: [scroll units / (pixel * sec)] + const double mouseDragSpeedIncScrollU = pixelsPerUnitY > 0 ? MOUSE_DRAG_ACCELERATION_DIP * wnd_.rowLabelWin_.getRowHeight() / pixelsPerUnitY : 0; //unit: [scroll units / (DIP * sec)] auto autoScroll = [&](int overlapPix, double& toScroll) { if (overlapPix != 0) { - const double scrollSpeed = overlapPix * mouseDragSpeedIncScrollU; //unit: [scroll units / sec] + const double scrollSpeed = wnd_.ToDIP(overlapPix) * mouseDragSpeedIncScrollU; //unit: [scroll units / sec] toScroll += scrollSpeed * deltaSecs; } else @@ -1291,7 +1291,7 @@ Grid::Grid(wxWindow* parent, colLabelWin_ = new ColLabelWin(*this); // mainWin_ = new MainWin (*this, *rowLabelWin_, *colLabelWin_); // - colLabelHeight_ = 2 * DEFAULT_COL_LABEL_BORDER + [&]() -> int + colLabelHeight_ = fastFromDIP(2 * DEFAULT_COL_LABEL_BORDER_DIP) + [&] { //coordinate with ColLabelWin::render(): wxFont labelFont = colLabelWin_->GetFont(); @@ -1306,9 +1306,9 @@ Grid::Grid(wxWindow* parent, assert(GetClientSize() == GetSize()); //borders are NOT allowed for Grid //reason: updateWindowSizes() wants to use "GetSize()" as a "GetClientSize()" including scrollbars - Connect(wxEVT_PAINT, wxPaintEventHandler(Grid::onPaintEvent ), nullptr, this); - Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Grid::onEraseBackGround), nullptr, this); - Connect(wxEVT_SIZE, wxSizeEventHandler (Grid::onSizeEvent ), nullptr, this); + Connect(wxEVT_PAINT, wxPaintEventHandler(Grid::onPaintEvent), nullptr, this); + Connect(wxEVT_SIZE, wxSizeEventHandler (Grid::onSizeEvent ), nullptr, this); + Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //http://wiki.wxwidgets.org/Flicker-Free_Drawing Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(Grid::onKeyDown), nullptr, this); } @@ -1820,7 +1820,7 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const const int absPosX = CalcUnscrolledPosition(pos).x; if (absPosX >= 0) { - const int resizeTolerance = allowColumnResize_ ? COLUMN_RESIZE_TOLERANCE : 0; + const int resizeTolerance = allowColumnResize_ ? fastFromDIP(COLUMN_RESIZE_TOLERANCE_DIP) : 0; std::vector<ColumnWidth> absWidths = getColWidths(); //resolve stretched widths int accuWidth = 0; @@ -2104,10 +2104,10 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve return; } //CAVEATS: - //I. fixed-size columns: normalize offset so that resulting width is at least COLUMN_MIN_WIDTH: this is NOT enforced by getColWidths()! + //I. fixed-size columns: normalize offset so that resulting width is at least COLUMN_MIN_WIDTH_DIP: 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 - width = std::max(width, COLUMN_MIN_WIDTH); + width = std::max(width, fastFromDIP(COLUMN_MIN_WIDTH_DIP)); vcRs.offset = width - stretchedWidths[col]; //width := stretchedWidth + offset @@ -2119,7 +2119,7 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve //4. now verify that the stretched column is resizing immediately if main window is enlarged again 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[col2]); + visibleCols_[col2].offset = std::max(visibleCols_[col2].offset, fastFromDIP(COLUMN_MIN_WIDTH_DIP) - stretchedWidths[col2]); if (columnResizeEventPolicy == ALLOW_GRID_EVENT) { @@ -2213,9 +2213,9 @@ std::vector<Grid::ColumnWidth> Grid::getColWidths(int mainWinWidth) const //eval int width = stretchedWidths[col2] + vc.offset; if (vc.stretch > 0) - width = std::max(width, COLUMN_MIN_WIDTH); //normalization really needed here: e.g. smaller main window would result in negative width + width = std::max(width, fastFromDIP(COLUMN_MIN_WIDTH_DIP)); //normalization really needed here: e.g. smaller main window would result in negative width else - width = std::max(width, 0); //support smaller width than COLUMN_MIN_WIDTH if set via configuration + width = std::max(width, 0); //support smaller width than COLUMN_MIN_WIDTH_DIP if set via configuration output.push_back({ vc.type, width }); } @@ -118,7 +118,9 @@ public: virtual void renderColumnLabel(Grid& grid, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted); //default implementation virtual std::wstring getToolTip(ColumnType colType) const { return std::wstring(); } - static const int COLUMN_GAP_LEFT; //for left-aligned text + static int getColumnGapLeft(); //for left-aligned text + static wxColor getColorSelectionGradientFrom(); + static wxColor getColorSelectionGradientTo(); //optional helper routines: static wxSize drawCellText (wxDC& dc, const wxRect& rect, const std::wstring& text, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); //returns text extent @@ -223,12 +225,8 @@ public: //############################################################################################################ - static wxColor getColorSelectionGradientFrom(); - static wxColor getColorSelectionGradientTo(); - private: void onPaintEvent(wxPaintEvent& event); - void onEraseBackGround(wxEraseEvent& event) {} //[!] void onSizeEvent(wxSizeEvent& event) { updateWindowSizes(); event.Skip(); } void onKeyDown(wxKeyEvent& event); diff --git a/wx+/image_holder.h b/wx+/image_holder.h new file mode 100755 index 00000000..6804d5fc --- /dev/null +++ b/wx+/image_holder.h @@ -0,0 +1,52 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef IMAGE_HOLDER_H_284578426342567457 +#define IMAGE_HOLDER_H_284578426342567457 + +#include <memory> + +//used by fs/abstract.h => check carefully before adding dependencies! +//DO NOT add any wx/wx+ includes! + +namespace zen +{ +struct ImageHolder //prepare conversion to wxImage as much as possible while staying thread-safe (in contrast to wxIcon/wxBitmap) +{ + ImageHolder() {} + + ImageHolder(int w, int h, bool withAlpha) : //init with allocated memory + width_(w), height_(h), + rgb_(static_cast<unsigned char*>(::malloc(w * h * 3))), + alpha_(withAlpha ? static_cast<unsigned char*>(::malloc(w * h)) : nullptr) {} + + ImageHolder (ImageHolder&& tmp) = default; // + ImageHolder& operator=(ImageHolder&& tmp) = default; //move semantics only! + ImageHolder (const ImageHolder&) = delete; // + ImageHolder& operator=(const ImageHolder&) = delete; // + + explicit operator bool() const { return rgb_.get() != nullptr; } + + int getWidth () const { return width_; } + int getHeight() const { return height_; } + + unsigned char* getRgb () { return rgb_ .get(); } + unsigned char* getAlpha() { return alpha_.get(); } + + unsigned char* releaseRgb () { return rgb_ .release(); } + unsigned char* releaseAlpha() { return alpha_.release(); } + + struct CLibFree { void operator()(unsigned char* p) const { ::free(p); } }; //use malloc/free to allow direct move into wxImage! + +private: + int width_ = 0; + int height_ = 0; + std::unique_ptr<unsigned char, CLibFree> rgb_; //optional + std::unique_ptr<unsigned char, CLibFree> alpha_; // +}; +} + +#endif //IMAGE_HOLDER_H_284578426342567457 diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index d4547d35..e87b245c 100755 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -9,12 +9,17 @@ #include <map> #include <zen/utf.h> #include <zen/globals.h> +#include <zen/perf.h> #include <zen/thread.h> #include <wx/wfstream.h> #include <wx/zipstrm.h> #include <wx/image.h> #include <wx/mstream.h> +#include <xBRZ/src/xbrz.h> +#include <xBRZ/src/xbrz_tools.h> #include "image_tools.h" +#include "image_holder.h" +#include "dc.h" using namespace zen; @@ -22,6 +27,195 @@ using namespace zen; namespace { + +ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const unsigned char* imageRgb, const unsigned char* imageAlpha, int hqScale) +{ + assert(imageRgb && imageAlpha); //see convertToVanillaImage() + if (width <= 0 || height <= 0 || dpiWidth <= 0 || dpiHeight <= 0) + return ImageHolder(0, 0, true /*withAlpha*/); + + const int hqWidth = width * hqScale; + const int hqHeight = height * hqScale; + + //get rid of allocation and buffer std::vector<> at thread-level? => no discernable perf improvement + std::vector<uint32_t> buf(hqWidth * hqHeight + std::max(width * height, dpiWidth * dpiHeight)); + uint32_t* const argbSrc = &buf[0] + hqWidth * hqHeight; + uint32_t* const xbrTrg = &buf[0]; + uint32_t* const dpiTrg = argbSrc; + + //convert RGB (RGB byte order) to ARGB (BGRA byte order) + { + const unsigned char* rgb = imageRgb; + const unsigned char* rgbEnd = rgb + 3 * width * height; + const unsigned char* alpha = imageAlpha; + uint32_t* out = argbSrc; + + for (; rgb < rgbEnd; rgb += 3) + *out++ = xbrz::makePixel(*alpha++, rgb[0], rgb[1], rgb[2]); + } + //----------------------------------------------------- + xbrz::scale(hqScale, //size_t factor, //valid range: 2 - SCALE_FACTOR_MAX + argbSrc, //const uint32_t* src, + xbrTrg, //uint32_t* trg, + width, height, //int srcWidth, int srcHeight, + xbrz::ColorFormat::ARGB_UNBUFFERED); //ColorFormat colFmt, + //test: total xBRZ scaling time with ARGB: 300ms, ARGB_UNBUFFERED: 50ms + //----------------------------------------------------- + xbrz::bilinearScale(xbrTrg, //const uint32_t* src, + hqWidth, hqHeight, //int srcWidth, int srcHeight, + dpiTrg, //uint32_t* trg, + dpiWidth, dpiHeight); //int trgWidth, int trgHeight + //----------------------------------------------------- + //convert BGRA to RGB + alpha + ImageHolder trgImg(dpiWidth, dpiHeight, true /*withAlpha*/); + { + unsigned char* rgb = trgImg.getRgb(); + unsigned char* alpha = trgImg.getAlpha(); + + std::for_each(dpiTrg, dpiTrg + dpiWidth * dpiHeight, [&](uint32_t col) + { + *alpha++ = xbrz::getAlpha(col); + *rgb++ = xbrz::getRed (col); + *rgb++ = xbrz::getGreen(col); + *rgb++ = xbrz::getBlue (col); + }); + } + return trgImg; +} + + +struct WorkItem +{ + Zbase<wchar_t> name; + int width = 0; + int height = 0; + int dpiWidth = 0; + int dpiHeight = 0; + const unsigned char* rgb = nullptr; + const unsigned char* alpha = nullptr; +}; + + +class WorkLoad +{ +public: + void add(const WorkItem& wi) //context of main thread + { + assert(std::this_thread::get_id() == mainThreadId); + { + std::lock_guard<std::mutex> dummy(lockWork_); + workLoad_.push_back(wi); + } + conditionNewWork_.notify_all(); + } + + void noMoreWork() + { + assert(std::this_thread::get_id() == mainThreadId); + { + std::lock_guard<std::mutex> dummy(lockWork_); + expectMoreWork_ = false; + } + conditionNewWork_.notify_all(); + } + + //context of worker thread, blocking: + Opt<WorkItem> extractNext() //throw ThreadInterruption + { + assert(std::this_thread::get_id() != mainThreadId); + std::unique_lock<std::mutex> dummy(lockWork_); + + interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty() || !expectMoreWork_; }); //throw ThreadInterruption + + if (workLoad_.empty()) + return NoValue(); + + WorkItem wi = workLoad_.back(); // + workLoad_.pop_back(); //yes, no strong exception guarantee (std::bad_alloc) + return wi; // + } + +private: + bool expectMoreWork_ = true; + std::vector<WorkItem> workLoad_; + std::mutex lockWork_; + std::condition_variable conditionNewWork_; //signal event: data for processing available +}; + + +class DpiParallelScaler +{ +public: + DpiParallelScaler(int hqScale) + { + assert(hqScale > 1); + const int threadCount = std::max<int>(std::thread::hardware_concurrency(), 1); //hardware_concurrency() == 0 if "not computable or well defined" + + for (int i = 0; i < threadCount; ++i) + worker_.push_back([hqScale, &workload = workload_, &result = result_] + { + setCurrentThreadName("xBRZ Scaler"); + while (Opt<WorkItem> wi = workload.extractNext()) //throw ThreadInterruption + { + ImageHolder ih = dpiScale(wi->width, wi->height, + wi->dpiWidth, wi->dpiHeight, + wi->rgb, wi->alpha, hqScale); + result.access([&](std::vector<std::pair<Zbase<wchar_t>, ImageHolder>>& r) { r.emplace_back(wi->name, std::move(ih)); }); + } + }); + } + + ~DpiParallelScaler() + { + for (InterruptibleThread& w : worker_) + w.interrupt(); + + for (InterruptibleThread& w : worker_) + if (w.joinable()) + w.join(); + } + + void add(const wxString& name, const wxImage& img) + { + imgKeeper_.push_back(img); //retain (ref-counted) wxImage so that the rgb/alpha pointers remain valid after passed to threads + workload_.add({ copyStringTo<Zbase<wchar_t>>(name), + img.GetWidth(), img.GetHeight(), + fastFromDIP(img.GetWidth()), fastFromDIP(img.GetHeight()), //don't call fastFromDIP() from worker thread (wxWidgets function!) + img.GetData(), img.GetAlpha() }); + } + + std::map<wxString, wxBitmap> waitAndGetResult() + { + workload_.noMoreWork(); + + for (InterruptibleThread& w : worker_) + w.join(); + + std::map<wxString, wxBitmap> output; + + result_.access([&](std::vector<std::pair<Zbase<wchar_t>, ImageHolder>>& r) + { + for (auto& item : r) + { + ImageHolder& ih = item.second; + + wxImage img(ih.getWidth(), ih.getHeight(), ih.releaseRgb(), false /*static_data*/); //pass ownership + img.SetAlpha(ih.releaseAlpha(), false /*static_data*/); + + output[utfTo<wxString>(item.first)] = wxBitmap(img); + } + }); + return output; + } + +private: + std::vector<InterruptibleThread> worker_; + WorkLoad workload_; + Protected<std::vector<std::pair<Zbase<wchar_t>, ImageHolder>>> result_; + std::vector<wxImage> imgKeeper_; +}; + + void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim) { //work around wxWidgets bug: @@ -39,6 +233,8 @@ void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim) anim.Load(seekAbleStream, wxANIMATION_TYPE_GIF); } +//================================================================================================ +//================================================================================================ class GlobalBitmaps { @@ -51,32 +247,35 @@ public: } GlobalBitmaps() {} - ~GlobalBitmaps() { assert(bitmaps.empty() && anims.empty()); } //don't leave wxWidgets objects for static destruction! + ~GlobalBitmaps() { assert(bitmaps_.empty() && anims_.empty()); } //don't leave wxWidgets objects for static destruction! void init(const Zstring& filepath); void cleanup() { - bitmaps.clear(); - anims.clear(); + bitmaps_.clear(); + anims_ .clear(); + dpiScaler_.reset(); } - const wxBitmap& getImage (const wxString& name) const; + const wxBitmap& getImage (const wxString& name); const wxAnimation& getAnimation(const wxString& name) const; private: GlobalBitmaps (const GlobalBitmaps&) = delete; GlobalBitmaps& operator=(const GlobalBitmaps&) = delete; - std::map<wxString, wxBitmap> bitmaps; - std::map<wxString, wxAnimation> anims; + std::map<wxString, wxBitmap> bitmaps_; + std::map<wxString, wxAnimation> anims_; + + std::unique_ptr<DpiParallelScaler> dpiScaler_; }; -void GlobalBitmaps::init(const Zstring& filepath) +void GlobalBitmaps::init(const Zstring& filePath) { - assert(bitmaps.empty() && anims.empty()); + assert(bitmaps_.empty() && anims_.empty()); - wxFFileInputStream input(utfTo<wxString>(filepath)); + wxFFileInputStream input(utfTo<wxString>(filePath)); if (input.IsOk()) //if not... we don't want to react too harsh here { //activate support for .png files @@ -85,36 +284,49 @@ void GlobalBitmaps::init(const Zstring& filepath) wxZipInputStream streamIn(input, wxConvUTF8); //do NOT rely on wxConvLocal! On failure shows unhelpful popup "Cannot convert from the charset 'Unknown encoding (-1)'!" - for (;;) - { - std::unique_ptr<wxZipEntry> entry(streamIn.GetNextEntry()); //take ownership! - if (!entry) - break; + //do we need xBRZ scaling for high quality DPI images? + const int hqScale = numeric::clampCpy<int>(std::ceil(fastFromDIP(1000) / 1000.0), 1, xbrz::SCALE_FACTOR_MAX); + //even for 125% DPI scaling, "2xBRZ + bilinear downscale" gives a better result than mere "125% bilinear upscale"! + if (hqScale > 1) + dpiScaler_ = std::make_unique<DpiParallelScaler>(hqScale); + while (const auto& entry = std::unique_ptr<wxZipEntry>(streamIn.GetNextEntry())) //take ownership!) + { const wxString name = entry->GetName(); - //generic image loading if (endsWith(name, L".png")) { wxImage img(streamIn, wxBITMAP_TYPE_PNG); //end this alpha/no-alpha/mask/wxDC::DrawBitmap/RTL/high-contrast-scheme interoperability nightmare here and now!!!! - //=> there's only one type of png image: with alpha channel, no mask!!! + //=> there's only one type of wxImage: with alpha channel, no mask!!! convertToVanillaImage(img); - bitmaps.emplace(name, img); + if (dpiScaler_) + dpiScaler_->add(name, img); //scale in parallel! + else + bitmaps_.emplace(name, img); } else if (endsWith(name, L".gif")) - loadAnimFromZip(streamIn, anims[name]); + loadAnimFromZip(streamIn, anims_[name]); } } } -const wxBitmap& GlobalBitmaps::getImage(const wxString& name) const +const wxBitmap& GlobalBitmaps::getImage(const wxString& name) { - auto it = bitmaps.find(contains(name, L'.') ? name : name + L".png"); //assume .png ending if nothing else specified - if (it != bitmaps.end()) + //test: this function is first called about 220ms after GlobalBitmaps::init() has ended + // => should be enough time to finish xBRZ scaling in parallel (which takes 50ms) + //debug perf: extra 800-1000ms during startup + if (dpiScaler_) + { + bitmaps_ = dpiScaler_->waitAndGetResult(); + dpiScaler_.reset(); + } + + auto it = bitmaps_.find(contains(name, L'.') ? name : name + L".png"); //assume .png ending if nothing else specified + if (it != bitmaps_.end()) return it->second; assert(false); return wxNullBitmap; @@ -123,8 +335,8 @@ const wxBitmap& GlobalBitmaps::getImage(const wxString& name) const const wxAnimation& GlobalBitmaps::getAnimation(const wxString& name) const { - auto it = anims.find(contains(name, L'.') ? name : name + L".gif"); - if (it != anims.end()) + auto it = anims_.find(contains(name, L'.') ? name : name + L".gif"); + if (it != anims_.end()) return it->second; assert(false); return wxNullAnimation; diff --git a/wx+/image_resources.h b/wx+/image_resources.h index cc4ab2cc..5ea56679 100755 --- a/wx+/image_resources.h +++ b/wx+/image_resources.h @@ -14,7 +14,7 @@ namespace zen { -void initResourceImages(const Zstring& filepath); //pass resources .zip file at application startup +void initResourceImages(const Zstring& filePath); //pass resources .zip file at application startup void cleanupResourceImages(); const wxBitmap& getResourceImage (const wxString& name); diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index 235ebe59..26a5b37c 100755 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -9,9 +9,9 @@ #include <zen/zstring.h> #include <wx/app.h> - using namespace zen; + namespace { void writeToImage(const wxImage& source, wxImage& target, const wxPoint& pos) diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index 5babaff3..6633d2ab 100755 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -15,18 +15,12 @@ using namespace zen; + namespace { -void setAsStandard(wxButton& btn) -{ - btn.SetDefault(); - btn.SetFocus(); -} - - void setBestInitialSize(wxTextCtrl& ctrl, const wxString& text, wxSize maxSize) { - const int scrollbarWidth = 30; + const int scrollbarWidth = fastFromDIP(20); if (maxSize.x <= scrollbarWidth) //implicitly checks for non-zero, too! return; maxSize.x -= scrollbarWidth; @@ -117,8 +111,9 @@ public: SetTitle(wxTheApp->GetAppDisplayName() + SPACED_DASH + titleTmp); } - int maxWidth = 500; - int maxHeight = 400; //try to determine better value based on actual display resolution: + int maxWidth = fastFromDIP(500); + int maxHeight = fastFromDIP(400); //try to determine better value based on actual display resolution: + //int [maxWidth, maxHeight] = wxSize(fastFromDIP(500), fastFromDIP(400)); if (parent) { @@ -139,7 +134,10 @@ public: if (!cfg.textDetail.empty()) { - const wxString& text = L"\n" + trimCpy(cfg.textDetail) + L"\n"; //add empty top/bottom lines *instead* of using border space! + wxString text; + if (!cfg.textMain.empty()) + text += L"\n"; + text += trimCpy(cfg.textDetail) + L"\n"; //add empty top/bottom lines *instead* of using border space! setBestInitialSize(*m_textCtrlTextDetail, text, wxSize(maxWidth, maxHeight)); m_textCtrlTextDetail->ChangeValue(text); } @@ -200,9 +198,10 @@ public: //set std order after button visibility was set setStandardButtonLayout(*bSizerStdButtons, stdBtns); - setAsStandard(*m_buttonAccept); GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() Center(); //needs to be re-applied after a dialog size change! + + m_buttonAccept->SetFocus(); } private: diff --git a/wx+/popup_dlg_generated.cpp b/wx+/popup_dlg_generated.cpp index c8c504ae..25ac00e1 100755 --- a/wx+/popup_dlg_generated.cpp +++ b/wx+/popup_dlg_generated.cpp @@ -1,8 +1,8 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Jun 17 2015) +// C++ code generated with wxFormBuilder (version Jan 23 2018) // http://www.wxformbuilder.org/ // -// PLEASE DO "NOT" EDIT THIS FILE! +// PLEASE DO *NOT* EDIT THIS FILE! /////////////////////////////////////////////////////////////////////////// #include "popup_dlg_generated.h" @@ -63,6 +63,7 @@ PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, con bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); m_buttonAccept = new wxButton( this, wxID_YES, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + m_buttonAccept->SetDefault(); bSizerStdButtons->Add( m_buttonAccept, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); m_buttonAcceptAll = new wxButton( this, wxID_YESTOALL, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); diff --git a/wx+/popup_dlg_generated.h b/wx+/popup_dlg_generated.h index 896f8d0c..0d3459e2 100755 --- a/wx+/popup_dlg_generated.h +++ b/wx+/popup_dlg_generated.h @@ -1,8 +1,8 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Jun 17 2015) +// C++ code generated with wxFormBuilder (version Jan 23 2018) // http://www.wxformbuilder.org/ // -// PLEASE DO "NOT" EDIT THIS FILE! +// PLEASE DO *NOT* EDIT THIS FILE! /////////////////////////////////////////////////////////////////////////// #ifndef __POPUP_DLG_GENERATED_H__ diff --git a/wx+/std_button_layout.h b/wx+/std_button_layout.h index 8fe5f405..cf0152de 100755 --- a/wx+/std_button_layout.h +++ b/wx+/std_button_layout.h @@ -10,6 +10,7 @@ #include <algorithm> #include <wx/sizer.h> #include <wx/button.h> +#include "dc.h" namespace zen @@ -71,9 +72,9 @@ void setStandardButtonLayout(wxBoxSizer& sizer, const StdButtons& buttons) detach(buttonsTmp.btnCancel); //GNOME Human Interface Guidelines: https://developer.gnome.org/hig-book/3.2/hig-book.html#alert-spacing - const int spaceH = 6; //OK - const int spaceRimH = 12; //OK - const int spaceRimV = 12; //OK + const int spaceH = fastFromDIP( 6); //OK + const int spaceRimH = fastFromDIP(12); //OK + const int spaceRimV = fastFromDIP(12); //OK bool settingFirstButton = true; auto attach = [&](wxButton* btn) @@ -82,7 +83,7 @@ void setStandardButtonLayout(wxBoxSizer& sizer, const StdButtons& buttons) { assert(btn->GetMinSize().GetHeight() == -1); //let OS or this routine do the sizing! note: OS X does not allow changing the (visible!) button height! const int defaultHeight = wxButton::GetDefaultSize().GetHeight(); //buffered by wxWidgets - btn->SetMinSize(wxSize(-1, std::max(defaultHeight, 30))); //default button height is much too small => increase! + btn->SetMinSize(wxSize(-1, std::max(defaultHeight, fastFromDIP(30)))); //default button height is much too small => increase! if (settingFirstButton) settingFirstButton = false; diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp index a8fd1da1..a7bf85fe 100755 --- a/wx+/tooltip.cpp +++ b/wx+/tooltip.cpp @@ -11,20 +11,22 @@ #include <wx/statbmp.h> #include <wx/settings.h> #include <wx/app.h> -#include <wx+/image_tools.h> +#include "image_tools.h" +#include "dc.h" using namespace zen; +namespace +{ +const int TIP_WINDOW_OFFSET_DIP = 30; +} + + class Tooltip::TooltipDlgGenerated : public wxDialog { public: - TooltipDlgGenerated(wxWindow* parent, - wxWindowID id = wxID_ANY, - const wxString& title = {}, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = 0) : wxDialog(parent, id, title, pos, size, style) + TooltipDlgGenerated(wxWindow* parent) : wxDialog(parent, wxID_ANY, L"" /*title*/, wxDefaultPosition, wxDefaultSize, 0 /*style*/) { //Suse Linux/X11: needs parent window, else there are z-order issues @@ -66,15 +68,15 @@ void Tooltip::show(const wxString& text, wxPoint mousePos, const wxBitmap* bmp) if (text != tipWindow_->staticTextMain_->GetLabel()) { tipWindow_->staticTextMain_->SetLabel(text); - tipWindow_->staticTextMain_->Wrap(600); + tipWindow_->staticTextMain_->Wrap(fastFromDIP(600)); } tipWindow_->GetSizer()->SetSizeHints(tipWindow_); //~=Fit() + SetMinSize() //Linux: Fit() seems to be broken => this needs to be called EVERY time inside show, not only if text or bmp change const wxPoint newPos = wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? - mousePos - wxPoint(30 + tipWindow_->GetSize().GetWidth(), 0) : - mousePos + wxPoint(30, 0); + mousePos - wxPoint(fastFromDIP(TIP_WINDOW_OFFSET_DIP) + tipWindow_->GetSize().GetWidth(), 0) : + mousePos + wxPoint(fastFromDIP(TIP_WINDOW_OFFSET_DIP), 0); if (newPos != tipWindow_->GetScreenPosition()) tipWindow_->SetSize(newPos.x, newPos.y, wxDefaultCoord, wxDefaultCoord); diff --git a/wx+/zlib_wrap.cpp b/wx+/zlib_wrap.cpp index 540854a1..cb6e3083 100755 --- a/wx+/zlib_wrap.cpp +++ b/wx+/zlib_wrap.cpp @@ -5,7 +5,9 @@ // ***************************************************************************** #include "zlib_wrap.h" - #include <zlib.h> //let's pray this is the same version wxWidgets is linking against! +//include the SAME zlib version that wxWidgets is linking against! + //#include <wx/../../../../../Source/src/zlib/zlib.h> //wxWidgets compiled with: --with-zlib=builtin + #include <zlib.h> //use same library as used by Curl (zlib is required for HTTP) using namespace zen; |