summaryrefslogtreecommitdiff
path: root/wx+
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2025-01-20 19:25:18 -0500
committerB. Stack <bgstack15@gmail.com>2025-01-20 19:25:18 -0500
commitde65d3c0295894f8eafc4c7582dfe180dc58c81e (patch)
tree3ba8ec770b81468ca4ad83d985b991c5f669de22 /wx+
parentadd upstream 13.9 (diff)
downloadFreeFileSync-de65d3c0295894f8eafc4c7582dfe180dc58c81e.tar.gz
FreeFileSync-de65d3c0295894f8eafc4c7582dfe180dc58c81e.tar.bz2
FreeFileSync-de65d3c0295894f8eafc4c7582dfe180dc58c81e.zip
add upstream 14.0, depends on wx 3.3.0HEAD14.0master
Diffstat (limited to 'wx+')
-rw-r--r--wx+/app_main.h1
-rw-r--r--wx+/async_task.h1
-rw-r--r--wx+/choice_enum.h127
-rw-r--r--wx+/color_tools.h234
-rw-r--r--wx+/context_menu.h3
-rw-r--r--wx+/darkmode.cpp110
-rw-r--r--wx+/darkmode.h28
-rw-r--r--wx+/dc.h87
-rw-r--r--wx+/file_drop.h1
-rw-r--r--wx+/graph.cpp39
-rw-r--r--wx+/graph.h33
-rw-r--r--wx+/grid.cpp119
-rw-r--r--wx+/grid.h7
-rw-r--r--wx+/image_resources.cpp3
-rw-r--r--wx+/image_tools.cpp66
-rw-r--r--wx+/image_tools.h74
-rw-r--r--wx+/no_flicker.h6
-rw-r--r--wx+/popup_dlg.cpp1
-rw-r--r--wx+/std_button_layout.h1
-rw-r--r--wx+/tooltip.cpp2
-rw-r--r--wx+/window_layout.h3
-rw-r--r--wx+/window_tools.h2
22 files changed, 658 insertions, 290 deletions
diff --git a/wx+/app_main.h b/wx+/app_main.h
index c1fd2441..fa8b1069 100644
--- a/wx+/app_main.h
+++ b/wx+/app_main.h
@@ -7,7 +7,6 @@
#ifndef APP_MAIN_H_08215601837818347575856
#define APP_MAIN_H_08215601837818347575856
-//#include <wx/window.h>
#include <wx/app.h>
diff --git a/wx+/async_task.h b/wx+/async_task.h
index 85f0d9d0..9b4b6573 100644
--- a/wx+/async_task.h
+++ b/wx+/async_task.h
@@ -7,7 +7,6 @@
#ifndef ASYNC_TASK_H_839147839170432143214321
#define ASYNC_TASK_H_839147839170432143214321
-//#include <functional>
#include <zen/thread.h>
#include <zen/scope_guard.h>
#include <zen/stl_tools.h>
diff --git a/wx+/choice_enum.h b/wx+/choice_enum.h
index a25bf104..6b28b4f9 100644
--- a/wx+/choice_enum.h
+++ b/wx+/choice_enum.h
@@ -7,52 +7,33 @@
#ifndef CHOICE_ENUM_H_132413545345687
#define CHOICE_ENUM_H_132413545345687
-#include <unordered_map>
-#include <vector>
+//#include <vector>
#include <wx/choice.h>
-//handle mapping of enum values to wxChoice controls
-/*
-Example:
-
-Member variable:
- zen::EnumDescrList<EnumOnError> enumDescrMap;
-
-Constructor code:
- enumDescrMap.
- add(ON_ERROR_POPUP, "Show pop-up", "Show pop-up on errors or warnings"). <- add localization
- add(ON_ERROR_IGNORE, "Ignore errors", "Hide all error and warning messages").
- add(ON_ERROR_EXIT, "Exit instantly", "Abort synchronization immediately");
-
-Set enum value:
- setEnumVal(enumDescrMap, *m_choiceHandleError, value);
-
-Get enum value:
- value = getEnumVal(enumDescrMap, *m_choiceHandleError)
-
-Update enum tooltips (after user changed selection):
- updateTooltipEnumVal(enumDescrMap, *m_choiceHandleError);
-*/
namespace zen
{
+//handle mapping of enum values to wxChoice controls
template <class Enum>
-struct EnumDescrList
+class EnumDescrList
{
- EnumDescrList& add(Enum value, const wxString& text, const wxString& tooltip = {})
- {
- descrList.push_back({value, {text, tooltip}});
- return *this;
- }
+public:
+ using DescrItem = std::tuple<Enum, wxString /*label*/, wxString /*tooltip*/>;
+
+ EnumDescrList(wxChoice& ctrl, std::vector<DescrItem> list);
+ ~EnumDescrList();
- using DescrList = std::vector<std::pair<Enum, std::pair<wxString, wxString>>>;
- DescrList descrList;
+ void set(Enum value);
+ Enum get() const ;
+ void updateTooltip(); //after user changed selection
- std::unordered_map<const wxChoice*, std::vector<wxString>> labelsSetLast;
+ const std::vector<DescrItem>& getConfig() const { return descrList_; }
+
+private:
+ wxChoice& ctrl_;
+ const std::vector<DescrItem> descrList_;
+ std::vector<wxString> labels_;
};
-template <class Enum> void setEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl, Enum value);
-template <class Enum> Enum getEnumVal(const EnumDescrList<Enum>& mapping, const wxChoice& ctrl);
-template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl);
@@ -69,61 +50,65 @@ template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mappi
//--------------- impelementation -------------------------------------------
template <class Enum>
-void setEnumVal(EnumDescrList<Enum>& mapping, wxChoice& ctrl, Enum value)
+EnumDescrList<Enum>::EnumDescrList(wxChoice& ctrl, std::vector<DescrItem> list) : ctrl_(ctrl), descrList_(std::move(list))
{
- auto& labelsSetLast = mapping.labelsSetLast[&ctrl];
+ for (const auto& [val, label, tooltip] : descrList_)
+ labels_.push_back(label);
- std::vector<wxString> labels;
- for (const auto& [val, texts] : mapping.descrList)
- labels.push_back(texts.first);
+ ctrl_.Set(labels_); //expensive as fuck! => only call when needed!
+}
- if (labels != labelsSetLast)
- {
- ctrl.Set(labels); //expensive as fuck! => only call when absolutely needed!
- labelsSetLast = std::move(labels);
- }
- //-----------------------------------------------------------------
- const auto it = std::find_if(mapping.descrList.begin(), mapping.descrList.end(), [&](const auto& mapItem) { return mapItem.first == value; });
- if (it != mapping.descrList.end())
+template <class Enum> inline
+EnumDescrList<Enum>::~EnumDescrList()
+{
+}
+
+
+template <class Enum>
+void EnumDescrList<Enum>::set(Enum value)
+{
+ const auto it = std::find_if(descrList_.begin(), descrList_.end(), [&](const auto& mapItem) { return std::get<Enum>(mapItem) == value; });
+ if (it != descrList_.end())
{
- if (const wxString& tooltip = it->second.second;
- !tooltip.empty())
- ctrl.SetToolTip(tooltip);
+ const auto& [val, label, tooltip] = *it;
+ if (!tooltip.empty())
+ ctrl_.SetToolTip(tooltip);
else
- ctrl.UnsetToolTip();
+ ctrl_.UnsetToolTip();
- const int selectedPos = it - mapping.descrList.begin();
- ctrl.SetSelection(selectedPos);
+ const int selectedPos = it - descrList_.begin();
+ ctrl_.SetSelection(selectedPos);
}
else assert(false);
}
+
template <class Enum>
-Enum getEnumVal(const EnumDescrList<Enum>& mapping, const wxChoice& ctrl)
+Enum EnumDescrList<Enum>::get() const
{
- const int selectedPos = ctrl.GetSelection();
+ const int selectedPos = ctrl_.GetSelection();
- if (0 <= selectedPos && selectedPos < std::ssize(mapping.descrList))
- return mapping.descrList[selectedPos].first;
- else
- {
- assert(false);
- return Enum(0);
- }
+ if (0 <= selectedPos && selectedPos < std::ssize(descrList_))
+ return std::get<Enum>(descrList_[selectedPos]);
+
+ assert(false);
+ return Enum(0);
}
-template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl)
+
+template <class Enum>
+void EnumDescrList<Enum>::updateTooltip()
{
- const int selectedPos = ctrl.GetSelection();
+ const int selectedPos = ctrl_.GetSelection();
- if (0 <= selectedPos && selectedPos < std::ssize(mapping.descrList))
+ if (0 <= selectedPos && selectedPos < std::ssize(descrList_))
{
- if (const auto& [text, tooltip] = mapping.descrList[selectedPos].second;
- !tooltip.empty())
- ctrl.SetToolTip(tooltip);
+ const auto& [val, label, tooltip] = descrList_[selectedPos];
+ if (!tooltip.empty())
+ ctrl_.SetToolTip(tooltip);
else
- ctrl.UnsetToolTip();
+ ctrl_.UnsetToolTip();
}
else assert(false);
}
diff --git a/wx+/color_tools.h b/wx+/color_tools.h
new file mode 100644
index 00000000..19d34caf
--- /dev/null
+++ b/wx+/color_tools.h
@@ -0,0 +1,234 @@
+// *****************************************************************************
+// * 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 COLOR_TOOLS_H_18301239864123785613
+#define COLOR_TOOLS_H_18301239864123785613
+
+#include <zen/basic_math.h>
+#include <wx/colour.h>
+
+
+namespace zen
+{
+inline
+double srgbDecode(unsigned char c) //https://en.wikipedia.org/wiki/SRGB
+{
+ const double c_ = c / 255.0;
+ return c_ <= 0.04045 ? c_ / 12.92 : std::pow((c_ + 0.055) / 1.055, 2.4);
+}
+
+
+inline
+unsigned char srgbEncode(double c)
+{
+ const double c_ = c <= 0.0031308 ? c * 12.92 : std::pow(c, 1 / 2.4) * 1.055 - 0.055;
+ return std::clamp<int>(std::round(c_ * 255), 0, 255);
+}
+
+
+inline //https://www.w3.org/WAI/GL/wiki/Relative_luminance
+double relLuminance(double r, double g, double b) //input: gamma-decoded sRGB
+{
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b; //= the Y part of CIEXYZ
+}
+
+
+inline
+double relativeLuminance(const wxColor& col) //[0, 1]
+{
+ assert(col.Alpha() == wxALPHA_OPAQUE);
+ return relLuminance(srgbDecode(col.Red()), srgbDecode(col.Green()), srgbDecode(col.Blue()));
+}
+
+
+inline
+double relativeContrast(const wxColor& c1, const wxColor& c2)
+{
+ //https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
+ //https://snook.ca/technical/colour_contrast/colour.html
+ double lum1 = relativeLuminance(c1);
+ double lum2 = relativeLuminance(c2);
+ if (lum1 < lum2)
+ std::swap(lum1, lum2);
+ return (lum1 + 0.05) / (lum2 + 0.05);
+}
+
+
+namespace
+{
+//get first color between [col1, colMax] (assuming direct line in decoded sRGB) where minimum contrast is satisfied against col2
+wxColor enhanceContrast(wxColor col1, wxColor colMax, const wxColor& col2, double contrastRatioMin)
+{
+ /* Caveat: macOS uses partially-transparent colors! e.g. #RGBA
+ wxSYS_COLOUR_GRAYTEXT #FFFFFF3F
+ wxSYS_COLOUR_WINDOWTEXT #FFFFFFD8
+ wxSYS_COLOUR_WINDOW #171717FF */
+ auto alphaBlend = [](const wxColor& front, const wxColor& back)
+ {
+ if (front.Alpha() == wxALPHA_OPAQUE)
+ return front;
+
+ if (back.Alpha() != wxALPHA_OPAQUE)
+ {
+ assert(false);
+ return *wxRED; //make some noise
+ }
+
+ auto calcChannel = [a = front.Alpha()](unsigned char f, unsigned char b)
+ {
+ return static_cast<unsigned char>(numeric::intDivRound(f * a + b * (255 - a), 255));
+ };
+
+ return wxColor(calcChannel(front.Red (), back.Red ()),
+ calcChannel(front.Green(), back.Green()),
+ calcChannel(front.Blue (), back.Blue ()));
+ };
+ col1 = alphaBlend(col1, col2);
+ colMax = alphaBlend(colMax, col2);
+
+ //---------------------------------------------------------------
+ assert(contrastRatioMin >= 4); //lower values (especially near 1) probably aren't sensible mathematically, also: W3C recommends >= 4.5 for base AA compliance
+ auto contrast = [](double lum1, double lum2) //input: relative luminance
+ {
+ if (lum1 < lum2)
+ std::swap(lum1, lum2);
+ return (lum1 + 0.05) / (lum2 + 0.05);
+ };
+ const double r_1 = srgbDecode(col1.Red());
+ const double g_1 = srgbDecode(col1.Green());
+ const double b_1 = srgbDecode(col1.Blue());
+ const double r_m = srgbDecode(colMax.Red());
+ const double g_m = srgbDecode(colMax.Green());
+ const double b_m = srgbDecode(colMax.Blue());
+
+ const double lum_1 = relLuminance(r_1, g_1, b_1);
+ const double lum_m = relLuminance(r_m, g_m, b_m);
+ const double lum_2 = relativeLuminance(col2);
+
+ if (contrast(lum_1, lum_2) >= contrastRatioMin)
+ return col1; //nothing to do!
+
+ if (contrast(lum_m, lum_2) <= contrastRatioMin)
+ {
+ assert(false); //problem!
+ return colMax;
+ }
+
+ if (lum_m < lum_2)
+ contrastRatioMin = 1 / contrastRatioMin;
+
+ const double lum_t = contrastRatioMin * (lum_2 + 0.05) - 0.05; //target luminance
+ const double t = (lum_t - lum_1) / (lum_m - lum_1);
+
+ return wxColor(srgbEncode(t * (r_m - r_1) + r_1),
+ srgbEncode(t * (g_m - g_1) + g_1),
+ srgbEncode(t * (b_m - b_1) + b_1));
+}
+}
+
+inline //get first color between [col1, white/black] (assuming direct line in decoded sRGB) where minimum contrast is satisfied against col2
+wxColor enhanceContrast(const wxColor& col1, const wxColor& col2, double contrastRatioMin)
+{
+ return enhanceContrast(col1,
+ relativeContrast(col2, *wxWHITE) > //equivalent to: relativeLuminance(col2) < 0.1791287847
+ relativeContrast(col2, *wxBLACK) ? //
+ *wxWHITE : *wxBLACK,
+ col2, contrastRatioMin);
+}
+
+
+#if 0
+//toy sample code: gamma-encoded sRGB -> CIEXYZ -> CIELAB and back: input === output RGB color (verified)
+wxColor colorConversion(const wxColor& col)
+{
+ assert(col.GetAlpha() == wxALPHA_OPAQUE);
+ const double r = srgbDecode(col.Red());
+ const double g = srgbDecode(col.Green());
+ const double b = srgbDecode(col.Blue());
+
+ //https://en.wikipedia.org/wiki/SRGB#Correspondence_to_CIE_XYZ_stimulus
+ const double x = 0.4124 * r + 0.3576 * g + 0.1805 * b;
+ const double y = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+ const double z = 0.0193 * r + 0.1192 * g + 0.9505 * b;
+ //-----------------------------------------------
+ //https://en.wikipedia.org/wiki/CIELAB_color_space#Converting_between_CIELAB_and_CIEXYZ_coordinates
+ using numeric::power;
+ auto f = [](double t)
+ {
+ constexpr double delta = 6.0 / 29;
+ return t > power<3>(delta) ?
+ std::pow(t, 1.0 / 3) :
+ t / (3 * power<2>(delta)) + 4.0 / 29;
+ };
+ const double L_ = 116 * f(y) - 16; //[ 0, 100]
+ const double a_ = 500 * (f(x / 0.950489) - f(y)); //[-128, 127]
+ const double b_ = 200 * (f(y) - f(z / 1.088840)); //[-128, 127]
+ //-----------------------------------------------
+ auto f_1 = [](double t)
+ {
+ constexpr double delta = 6.0 / 29;
+ return t > delta ?
+ power<3>(t) :
+ 3 * power<2>(delta) * (t - 4.0 / 29);
+ };
+ const double x2 = 0.950489 * f_1((L_ + 16) / 116 + a_ / 500);
+ const double y2 = f_1((L_ + 16) / 116);
+ const double z2 = 1.088840 * f_1((L_ + 16) / 116 - b_ / 200);
+ //-----------------------------------------------
+ const double r2 = 3.2406255 * x2 + -1.5372080 * y2 + -0.4986286 * z2;
+ const double g2 = -0.9689307 * x2 + 1.8757561 * y2 + 0.0415175 * z2;
+ const double b2 = 0.0557101 * x2 + -0.2040211 * y2 + 1.0569959 * z2;
+
+ return wxColor(srgbEncode(r2), srgbEncode(g2), srgbEncode(b2));
+}
+
+
+//https://en.wikipedia.org/wiki/HSL_and_HSV
+wxColor hsvColor(double h, double s, double v) //h within [0, 360), s, v within [0, 1]
+{
+ //make input values fit into bounds
+ if (h > 360)
+ h -= static_cast<int>(h / 360) * 360;
+ else if (h < 0)
+ h -= static_cast<int>(h / 360) * 360 - 360;
+ s = std::clamp(s, 0.0, 1.0);
+ v = std::clamp(v, 0.0, 1.0);
+ //------------------------------------
+ const int h_i = h / 60;
+ const float f = h / 60 - h_i;
+
+ auto to8Bit = [](double val) -> unsigned char
+ {
+ return std::clamp<int>(std::round(val * 255), 0, 255);
+ };
+
+ const unsigned char p = to8Bit(v * (1 - s));
+ const unsigned char q = to8Bit(v * (1 - s * f));
+ const unsigned char t = to8Bit(v * (1 - s * (1 - f)));
+ const unsigned char vi = to8Bit(v);
+
+ switch (h_i)
+ {
+ case 0:
+ return wxColor(vi, t, p);
+ case 1:
+ return wxColor(q, vi, p);
+ case 2:
+ return wxColor(p, vi, t);
+ case 3:
+ return wxColor(p, q, vi);
+ case 4:
+ return wxColor(t, p, vi);
+ case 5:
+ return wxColor(vi, p, q);
+ }
+ assert(false);
+ return *wxBLACK;
+}
+#endif
+}
+
+#endif //COLOR_TOOLS_H_18301239864123785613
diff --git a/wx+/context_menu.h b/wx+/context_menu.h
index 1e2d4d69..06e84709 100644
--- a/wx+/context_menu.h
+++ b/wx+/context_menu.h
@@ -7,7 +7,6 @@
#ifndef CONTEXT_MENU_H_18047302153418174632141234
#define CONTEXT_MENU_H_18047302153418174632141234
-//#include <map>
#include <vector>
#include <functional>
#include <wx/app.h>
@@ -87,7 +86,7 @@ public:
void popup(wxWindow& wnd, const wxPoint& pos = wxDefaultPosition) //show popup menu + process lambdas
{
- //eventually all events from submenu items will be received by this menu
+ //eventually all events from submenu items will be received by the parent menu
for (const auto& [itemId, command] : commandList_)
menu_->Bind(wxEVT_COMMAND_MENU_SELECTED, [command /*clang bug*/= command](wxCommandEvent& event) { command(); }, itemId);
diff --git a/wx+/darkmode.cpp b/wx+/darkmode.cpp
new file mode 100644
index 00000000..c8a7ea28
--- /dev/null
+++ b/wx+/darkmode.cpp
@@ -0,0 +1,110 @@
+// *****************************************************************************
+// * 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 *
+// *****************************************************************************
+
+#include "darkmode.h"
+#include <zen/sys_version.h>
+#include <wx/settings.h>
+#include "color_tools.h"
+ #include <gtk/gtk.h>
+
+using namespace zen;
+
+
+bool zen::darkModeAvailable()
+{
+
+#if GTK_MAJOR_VERSION == 2
+ return false;
+#elif GTK_MAJOR_VERSION >= 3
+ return true;
+#else
+#error unknown GTK version!
+#endif
+
+}
+
+
+namespace
+{
+class SysColorsHook : public wxColorHook
+{
+public:
+
+ wxColor getColor(wxSystemColour index) const override
+ {
+ //fix contrast e.g. Ubuntu's Adwaita-Dark theme and macOS dark mode:
+ if (index == wxSYS_COLOUR_GRAYTEXT)
+ return colGreyTextEnhContrast_;
+#if 0
+ auto colToString = [](wxColor c)
+ {
+ const auto& [rh, rl] = hexify(c.Red ());
+ const auto& [gh, gl] = hexify(c.Green());
+ const auto& [bh, bl] = hexify(c.Blue ());
+ const auto& [ah, al] = hexify(c.Alpha());
+ return "#" + std::string({rh, rl, gh, gl, bh, bl, ah, al});
+ };
+ std::cerr << "wxSYS_COLOUR_GRAYTEXT " << colToString(wxSystemSettingsNative::GetColour(wxSYS_COLOUR_GRAYTEXT)) << "\n";
+#endif
+ return wxSystemSettingsNative::GetColour(index); //fallback
+ }
+
+private:
+ const wxColor colGreyTextEnhContrast_ =
+ enhanceContrast(wxSystemSettingsNative::GetColour(wxSYS_COLOUR_GRAYTEXT),
+ wxSystemSettingsNative::GetColour(wxSYS_COLOUR_WINDOWTEXT),
+ wxSystemSettingsNative::GetColour(wxSYS_COLOUR_WINDOW), 4.5 /*contrastRatioMin*/); //W3C recommends >= 4.5
+};
+
+
+std::optional<bool> globalDefaultThemeIsDark;
+}
+
+
+void zen::colorThemeInit(wxApp& app, ColorTheme colTheme) //throw FileError
+{
+ assert(!refGlobalColorHook());
+
+ globalDefaultThemeIsDark = wxSystemSettings::GetAppearance().AreAppsDark();
+ ZEN_ON_SCOPE_EXIT(if (!refGlobalColorHook()) refGlobalColorHook() = std::make_unique<SysColorsHook>()); //*after* SetAppearance() and despite errors
+
+ //caveat: on macOS there are more themes than light/dark: https://developer.apple.com/documentation/appkit/nsappearance/name-swift.struct
+ if (colTheme != ColorTheme::System && //"System" is already the default for macOS/Linux(GTK3)
+ darkModeAvailable())
+ changeColorTheme(colTheme); //throw FileError
+}
+
+
+void zen::colorThemeCleanup()
+{
+ assert(refGlobalColorHook());
+ refGlobalColorHook().reset();
+}
+
+
+bool zen::equalAppearance(ColorTheme colTheme1, ColorTheme colTheme2)
+{
+ if (colTheme1 == ColorTheme::System) colTheme1 = *globalDefaultThemeIsDark ? ColorTheme::Dark : ColorTheme::Light;
+ if (colTheme2 == ColorTheme::System) colTheme2 = *globalDefaultThemeIsDark ? ColorTheme::Dark : ColorTheme::Light;
+ return colTheme1 == colTheme2;
+}
+
+
+void zen::changeColorTheme(ColorTheme colTheme) //throw FileError
+{
+ if (colTheme == ColorTheme::System) //SetAppearance(System) isn't working reliably! surprise!?
+ colTheme = *globalDefaultThemeIsDark ? ColorTheme::Dark : ColorTheme::Light;
+
+ try
+ {
+ ZEN_ON_SCOPE_SUCCESS(refGlobalColorHook() = std::make_unique<SysColorsHook>()); //*after* SetAppearance()
+ if (wxApp::AppearanceResult rv = wxTheApp->SetAppearance(colTheme);
+ rv != wxApp::AppearanceResult::Ok)
+ throw SysError(formatSystemError("wxApp::SetAppearance",
+ rv == wxApp::AppearanceResult::CannotChange ? L"CannotChange" : L"Failure", L"" /*errorMsg*/));
+ }
+ catch (const SysError& e) { throw FileError(_("Failed to update the color theme."), e.toString()); }
+}
diff --git a/wx+/darkmode.h b/wx+/darkmode.h
new file mode 100644
index 00000000..91d2a789
--- /dev/null
+++ b/wx+/darkmode.h
@@ -0,0 +1,28 @@
+// *****************************************************************************
+// * 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 DARKMODE_H_754298057018
+#define DARKMODE_H_754298057018
+
+#include <zen/file_error.h>
+#include <wx/app.h>
+
+
+namespace zen
+{
+bool darkModeAvailable();
+
+//support not only "dark mode" but dark themes in general
+using ColorTheme = wxApp::Appearance; //why reinvent the wheel?
+
+void colorThemeInit(wxApp& app, ColorTheme colTheme); //throw FileError
+void colorThemeCleanup();
+
+bool equalAppearance(ColorTheme colTheme1, ColorTheme colTheme2);
+void changeColorTheme(ColorTheme colTheme); //throw FileError
+}
+
+#endif //DARKMODE_H_754298057018
diff --git a/wx+/dc.h b/wx+/dc.h
index 161765a0..9cb31e17 100644
--- a/wx+/dc.h
+++ b/wx+/dc.h
@@ -9,29 +9,12 @@
#include <unordered_map>
#include <optional>
-//#include <zen/basic_math.h>
#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER
#include <wx/dcscreen.h>
-//#include <wx/bmpbndl.h>
- // #include <gtk/gtk.h>
namespace zen
{
-/* 1. wxDCClipper does *not* stack: another fix for yet another poor wxWidgets implementation
-
- class RecursiveDcClipper
- {
- RecursiveDcClipper(wxDC& dc, const wxRect& r)
- };
-
- 2. wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active: a fix for a poor wxWidgets implementation
-
- class BufferedPaintDC
- {
- BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer)
- }; */
-
inline
void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col)
{
@@ -176,44 +159,68 @@ wxRect getIntersection(const wxRect& rect1, const wxRect& rect2)
//---------------------- implementation ------------------------
-class RecursiveDcClipper
+class RecursiveDcClipper //wxDCClipper does *not* stack => fix for yet another poor wxWidgets implementation:
{
public:
RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc)
{
- if (auto it = clippingAreas_.find(&dc_);
+ if (auto it = clippingAreas_.find(&dc);
it != clippingAreas_.end())
{
oldRect_ = it->second;
const wxRect tmp = getIntersection(r, *oldRect_); //better safe than sorry
-
assert(!tmp.IsEmpty()); //"setting an empty clipping region is equivalent to DestroyClippingRegion()"
- dc_.SetClippingRegion(tmp); //new clipping region is intersection of given and previously set regions
- it->second = tmp;
+ if (tmp != *oldRect_)
+ {
+ dc.SetClippingRegion(tmp); //new clipping region is intersection of given and previously set regions
+ it->second = tmp;
+ clippingDone = true;
+ }
}
else
{
- //caveat: actual clipping region is smaller when rect is not fully inside the DC
+ const wxRect dcArea(dc.GetSize());
+
+ //since wxWidgets 3.3.0 the DC may be pre-clipped to wxDC::GetSize() or smaller (related to double-buffering)
+ //=> consider "no clipping" and "clipped to wxDC::GetSize()" equivalent!
+ wxRect rectClip;
+ if (dc.GetClippingBox(rectClip))
+ {
+ rectClip = getIntersection(rectClip, dcArea);
+ if (rectClip != dcArea)
+ oldRect_ = rectClip;
+ }
+
+ //caveat: actual clipping region is smaller when rect is partially outside the DC
//=> ensure consistency for validateClippingBuffer()
- const wxRect tmp = getIntersection(r, wxRect(dc.GetSize()));
+ const wxRect tmp = getIntersection(r, oldRect_? *oldRect_ : dcArea);
+ assert(!tmp.IsEmpty());
- dc_.SetClippingRegion(tmp);
- clippingAreas_.emplace(&dc_, tmp);
+ if (tmp != (oldRect_? *oldRect_ : dcArea))
+ {
+ dc.SetClippingRegion(tmp);
+ clippingAreas_.emplace(&dc, tmp);
+ clippingDone = true;
+ recursionBegin_ = true;
+ }
}
}
~RecursiveDcClipper()
{
- dc_.DestroyClippingRegion();
- if (oldRect_)
+ if (clippingDone)
{
- dc_.SetClippingRegion(*oldRect_);
- clippingAreas_[&dc_] = *oldRect_;
+ dc_.DestroyClippingRegion();
+ if (oldRect_)
+ dc_.SetClippingRegion(*oldRect_);
+
+ if (recursionBegin_)
+ clippingAreas_.erase(&dc_);
+ else
+ clippingAreas_[&dc_] = *oldRect_;
}
- else
- clippingAreas_.erase(&dc_);
}
private:
@@ -224,25 +231,21 @@ private:
//associate "active" clipping area with each DC
inline static std::unordered_map<wxDC*, wxRect> clippingAreas_;
+ bool recursionBegin_ = false;
+ bool clippingDone = false;
std::optional<wxRect> oldRect_;
wxDC& dc_;
};
-#ifndef wxALWAYS_NATIVE_DOUBLE_BUFFER
- #error we need this one!
-#endif
-
-//CAVEAT: wxPaintDC on wxGTK/wxMAC does not implement SetLayoutDirection()!!! => GetLayoutDirection() == wxLayout_Default
-#if wxALWAYS_NATIVE_DOUBLE_BUFFER
-struct BufferedPaintDC : public wxPaintDC { BufferedPaintDC(wxWindow& wnd, std::optional<wxBitmap>& buffer) : wxPaintDC(&wnd) {} };
-
-#else
+//fix wxBufferedPaintDC: happily fucks up for RTL layout by not drawing the first column (x = 0)!
class BufferedPaintDC : public wxMemoryDC
{
public:
BufferedPaintDC(wxWindow& wnd, std::optional<wxBitmap>& buffer) : buffer_(buffer), paintDc_(&wnd)
{
+ assert(!wnd.IsDoubleBuffered());
+
const wxSize clientSize = wnd.GetClientSize();
if (clientSize.GetWidth() > 0 && clientSize.GetHeight() > 0) //wxBitmap asserts this!! width can be 0; test case "Grid::CornerWin": compare both sides, then change config
{
@@ -254,6 +257,7 @@ public:
SelectObject(*buffer); //copies scale factor from wxBitmap
+ //note: wxPaintDC on wxGTK/wxMAC does not implement SetLayoutDirection()!!! => GetLayoutDirection() == wxLayout_Default
if (paintDc_.IsOk() && paintDc_.GetLayoutDirection() == wxLayout_RightToLeft)
SetLayoutDirection(wxLayout_RightToLeft);
}
@@ -283,7 +287,6 @@ private:
std::optional<wxBitmap>& buffer_;
wxPaintDC paintDc_;
};
-#endif
}
#endif //DC_H_4987123956832143243214
diff --git a/wx+/file_drop.h b/wx+/file_drop.h
index e9cb9e72..2474ae45 100644
--- a/wx+/file_drop.h
+++ b/wx+/file_drop.h
@@ -8,7 +8,6 @@
#define FILE_DROP_H_09457802957842560325626
#include <vector>
-//#include <functional>
#include <zen/zstring.h>
#include <wx/window.h>
#include <wx/event.h>
diff --git a/wx+/graph.cpp b/wx+/graph.cpp
index da5c1d98..e589976d 100644
--- a/wx+/graph.cpp
+++ b/wx+/graph.cpp
@@ -7,15 +7,14 @@
#include "graph.h"
#include <cassert>
#include <algorithm>
-//#include <numeric>
#include <zen/basic_math.h>
#include <zen/scope_guard.h>
-//#include <zen/perf.h>
using namespace zen;
-//todo: support zoom via mouse wheel?
+
+//TODO: support zoom via mouse wheel?
namespace zen
{
@@ -139,7 +138,8 @@ int widenRange(double& valMin, double& valMax, //in/out
}
-void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const ConvertCoord& cvrtX, const wxRect& graphArea, const wxRect& labelArea, const LabelFormatter& labelFmt)
+void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const ConvertCoord& cvrtX, const wxRect& graphArea,
+ const wxRect& labelArea, const LabelFormatter& labelFmt, const wxColor& colGridLine)
{
assert(graphArea.width == labelArea.width && graphArea.x == labelArea.x);
if (blockCount <= 0)
@@ -153,7 +153,7 @@ void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const Conver
const int x = graphArea.x + cvrtX.realToScreenRound(valX);
//draw grey vertical lines
- clearArea(dc, {x - dipToWxsize(1) / 2, graphArea.y, dipToWxsize(1), graphArea.height}, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal...
+ clearArea(dc, {x - dipToWxsize(1) / 2, graphArea.y, dipToWxsize(1), graphArea.height}, colGridLine);
//draw x axis labels
const wxString label = labelFmt.formatText(valX, valRangePerBlock);
@@ -163,7 +163,8 @@ void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const Conver
}
-void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const ConvertCoord& cvrtY, const wxRect& graphArea, const wxRect& labelArea, const LabelFormatter& labelFmt)
+void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const ConvertCoord& cvrtY, const wxRect& graphArea,
+ const wxRect& labelArea, const LabelFormatter& labelFmt, const wxColor& colGridLine)
{
assert(graphArea.height == labelArea.height && graphArea.y == labelArea.y);
if (blockCount <= 0)
@@ -177,7 +178,7 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver
const double valY = yMin + i * valRangePerBlock; //step over raw data, not graph area pixels, to not lose precision
const int y = graphArea.y + cvrtY.realToScreenRound(valY);
- clearArea(dc, {graphArea.x, y - dipToWxsize(1) / 2, graphArea.width, dipToWxsize(1)}, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal...
+ clearArea(dc, {graphArea.x, y - dipToWxsize(1) / 2, graphArea.width, dipToWxsize(1)}, colGridLine);
//draw y axis labels
const wxString label = labelFmt.formatText(valY, valRangePerBlock);
@@ -436,13 +437,13 @@ Graph2D::Graph2D(wxWindow* parent,
long style,
const wxString& name) : wxPanel(parent, winid, pos, size, style, name)
{
+#ifdef FFS_CUSTOM_DOUBLE_BUFFERING
+ MSWDisableComposited();
+#endif
+ //https://wiki.wxwidgets.org/Flicker-Free_Drawing
+ SetBackgroundStyle(wxBG_STYLE_PAINT); //get's rid of needless wxEVT_ERASE_BACKGROUND
Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { onPaintEvent(event); });
- Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { Refresh(); event.Skip(); });
- Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //https://wiki.wxwidgets.org/Flicker-Free_Drawing
-
- //SetDoubleBuffered(true); slow as hell!
-
- SetBackgroundStyle(wxBG_STYLE_PAINT);
+ Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { Refresh(); event.Skip(); });
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { onMouseLeftDown(event); });
Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { onMouseMovement(event); });
@@ -453,8 +454,12 @@ Graph2D::Graph2D(wxWindow* parent,
void Graph2D::onPaintEvent(wxPaintEvent& event)
{
- //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)!
+#ifdef FFS_CUSTOM_DOUBLE_BUFFERING
BufferedPaintDC dc(*this, doubleBuffer_);
+#else
+ wxPaintDC dc(this);
+ static_assert(wxALWAYS_NATIVE_DOUBLE_BUFFER);
+#endif
render(dc);
}
@@ -569,7 +574,7 @@ void Graph2D::render(wxDC& dc) const
assert(attr_.yLabelpos == YLabelPos::none || attr_.labelFmtY);
//paint graph background (excluding label area)
- drawFilledRectangle(dc, graphArea, attr_.colorBack, getBorderColor(), dipToWxsize(1));
+ drawFilledRectangle(dc, graphArea, attr_.colorBack, attr_.colorGridLine, dipToWxsize(1));
graphArea.Deflate(dipToWxsize(1));
//set label areas respecting graph area border!
@@ -759,8 +764,8 @@ void Graph2D::render(wxDC& dc) const
}
//3. draw labels and background grid
- 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);
+ if (attr_.labelFmtX) drawXLabel(dc, minX, maxX, blockCountX, cvrtX, graphArea, xLabelArea, *attr_.labelFmtX, attr_.colorGridLine);
+ if (attr_.labelFmtY) drawYLabel(dc, minY, maxY, blockCountY, cvrtY, graphArea, yLabelArea, *attr_.labelFmtY, attr_.colorGridLine);
//4. finally draw curves
{
diff --git a/wx+/graph.h b/wx+/graph.h
index d7ea51b5..1bd06635 100644
--- a/wx+/graph.h
+++ b/wx+/graph.h
@@ -14,6 +14,7 @@
#include <wx/settings.h>
#include <wx/bitmap.h>
#include <zen/string_tools.h>
+#include "color_tools.h"
#include "dc.h"
@@ -218,11 +219,16 @@ public:
void addCurve(const SharedRef<CurveData>& data, const CurveAttributes& ca = CurveAttributes());
void clearCurves() { curves_.clear(); }
- static wxColor getBorderColor() { return {130, 135, 144}; } //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal...
-
class MainAttributes
{
public:
+ MainAttributes()
+ {
+ //accessibility: consider system text and background colors;
+ //small drawback: color of graphs is NOT related to the background! => responsibility of client to use correct colors
+ setBaseColors(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),
+ wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+ }
MainAttributes& setMinX(double newMinX) { minX = newMinX; return *this; }
MainAttributes& setMaxX(double newMaxX) { maxX = newMaxX; return *this; }
@@ -248,8 +254,16 @@ public:
MainAttributes& setCornerText(const wxString& txt, GraphCorner pos) { cornerTexts[pos] = txt; return *this; }
- //accessibility: always set both colors
- MainAttributes& setBaseColors(const wxColor& text, const wxColor& back) { colorText = text; colorBack = back; return *this; }
+ MainAttributes& setBaseColors(const wxColor& text, const wxColor& back) //accessibility: always set both colors
+ {
+ colorText = text;
+ colorBack = back;
+ colorGridLine = enhanceContrast(colorBack, //start with back color and deviate only as little as required
+ colorText, colorBack, 4 /*contrastRatioMin*/); //W3C recommends >= 4.5 for text
+ return *this;
+ }
+
+ wxColor getGridLineColor() const { return colorGridLine; }
MainAttributes& setSelectionMode(GraphSelMode mode) { mouseSelMode = mode; return *this; }
@@ -272,10 +286,9 @@ public:
std::map<GraphCorner, wxString> cornerTexts;
- //accessibility: consider system text and background colors;
- //small drawback: color of graphs is NOT connected to the background! => responsibility of client to use correct colors
- wxColor colorText = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
- wxColor colorBack = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+ wxColor colorText;
+ wxColor colorBack;
+ wxColor colorGridLine;
GraphSelMode mouseSelMode = GraphSelMode::rect;
};
@@ -324,9 +337,9 @@ private:
MainAttributes attr_; //global attributes
- std::optional<wxBitmap> doubleBuffer_;
-
std::vector<std::pair<SharedRef<CurveData>, CurveAttributes>> curves_;
+
+ std::optional<wxBitmap> doubleBuffer_;
};
}
diff --git a/wx+/grid.cpp b/wx+/grid.cpp
index 3665c6cf..147feef0 100644
--- a/wx+/grid.cpp
+++ b/wx+/grid.cpp
@@ -6,19 +6,17 @@
#include "grid.h"
#include <cassert>
-//#include <set>
#include <chrono>
#include <wx/settings.h>
-//#include <wx/listbox.h>
#include <wx/tooltip.h>
#include <wx/timer.h>
-//#include <wx/utils.h>
#include <zen/basic_math.h>
#include <zen/string_tools.h>
#include <zen/scope_guard.h>
#include <zen/utf.h>
#include <zen/zstring.h>
#include <zen/format_unit.h>
+#include "color_tools.h"
#include "dc.h"
#include <gtk/gtk.h>
@@ -26,6 +24,16 @@
using namespace zen;
+/* wxWidgets 3.3 defaults to system-powered double-buffering (WS_EX_COMPOSITED) on Windows:
+ => ~60% higher CPU time (test case: scrolling large file list via keyboard) :((
+ => opt-out!
+
+ "wxMSW now uses double buffering by default, meaning that updating the
+ windows using wxClientDC doesn't work any longer, which is consistent with
+ the behaviour of wxGTK with Wayland backend and of wxOSX, but not with the
+ traditional historic behaviour of wxMSW (or wxGTK/X11). You may call
+ MSWDisableComposited() to restore the previous behaviour [...]" */
+
//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
@@ -36,14 +44,28 @@ int GridData::getColumnGapLeft() { return dipToWxsize(4); }
namespace
{
//------------------------------ Grid Parameters --------------------------------
-inline wxColor getColorLabelText(bool enabled) { return wxSystemSettings::GetColour(enabled ? wxSYS_COLOUR_BTNTEXT : wxSYS_COLOUR_GRAYTEXT); }
-inline wxColor getColorGridLine() { return wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW); }
+wxColor getColorLabelText(bool enabled) { return wxSystemSettings::GetColour(enabled ? wxSYS_COLOUR_BTNTEXT : wxSYS_COLOUR_GRAYTEXT); }
+wxColor getColorGridLine() { return wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW); }
+
+wxColor getColorLabelGradientFrom()
+{
+ if (wxSystemSettings::GetAppearance().IsDark()) //upper gradient part must always be lighter than lower part!
+ {
+ const wxColor backCol = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
-inline wxColor getColorLabelGradientFrom() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); }
-inline wxColor getColorLabelGradientTo () { return wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); }
+ auto liftChannel = [](unsigned char c) { return static_cast<unsigned char>(std::clamp(c + 25, 0, 255)); };
-inline wxColor getColorLabelGradientFocusFrom() { return getColorLabelGradientFrom(); }
-inline wxColor getColorLabelGradientFocusTo () { return GridData::getColorSelectionGradientFrom(); }
+ return wxColor(liftChannel(backCol.Red ()),
+ liftChannel(backCol.Green()),
+ liftChannel(backCol.Blue ()));
+ }
+ else
+ return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+}
+wxColor getColorLabelGradientTo() { return wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); }
+
+wxColor getColorLabelGradientFocusFrom() { return wxSystemSettings::GetAppearance().IsDark() ? wxSystemSettings::GetColour(wxSYS_COLOUR_BTNHIGHLIGHT) : getColorLabelGradientFrom(); }
+wxColor getColorLabelGradientFocusTo () { return wxSystemSettings::GetAppearance().IsDark() ? getColorLabelGradientTo() : GridData::getColorSelectionGradientFrom(); }
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
@@ -127,7 +149,7 @@ void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType c
}
-int GridData::getBestSize(wxDC& dc, size_t row, ColumnType colType)
+int GridData::getBestSize(const wxReadOnlyDC& dc, size_t row, ColumnType colType)
{
return dc.GetTextExtent(getValue(row, colType)).GetWidth() + 2 * getColumnGapLeft() + dipToWxsize(1); //gap on left and right side + border
}
@@ -264,10 +286,10 @@ public:
wxWindow(&parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBORDER_NONE, wxASCII_STR(wxPanelNameStr)),
parent_(parent)
{
+ //https://wiki.wxwidgets.org/Flicker-Free_Drawing
+ SetBackgroundStyle(wxBG_STYLE_PAINT); //get's rid of needless wxEVT_ERASE_BACKGROUND
Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { onPaintEvent(event); });
Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { Refresh(); event.Skip(); });
- Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //https://wiki.wxwidgets.org/Flicker-Free_Drawing
- SetBackgroundStyle(wxBG_STYLE_PAINT);
//SetDoubleBuffered(true); -> slow as hell!
Bind(wxEVT_CHILD_FOCUS, [](wxChildFocusEvent& event) {}); //wxGTK::wxScrolledWindow automatically scrolls to child window when child gets focus -> prevent!
@@ -376,9 +398,13 @@ private:
void onPaintEvent(wxPaintEvent& event)
{
- //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)!
- BufferedPaintDC dc(*this, doubleBuffer_);
+#ifdef FFS_CUSTOM_DOUBLE_BUFFERING
+ BufferedPaintDC dc(*this, doubleBuffer_);
+#else
+ wxPaintDC dc(this);
+ static_assert(wxALWAYS_NATIVE_DOUBLE_BUFFER);
+#endif
assert(GetSize() == GetClientSize());
const wxRegion& updateReg = GetUpdateRegion();
@@ -387,7 +413,9 @@ private:
}
Grid& parent_;
+#ifdef FFS_CUSTOM_DOUBLE_BUFFERING
std::optional<wxBitmap> doubleBuffer_;
+#endif
int mouseRotateRemainder_ = 0;
};
@@ -442,8 +470,7 @@ public:
int getBestWidth(ptrdiff_t rowFrom, ptrdiff_t rowTo)
{
- wxClientDC dc(this);
-
+ wxInfoDC dc(this);
dc.SetFont(GetFont()); //harmonize with RowLabelWin::render()!
int bestWidth = 0;
@@ -676,9 +703,9 @@ private:
const int markerWidth = dipToWxsize(COLUMN_MOVE_MARKER_WIDTH_DIP);
if (col + 1 == activeClickOrMove_->refColumnTo()) //handle pos 1, 2, .. up to "at end" position
- dc.GradientFillLinear(wxRect(rect.x + rect.width - markerWidth, rect.y, markerWidth, rect.height), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH);
+ dc.GradientFillLinear(wxRect(rect.x + rect.width - markerWidth, rect.y, markerWidth, rect.height), getColorLabelGradientFrom(), colDropMarkerColor_, wxSOUTH);
else if (col == activeClickOrMove_->refColumnTo() && col == 0) //pos 0
- dc.GradientFillLinear(wxRect(rect.GetTopLeft(), wxSize(markerWidth, rect.height)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH);
+ dc.GradientFillLinear(wxRect(rect.GetTopLeft(), wxSize(markerWidth, rect.height)), getColorLabelGradientFrom(), colDropMarkerColor_, wxSOUTH);
}
}
}
@@ -925,14 +952,13 @@ private:
int colLabelHeight_ = 0;
const wxFont labelFont_;
+
+ const wxColor colDropMarkerColor_ = enhanceContrast(*wxBLUE, //primarily needed for dark mode!
+ wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),
+ getColorLabelGradientTo(), 5 /*contrastRatioMin*/); //W3C recommends >= 4.5
};
//----------------------------------------------------------------------------------------------------------------
-namespace
-{
-wxDEFINE_EVENT(EVENT_GRID_HAS_SCROLLED, wxCommandEvent);
-}
-//----------------------------------------------------------------------------------------------------------------
class Grid::MainWin : public SubWindow
{
@@ -943,8 +969,6 @@ public:
rowLabelWin_(rowLabelWin),
colLabelWin_(colLabelWin)
{
- Bind(EVENT_GRID_HAS_SCROLLED, [this](wxCommandEvent& event) { onRequestWindowUpdate(event); });
-
Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event)
{
if (event.GetKeyCode() == WXK_ESCAPE && activeSelection_) //allow Escape key to cancel active selection!
@@ -1082,7 +1106,7 @@ private:
{
if (0 <= row && row < rowCount && cpi.colType != ColumnType::none)
{
- wxClientDC dc(this);
+ wxInfoDC dc(this);
dc.SetFont(GetFont());
return prov->getMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
}
@@ -1116,7 +1140,7 @@ private:
{
if (0 <= row && row < rowCount && cpi.colType != ColumnType::none)
{
- wxClientDC dc(this);
+ wxInfoDC dc(this);
dc.SetFont(GetFont());
return prov->getMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
}
@@ -1213,7 +1237,7 @@ private:
{
if (0 <= row && row < rowCount && cpi.colType != ColumnType::none)
{
- wxClientDC dc(this);
+ wxInfoDC dc(this);
dc.SetFont(GetFont());
return prov->getMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
}
@@ -1247,7 +1271,7 @@ private:
{
if (0 <= row && row < rowCount && cpi.colType != ColumnType::none)
{
- wxClientDC dc(this);
+ wxInfoDC dc(this);
dc.SetFont(GetFont());
return prov->getMouseHover(dc, row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
}
@@ -1403,17 +1427,16 @@ private:
if (!gridUpdatePending_) //without guarding, the number of outstanding async events can become very high during scrolling!! test case: Ubuntu: 170; Windows: 20
{
gridUpdatePending_ = true;
- GetEventHandler()->AddPendingEvent(wxCommandEvent(EVENT_GRID_HAS_SCROLLED)); //asynchronously call updateAfterScroll()
- }
- }
- void onRequestWindowUpdate(wxEvent& event)
- {
- assert(gridUpdatePending_);
- ZEN_ON_SCOPE_EXIT(gridUpdatePending_ = false);
+ GetEventHandler()->CallAfter([this]
+ {
+ refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! -> still a problem, now that this function is called async??
+ rowLabelWin_.Update(); //update while dragging scroll thumb
- refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! -> still a problem, now that this function is called async??
- rowLabelWin_.Update(); //update while dragging scroll thumb
+ assert(gridUpdatePending_);
+ gridUpdatePending_ = false;
+ });
+ }
}
void refreshRow(size_t row)
@@ -1474,6 +1497,13 @@ Grid::Grid(wxWindow* parent,
colLabelWin_ = new ColLabelWin(*this); //
mainWin_ = new MainWin (*this, *rowLabelWin_, *colLabelWin_); //
+#ifdef FFS_CUSTOM_DOUBLE_BUFFERING
+ cornerWin_ ->MSWDisableComposited();
+ rowLabelWin_->MSWDisableComposited();
+ colLabelWin_->MSWDisableComposited();
+ mainWin_ ->MSWDisableComposited();
+#endif
+
SetTargetWindow(mainWin_);
SetInitialSize(size); //"Most controls will use this to set their initial size" -> why not
@@ -1481,11 +1511,12 @@ Grid::Grid(wxWindow* parent,
assert(GetClientSize() == GetSize() && GetWindowBorderSize() == wxSize()); //borders are NOT allowed for Grid
//reason: updateWindowSizes() wants to use "GetSize()" as a "GetClientSize()" including scrollbars
- Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { wxPaintDC dc(this); });
- Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { updateWindowSizes(); event.Skip(); });
- Bind(wxEVT_ERASE_BACKGROUND, [](wxEraseEvent& event) {}); //https://wiki.wxwidgets.org/Flicker-Free_Drawing
-
- Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event); });
+ //https://wiki.wxwidgets.org/Flicker-Free_Drawing
+ SetBackgroundStyle(wxBG_STYLE_PAINT); //get's rid of needless wxEVT_ERASE_BACKGROUND
+ //wxEVT_PAINT: "If you have an EVT_PAINT() handler, you must create a wxPaintDC object within it even if you don't actually use it."
+ //=> and if not, wxScrollHelperEvtHandler::ProcessEvent() helps out and creates wxPaintDC (without rendering anything)
+ Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { updateWindowSizes(); event.Skip(); });
+ Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& event) { onKeyDown(event); });
}
@@ -2260,7 +2291,7 @@ int Grid::getBestColumnSize(size_t col) const
{
const ColumnType type = visibleCols_[col].type;
- wxClientDC dc(mainWin_);
+ wxInfoDC dc(mainWin_);
dc.SetFont(mainWin_->GetFont()); //harmonize with MainWin::render()
const auto& [rowFirst, rowLast] = getVisibleRows(mainWin_->GetClientRect());
diff --git a/wx+/grid.h b/wx+/grid.h
index 57a8ffe3..9ee811b5 100644
--- a/wx+/grid.h
+++ b/wx+/grid.h
@@ -8,7 +8,6 @@
#define GRID_H_834702134831734869987
#include <memory>
-//#include <numeric>
#include <optional>
#include <vector>
#include <zen/stl_tools.h>
@@ -108,9 +107,9 @@ public:
virtual std::wstring getValue(size_t row, ColumnType colType) const = 0;
virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover); //default implementation
virtual void renderCell (wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover);
- virtual int getBestSize (wxDC& dc, size_t row, ColumnType colType); //must correspond to renderCell()!
- virtual HoverArea getMouseHover (wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::none; }
- virtual std::wstring getToolTip ( size_t row, ColumnType colType, HoverArea rowHover) { return std::wstring(); }
+ virtual int getBestSize (const wxReadOnlyDC& dc, size_t row, ColumnType colType); //must correspond to renderCell()!
+ virtual HoverArea getMouseHover(const wxReadOnlyDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::none; }
+ virtual std::wstring getToolTip (size_t row, ColumnType colType, HoverArea rowHover) { return std::wstring(); }
//label area:
virtual std::wstring getColumnLabel(ColumnType colType) const = 0;
diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp
index 3e162651..14d41a8e 100644
--- a/wx+/image_resources.cpp
+++ b/wx+/image_resources.cpp
@@ -5,7 +5,6 @@
// *****************************************************************************
#include "image_resources.h"
-//#include <map>
#include <zen/utf.h>
#include <zen/thread.h>
#include <zen/file_io.h>
@@ -74,7 +73,7 @@ auto createScalerTask(const std::string& imageName, const wxImage& img, int hqSc
assert(runningOnMainThread());
return [imageName,
width = img.GetWidth(), //
- height = img.GetHeight(), //don't call wxWidgets functions from worker thread
+ height = img.GetHeight(), //don't call these wxWidgets functions from worker thread
rgb = img.GetData(), //
alpha = img.GetAlpha(), //
hqScale, &protResult]
diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp
index 4dfa7b30..cefc800e 100644
--- a/wx+/image_tools.cpp
+++ b/wx+/image_tools.cpp
@@ -9,6 +9,7 @@
#include <zen/scope_guard.h>
#include <wx/app.h>
#include <wx/dcmemory.h>
+#include <wx/settings.h>
#include <wx+/dc.h>
#include <xBRZ/src/xbrz_tools.h>
@@ -188,6 +189,8 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const
if (maxWidth == 0 || lineHeight == 0)
return wxNullImage;
+ const bool darkMode = wxSystemSettings::GetAppearance().IsDark(); //small but noticeable difference for "ClearType"
+
wxBitmap newBitmap(wxsizeToScreen(maxWidth),
wxsizeToScreen(static_cast<int>(lineHeight * lineInfo.size()))); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes
newBitmap.SetScaleFactor(getScreenDpiScale());
@@ -198,11 +201,11 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const
if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
dc.SetLayoutDirection(wxLayout_RightToLeft); //handle e.g. "weak" bidi characters: -> arrows in hebrew/arabic
- dc.SetBackground(*wxWHITE_BRUSH);
+ dc.SetBackground(darkMode ? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
dc.Clear();
- dc.SetTextForeground(*wxBLACK); //for proper alpha-channel calculation
- dc.SetTextBackground(*wxWHITE); //
+ dc.SetTextBackground(darkMode ? *wxBLACK : *wxWHITE); //for proper alpha-channel calculation
+ dc.SetTextForeground(darkMode ? *wxWHITE : *wxBLACK); //
int posY = 0;
for (const auto& [lineText, lineSize] : lineInfo)
@@ -225,6 +228,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const
}
//wxDC::DrawLabel() doesn't respect alpha channel => calculate alpha values manually:
+ //gamma correction? does not seem to apply here!
wxImage output(newBitmap.ConvertToImage());
output.SetAlpha();
@@ -232,15 +236,29 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const
unsigned char* alpha = output.GetAlpha();
const int pixelCount = output.GetWidth() * output.GetHeight();
- for (int i = 0; i < pixelCount; ++i)
- {
- //black(0,0,0) becomes wxIMAGE_ALPHA_OPAQUE(255), while white(255,255,255) becomes wxIMAGE_ALPHA_TRANSPARENT(0)
- //gamma correction? does not seem to apply here!
- *alpha++ = static_cast<unsigned char>(numeric::intDivRound(3 * 255 - rgb[0] - rgb[1] - rgb[2], 3)); //mixed-mode arithmetics!
- *rgb++ = col.Red (); //
- *rgb++ = col.Green(); //apply actual text color
- *rgb++ = col.Blue (); //
- }
+ const unsigned char r = col.Red (); //
+ const unsigned char g = col.Green(); //getting RGB involves virtual function calls!
+ const unsigned char b = col.Blue (); //
+
+ if (darkMode)
+ for (int i = 0; i < pixelCount; ++i)
+ {
+ //black(0,0,0) becomes wxIMAGE_ALPHA_TRANSPARENT(0), white(255,255,255) becomes wxIMAGE_ALPHA_OPAQUE(255)
+ *alpha++ = static_cast<unsigned char>(numeric::intDivRound(rgb[0] + rgb[1] + rgb[2], 3)); //mixed-mode arithmetics!
+ *rgb++ = r; //
+ *rgb++ = g; //apply actual text color
+ *rgb++ = b; //
+ }
+ else
+ for (int i = 0; i < pixelCount; ++i)
+ {
+ //black(0,0,0) becomes wxIMAGE_ALPHA_OPAQUE(255), white(255,255,255) becomes wxIMAGE_ALPHA_TRANSPARENT(0)
+ *alpha++ = static_cast<unsigned char>(numeric::intDivRound(3 * 255 - rgb[0] - rgb[1] - rgb[2], 3)); //mixed-mode arithmetics!
+ *rgb++ = r; //
+ *rgb++ = g; //apply actual text color
+ *rgb++ = b; //
+ }
+
return output;
}
@@ -304,7 +322,7 @@ wxImage zen::resizeCanvas(const wxImage& img, wxSize newSize, int alignment)
std::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, newSize.x * newSize.y);
copySubImage(img, wxPoint(), output, newPos, img.GetSize());
- //about 50x faster than e.g. wxImage::Resize!!! suprise :>
+ //about 50x faster than e.g. wxImage::Resize!!! surprise :>
return output;
}
@@ -418,18 +436,23 @@ void zen::convertToVanillaImage(wxImage& img)
}
}
+
wxImage zen::rectangleImage(wxSize size, const wxColor& col)
{
assert(col.IsSolid());
wxImage img(size);
+ const unsigned char r = col.Red (); //
+ const unsigned char g = col.Green(); //getting RGB involves virtual function calls!
+ const unsigned char b = col.Blue (); //
+
unsigned char* rgb = img.GetData();
const int pixelCount = size.GetWidth() * size.GetHeight();
for (int i = 0; i < pixelCount; ++i)
{
- *rgb++ = col.GetRed();
- *rgb++ = col.GetGreen();
- *rgb++ = col.GetBlue();
+ *rgb++ = r;
+ *rgb++ = g;
+ *rgb++ = b;
}
convertToVanillaImage(img);
return img;
@@ -439,11 +462,16 @@ wxImage zen::rectangleImage(wxSize size, const wxColor& col)
wxImage zen::rectangleImage(wxSize size, const wxColor& innerCol, const wxColor& borderCol, int borderWidth)
{
assert(innerCol.IsSolid() && borderCol.IsSolid());
+ assert(borderWidth > 0);
wxImage img = rectangleImage(size, borderCol);
const int heightInner = size.GetHeight() - 2 * borderWidth;
const int widthInner = size.GetWidth () - 2 * borderWidth;
+ const unsigned char r = innerCol.Red (); //
+ const unsigned char g = innerCol.Green(); //getting RGB involves virtual function calls!
+ const unsigned char b = innerCol.Blue (); //
+
if (widthInner > 0 && heightInner > 0 && innerCol != borderCol)
//copyImageLayover(rectangleImage({widthInner, heightInner}, innerCol), img, {borderWidth, borderWidth}); => inline:
for (int y = 0; y < heightInner; ++y)
@@ -452,9 +480,9 @@ wxImage zen::rectangleImage(wxSize size, const wxColor& innerCol, const wxColor&
for (int x = 0; x < widthInner; ++x)
{
- *rgb++ = innerCol.GetRed();
- *rgb++ = innerCol.GetGreen();
- *rgb++ = innerCol.GetBlue();
+ *rgb++ = r;
+ *rgb++ = g;
+ *rgb++ = b;
}
}
diff --git a/wx+/image_tools.h b/wx+/image_tools.h
index 8374a26f..9cb5b718 100644
--- a/wx+/image_tools.h
+++ b/wx+/image_tools.h
@@ -38,7 +38,6 @@ wxImage layOver(const wxImage& back, const wxImage& front, int alignment = wxALI
wxImage greyScale(const wxImage& img); //greyscale + brightness adaption
wxImage greyScaleIfDisabled(const wxImage& img, bool enabled);
-//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
@@ -72,9 +71,9 @@ wxImage rectangleImage(wxSize size, const wxColor& innerCol, const wxColor& bord
//################################### implementation ###################################
inline
-wxImage greyScale(const wxImage& img)
+wxImage greyScale(const wxImage& img) //TODO support gamma-decoding and perceptual colors!?
{
- wxImage output = img.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
adjustBrightness(output, 160);
return output;
}
@@ -91,7 +90,7 @@ wxImage greyScaleIfDisabled(const wxImage& img, bool enabled)
inline
-double getAvgBrightness(const wxImage& img)
+double getAvgBrightness(const wxImage& img) //TODO: consider gamma-encoded sRGB!?
{
const int pixelCount = img.GetWidth() * img.GetHeight();
auto pixBegin = img.GetData();
@@ -128,9 +127,9 @@ void brighten(wxImage& img, int level)
const int pixelCount = img.GetWidth() * img.GetHeight();
auto pixEnd = pixBegin + 3 * pixelCount; //RGB
if (level > 0)
- std::for_each(pixBegin, pixEnd, [&](unsigned char& c) { c = static_cast<unsigned char>(std::min(255, c + level)); });
+ std::for_each(pixBegin, pixEnd, [level](unsigned char& c) { c = static_cast<unsigned char>(std::min(c + level, 255)); });
else
- std::for_each(pixBegin, pixEnd, [&](unsigned char& c) { c = static_cast<unsigned char>(std::max(0, c + level)); });
+ std::for_each(pixBegin, pixEnd, [level](unsigned char& c) { c = static_cast<unsigned char>(std::max(c + level, 0)); });
}
}
@@ -140,69 +139,6 @@ void adjustBrightness(wxImage& img, int targetLevel)
{
brighten(img, targetLevel - getAvgBrightness(img));
}
-
-
-/*
-inline
-wxColor gradient(const wxColor& from, const wxColor& to, double fraction)
-{
- fraction = std::max(0.0, fraction);
- fraction = std::min(1.0, fraction);
- return wxColor(from.Red () + (to.Red () - from.Red ()) * fraction,
- from.Green() + (to.Green() - from.Green()) * fraction,
- from.Blue () + (to.Blue () - from.Blue ()) * fraction,
- from.Alpha() + (to.Alpha() - from.Alpha()) * fraction);
-}
-*/
-
-/*
-inline
-wxColor hsvColor(double h, double s, double v) //h within [0, 360), s, v within [0, 1]
-{
- //https://en.wikipedia.org/wiki/HSL_and_HSV
-
- //make input values fit into bounds
- if (h > 360)
- h -= static_cast<int>(h / 360) * 360;
- else if (h < 0)
- h -= static_cast<int>(h / 360) * 360 - 360;
- numeric::confine<double>(s, 0, 1);
- numeric::confine<double>(v, 0, 1);
- //------------------------------------
- const int h_i = h / 60;
- const float f = h / 60 - h_i;
-
- auto polish = [](double val) -> unsigned char
- {
- int result = std::round(val * 255);
- numeric::confine(result, 0, 255);
- return static_cast<unsigned char>(result);
- };
-
- const unsigned char p = polish(v * (1 - s));
- const unsigned char q = polish(v * (1 - s * f));
- const unsigned char t = polish(v * (1 - s * (1 - f)));
- const unsigned char vi = polish(v);
-
- switch (h_i)
- {
- case 0:
- return wxColor(vi, t, p);
- case 1:
- return wxColor(q, vi, p);
- case 2:
- return wxColor(p, vi, t);
- case 3:
- return wxColor(p, q, vi);
- case 4:
- return wxColor(t, p, vi);
- case 5:
- return wxColor(vi, p, q);
- }
- assert(false);
- return *wxBLACK;
-}
-*/
}
#endif //IMAGE_TOOLS_H_45782456427634254
diff --git a/wx+/no_flicker.h b/wx+/no_flicker.h
index 3d7c0ee0..b9f82349 100644
--- a/wx+/no_flicker.h
+++ b/wx+/no_flicker.h
@@ -12,7 +12,9 @@
#include <wx/textctrl.h>
#include <wx/stattext.h>
#include <wx/richtext/richtextctrl.h>
+#include <wx/settings.h>
#include <wx/wupdlock.h>
+#include "color_tools.h"
namespace zen
@@ -81,7 +83,9 @@ void setTextWithUrls(wxRichTextCtrl& richCtrl, const wxString& newText)
richCtrl.Clear();
wxRichTextAttr urlStyle;
- urlStyle.SetTextColour(*wxBLUE);
+ urlStyle.SetTextColour(enhanceContrast(*wxBLUE,
+ wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),
+ wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), 5 /*contrastRatioMin*/)); //W3C recommends >= 4.5
urlStyle.SetFontUnderlined(true);
for (auto& [type, text] : blocks)
diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp
index d6c25fc8..291bfc38 100644
--- a/wx+/popup_dlg.cpp
+++ b/wx+/popup_dlg.cpp
@@ -10,7 +10,6 @@
#include <wx/app.h>
#include <wx/display.h>
#include <wx/sound.h>
-//#include "app_main.h"
#include "bitmap_button.h"
#include "no_flicker.h"
#include "window_layout.h"
diff --git a/wx+/std_button_layout.h b/wx+/std_button_layout.h
index 9a38ec1b..2a2fd7ce 100644
--- a/wx+/std_button_layout.h
+++ b/wx+/std_button_layout.h
@@ -7,7 +7,6 @@
#ifndef STD_BUTTON_LAYOUT_H_183470321478317214
#define STD_BUTTON_LAYOUT_H_183470321478317214
-//#include <algorithm>
#include <wx/sizer.h>
#include <wx/button.h>
#include "dc.h"
diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp
index 5b6b5f45..007abbfc 100644
--- a/wx+/tooltip.cpp
+++ b/wx+/tooltip.cpp
@@ -8,10 +8,8 @@
#include <wx/dialog.h>
#include <wx/stattext.h>
#include <wx/sizer.h>
-//#include <wx/statbmp.h>
#include <wx/settings.h>
#include <wx/app.h>
-//#include "image_tools.h"
#include "bitmap_button.h"
#include "dc.h"
#include <gtk/gtk.h>
diff --git a/wx+/window_layout.h b/wx+/window_layout.h
index 05b9316e..dee29fb5 100644
--- a/wx+/window_layout.h
+++ b/wx+/window_layout.h
@@ -7,11 +7,10 @@
#ifndef WINDOW_LAYOUT_H_23849632846734343234532
#define WINDOW_LAYOUT_H_23849632846734343234532
-//#include <zen/basic_math.h>
-//#include <wx/window.h>
#include <wx/spinctrl.h>
#include <zen/scope_guard.h>
#include <gtk/gtk.h>
+#include "color_tools.h"
#include "dc.h"
diff --git a/wx+/window_tools.h b/wx+/window_tools.h
index 45f43556..5be28fc8 100644
--- a/wx+/window_tools.h
+++ b/wx+/window_tools.h
@@ -93,6 +93,8 @@ struct FocusPreserver
assert(oldFocusId_ != wxID_ANY);
}
+ void dismiss() { oldFocusId_ = wxID_ANY; }
+
private:
wxWindowID oldFocusId_ = wxID_ANY;
//don't store wxWindow* which may be dangling during ~FocusPreserver()!
bgstack15